Comparing version 0.6.4 to 0.7.0
{ | ||
"name": "sip.js", | ||
"version": "0.6.4", | ||
"version": "0.7.0", | ||
"authors": [ | ||
@@ -5,0 +5,0 @@ "Will Mitchell <will@onsip.com>", |
@@ -7,6 +7,7 @@ /*jshint multistr:true, devel:true*/ | ||
var pkg = grunt.file.readJSON('package.json'); | ||
var year = new Date().getFullYear() | ||
var banner = '\ | ||
/*\n\ | ||
* SIP version <%= pkg.version %>\n\ | ||
* Copyright (c) 2014-<%= grunt.template.today("yyyy") %> Junction Networks, Inc <http://www.onsip.com>\n\ | ||
* SIP version ' + pkg.version + '\n\ | ||
* Copyright (c) 2014-' + year + ' Junction Networks, Inc <http://www.onsip.com>\n\ | ||
* Homepage: http://sipjs.com\n\ | ||
@@ -27,6 +28,6 @@ * License: http://sipjs.com/license/\n\ | ||
* the following conditions:\n\ | ||
* \n\ | ||
*\n\ | ||
* The above copyright notice and this permission notice shall be\n\ | ||
* included in all copies or substantial portions of the Software.\n\ | ||
* \n\ | ||
*\n\ | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\n\ | ||
@@ -52,3 +53,3 @@ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n\ | ||
devel: { | ||
src: 'src/SIP.js', | ||
src: pkg.main, | ||
dest: 'dist/<%= name %>-<%= pkg.version %>.js' | ||
@@ -61,8 +62,4 @@ }, | ||
postBundleCB: function (err, src, next) { | ||
// prepend the banner and fill in placeholders | ||
src = (banner + src).replace(/<%=(.*)%>/g, function (match, expr) { | ||
// jshint evil:true | ||
return eval(expr); | ||
}); | ||
next(err, src); | ||
// prepend the banner | ||
next(err, banner + src); | ||
} | ||
@@ -72,2 +69,6 @@ } | ||
copy: { | ||
min: { | ||
src: 'dist/<%= name %>-<%= pkg.version %>.min.js', | ||
dest: 'dist/<%= name %>.min.js' | ||
}, | ||
dist: { | ||
@@ -79,3 +80,3 @@ src: 'dist/<%= name %>-<%= pkg.version %>.js', | ||
jshint: { | ||
src: 'src/**/*.js', | ||
src: ['src/**/*.js', "!src/polyfills/**/*.js", "!src/Grammar/dist/Grammar.js"], | ||
options: { | ||
@@ -86,7 +87,2 @@ jshintrc: true | ||
uglify: { | ||
dist: { | ||
files: { | ||
'dist/<%= name %>.min.js': ['dist/<%= name %>.js'] | ||
} | ||
}, | ||
devel: { | ||
@@ -112,2 +108,3 @@ files: { | ||
keepRunner : true, | ||
vendor: 'test/polyfills/*.js', | ||
helpers: 'test/helpers/*.js' | ||
@@ -121,34 +118,3 @@ } | ||
dest: 'src/Grammar/dist/Grammar.js', | ||
options: { | ||
optimize: 'size', | ||
allowedStartRules: [ | ||
'Contact', | ||
'Name_Addr_Header', | ||
'Record_Route', | ||
'Request_Response', | ||
'SIP_URI', | ||
'Subscription_State', | ||
'Via', | ||
'absoluteURI', | ||
'Call_ID', | ||
'Content_Disposition', | ||
'Content_Length', | ||
'Content_Type', | ||
'CSeq', | ||
'displayName', | ||
'Event', | ||
'From', | ||
'host', | ||
'Max_Forwards', | ||
'Proxy_Authenticate', | ||
'quoted_string', | ||
'Refer_To', | ||
'stun_URI', | ||
'To', | ||
'turn_URI', | ||
'uuid', | ||
'WWW_Authenticate', | ||
'challenge' | ||
] | ||
} | ||
options: require('./src/Grammar/peg.json') | ||
} | ||
@@ -178,35 +144,13 @@ }, | ||
grunt.registerTask('grammar', ['peg']); | ||
// Task for building SIP.js Grammar.js and Grammar.min.js files. | ||
grunt.registerTask('post_peg', function(){ | ||
// Modify the generated Grammar.js file with custom changes. | ||
console.log('"grammar" task: applying custom changes to Grammar.js ...'); | ||
var fs = require('fs'); | ||
var grammar = fs.readFileSync('src/Grammar/dist/Grammar.js').toString(); | ||
var modified_grammar = grammar.replace(/throw peg.*maxFailPos.*/, 'return -1;'); | ||
modified_grammar = modified_grammar.replace(/return peg.*result.*/, 'return data;'); | ||
modified_grammar = modified_grammar.replace(/parse:( *)parse/, 'parse:$1function (input, startRule) {return parse(input, {startRule: startRule});}'); | ||
modified_grammar = modified_grammar.replace(/\(function\(\)/, 'function(SIP)').replace(/\}\)\(\)/, '}'); | ||
// Don't jshint this big chunk of minified code | ||
modified_grammar = | ||
"/* jshint ignore:start */\n" + | ||
modified_grammar + | ||
"\n/* jshint ignore:end */\n"; | ||
fs.writeFileSync('src/Grammar/dist/Grammar.js', modified_grammar); | ||
console.log('OK'); | ||
}); | ||
grunt.registerTask('grammar', ['peg', 'post_peg']); | ||
// Task for building sip-devel.js (uncompressed), sip-X.Y.Z.js (uncompressed) | ||
// and sip-X.Y.Z.min.js (minified). | ||
// Both sip-devel.js and sip-X.Y.Z.js are the same file with different name. | ||
grunt.registerTask('build', ['trimtrailingspaces:main', 'devel', 'copy', 'uglify']); | ||
grunt.registerTask('build', ['trimtrailingspaces:main', 'devel', 'uglify', 'copy']); | ||
// Task for building sip-devel.js (uncompressed). | ||
grunt.registerTask('devel', ['jshint', 'browserify']); | ||
grunt.registerTask('devel', ['jshint', 'quick']); | ||
grunt.registerTask('quick', ['browserify']); | ||
grunt.registerTask('quick', ['grammar', 'browserify']); | ||
@@ -218,3 +162,3 @@ // Test tasks. | ||
// Doc: http://manuel.manuelles.nl/blog/2012/06/22/integrate-travis-ci-into-grunt/ | ||
grunt.registerTask('travis', ['grammar', 'devel', 'test']); | ||
grunt.registerTask('travis', ['devel', 'test']); | ||
@@ -221,0 +165,0 @@ // Default task is an alias for 'build'. |
@@ -5,4 +5,7 @@ { | ||
"description": "A simple, intuitive, and powerful JavaScript signaling library", | ||
"version": "0.6.4", | ||
"main": "src/SIP.js", | ||
"version": "0.7.0", | ||
"main": "src/index.js", | ||
"browser": { | ||
"./src/environment.js": "./src/environment_browser.js" | ||
}, | ||
"homepage": "http://sipjs.com", | ||
@@ -27,5 +30,8 @@ "author": "Will Mitchell <will@onsip.com>", | ||
"devDependencies": { | ||
"browserify": "^4.1.8", | ||
"grunt": "~0.4.0", | ||
"grunt-browserify": "^2.1.0", | ||
"grunt-cli": "~0.1.6", | ||
"grunt-contrib-jasmine": "~0.6.0", | ||
"grunt-contrib-copy": "^0.5.0", | ||
"grunt-contrib-jasmine": "~0.8.0", | ||
"grunt-contrib-jshint": ">0.5.0", | ||
@@ -35,8 +41,3 @@ "grunt-contrib-uglify": "~0.2.0", | ||
"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" | ||
"pegjs": "^0.8.0" | ||
}, | ||
@@ -48,4 +49,11 @@ "engines": { | ||
"scripts": { | ||
"prepublish": "cd src/Grammar && mkdir -p dist && pegjs --extra-options-file peg.json src/Grammar.pegjs dist/Grammar.js", | ||
"test": "grunt travis --verbose" | ||
}, | ||
"dependencies": { | ||
"ws": "^0.6.4" | ||
}, | ||
"optionalDependencies": { | ||
"promiscuous": "^0.6.0" | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
module.exports = function (SIP) { | ||
@@ -5,12 +6,5 @@ var ClientContext; | ||
ClientContext = function (ua, method, target, options) { | ||
var params, extraHeaders, | ||
originalTarget = target, | ||
events = [ | ||
'progress', | ||
'accepted', | ||
'rejected', | ||
'failed', | ||
'cancel' | ||
]; | ||
var originalTarget = target; | ||
// Validate arguments | ||
if (target === undefined) { | ||
@@ -20,3 +14,5 @@ throw new TypeError('Not enough arguments'); | ||
// Check target validity | ||
this.ua = ua; | ||
this.logger = ua.getLogger('sip.clientcontext'); | ||
this.method = method; | ||
target = ua.normalizeTarget(target); | ||
@@ -27,31 +23,34 @@ if (!target) { | ||
this.ua = ua; | ||
this.logger = ua.getLogger('sip.clientcontext'); | ||
this.method = method; | ||
/* Options | ||
* - extraHeaders | ||
* - params | ||
* - contentType | ||
* - body | ||
*/ | ||
options = Object.create(options || Object.prototype); | ||
options.extraHeaders = (options.extraHeaders || []).slice(); | ||
params = options && options.params; | ||
extraHeaders = (options && options.extraHeaders || []).slice(); | ||
if (options.contentType) { | ||
this.contentType = options.contentType; | ||
options.extraHeaders.push('Content-Type: ' + this.contentType); | ||
} | ||
if (options && options.body) { | ||
// Build the request | ||
this.request = new SIP.OutgoingRequest(this.method, | ||
target, | ||
this.ua, | ||
options.params, | ||
options.extraHeaders); | ||
if (options.body) { | ||
this.body = options.body; | ||
this.request.body = this.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); | ||
/* Set other properties from the request */ | ||
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 = Object.create(SIP.EventEmitter.prototype); | ||
@@ -66,13 +65,3 @@ ClientContext.prototype.send = function () { | ||
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 + '"'; | ||
} | ||
var cancel_reason = SIP.Utils.getCancelReason(options.status_code, options.reason_phrase); | ||
this.request.cancel(cancel_reason); | ||
@@ -84,3 +73,3 @@ | ||
ClientContext.prototype.receiveResponse = function (response) { | ||
var cause = SIP.C.REASON_PHRASE[response.status_code] || ''; | ||
var cause = SIP.Utils.getReasonPhrase(response.status_code); | ||
@@ -87,0 +76,0 @@ switch(true) { |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -2,0 +3,0 @@ * @fileoverview SIP Constants |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
@@ -46,3 +47,3 @@ /** | ||
this.off('stateChanged', stateChanged); | ||
this.removeListener('stateChanged', stateChanged); | ||
self.dialog.uac_pending_reply = false; | ||
@@ -49,0 +50,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -13,4 +14,6 @@ * @fileoverview SIP Dialog | ||
*/ | ||
module.exports = function (SIP, RequestSender) { | ||
module.exports = function (SIP) { | ||
var RequestSender = require('./Dialog/RequestSender')(SIP); | ||
var Dialog, | ||
@@ -93,2 +96,3 @@ C = { | ||
this.logger.log('new ' + type + ' dialog created with status ' + (this.state === C.STATUS_EARLY ? 'EARLY': 'CONFIRMED')); | ||
owner.emit('dialog', this); | ||
}; | ||
@@ -193,3 +197,3 @@ | ||
this.off('stateChanged', stateChanged); | ||
this.removeListener('stateChanged', stateChanged); | ||
self.uas_pending_reply = false; | ||
@@ -196,0 +200,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
@@ -2,0 +3,0 @@ /** |
@@ -1,208 +0,38 @@ | ||
/** | ||
* @fileoverview EventEmitter | ||
*/ | ||
"use strict"; | ||
var NodeEventEmitter = require('events').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 | ||
}; | ||
module.exports = function (console) { | ||
EventEmitter = function(){}; | ||
EventEmitter.prototype = { | ||
/** | ||
* Initialize events dictionaries. | ||
* @param {Array} events | ||
*/ | ||
initEvents: function(events) { | ||
this.events = {}; | ||
// Don't use `new SIP.EventEmitter()` for inheriting. | ||
// Use Object.create(SIP.EventEmitter.prototoype); | ||
function EventEmitter () { | ||
NodeEventEmitter.call(this); | ||
} | ||
return this.initMoreEvents(events); | ||
}, | ||
EventEmitter.prototype = Object.create(NodeEventEmitter.prototype, { | ||
constructor: { | ||
value: EventEmitter, | ||
enumerable: false, | ||
writable: true, | ||
configurable: true | ||
} | ||
}); | ||
initMoreEvents: function(events) { | ||
var idx; | ||
EventEmitter.prototype.off = function off (eventName, listener) { | ||
var warning = ''; | ||
warning += 'SIP.EventEmitter#off is deprecated and may be removed in future SIP.js versions.\n'; | ||
warning += 'Please use removeListener or removeAllListeners instead.\n'; | ||
warning += 'See here for more details:\n'; | ||
warning += 'http://nodejs.org/api/events.html#events_emitter_removelistener_event_listener'; | ||
console.warn(warning); | ||
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; | ||
if (arguments.length < 2) { | ||
return this.removeAllListeners.apply(this, arguments); | ||
} else { | ||
return this.removeListener(eventName, listener); | ||
} | ||
}; | ||
Event = function(type, sender, data) { | ||
this.type = type; | ||
this.sender= sender; | ||
this.data = data; | ||
}; | ||
return EventEmitter; | ||
EventEmitter.C = C; | ||
SIP.EventEmitter = EventEmitter; | ||
SIP.Event = Event; | ||
}; |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -43,6 +44,6 @@ * @fileoverview Exceptions | ||
NotReadyError: (function(){ | ||
GetDescriptionError: (function(){ | ||
var exception = function(message) { | ||
this.code = 4; | ||
this.name = 'NOT_READY_ERROR'; | ||
this.name = 'GET_DESCRIPTION_ERROR'; | ||
this.message = message; | ||
@@ -49,0 +50,0 @@ }; |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -35,3 +36,3 @@ * @fileoverview Hacks - This file contains all of the things we | ||
isFirefox: function () { | ||
return window.mozRTCPeerConnection !== undefined; | ||
return typeof mozRTCPeerConnection !== 'undefined'; | ||
}, | ||
@@ -121,5 +122,3 @@ | ||
}; | ||
return Hacks; | ||
}; | ||
}; |
@@ -0,21 +1,13 @@ | ||
"use strict"; | ||
var levels = { | ||
'error': 0, | ||
'warn': 1, | ||
'log': 2, | ||
'debug': 3 | ||
}; | ||
module.exports = function (window, Logger) { | ||
module.exports = function (console) { | ||
// 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 LoggerFactory = function () { | ||
var logger, | ||
levels = { | ||
'error': 0, | ||
'warn': 1, | ||
'log': 2, | ||
'debug': 3 | ||
}, | ||
level = 2, | ||
@@ -73,67 +65,36 @@ builtinEnabled = true, | ||
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); | ||
var prefix = [new Date(), category]; | ||
if (label) { | ||
prefix.push(label); | ||
} | ||
if (this.connector) { | ||
this.connector('debug', category, label, content); | ||
} | ||
content = prefix.concat(content).join(' | '); | ||
} | ||
target.call(console, content); | ||
}; | ||
LoggerFactory.prototype.log = function(category, label, content) { | ||
if (this.level >= 2) { | ||
if (this.builtinEnabled) { | ||
this.print(console.log, category, label, content); | ||
} | ||
function Logger (logger, category, label) { | ||
this.logger = logger; | ||
this.category = category; | ||
this.label = label; | ||
} | ||
if (this.connector) { | ||
this.connector('log', category, label, content); | ||
} | ||
} | ||
}; | ||
Object.keys(levels).forEach(function (targetName) { | ||
Logger.prototype[targetName] = function (content) { | ||
this.logger[targetName](this.category, this.label, content); | ||
}; | ||
LoggerFactory.prototype.warn = function(category, label, content) { | ||
if (this.level >= 1) { | ||
if (this.builtinEnabled) { | ||
this.print(console.warn, category, label, content); | ||
} | ||
LoggerFactory.prototype[targetName] = function (category, label, content) { | ||
if (this.level >= levels[targetName]) { | ||
if (this.builtinEnabled) { | ||
this.print(console[targetName], category, label, content); | ||
} | ||
if (this.connector) { | ||
this.connector('warn', category, label, content); | ||
if (this.connector) { | ||
this.connector(targetName, 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) { | ||
@@ -140,0 +101,0 @@ var logger; |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -23,10 +24,6 @@ * @fileoverview MediaHandler | ||
/** | ||
* @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) { | ||
getDescription: {value: function getDescription (mediaHint) { | ||
// keep jshint happy | ||
onSuccess = onSuccess; | ||
onFailure = onFailure; | ||
mediaHint = mediaHint; | ||
@@ -39,10 +36,6 @@ }}, | ||
* @param {String} description | ||
* @param {Function} onSuccess | ||
* @param {Function} onFailure | ||
*/ | ||
setDescription: {value: function setDescription (description, onSuccess, onFailure) { | ||
setDescription: {value: function setDescription (description) { | ||
// keep jshint happy | ||
description = description; | ||
onSuccess = onSuccess; | ||
onFailure = onFailure; | ||
}} | ||
@@ -49,0 +42,0 @@ }); |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -34,2 +35,6 @@ * @fileoverview SIP NameAddrHeader | ||
Object.defineProperties(this, { | ||
friendlyName: { | ||
get: function() { return this.displayName || uri.aor; } | ||
}, | ||
displayName: { | ||
@@ -36,0 +41,0 @@ get: function() { return displayName; }, |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -2,0 +3,0 @@ * @fileoverview SIP Message Parser |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
module.exports = function (SIP) { | ||
@@ -7,7 +8,3 @@ | ||
var params = {}, | ||
regId = 1, | ||
events = [ | ||
'registered', | ||
'unregistered' | ||
]; | ||
regId = 1; | ||
@@ -46,3 +43,2 @@ this.registrar = ua.configuration.registrarServer; | ||
this.logger = ua.getLogger('sip.registercontext'); | ||
this.initMoreEvents(events); | ||
}; | ||
@@ -55,4 +51,4 @@ | ||
// Handle Options | ||
options = options || {}; | ||
extraHeaders = (options.extraHeaders || []).slice(); | ||
this.options = options || {}; | ||
extraHeaders = (this.options.extraHeaders || []).slice(); | ||
extraHeaders.push('Contact: ' + this.contact + ';expires=' + this.expires); | ||
@@ -122,3 +118,3 @@ extraHeaders.push('Allow: ' + SIP.Utils.getAllowedMethods(this.ua)); | ||
self.registrationTimer = null; | ||
self.register(options); | ||
self.register(this.options); | ||
}, (expires * 1000) - 3000); | ||
@@ -149,3 +145,3 @@ this.registrationExpiredTimer = SIP.Timers.setTimeout(function () { | ||
// Attempt the registration again immediately | ||
this.register(options); | ||
this.register(this.options); | ||
} else { //This response MUST contain a Min-Expires header field | ||
@@ -199,3 +195,3 @@ this.logger.warn('423 response received for REGISTER without Min-Expires'); | ||
onTransportConnected: function() { | ||
this.register(); | ||
this.register(this.options); | ||
}, | ||
@@ -202,0 +198,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
@@ -82,3 +83,3 @@ /** | ||
*/ | ||
if ((status_code === 401 || status_code === 407) && this.ua.configuration.password !== null) { | ||
if (status_code === 401 || status_code === 407) { | ||
@@ -103,3 +104,3 @@ // Get and parse the appropriate WWW-Authenticate or Proxy-Authenticate header. | ||
if (!this.credentials) { | ||
this.credentials = new SIP.DigestAuthentication(this.ua); | ||
this.credentials = this.ua.configuration.authenticationFactory(this.ua); | ||
} | ||
@@ -106,0 +107,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -150,3 +151,3 @@ * @fileoverview Incoming SIP Message Sanity Check | ||
var to, | ||
response = "SIP/2.0 " + status_code + " " + SIP.C.REASON_PHRASE[status_code] + "\r\n", | ||
response = SIP.Utils.buildStatusLine(status_code), | ||
vias = message.getHeaders('via'), | ||
@@ -153,0 +154,0 @@ length = vias.length, |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
module.exports = function (SIP) { | ||
@@ -5,8 +6,2 @@ var ServerContext; | ||
ServerContext = function (ua, request) { | ||
var events = [ | ||
'progress', | ||
'accepted', | ||
'rejected', | ||
'failed' | ||
]; | ||
this.ua = ua; | ||
@@ -33,59 +28,52 @@ this.logger = ua.getLogger('sip.servercontext'); | ||
this.remoteIdentity = request.from; | ||
this.initEvents(events); | ||
}; | ||
ServerContext.prototype = new SIP.EventEmitter(); | ||
ServerContext.prototype = Object.create(SIP.EventEmitter.prototype); | ||
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; | ||
options = Object.create(options || Object.prototype); | ||
options.statusCode || (options.statusCode = 180); | ||
options.minCode = 100; | ||
options.maxCode = 199; | ||
options.events = ['progress']; | ||
return this.reply(options); | ||
}; | ||
ServerContext.prototype.accept = function (options) { | ||
options = options || {}; | ||
var | ||
statusCode = options.statusCode || 200, | ||
reasonPhrase = options.reasonPhrase || SIP.C.REASON_PHRASE[statusCode], | ||
extraHeaders = (options.extraHeaders || []).slice(), | ||
body = options.body, | ||
response; | ||
options = Object.create(options || Object.prototype); | ||
options.statusCode || (options.statusCode = 200); | ||
options.minCode = 200; | ||
options.maxCode = 299; | ||
options.events = ['accepted']; | ||
return this.reply(options); | ||
}; | ||
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 = Object.create(options || Object.prototype); | ||
options.statusCode || (options.statusCode = 480); | ||
options.minCode = 300; | ||
options.maxCode = 699; | ||
options.events = ['rejected', 'failed']; | ||
return this.reply(options); | ||
}; | ||
ServerContext.prototype.reject = function (options) { | ||
options = options || {}; | ||
ServerContext.prototype.reply = function (options) { | ||
options = options || {}; // This is okay, so long as we treat options as read-only in this method | ||
var | ||
statusCode = options.statusCode || 480, | ||
reasonPhrase = options.reasonPhrase || SIP.C.REASON_PHRASE[statusCode], | ||
extraHeaders = (options.extraHeaders || []).slice(), | ||
statusCode = options.statusCode || 100, | ||
minCode = options.minCode || 100, | ||
maxCode = options.maxCode || 699, | ||
reasonPhrase = SIP.Utils.getReasonPhrase(statusCode, options.reasonPhrase), | ||
extraHeaders = options.extraHeaders || [], | ||
body = options.body, | ||
events = options.events || [], | ||
response; | ||
if (statusCode < 300 || statusCode > 699) { | ||
if (statusCode < minCode || statusCode > maxCode) { | ||
throw new TypeError('Invalid statusCode: ' + statusCode); | ||
} | ||
response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body); | ||
this.emit('rejected', response, reasonPhrase); | ||
this.emit('failed', response, reasonPhrase); | ||
events.forEach(function (event) { | ||
this.emit(event, response, reasonPhrase); | ||
}, this); | ||
@@ -95,15 +83,2 @@ 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 () { | ||
@@ -110,0 +85,0 @@ this.emit('failed', null, SIP.C.causes.REQUEST_TIMEOUT); |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -21,6 +22,3 @@ * @fileoverview DTMF | ||
DTMF = function(session, tone, options) { | ||
var events = [ | ||
'succeeded', | ||
'failed' | ||
], duration, interToneGap; | ||
var duration, interToneGap; | ||
@@ -83,6 +81,4 @@ if (tone === undefined) { | ||
this.interToneGap = interToneGap; | ||
this.initEvents(events); | ||
}; | ||
DTMF.prototype = new SIP.EventEmitter(); | ||
DTMF.prototype = Object.create(SIP.EventEmitter.prototype); | ||
@@ -89,0 +85,0 @@ |
@@ -5,51 +5,46 @@ /** | ||
*/ | ||
module.exports = (function(window) { | ||
"use strict"; | ||
"use strict"; | ||
var SIP = {}; | ||
var SIP = {}; | ||
module.exports = function (environment) { | ||
var pkg = require('../package.json'); | ||
var pkg = require('../package.json'); | ||
Object.defineProperties(SIP, { | ||
version: { | ||
get: function(){ return pkg.version; } | ||
}, | ||
name: { | ||
get: function(){ return pkg.title; } | ||
} | ||
}); | ||
Object.defineProperties(SIP, { | ||
version: { | ||
get: function(){ return pkg.version; } | ||
}, | ||
name: { | ||
get: function(){ return pkg.title; } | ||
} | ||
}); | ||
require('./Utils.js')(SIP); | ||
var Logger = require('./Logger.js'); | ||
SIP.LoggerFactory = require('./LoggerFactory.js')(window, Logger); | ||
require('./EventEmitter.js')(SIP); | ||
SIP.C = require('./Constants.js')(SIP.name, SIP.version); | ||
SIP.Exceptions = require('./Exceptions.js'); | ||
SIP.Timers = require('./Timers.js')(window); | ||
require('./Transport.js')(SIP, window); | ||
require('./Parser.js')(SIP); | ||
require('./SIPMessage.js')(SIP); | ||
require('./URI.js')(SIP); | ||
require('./NameAddrHeader.js')(SIP); | ||
require('./Transactions.js')(SIP, window); | ||
var DialogRequestSender = require('./Dialog/RequestSender.js')(SIP, window); | ||
require('./Dialogs.js')(SIP, DialogRequestSender); | ||
require('./RequestSender.js')(SIP); | ||
require('./RegisterContext.js')(SIP, window); | ||
SIP.MediaHandler = require('./MediaHandler.js')(SIP.EventEmitter); | ||
require('./ClientContext.js')(SIP); | ||
require('./ServerContext.js')(SIP); | ||
var SessionDTMF = require('./Session/DTMF.js')(SIP); | ||
require('./Session.js')(SIP, window, SessionDTMF); | ||
require('./Subscription.js')(SIP, window); | ||
var WebRTCMediaHandler = require('./WebRTC/MediaHandler.js')(SIP); | ||
var WebRTCMediaStreamManager = require('./WebRTC/MediaStreamManager.js')(SIP); | ||
SIP.WebRTC = require('./WebRTC.js')(SIP.Utils, window, WebRTCMediaHandler, WebRTCMediaStreamManager); | ||
require('./UA.js')(SIP, window); | ||
SIP.Hacks = require('./Hacks.js')(SIP); | ||
require('./SanityCheck.js')(SIP); | ||
SIP.DigestAuthentication = require('./DigestAuthentication.js')(SIP.Utils); | ||
SIP.Grammar = require('./Grammar/dist/Grammar')(SIP); | ||
require('./Utils')(SIP, environment); | ||
SIP.LoggerFactory = require('./LoggerFactory')(environment.console); | ||
SIP.EventEmitter = require('./EventEmitter')(environment.console); | ||
SIP.C = require('./Constants')(SIP.name, SIP.version); | ||
SIP.Exceptions = require('./Exceptions'); | ||
SIP.Timers = require('./Timers')(environment.timers); | ||
SIP.Transport = environment.Transport(SIP, environment.WebSocket); | ||
require('./Parser')(SIP); | ||
require('./SIPMessage')(SIP); | ||
require('./URI')(SIP); | ||
require('./NameAddrHeader')(SIP); | ||
require('./Transactions')(SIP); | ||
require('./Dialogs')(SIP); | ||
require('./RequestSender')(SIP); | ||
require('./RegisterContext')(SIP); | ||
SIP.MediaHandler = require('./MediaHandler')(SIP.EventEmitter); | ||
require('./ClientContext')(SIP); | ||
require('./ServerContext')(SIP); | ||
require('./Session')(SIP, environment); | ||
require('./Subscription')(SIP); | ||
SIP.WebRTC = require('./WebRTC')(SIP, environment); | ||
require('./UA')(SIP, environment); | ||
SIP.Hacks = require('./Hacks')(SIP); | ||
require('./SanityCheck')(SIP); | ||
SIP.DigestAuthentication = require('./DigestAuthentication')(SIP.Utils); | ||
SIP.Grammar = require('./Grammar')(SIP); | ||
return SIP; | ||
})((typeof window !== 'undefined') ? window : global); | ||
return SIP; | ||
}; |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -212,2 +213,5 @@ * @fileoverview SIP Message | ||
} | ||
if (this.ua.configuration.replaces === SIP.C.supported.SUPPORTED) { | ||
supported.push('replaces'); | ||
} | ||
@@ -416,17 +420,5 @@ supported.push('outbound'); | ||
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.Utils.buildStatusLine(code, reason); | ||
extraHeaders = (extraHeaders || []).slice(); | ||
response = 'SIP/2.0 ' + code + ' ' + reason + '\r\n'; | ||
if(this.method === SIP.C.INVITE && code > 100 && code <= 200) { | ||
@@ -473,2 +465,5 @@ rr = this.getHeaders('record-route'); | ||
} | ||
if (this.ua.configuration.replaces === SIP.C.supported.SUPPORTED) { | ||
supported.push('replaces'); | ||
} | ||
@@ -478,2 +473,3 @@ supported.push('outbound'); | ||
response += 'Supported: ' + supported + '\r\n'; | ||
response += 'User-Agent: ' + this.ua.configuration.userAgentString +'\r\n'; | ||
@@ -489,3 +485,3 @@ if(body) { | ||
this.server_transaction.receiveResponse(code, response, onSuccess, onFailure); | ||
this.server_transaction.receiveResponse(code, response).then(onSuccess, onFailure); | ||
@@ -506,16 +502,4 @@ return response; | ||
code = code || null; | ||
reason = reason || null; | ||
response = SIP.Utils.buildStatusLine(code, reason); | ||
// 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++) { | ||
@@ -537,2 +521,3 @@ response += 'Via: ' + vias[v] + '\r\n'; | ||
response += 'CSeq: ' + this.cseq + ' ' + this.method + '\r\n'; | ||
response += 'User-Agent: ' + this.ua.configuration.userAgentString +'\r\n'; | ||
response += 'Content-Length: ' + 0 + '\r\n\r\n'; | ||
@@ -539,0 +524,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
@@ -12,8 +13,5 @@ /** | ||
SIP.Subscription = function (ua, target, event, options) { | ||
var events; | ||
options = Object.create(options || Object.prototype); | ||
this.extraHeaders = options.extraHeaders = (options.extraHeaders || []).slice(); | ||
options = options || {}; | ||
options.extraHeaders = (options.extraHeaders || []).slice(); | ||
events = ['notify']; | ||
this.id = null; | ||
@@ -56,4 +54,2 @@ this.state = 'init'; | ||
this.errorCodes = [404,405,410,416,480,481,482,483,484,485,489,501,604]; | ||
this.initMoreEvents(events); | ||
}; | ||
@@ -76,5 +72,16 @@ | ||
refresh: function () { | ||
if (this.state === 'terminated' || this.state === 'pending' || this.state === 'notify_wait') { | ||
return; | ||
} | ||
this.dialog.sendRequest(this, SIP.C.SUBSCRIBE, { | ||
extraHeaders: this.extraHeaders, | ||
body: this.body | ||
}); | ||
}, | ||
receiveResponse: function(response) { | ||
var expires, sub = this, | ||
cause = SIP.C.REASON_PHRASE[response.status_code] || ''; | ||
cause = SIP.Utils.getReasonPhrase(response.status_code); | ||
@@ -95,3 +102,3 @@ if (this.errorCodes.indexOf(response.status_code) !== -1) { | ||
if (expires && expires <= this.expires) { | ||
this.timers.sub_duration = SIP.Timers.setTimeout(sub.subscribe.bind(sub), expires * 1000); | ||
this.timers.sub_duration = SIP.Timers.setTimeout(sub.refresh.bind(sub), expires * 900); | ||
} else { | ||
@@ -142,3 +149,3 @@ if (!expires) { | ||
} else { | ||
this.subscribe(); | ||
this.refresh(); | ||
} | ||
@@ -202,4 +209,4 @@ }, | ||
Math.max(sub_state.expires, 0)); | ||
sub.timers.sub_duration = SIP.Timers.setTimeout(sub.subscribe.bind(sub), | ||
sub_state.expires * 1000); | ||
sub.timers.sub_duration = SIP.Timers.setTimeout(sub.refresh.bind(sub), | ||
sub_state.expires * 900); | ||
} | ||
@@ -262,3 +269,4 @@ } | ||
this.close(); | ||
return this.emit('failed', response, cause); | ||
this.emit('failed', response, cause); | ||
return this; | ||
}, | ||
@@ -265,0 +273,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -13,3 +14,3 @@ * @fileoverview SIP TIMERS | ||
module.exports = function (timers) { | ||
var exports = { | ||
var Timers = { | ||
T1: T1, | ||
@@ -35,3 +36,3 @@ T2: T2, | ||
// clock-mocking | ||
exports[name] = function () { | ||
Timers[name] = function () { | ||
return timers[name].apply(timers, arguments); | ||
@@ -41,3 +42,3 @@ }; | ||
return exports; | ||
return Timers; | ||
}; |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -36,4 +37,3 @@ * @fileoverview SIP Transactions | ||
var NonInviteClientTransaction = function(request_sender, request, transport) { | ||
var via, | ||
events = ['stateChanged']; | ||
var via; | ||
@@ -54,6 +54,4 @@ this.type = C.NON_INVITE_CLIENT; | ||
this.request_sender.ua.newTransaction(this); | ||
this.initEvents(events); | ||
}; | ||
NonInviteClientTransaction.prototype = new SIP.EventEmitter(); | ||
NonInviteClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype); | ||
@@ -142,4 +140,3 @@ NonInviteClientTransaction.prototype.stateChanged = function(state) { | ||
var via, | ||
tr = this, | ||
events = ['stateChanged']; | ||
tr = this; | ||
@@ -166,6 +163,4 @@ this.type = C.INVITE_CLIENT; | ||
}; | ||
this.initEvents(events); | ||
}; | ||
InviteClientTransaction.prototype = new SIP.EventEmitter(); | ||
InviteClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype); | ||
@@ -345,3 +340,3 @@ InviteClientTransaction.prototype.stateChanged = function(state) { | ||
}; | ||
AckClientTransaction.prototype = new SIP.EventEmitter(); | ||
AckClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype); | ||
@@ -367,4 +362,2 @@ AckClientTransaction.prototype.send = function() { | ||
var NonInviteServerTransaction = function(request, ua) { | ||
var events = ['stateChanged']; | ||
this.type = C.NON_INVITE_SERVER; | ||
@@ -383,6 +376,4 @@ this.id = request.via_branch; | ||
ua.newTransaction(this); | ||
this.initEvents(events); | ||
}; | ||
NonInviteServerTransaction.prototype = new SIP.EventEmitter(); | ||
NonInviteServerTransaction.prototype = Object.create(SIP.EventEmitter.prototype); | ||
@@ -412,4 +403,5 @@ NonInviteServerTransaction.prototype.stateChanged = function(state) { | ||
NonInviteServerTransaction.prototype.receiveResponse = function(status_code, response, onSuccess, onFailure) { | ||
NonInviteServerTransaction.prototype.receiveResponse = function(status_code, response) { | ||
var tr = this; | ||
var deferred = SIP.Utils.defer(); | ||
@@ -433,7 +425,5 @@ if(status_code === 100) { | ||
this.onTransportError(); | ||
if (onFailure) { | ||
onFailure(); | ||
} | ||
} else if (onSuccess) { | ||
onSuccess(); | ||
deferred.reject(); | ||
} else { | ||
deferred.resolve(); | ||
} | ||
@@ -451,7 +441,5 @@ break; | ||
this.onTransportError(); | ||
if (onFailure) { | ||
onFailure(); | ||
} | ||
} else if (onSuccess) { | ||
onSuccess(); | ||
deferred.reject(); | ||
} else { | ||
deferred.resolve(); | ||
} | ||
@@ -463,2 +451,4 @@ break; | ||
} | ||
return deferred.promise; | ||
}; | ||
@@ -473,4 +463,2 @@ | ||
var InviteServerTransaction = function(request, ua) { | ||
var events = ['stateChanged']; | ||
this.type = C.INVITE_SERVER; | ||
@@ -493,6 +481,4 @@ this.id = request.via_branch; | ||
request.reply(100); | ||
this.initEvents(events); | ||
}; | ||
InviteServerTransaction.prototype = new SIP.EventEmitter(); | ||
InviteServerTransaction.prototype = Object.create(SIP.EventEmitter.prototype); | ||
@@ -557,4 +543,5 @@ InviteServerTransaction.prototype.stateChanged = function(state) { | ||
// INVITE Server Transaction RFC 3261 17.2.1 | ||
InviteServerTransaction.prototype.receiveResponse = function(status_code, response, onSuccess, onFailure) { | ||
InviteServerTransaction.prototype.receiveResponse = function(status_code, response) { | ||
var tr = this; | ||
var deferred = SIP.Utils.defer(); | ||
@@ -594,7 +581,5 @@ if(status_code >= 100 && status_code <= 199) { | ||
this.onTransportError(); | ||
if (onFailure) { | ||
onFailure(); | ||
} | ||
} else if (onSuccess) { | ||
onSuccess(); | ||
deferred.reject(); | ||
} else { | ||
deferred.resolve(); | ||
} | ||
@@ -613,11 +598,7 @@ break; | ||
this.onTransportError(); | ||
if (onFailure) { | ||
onFailure(); | ||
} | ||
deferred.reject(); | ||
} else { | ||
this.stateChanged(C.STATUS_COMPLETED); | ||
this.H = SIP.Timers.setTimeout(tr.timer_H.bind(tr), SIP.Timers.TIMER_H); | ||
if (onSuccess) { | ||
onSuccess(); | ||
} | ||
deferred.resolve(); | ||
} | ||
@@ -627,2 +608,4 @@ break; | ||
} | ||
return deferred.promise; | ||
}; | ||
@@ -629,0 +612,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -11,3 +12,3 @@ * @fileoverview Transport | ||
*/ | ||
module.exports = function (SIP, window) { | ||
module.exports = function (SIP, WebSocket) { | ||
var Transport, | ||
@@ -48,3 +49,3 @@ C = { | ||
if(this.ws && this.ws.readyState === window.WebSocket.OPEN) { | ||
if(this.ws && this.ws.readyState === WebSocket.OPEN) { | ||
if (this.ua.configuration.traceSip === true) { | ||
@@ -105,3 +106,3 @@ this.logger.log('sending WebSocket message:\n\n' + message + '\n'); | ||
try { | ||
this.ws = new window.WebSocket(this.server.ws_uri, 'sip'); | ||
this.ws = new WebSocket(this.server.ws_uri, 'sip'); | ||
} catch(e) { | ||
@@ -160,27 +161,34 @@ this.logger.warn('error connecting to WebSocket ' + this.server.ws_uri + ': ' + e); | ||
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(); | ||
if (this.reconnection_attempts > 0) { | ||
this.logger.log('Reconnection attempt ' + this.reconnection_attempts + ' failed (code: ' + e.code + (e.reason? '| reason: ' + e.reason : '') +')'); | ||
this.reconnect(); | ||
} else { | ||
this.connected = false; | ||
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.ua.emit('disconnected', { | ||
transport: this, | ||
code: this.lastTransportError.code, | ||
reason: this.lastTransportError.reason | ||
}); | ||
// This is the first connection attempt | ||
//Network error | ||
this.ua.onTransportError(this); | ||
} | ||
} else { | ||
// This is the first connection attempt | ||
//Network error | ||
this.ua.onTransportError(this); | ||
} | ||
@@ -272,3 +280,3 @@ }, | ||
onError: function(e) { | ||
this.logger.warn('WebSocket connection error: ' + e); | ||
this.logger.warn('WebSocket connection error: ' + JSON.stringify(e)); | ||
}, | ||
@@ -280,3 +288,3 @@ | ||
*/ | ||
reConnect: function() { | ||
reconnect: function() { | ||
var transport = this; | ||
@@ -289,2 +297,5 @@ | ||
this.ua.onTransportError(this); | ||
} else if (this.reconnection_attempts === 1) { | ||
this.logger.log('Connection to WebSocket ' + this.server.ws_uri + ' severed, attempting first reconnect'); | ||
transport.connect(); | ||
} else { | ||
@@ -302,3 +313,3 @@ this.logger.log('trying to reconnect to WebSocket ' + this.server.ws_uri + ' (reconnection attempt ' + this.reconnection_attempts + ')'); | ||
Transport.C = C; | ||
SIP.Transport = Transport; | ||
return Transport; | ||
}; |
203
src/UA.js
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -10,3 +11,3 @@ * @augments SIP | ||
*/ | ||
module.exports = function (SIP) { | ||
module.exports = function (SIP, environment) { | ||
var UA, | ||
@@ -52,16 +53,3 @@ C = { | ||
UA = function(configuration) { | ||
var self = this, | ||
events = [ | ||
'connecting', | ||
'connected', | ||
'disconnected', | ||
'newTransaction', | ||
'transactionDestroyed', | ||
'registered', | ||
'unregistered', | ||
'registrationFailed', | ||
'invite', | ||
'newSession', | ||
'message' | ||
], i, len; | ||
var self = this; | ||
@@ -74,6 +62,2 @@ // Helper function for forwarding events | ||
for (i = 0, len = C.ALLOWED_METHODS.length; i < len; i++) { | ||
events.push(C.ALLOWED_METHODS[i].toLowerCase()); | ||
} | ||
// Set Accepted Body Types | ||
@@ -184,3 +168,2 @@ C.ACCEPTED_BODY_TYPES = C.ACCEPTED_BODY_TYPES.toString(); | ||
this.loadConfig(configuration); | ||
this.initEvents(events); | ||
} catch(e) { | ||
@@ -202,7 +185,7 @@ this.status = C.STATUS_NOT_READY; | ||
if (typeof global.addEventListener === 'function') { | ||
global.addEventListener('unload', this.stop.bind(this)); | ||
if (typeof environment.addEventListener === 'function') { | ||
environment.addEventListener('unload', this.stop.bind(this)); | ||
} | ||
}; | ||
UA.prototype = new SIP.EventEmitter(); | ||
UA.prototype = Object.create(SIP.EventEmitter.prototype); | ||
@@ -245,2 +228,10 @@ //================= | ||
UA.prototype.afterConnected = function afterConnected (callback) { | ||
if (this.isConnected()) { | ||
callback(); | ||
} else { | ||
this.once('connected', callback); | ||
} | ||
}; | ||
/** | ||
@@ -257,15 +248,5 @@ * Make an outgoing call. | ||
UA.prototype.invite = function(target, options) { | ||
options = options || {}; | ||
options = SIP.Utils.desugarSessionOptions(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}); | ||
}); | ||
} | ||
this.afterConnected(context.invite.bind(context)); | ||
return context; | ||
@@ -277,9 +258,3 @@ }; | ||
if (this.isConnected()) { | ||
sub.subscribe(); | ||
} else { | ||
this.once('connected', function() { | ||
sub.subscribe(); | ||
}); | ||
} | ||
this.afterConnected(sub.subscribe.bind(sub)); | ||
return sub; | ||
@@ -303,17 +278,8 @@ }; | ||
options = options || {}; | ||
options.contentType = options.contentType || 'text/plain'; | ||
// There is no Message module, so it is okay that the UA handles defaults here. | ||
options = Object.create(options || Object.prototype); | ||
options.contentType || (options.contentType = 'text/plain'); | ||
options.body = body; | ||
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; | ||
return this.request(SIP.C.MESSAGE, target, options); | ||
}; | ||
@@ -324,10 +290,3 @@ | ||
if (this.isConnected()) { | ||
req.send(); | ||
} else { | ||
this.once('connected', function() { | ||
req.send(); | ||
}); | ||
} | ||
this.afterConnected(req.send.bind(req)); | ||
return req; | ||
@@ -346,3 +305,3 @@ }; | ||
if (ua.nistTransactionsCount === 0 && ua.nictTransactionsCount === 0) { | ||
ua.off('transactionDestroyed', transactionsListener); | ||
ua.removeListener('transactionDestroyed', transactionsListener); | ||
ua.transport.disconnect(); | ||
@@ -563,3 +522,5 @@ } | ||
if(this.configuration.register) { | ||
this.registerContext.onTransportConnected(); | ||
this.configuration.authenticationFactory.initialize().then(function () { | ||
this.registerContext.onTransportConnected(); | ||
}.bind(this)); | ||
} | ||
@@ -624,2 +585,4 @@ | ||
transaction, | ||
replaces, | ||
replacedDialog, | ||
methodLower = request.method.toLowerCase(), | ||
@@ -667,3 +630,3 @@ self = this; | ||
} else if (method === SIP.C.MESSAGE) { | ||
if (!this.checkListener(methodLower)) { | ||
if (!this.listeners(methodLower).length) { | ||
// UA is not listening for this. Reject immediately. | ||
@@ -690,8 +653,29 @@ new SIP.Transactions.NonInviteServerTransaction(request, this); | ||
case SIP.C.INVITE: | ||
replaces = | ||
this.configuration.replaces !== SIP.C.supported.UNSUPPORTED && | ||
request.parseHeader('replaces'); | ||
if (replaces) { | ||
replacedDialog = this.dialogs[replaces.call_id + replaces.replaces_to_tag + replaces.replaces_from_tag]; | ||
if (!replacedDialog) { | ||
//Replaced header without a matching dialog, reject | ||
request.reply_sl(481, null); | ||
return; | ||
} else if (replacedDialog.owner.status === SIP.Session.C.STATUS_TERMINATED) { | ||
request.reply_sl(603, null); | ||
return; | ||
} else if (replacedDialog.state === SIP.Dialog.C.STATUS_CONFIRMED && replaces.early_only) { | ||
request.reply_sl(486, null); | ||
return; | ||
} | ||
} | ||
var isMediaSupported = this.configuration.mediaHandlerFactory.isSupported; | ||
if(!isMediaSupported || isMediaSupported()) { | ||
session = new SIP.InviteServerContext(this, request) | ||
.on('invite', function() { | ||
self.emit('invite', this); | ||
}); | ||
session = new SIP.InviteServerContext(this, request); | ||
session.replacee = replacedDialog && replacedDialog.owner; | ||
session.on('invite', function() { | ||
self.emit('invite', this); | ||
}); | ||
} else { | ||
@@ -860,2 +844,14 @@ this.logger.warn('INVITE received but WebRTC is not supported'); | ||
function checkAuthenticationFactory (authenticationFactory) { | ||
if (!(authenticationFactory instanceof Function)) { | ||
return; | ||
} | ||
if (!authenticationFactory.initialize) { | ||
authenticationFactory.initialize = function initialize () { | ||
return SIP.Utils.Promise.resolve(); | ||
}; | ||
} | ||
return authenticationFactory; | ||
} | ||
/** | ||
@@ -905,2 +901,3 @@ * Configuration load. | ||
// Session parameters | ||
iceCheckingTimeout: 5000, | ||
noAnswerTimeout: 60, | ||
@@ -924,3 +921,11 @@ stunServers: ['stun:stun.l.google.com:19302'], | ||
mediaHandlerFactory: SIP.WebRTC.MediaHandler.defaultFactory | ||
// Replaces header (RFC 3891) | ||
// http://tools.ietf.org/html/rfc3891 | ||
replaces: SIP.C.supported.UNSUPPORTED, | ||
mediaHandlerFactory: SIP.WebRTC.MediaHandler.defaultFactory, | ||
authenticationFactory: checkAuthenticationFactory(function authenticationFactory (ua) { | ||
return new SIP.DigestAuthentication(ua); | ||
}) | ||
}; | ||
@@ -1126,2 +1131,3 @@ | ||
"hackWssInTransport", //false | ||
"iceCheckingTimeout", | ||
"instanceId", | ||
@@ -1134,2 +1140,3 @@ "noAnswerTimeout", // 30 seconds. | ||
"rel100", | ||
"replaces", | ||
"userAgentString", //SIP.C.USER_AGENT | ||
@@ -1146,2 +1153,3 @@ "autostart", | ||
"mediaConstraints", | ||
"authenticationFactory", | ||
@@ -1303,2 +1311,11 @@ // Post-configuration generated parameters | ||
iceCheckingTimeout: function(iceCheckingTimeout) { | ||
if(SIP.Utils.isDecimal(iceCheckingTimeout)) { | ||
if (iceCheckingTimeout < 500) { | ||
return 5000; | ||
} | ||
return iceCheckingTimeout; | ||
} | ||
}, | ||
hackWssInTransport: function(hackWssInTransport) { | ||
@@ -1350,2 +1367,12 @@ if (typeof hackWssInTransport === 'boolean') { | ||
replaces: function(replaces) { | ||
if(replaces === SIP.C.supported.REQUIRED) { | ||
return SIP.C.supported.REQUIRED; | ||
} else if (replaces === SIP.C.supported.SUPPORTED) { | ||
return SIP.C.supported.SUPPORTED; | ||
} else { | ||
return SIP.C.supported.UNSUPPORTED; | ||
} | ||
}, | ||
register: function(register) { | ||
@@ -1420,3 +1447,3 @@ if (typeof register === 'boolean') { | ||
turnServers: function(turnServers) { | ||
var idx, length, turn_server, url; | ||
var idx, jdx, length, turn_server, num_turn_server_urls, url; | ||
@@ -1441,9 +1468,11 @@ if (turnServers instanceof Array) { | ||
if (!(turn_server.urls instanceof Array)) { | ||
if (turn_server.urls instanceof Array) { | ||
num_turn_server_urls = turn_server.urls.length; | ||
} else { | ||
turn_server.urls = [turn_server.urls]; | ||
num_turn_server_urls = 1; | ||
} | ||
length = turn_server.urls.length; | ||
for (idx = 0; idx < length; idx++) { | ||
url = turn_server.urls[idx]; | ||
for (jdx = 0; jdx < num_turn_server_urls; jdx++) { | ||
url = turn_server.urls[jdx]; | ||
@@ -1502,5 +1531,25 @@ if (!(/^turns?:/.test(url))) { | ||
if (mediaHandlerFactory instanceof Function) { | ||
return mediaHandlerFactory; | ||
var promisifiedFactory = function promisifiedFactory () { | ||
var mediaHandler = mediaHandlerFactory.apply(this, arguments); | ||
function patchMethod (methodName) { | ||
var method = mediaHandler[methodName]; | ||
if (method.length > 1) { | ||
var callbacksFirst = methodName === 'getDescription'; | ||
mediaHandler[methodName] = SIP.Utils.promisify(mediaHandler, methodName, callbacksFirst); | ||
} | ||
} | ||
patchMethod('getDescription'); | ||
patchMethod('setDescription'); | ||
return mediaHandler; | ||
}; | ||
promisifiedFactory.isSupported = mediaHandlerFactory.isSupported; | ||
return promisifiedFactory; | ||
} | ||
} | ||
}, | ||
authenticationFactory: checkAuthenticationFactory | ||
} | ||
@@ -1507,0 +1556,0 @@ }; |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -63,2 +64,6 @@ * @fileoverview SIP URI | ||
aor: { | ||
get: function(){ return user + '@' + host; } | ||
}, | ||
port: { | ||
@@ -65,0 +70,0 @@ get: function(){ return port; }, |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -5,3 +6,3 @@ * @fileoverview Utils | ||
module.exports = function (SIP) { | ||
module.exports = function (SIP, environment) { | ||
var Utils; | ||
@@ -11,2 +12,26 @@ | ||
Promise: environment.Promise, | ||
defer: function defer () { | ||
var deferred = {}; | ||
deferred.promise = new Utils.Promise(function (resolve, reject) { | ||
deferred.resolve = resolve; | ||
deferred.reject = reject; | ||
}); | ||
return deferred; | ||
}, | ||
promisify: function promisify (object, methodName, callbacksFirst) { | ||
var oldMethod = object[methodName]; | ||
return function promisifiedMethod (arg, onSuccess, onFailure) { | ||
return new Utils.Promise(function (resolve, reject) { | ||
var oldArgs = [arg, resolve, reject]; | ||
if (callbacksFirst) { | ||
oldArgs = [resolve, reject, arg]; | ||
} | ||
oldMethod.apply(object, oldArgs); | ||
}).then(onSuccess, onFailure); | ||
}; | ||
}, | ||
augment: function (object, constructor, args, override) { | ||
@@ -39,21 +64,2 @@ var idx, proto; | ||
desugarSessionOptions: function desugarSessionOptions (options) { | ||
if (global.HTMLMediaElement && options instanceof global.HTMLMediaElement) { | ||
options = { | ||
media: { | ||
constraints: { | ||
audio: true, | ||
video: options.tagName === 'VIDEO' | ||
}, | ||
render: { | ||
remote: { | ||
video: options | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
return options; | ||
}, | ||
str_utf8_length: function(string) { | ||
@@ -63,16 +69,2 @@ 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) { | ||
@@ -251,2 +243,35 @@ if (!body) { | ||
getReasonPhrase: function getReasonPhrase (code, specific) { | ||
return specific || SIP.C.REASON_PHRASE[code] || ''; | ||
}, | ||
getReasonHeaderValue: function getReasonHeaderValue (code, reason) { | ||
reason = SIP.Utils.getReasonPhrase(code, reason); | ||
return 'SIP ;cause=' + code + ' ;text="' + reason + '"'; | ||
}, | ||
getCancelReason: function getCancelReason (code, reason) { | ||
if (code && code < 200 || code > 699) { | ||
throw new TypeError('Invalid status_code: ' + code); | ||
} else if (code) { | ||
return SIP.Utils.getReasonHeaderValue(code, reason); | ||
} | ||
}, | ||
buildStatusLine: function buildStatusLine (code, reason) { | ||
code = code || null; | ||
reason = reason || null; | ||
// Validate code and reason values | ||
if (!code || (code < 100 || code > 699)) { | ||
throw new TypeError('Invalid status_code: '+ code); | ||
} else if (reason && typeof reason !== 'string' && !(reason instanceof String)) { | ||
throw new TypeError('Invalid reason_phrase: '+ reason); | ||
} | ||
reason = Utils.getReasonPhrase(code, reason); | ||
return 'SIP/2.0 ' + code + ' ' + reason + '\r\n'; | ||
}, | ||
/** | ||
@@ -268,3 +293,3 @@ * Generate a random Test-Net IP (http://tools.ietf.org/html/rfc5735) | ||
for (event in SIP.UA.C.EVENT_METHODS) { | ||
if (ua.checkListener(event)) { | ||
if (ua.listeners(event).length) { | ||
allowed += ','+ SIP.UA.C.EVENT_METHODS[event]; | ||
@@ -271,0 +296,0 @@ } |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -5,3 +6,3 @@ * @fileoverview WebRTC | ||
module.exports = function (Utils, window, MediaHandler, MediaStreamManager) { | ||
module.exports = function (SIP, environment) { | ||
var WebRTC; | ||
@@ -11,4 +12,4 @@ | ||
WebRTC.MediaHandler = MediaHandler; | ||
WebRTC.MediaStreamManager = MediaStreamManager; | ||
WebRTC.MediaHandler = require('./WebRTC/MediaHandler')(SIP); | ||
WebRTC.MediaStreamManager = require('./WebRTC/MediaStreamManager')(SIP, environment); | ||
@@ -22,9 +23,11 @@ var _isSupported; | ||
WebRTC.MediaStream = Utils.getPrefixedProperty(window, 'MediaStream'); | ||
WebRTC.getUserMedia = Utils.getPrefixedProperty(window.navigator, 'getUserMedia'); | ||
WebRTC.RTCPeerConnection = Utils.getPrefixedProperty(window, 'RTCPeerConnection'); | ||
WebRTC.RTCSessionDescription = Utils.getPrefixedProperty(window, 'RTCSessionDescription'); | ||
WebRTC.MediaStream = environment.MediaStream; | ||
WebRTC.getUserMedia = environment.getUserMedia; | ||
WebRTC.RTCPeerConnection = environment.RTCPeerConnection; | ||
WebRTC.RTCSessionDescription = environment.RTCSessionDescription; | ||
if (WebRTC.getUserMedia && WebRTC.RTCPeerConnection && WebRTC.RTCSessionDescription) { | ||
WebRTC.getUserMedia = WebRTC.getUserMedia.bind(window.navigator); | ||
if (WebRTC.RTCPeerConnection && WebRTC.RTCSessionDescription) { | ||
if (WebRTC.getUserMedia) { | ||
WebRTC.getUserMedia = SIP.Utils.promisify(environment, 'getUserMedia'); | ||
} | ||
_isSupported = true; | ||
@@ -31,0 +34,0 @@ } |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -16,15 +17,2 @@ * @fileoverview MediaHandler | ||
var MediaHandler = function(session, options) { | ||
var events = [ | ||
'userMediaRequest', | ||
'userMedia', | ||
'userMediaFailed', | ||
'iceGathering', | ||
'iceCandidate', | ||
'iceComplete', | ||
'iceFailed', | ||
'getDescription', | ||
'setDescription', | ||
'dataChannel', | ||
'addStream' | ||
]; | ||
options = options || {}; | ||
@@ -41,3 +29,3 @@ | ||
// old init() from here on | ||
var idx, length, server, | ||
var idx, jdx, length, server, | ||
self = this, | ||
@@ -68,13 +56,30 @@ servers = [], | ||
server = turnServers[idx]; | ||
servers.push({ | ||
'url': server.urls, | ||
'username': server.username, | ||
'credential': server.password | ||
}); | ||
for (jdx = 0; jdx < server.urls.length; jdx++) { | ||
servers.push({ | ||
'url': server.urls[jdx], | ||
'username': server.username, | ||
'credential': server.password | ||
}); | ||
} | ||
} | ||
this.onIceCompleted = SIP.Utils.defer(); | ||
this.onIceCompleted.promise.then(function(pc) { | ||
self.emit('iceGatheringComplete', pc); | ||
if (self.iceCheckingTimer) { | ||
SIP.Timers.clearTimeout(self.iceCheckingTimer); | ||
self.iceCheckingTimer = null; | ||
} | ||
}); | ||
this.peerConnection = new SIP.WebRTC.RTCPeerConnection({'iceServers': servers}, this.RTCConstraints); | ||
// Firefox (35.0.1) sometimes throws on calls to peerConnection.getRemoteStreams | ||
// even if peerConnection.onaddstream was just called. In order to make | ||
// MediaHandler.prototype.getRemoteStreams work, keep track of them manually | ||
this._remoteStreams = []; | ||
this.peerConnection.onaddstream = function(e) { | ||
self.logger.log('stream added: '+ e.stream.id); | ||
self._remoteStreams.push(e.stream); | ||
self.render(); | ||
@@ -92,6 +97,4 @@ self.emit('addStream', e); | ||
self.logger.log('ICE candidate received: '+ (e.candidate.candidate === null ? null : e.candidate.candidate.trim())); | ||
} else if (self.onIceCompleted !== undefined) { | ||
self.onIceCompleted(this); | ||
} else { | ||
self.callOnIceCompleted = true; | ||
self.onIceCompleted.resolve(this); | ||
} | ||
@@ -106,7 +109,3 @@ }; | ||
if (this.iceGatheringState === 'complete') { | ||
if (self.onIceCompleted !== undefined) { | ||
self.onIceCompleted(this); | ||
} else { | ||
self.callOnIceCompleted = true; | ||
} | ||
self.onIceCompleted.resolve(this); | ||
} | ||
@@ -116,8 +115,40 @@ }; | ||
this.peerConnection.oniceconnectionstatechange = function() { //need e for commented out case | ||
self.logger.log('ICE connection state changed to "'+ this.iceConnectionState +'"'); | ||
var stateEvent; | ||
if (this.iceConnectionState === 'failed') { | ||
self.emit('iceFailed', this); | ||
if (this.iceConnectionState === 'checking') { | ||
self.iceCheckingTimer = SIP.Timers.setTimeout(function() { | ||
self.logger.log('RTCIceChecking Timeout Triggered after '+config.iceCheckingTimeout+' micro seconds'); | ||
self.onIceCompleted.resolve(this); | ||
}.bind(this), config.iceCheckingTimeout); | ||
} | ||
switch (this.iceConnectionState) { | ||
case 'new': | ||
stateEvent = 'iceConnection'; | ||
break; | ||
case 'checking': | ||
stateEvent = 'iceConnectionChecking'; | ||
break; | ||
case 'connected': | ||
stateEvent = 'iceConnectionConnected'; | ||
break; | ||
case 'completed': | ||
stateEvent = 'iceConnectionCompleted'; | ||
break; | ||
case 'failed': | ||
stateEvent = 'iceConnectionFailed'; | ||
break; | ||
case 'disconnected': | ||
stateEvent = 'iceConnectionDisconnected'; | ||
break; | ||
case 'closed': | ||
stateEvent = 'iceConnectionClosed'; | ||
break; | ||
default: | ||
self.logger.warn('Unknown iceConnection state:', this.iceConnectionState); | ||
return; | ||
} | ||
self.emit(stateEvent, this); | ||
//Bria state changes are always connected -> disconnected -> connected on accept, so session gets terminated | ||
@@ -140,8 +171,4 @@ //normal calls switch from failed to connected in some cases, so checking for failed and terminated | ||
this.initEvents(events); | ||
function selfEmit(mh, event) { | ||
if (mh.mediaStreamManager.on && | ||
mh.mediaStreamManager.checkEvent && | ||
mh.mediaStreamManager.checkEvent(event)) { | ||
if (mh.mediaStreamManager.on) { | ||
mh.mediaStreamManager.on(event, function () { | ||
@@ -173,2 +200,3 @@ mh.emit.apply(mh, [event].concat(Array.prototype.slice.call(arguments))); | ||
this.logger.log('closing PeerConnection'); | ||
this._remoteStreams = []; | ||
// have to check signalingState since this.close() gets called multiple times | ||
@@ -186,9 +214,11 @@ // TODO figure out why that happens | ||
/** | ||
* @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) { | ||
getDescription: {writable: true, value: function getDescription (mediaHint) { | ||
var self = this; | ||
var acquire = self.mediaStreamManager.acquire; | ||
if (acquire.length > 1) { | ||
acquire = SIP.Utils.promisify(this.mediaStreamManager, 'acquire', true); | ||
} | ||
mediaHint = mediaHint || {}; | ||
@@ -201,56 +231,50 @@ if (mediaHint.dataChannel === true) { | ||
/* | ||
* 1. acquire stream (skip if MediaStream passed in) | ||
* 2. addStream | ||
* 1. acquire streams (skip if MediaStreams passed in) | ||
* 2. addStreams | ||
* 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.render(); | ||
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 | ||
); | ||
} | ||
var streamPromise; | ||
if (self.localMedia) { | ||
self.logger.log('already have local media'); | ||
streamAdditionSucceeded(); | ||
return; | ||
streamPromise = SIP.Utils.Promise.resolve(self.localMedia); | ||
} | ||
else { | ||
self.logger.log('acquiring local media'); | ||
streamPromise = acquire.call(self.mediaStreamManager, mediaHint) | ||
.then(function acquireSucceeded(streams) { | ||
self.logger.log('acquired local media streams'); | ||
self.localMedia = streams; | ||
self.session.connecting(); | ||
return streams; | ||
}, function acquireFailed(err) { | ||
self.logger.error('unable to acquire streams'); | ||
self.logger.error(err); | ||
self.session.connecting(); | ||
throw err; | ||
}) | ||
.then(this.addStreams.bind(this)) | ||
; | ||
} | ||
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 | ||
); | ||
return streamPromise | ||
.then(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.render(); | ||
return self.createOfferOrAnswer(self.RTCConstraints); | ||
}) | ||
; | ||
}}, | ||
@@ -262,6 +286,4 @@ | ||
* @param {String} sdp | ||
* @param {Function} onSuccess | ||
* @param {Function} onFailure | ||
*/ | ||
setDescription: {writable: true, value: function setDescription (sdp, onSuccess, onFailure) { | ||
setDescription: {writable: true, value: function setDescription (sdp) { | ||
var rawDescription = { | ||
@@ -275,5 +297,28 @@ type: this.hasOffer('local') ? 'answer' : 'offer', | ||
var description = new SIP.WebRTC.RTCSessionDescription(rawDescription); | ||
this.peerConnection.setRemoteDescription(description, onSuccess, onFailure); | ||
return SIP.Utils.promisify(this.peerConnection, 'setRemoteDescription')(description); | ||
}}, | ||
/** | ||
* If the Session associated with this MediaHandler were to be referred, | ||
* what mediaHint should be provided to the UA's invite method? | ||
*/ | ||
getReferMedia: {writable: true, value: function getReferMedia () { | ||
function hasTracks (trackGetter, stream) { | ||
return stream[trackGetter]().length > 0; | ||
} | ||
function bothHaveTracks (trackGetter) { | ||
/* jshint validthis:true */ | ||
return this.getLocalStreams().some(hasTracks.bind(null, trackGetter)) && | ||
this.getRemoteStreams().some(hasTracks.bind(null, trackGetter)); | ||
} | ||
return { | ||
constraints: { | ||
audio: bothHaveTracks.call(this, 'getAudioTracks'), | ||
video: bothHaveTracks.call(this, 'getVideoTracks') | ||
} | ||
}; | ||
}}, | ||
// Functions the session can use, but only because it's convenient for the application | ||
@@ -392,4 +437,4 @@ isMuted: {writable: true, value: function isMuted () { | ||
if (pc && pc.signalingState === 'closed') { | ||
this.logger.warn('peerConnection is closed, getRemoteStreams returning []'); | ||
return []; | ||
this.logger.warn('peerConnection is closed, getRemoteStreams returning this._remoteStreams'); | ||
return this._remoteStreams; | ||
} | ||
@@ -412,5 +457,3 @@ return(pc.getRemoteStreams && pc.getRemoteStreams()) || | ||
var streams = this[streamGetter](); | ||
if (streams.length) { | ||
SIP.WebRTC.MediaStreamManager.render(streams[0], renderHint[loc]); | ||
} | ||
SIP.WebRTC.MediaStreamManager.render(streams, renderHint[loc]); | ||
}.bind(this)); | ||
@@ -426,75 +469,59 @@ }}, | ||
createOfferOrAnswer: {writable: true, value: function createOfferOrAnswer (onSuccess, onFailure, constraints) { | ||
createOfferOrAnswer: {writable: true, value: function createOfferOrAnswer (constraints) { | ||
var self = this; | ||
var methodName; | ||
var pc = self.peerConnection; | ||
function readySuccess () { | ||
var sdp = self.peerConnection.localDescription.sdp; | ||
self.ready = false; | ||
methodName = self.hasOffer('remote') ? 'createAnswer' : 'createOffer'; | ||
sdp = SIP.Hacks.Chrome.needsExplicitlyInactiveSDP(sdp); | ||
sdp = SIP.Hacks.AllBrowsers.unmaskDtls(sdp); | ||
sdp = SIP.Hacks.Firefox.hasIncompatibleCLineWithSomeSIPEndpoints(sdp); | ||
return SIP.Utils.promisify(pc, methodName, true)(constraints) | ||
.then(SIP.Utils.promisify(pc, 'setLocalDescription')) | ||
.then(function onSetLocalDescriptionSuccess() { | ||
var deferred = SIP.Utils.defer(); | ||
if (pc.iceGatheringState === 'complete' && (pc.iceConnectionState === 'connected' || pc.iceConnectionState === 'completed')) { | ||
deferred.resolve(); | ||
} else { | ||
self.onIceCompleted.promise.then(deferred.resolve); | ||
} | ||
return deferred.promise; | ||
}) | ||
.then(function readySuccess () { | ||
var sdp = pc.localDescription.sdp; | ||
var sdpWrapper = { | ||
type: methodName === 'createOffer' ? 'offer' : 'answer', | ||
sdp: sdp | ||
}; | ||
sdp = SIP.Hacks.Chrome.needsExplicitlyInactiveSDP(sdp); | ||
sdp = SIP.Hacks.AllBrowsers.unmaskDtls(sdp); | ||
sdp = SIP.Hacks.Firefox.hasIncompatibleCLineWithSomeSIPEndpoints(sdp); | ||
self.emit('getDescription', sdpWrapper); | ||
self.ready = true; | ||
onSuccess(sdpWrapper.sdp); | ||
} | ||
function onSetLocalDescriptionSuccess() { | ||
if (self.peerConnection.iceGatheringState === 'complete' && (self.peerConnection.iceConnectionState === 'connected' || self.peerConnection.iceConnectionState === 'completed')) { | ||
readySuccess(); | ||
} else { | ||
self.onIceCompleted = function(pc) { | ||
self.logger.log('ICE Gathering Completed'); | ||
self.onIceCompleted = undefined; | ||
self.emit('iceComplete', pc); | ||
readySuccess(); | ||
var sdpWrapper = { | ||
type: methodName === 'createOffer' ? 'offer' : 'answer', | ||
sdp: sdp | ||
}; | ||
if (self.callOnIceCompleted) { | ||
self.onIceCompleted(); | ||
} | ||
} | ||
} | ||
function methodFailed (methodName, e) { | ||
self.logger.error('peerConnection.' + methodName + ' failed'); | ||
self.logger.error(e); | ||
self.ready = true; | ||
onFailure(e); | ||
} | ||
self.emit('getDescription', sdpWrapper); | ||
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 | ||
); | ||
self.ready = true; | ||
return sdpWrapper.sdp; | ||
}) | ||
.catch(function methodFailed (e) { | ||
self.logger.error(e); | ||
self.ready = true; | ||
throw new SIP.Exceptions.GetDescriptionError(e); | ||
}) | ||
; | ||
}}, | ||
addStream: {writable: true, value: function addStream (stream, onSuccess, onFailure) { | ||
addStreams: {writable: true, value: function addStreams (streams) { | ||
try { | ||
this.peerConnection.addStream(stream); | ||
streams = [].concat(streams); | ||
streams.forEach(function (stream) { | ||
this.peerConnection.addStream(stream); | ||
}, this); | ||
} catch(e) { | ||
this.logger.error('error adding stream'); | ||
this.logger.error(e); | ||
onFailure(e); | ||
return; | ||
return SIP.Utils.Promise.reject(e); | ||
} | ||
onSuccess(); | ||
return SIP.Utils.Promise.resolve(); | ||
}}, | ||
@@ -501,0 +528,0 @@ |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
/** | ||
@@ -9,3 +10,3 @@ * @fileoverview MediaStreamManager | ||
*/ | ||
module.exports = function (SIP) { | ||
module.exports = function (SIP, environment) { | ||
@@ -18,7 +19,2 @@ // Default MediaStreamManager provides single-use streams created with getUserMedia | ||
var events = [ | ||
'userMediaRequest', | ||
'userMedia', | ||
'userMediaFailed' | ||
]; | ||
this.mediaHint = defaultMediaHint || { | ||
@@ -28,5 +24,2 @@ constraints: {audio: true, video: true} | ||
this.logger = logger; | ||
this.initEvents(events); | ||
// map of streams to acquisition manner: | ||
@@ -45,9 +38,25 @@ // true -> passed in as mediaHint.stream | ||
MediaStreamManager.render = function render (stream, elements) { | ||
/** | ||
* @param {(Array of) MediaStream} streams - The streams to render | ||
* | ||
* @param {(Array of) HTMLMediaElement} elements | ||
* - The <audio>/<video> element(s) that should render the streams | ||
* | ||
* Each stream in streams renders to the corresponding element in elements, | ||
* wrapping around elements if needed. | ||
*/ | ||
MediaStreamManager.render = function render (streams, elements) { | ||
if (!elements) { | ||
return false; | ||
} | ||
if (Array.isArray(elements) && !elements.length) { | ||
throw new TypeError('elements must not be empty'); | ||
} | ||
function attachAndPlay (element, stream) { | ||
(window.attachMediaStream || attachMediaStream)(element, stream); | ||
function attachAndPlay (elements, stream, index) { | ||
if (typeof elements === 'function') { | ||
elements = elements(); | ||
} | ||
var element = elements[index % elements.length]; | ||
(environment.attachMediaStream || attachMediaStream)(element, stream); | ||
ensureMediaPlaying(element); | ||
@@ -58,4 +67,4 @@ } | ||
if (typeof element.src !== 'undefined') { | ||
URL.revokeObjectURL(element.src); | ||
element.src = URL.createObjectURL(stream); | ||
environment.revokeObjectURL(element.src); | ||
element.src = environment.createObjectURL(stream); | ||
} else if (typeof (element.srcObject || element.mozSrcObject) !== 'undefined') { | ||
@@ -82,25 +91,23 @@ element.srcObject = element.mozSrcObject = stream; | ||
if (elements.video) { | ||
if (elements.audio) { | ||
elements.video.volume = 0; | ||
} | ||
attachAndPlay(elements.video, stream); | ||
} | ||
if (elements.audio) { | ||
attachAndPlay(elements.audio, stream); | ||
} | ||
// [].concat "casts" `elements` into an array | ||
// so forEach works even if `elements` was a single element | ||
elements = [].concat(elements); | ||
[].concat(streams).forEach(attachAndPlay.bind(null, elements)); | ||
}; | ||
MediaStreamManager.prototype = Object.create(SIP.EventEmitter.prototype, { | ||
'acquire': {value: function acquire (onSuccess, onFailure, mediaHint) { | ||
'acquire': {writable: true, value: function acquire (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); | ||
var saveSuccess = function (isHintStream, streams) { | ||
streams = [].concat(streams); | ||
streams.forEach(function (stream) { | ||
var streamId = MediaStreamManager.streamId(stream); | ||
this.acquisitions[streamId] = !!isHintStream; | ||
}, this); | ||
return SIP.Utils.Promise.resolve(streams); | ||
}.bind(this); | ||
if (mediaHint.stream) { | ||
saveSuccess(mediaHint.stream, true); | ||
return saveSuccess(true, mediaHint.stream); | ||
} else { | ||
@@ -112,2 +119,4 @@ // Fallback to audio/video enabled if no mediaHint can be found. | ||
var deferred = SIP.Utils.defer(); | ||
/* | ||
@@ -127,20 +136,27 @@ * Make the call asynchronous, so that ICCs have a chance | ||
callback.apply(null, callbackArgs); | ||
return callback.apply(null, callbackArgs); | ||
}.bind(this); | ||
SIP.WebRTC.getUserMedia( | ||
constraints, | ||
emitThenCall.bind(this, 'userMedia', saveSuccess), | ||
emitThenCall.bind(this, 'userMediaFailed', onFailure) | ||
deferred.resolve( | ||
SIP.WebRTC.getUserMedia(constraints) | ||
.then( | ||
emitThenCall.bind(this, 'userMedia', saveSuccess.bind(null, false)), | ||
emitThenCall.bind(this, 'userMediaFailed', function(e){throw e;}) | ||
) | ||
); | ||
}.bind(this), 0); | ||
return deferred.promise; | ||
} | ||
}}, | ||
'release': {value: function release (stream) { | ||
var streamId = MediaStreamManager.streamId(stream); | ||
if (this.acquisitions[streamId] === false) { | ||
stream.stop(); | ||
} | ||
delete this.acquisitions[streamId]; | ||
'release': {writable: true, value: function release (streams) { | ||
streams = [].concat(streams); | ||
streams.forEach(function (stream) { | ||
var streamId = MediaStreamManager.streamId(stream); | ||
if (this.acquisitions[streamId] === false) { | ||
stream.stop(); | ||
} | ||
delete this.acquisitions[streamId]; | ||
}, this); | ||
}}, | ||
@@ -147,0 +163,0 @@ }); |
@@ -18,4 +18,7 @@ (function () { | ||
close: function () { | ||
this.readyState = 3; // CLOSED | ||
if (this.onclose) this.onclose({code:3}); | ||
var that = this; | ||
setTimeout(function () { | ||
that.readyState = 3; // CLOSED | ||
if (that.onclose) that.onclose({code:3}); | ||
}, 0); | ||
}, | ||
@@ -22,0 +25,0 @@ |
@@ -177,3 +177,76 @@ /** Some valid SDP strings to pretend that browsers generated for SIP.js */ | ||
'a=ssrc:3254389050 label:aa5e18ed-eb5f-4475-8383-6d6b5abae41d\r\n' + | ||
'\r\n' | ||
'\r\n', | ||
replaces: 'INVITE sip:alice@example.com;transport=ws SIP/2.0\r\n' + | ||
'Replaces: or1ek18v4gti27r1vt91;to-tag=dt0sj4e5ek;from-tag=qviijql90r\r\n' + | ||
'Via: SIP/2.0/WSS u3legsua5tov.invalid;branch=z9hG4bK4798355\r\n' + | ||
'Max-Forwards: 65\r\n' + | ||
'To: <sip:alice@example.com>\r\n' + | ||
'From: <sip:bob@example.com>;tag=lug30cg783\r\n' + | ||
'Call-ID: 2e0tofg49n9qvhjlrr63\r\n' + | ||
'CSeq: 7773 INVITE\r\n' + | ||
'Contact: <sip:h9po1ojc@u3legsua5tov.invalid;transport=ws;ob>\r\n' + | ||
'Allow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE\r\n' + | ||
'Content-Type: application/sdp\r\n' + | ||
'Supported: outbound\r\n' + | ||
'User-Agent: SIP.js 0.5.0-devel\r\n' + | ||
'Content-Length: 0\r\n' + | ||
'\r\n', | ||
rps: { | ||
rock: 'INVITE sip:alice@example.com SIP/2.0\r\n' + | ||
'Via: SIP/2.0/WSS u3legsua5tov.invalid;branch=z9hG4bK4798355\r\n' + | ||
'Max-Forwards: 65\r\n' + | ||
'To: <sip:alice@example.com>\r\n' + | ||
'From: <sip:bob@example.com>;tag=lug30cg783\r\n' + | ||
'Call-ID: 2e0tofg49n9qvhjlrr63\r\n' + | ||
'CSeq: 7773 INVITE\r\n' + | ||
'Contact: <sip:h9po1ojc@u3legsua5tov.invalid;transport=ws;ob>\r\n' + | ||
'Allow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE\r\n' + | ||
'Content-Type: application/sdp\r\n' + | ||
'Supported: outbound\r\n' + | ||
'User-Agent: SIP.js 0.5.0-devel\r\n' + | ||
'Content-Length: 4\r\n' + | ||
'\r\n' + | ||
'rock\r\n' + | ||
'\r\n', | ||
cancel: 'CANCEL sip:alice@example.com SIP/2.0\r\n' + | ||
'Via: SIP/2.0/WSS nn6bh156cpod.invalid;branch=z9hG4bK4798355\r\n' + | ||
'To: <sip:alice@example.com>\r\n' + | ||
'From: <sip:bob@example.com>;tag=lug30cg783\r\n' + | ||
'Call-ID: 2e0tofg49n9qvhjlrr63\r\n' + | ||
'CSeq: 7773 CANCEL\r\n' + | ||
'Content-Length: 0\r\n' + | ||
'\r\n', | ||
ack: function (tag) { | ||
return 'ACK sip:alice@example.com SIP/2.0\r\n' + | ||
'Via: SIP/2.0/WSS nn6bh156cpod.invalid;branch=z9hG4bK4798355\r\n' + | ||
'Max-Forwards: 66\r\n' + | ||
'To: <sip:alice@example.com>;tag=' + tag + '\r\n' + | ||
'From: <sip:bob@example.com>;tag=lug30cg783\r\n' + | ||
'Call-ID: 2e0tofg49n9qvhjlrr63\r\n' + | ||
'CSeq: 7773 ACK\r\n' + | ||
'Contact: <sip:h9po1ojc@u3legsua5tov.invalid;transport=ws;ob>\r\n' + | ||
'Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, PRACK, NOTIFY\r\n' + | ||
'Content-Length: 0\r\n' + | ||
'\r\n'; | ||
}, | ||
bye: function (tag) { | ||
return 'BYE sip:alice@example.com SIP/2.0\r\n' + | ||
'Via: SIP/2.0/WSS nn6bh156cpod.invalid;branch=z9hG4bK4798355\r\n' + | ||
'Max-Forwards: 66\r\n' + | ||
'To: <sip:alice@example.com>;tag=' + tag + '\r\n' + | ||
'From: <sip:bob@example.com>;tag=lug30cg783\r\n' + | ||
'Call-ID: 2e0tofg49n9qvhjlrr63\r\n' + | ||
'CSeq: 7774 BYE\r\n' + | ||
'Contact: <sip:h9po1ojc@u3legsua5tov.invalid;transport=ws;ob>\r\n' + | ||
'Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, PRACK, NOTIFY\r\n' + | ||
'Content-Length: 0\r\n' + | ||
'\r\n'; | ||
}, | ||
} | ||
}; |
@@ -14,8 +14,2 @@ describe('MediaStreamManager', function () { | ||
it('initializes its events', function () { | ||
expect(mediaStreamManager.checkEvent('userMediaRequest')).toEqual(true); | ||
expect(mediaStreamManager.checkEvent('userMedia')).toEqual(true); | ||
expect(mediaStreamManager.checkEvent('userMediaFailed')).toEqual(true); | ||
}); | ||
it('defines mediaHint and acquisitions', function () { | ||
@@ -30,3 +24,3 @@ expect(mediaStreamManager.mediaHint).toBeDefined(); | ||
beforeEach(function () { | ||
spyOn(SIP.WebRTC, 'getUserMedia'); | ||
spyOn(SIP.WebRTC, 'getUserMedia').and.callThrough(); | ||
onSuccess = function yay () {}; | ||
@@ -42,7 +36,7 @@ onFailure = function boo () {}; | ||
var constraints = {audio: false, video: true}; | ||
mediaStreamManager.acquire(onSuccess, onFailure, {constraints: constraints}); | ||
setTimeout(function () { | ||
mediaStreamManager.acquire({constraints: constraints}).then(onSuccess, onFailure) | ||
.then(function () { | ||
expect(SIP.WebRTC.getUserMedia.calls.mostRecent().args[0]).toEqual(constraints); | ||
done(); | ||
}, 0); | ||
}); | ||
}); | ||
@@ -54,3 +48,3 @@ | ||
SIP.WebRTC.getUserMedia.and.callFake(function () { | ||
done(); | ||
return SIP.Utils.Promise.resolve().then(done); | ||
}); | ||
@@ -60,3 +54,3 @@ }; | ||
mediaStreamManager.acquire(onSuccess, onFailure, { | ||
mediaStreamManager.acquire({ | ||
constraints: { | ||
@@ -66,3 +60,3 @@ audio: true, | ||
} | ||
}); | ||
}).then(onSuccess, onFailure); | ||
}); | ||
@@ -83,3 +77,3 @@ | ||
mediaStreamManager.on('userMedia', onUM); | ||
mediaStreamManager.acquire(success, failure, { | ||
mediaStreamManager.acquire({ | ||
constraints: { | ||
@@ -89,3 +83,3 @@ audio: true, | ||
} | ||
}); | ||
}).then(success, failure); | ||
}); | ||
@@ -100,10 +94,7 @@ | ||
it('emits userMediaFailed when getUserMedia calls a failure callback', function () { | ||
it('emits userMediaFailed when getUserMedia calls a failure callback', function (done) { | ||
var success, failure, onUMF; | ||
SIP.WebRTC.getUserMedia.and.callFake(function (c, s, f) { | ||
f(); | ||
expect(onUMF).toHaveBeenCalled(); | ||
expect(success).not.toHaveBeenCalled(); | ||
expect(failure).toHaveBeenCalled(); | ||
SIP.WebRTC.getUserMedia.and.callFake(function (c) { | ||
return SIP.Utils.Promise.reject(); | ||
}); | ||
@@ -117,3 +108,3 @@ | ||
mediaStreamManager.acquire(success, failure, { | ||
mediaStreamManager.acquire({ | ||
constraints: { | ||
@@ -123,2 +114,8 @@ audio: true, | ||
} | ||
}).then(success, failure) | ||
.then(function () { | ||
expect(onUMF).toHaveBeenCalled(); | ||
expect(success).not.toHaveBeenCalled(); | ||
expect(failure).toHaveBeenCalled(); | ||
done(); | ||
}); | ||
@@ -133,3 +130,5 @@ }); | ||
mediaStreamManager.acquire( | ||
function onSuccess (stream) { | ||
{constraints: {audio: true}}).then( | ||
function onSuccess (streams) { | ||
var stream = streams[0]; | ||
acquiredStream = stream; | ||
@@ -141,4 +140,3 @@ mediaStreamManager.release(stream); | ||
throw new Error(); | ||
}, | ||
{constraints: {audio: true}} | ||
} | ||
); | ||
@@ -166,19 +164,32 @@ }); | ||
it('.acquire ignores constraints and succeeds with the stream', function () { | ||
mediaStreamManager.acquire(onSuccess, onFailure, mediaHint); | ||
expect(onSuccess).toHaveBeenCalledWith(mediaHint.stream); | ||
it('.acquire ignores constraints and succeeds with the stream', function (done) { | ||
mediaStreamManager.acquire(mediaHint).then(onSuccess, onFailure) | ||
.then(function () { | ||
expect(onSuccess).toHaveBeenCalledWith([mediaHint.stream]); | ||
done(); | ||
}); | ||
}); | ||
it('.acquire called twice in a row does not fail', function () { | ||
mediaStreamManager.acquire(onSuccess, onFailure); | ||
mediaStreamManager.acquire(onSuccess, onFailure); | ||
expect(onFailure).not.toHaveBeenCalled(); | ||
it('.acquire called twice in a row does not fail', function (done) { | ||
mediaStreamManager.acquire(mediaHint).then(onSuccess, onFailure). | ||
then(mediaStreamManager.acquire(mediaHint)).then(onSuccess, onFailure). | ||
then(function () { | ||
expect(onSuccess).toHaveBeenCalled(); | ||
done(); | ||
}). | ||
catch(function () { | ||
expect(onFailure).not.toHaveBeenCalled(); | ||
done(); | ||
}); | ||
}); | ||
it('.release does not stop the stream', function () { | ||
mediaStreamManager.acquire(onSuccess, onFailure, mediaHint); | ||
mediaStreamManager.release(stream); | ||
expect(stream.stop).not.toHaveBeenCalled(); | ||
it('.release does not stop the stream', function (done) { | ||
mediaStreamManager.acquire(mediaHint).then(onSuccess, onFailure) | ||
.then(function () { | ||
mediaStreamManager.release(stream); | ||
expect(stream.stop).not.toHaveBeenCalled(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -1,6 +0,1 @@ | ||
SIP.LoggerFactory.prototype.debug = | ||
SIP.LoggerFactory.prototype.log = | ||
SIP.LoggerFactory.prototype.warn = | ||
SIP.LoggerFactory.prototype.error = function f() {}; | ||
describe('ClientContext', function() { | ||
@@ -67,9 +62,2 @@ var ClientContext; | ||
it('initializes events', function() { | ||
expect(ClientContext.checkEvent('progress')).toBeTruthy(); | ||
expect(ClientContext.checkEvent('accepted')).toBeTruthy(); | ||
expect(ClientContext.checkEvent('rejected')).toBeTruthy(); | ||
expect(ClientContext.checkEvent('failed')).toBeTruthy(); | ||
}); | ||
it('checks that the target is not undefined', function() { | ||
@@ -114,6 +102,19 @@ expect(function () { new SIP.ClientContext(ua,method); }).toThrowError('Not enough arguments'); | ||
beforeEach(function() { | ||
response = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', ua); | ||
response = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
''].join('\r\n'), ua); | ||
}); | ||
it('emits progress on a 1xx response', function() { | ||
it('emits progress on a 100-199 response', function() { | ||
spyOn(ClientContext, 'emit'); | ||
@@ -120,0 +121,0 @@ |
@@ -9,3 +9,18 @@ describe('Dialogs', function() { | ||
ua.transport = jasmine.createSpyObj('transport', ['disconnect', 'send']); | ||
message = SIP.Parser.parseMessage('INVITE sip:gled5gsn@hk95bautgaa7.invalid;transport=ws;aor=james%40onsnip.onsip.com SIP/2.0\r\nMax-Forwards: 65\r\nTo: <sip:james@onsnip.onsip.com>\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=rto5ib4052\r\nCall-ID: grj0liun879lfj35evfq\r\nCSeq: 1798 INVITE\r\nContact: <sip:e55r35u3@kgu78r4e1e6j.invalid;transport=ws;ob>\r\nAllow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE\r\nContent-Type: application/sdp\r\nSupported: outbound\r\nUser-Agent: SIP.js 0.5.0-devel\r\nContent-Length: 11\r\n\r\na=sendrecv\r\n', ua); | ||
message = SIP.Parser.parseMessage([ | ||
'INVITE sip:gled5gsn@hk95bautgaa7.invalid;transport=ws;aor=james%40onsnip.onsip.com SIP/2.0', | ||
'Max-Forwards: 65', | ||
'To: <sip:james@onsnip.onsip.com>', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=rto5ib4052', | ||
'Call-ID: grj0liun879lfj35evfq', | ||
'CSeq: 1798 INVITE', | ||
'Contact: <sip:e55r35u3@kgu78r4e1e6j.invalid;transport=ws;ob>', | ||
'Allow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE', | ||
'Content-Type: application/sdp', | ||
'Supported: outbound', | ||
'User-Agent: SIP.js 0.5.0-devel', | ||
'Content-Length: 11', | ||
'', | ||
'a=sendrecv', | ||
''].join('\r\n'), ua); | ||
spyOn(message, 'reply'); | ||
@@ -30,3 +45,17 @@ | ||
it('returns an error if the message has no contact header', function() { | ||
var mes = SIP.Parser.parseMessage('INVITE sip:gled5gsn@hk95bautgaa7.invalid;transport=ws;aor=james%40onsnip.onsip.com SIP/2.0\r\nMax-Forwards: 65\r\nTo: <sip:james@onsnip.onsip.com>\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=rto5ib4052\r\nCall-ID: grj0liun879lfj35evfq\r\nCSeq: 1798 INVITE\r\nAllow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE\r\nContent-Type: application/sdp\r\nSupported: outbound\r\nUser-Agent: SIP.js 0.5.0-devel\r\nContent-Length: 11\r\n\r\na=sendrecv\r\n', owner.ua); | ||
var mes = SIP.Parser.parseMessage([ | ||
'INVITE sip:gled5gsn@hk95bautgaa7.invalid;transport=ws;aor=james%40onsnip.onsip.com SIP/2.0', | ||
'Max-Forwards: 65', | ||
'To: <sip:james@onsnip.onsip.com>', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=rto5ib4052', | ||
'Call-ID: grj0liun879lfj35evfq', | ||
'CSeq: 1798 INVITE', | ||
'Allow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE', | ||
'Content-Type: application/sdp', | ||
'Supported: outbound', | ||
'User-Agent: SIP.js 0.5.0-devel', | ||
'Content-Length: 11', | ||
'', | ||
'a=sendrecv', | ||
''].join('\r\n'), owner.ua); | ||
@@ -39,3 +68,16 @@ mes.transport = owner.ua.transport; | ||
it('sets the state correctly', function() { | ||
var resp = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', owner.ua); | ||
var resp = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
''].join('\r\n'), owner.ua); | ||
@@ -42,0 +84,0 @@ resp.transport = owner.ua.transport; |
describe('EventEmitter', function () { | ||
var EventEmitter, checkEvent, | ||
var EventEmitter, | ||
setA = ['aaa', 'bbb', 'ccc'], | ||
@@ -23,3 +23,2 @@ setB = ['ddd', 'eee', 'fff'], | ||
}; | ||
checkEvent = EventEmitter.checkEvent.bind(EventEmitter); | ||
}); | ||
@@ -31,99 +30,15 @@ | ||
it('checks its own events', function () { | ||
EventEmitter.events = {}; | ||
expect(EventEmitter.checkEvent('aaa')).toBe(false); | ||
expect(EventEmitter.checkEvent('789')).toBe(false); | ||
expect(EventEmitter.checkEvent('-.&')).toBe(false); | ||
EventEmitter.events.aaa = [function () {}]; | ||
expect(EventEmitter.checkEvent('aaa')).toBe(true); | ||
expect(EventEmitter.checkEvent('789')).toBe(false); | ||
expect(EventEmitter.checkEvent('-.&')).toBe(false); | ||
EventEmitter.events['789'] = [function () {}]; | ||
expect(EventEmitter.checkEvent('aaa')).toBe(true); | ||
expect(EventEmitter.checkEvent('789')).toBe(true); | ||
expect(EventEmitter.checkEvent('-.&')).toBe(false); | ||
EventEmitter.events = { | ||
'789': [function () {}], | ||
'-.&': [function () {}] | ||
}; | ||
expect(EventEmitter.checkEvent('aaa')).toBe(false); | ||
expect(EventEmitter.checkEvent('789')).toBe(true); | ||
expect(EventEmitter.checkEvent('-.&')).toBe(true); | ||
}); | ||
it('checks for events with listeners', function () { | ||
EventEmitter.events = {}; | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
EventEmitter.removeAllListeners(); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
EventEmitter.events = { | ||
'aaa': [] | ||
}; | ||
EventEmitter.on('aaa', function () {}); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
expect(EventEmitter.listeners('aaa').length).toBe(1); | ||
EventEmitter.events = { | ||
'aaa': [function () {}] | ||
}; | ||
EventEmitter.removeAllListeners(); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
EventEmitter.events = {}; | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
}); | ||
it('stores initiliazed events', function () { | ||
expectAll(setA, false, checkEvent); | ||
expectAll(setB, false, checkEvent); | ||
EventEmitter.initEvents(setA); | ||
expectAll(setA, true, checkEvent); | ||
expectAll(setB, false, checkEvent); | ||
EventEmitter.initMoreEvents(setB); | ||
expectAll(setA, true, checkEvent); | ||
expectAll(setB, true, checkEvent); | ||
}); | ||
it('clears existing events on initEvents', function () { | ||
expectAll(setA, false, checkEvent); | ||
expectAll(setB, false, checkEvent); | ||
EventEmitter.initEvents(setA); | ||
expectAll(setA, true, checkEvent); | ||
expectAll(setB, false, checkEvent); | ||
EventEmitter.initEvents(setB); | ||
expectAll(setA, false, checkEvent); | ||
expectAll(setB, true, checkEvent); | ||
}); | ||
/* Deprecated JsSIP functions */ | ||
it('has no method addListener', function () { | ||
expect(EventEmitter.addListener).not.toBeDefined(); | ||
}); | ||
it('has no method removeListener', function () { | ||
expect(EventEmitter.removeListener).not.toBeDefined(); | ||
}); | ||
it('has no method removeAllListener', function () { | ||
expect(EventEmitter.removeAllListener).not.toBeDefined(); | ||
}); | ||
it('has no method listeners', function () { | ||
expect(EventEmitter.listeners).not.toBeDefined(); | ||
}); | ||
/* ON */ | ||
@@ -134,25 +49,17 @@ describe('.on', function () { | ||
beforeEach(function () { | ||
EventEmitter.initEvents(setD); | ||
spy = jasmine.createSpy('callback'); | ||
}); | ||
it('refuses unknown events', function () { | ||
EventEmitter.initEvents([]); | ||
expect(EventEmitter.checkEvent('aaa')).toBe(false); | ||
function bad() { | ||
EventEmitter.on('aaa', function () {}); | ||
it('only accepts functions', function () { | ||
function expectOnToThrow (eventName, listener) { | ||
expect(function () { | ||
EventEmitter.on(eventName, listener); | ||
}).toThrow(); | ||
} | ||
expect(bad).toThrow(); | ||
expect(EventEmitter.checkEvent('aaa')).toBe(false); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
}); | ||
it('only accepts functions', function () { | ||
EventEmitter.on('aaa', function () {}); | ||
EventEmitter.on('bbb', 'Iamastring'); | ||
EventEmitter.on('ccc', 'stuff', function () {}); | ||
EventEmitter.on('ddd', 789); | ||
EventEmitter.on('eee', { | ||
expectOnToThrow('bbb', 'Iamastring'); | ||
expectOnToThrow('ccc', 'stuff', function () {}); | ||
expectOnToThrow('ddd', 789); | ||
expectOnToThrow('eee', { | ||
call: function () {}, | ||
@@ -162,7 +69,8 @@ apply: function () {} | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.listeners('aaa').length).toBe(1); | ||
expect(EventEmitter.checkListener('bbb')).toBe(false); | ||
expect(EventEmitter.checkListener('ccc')).toBe(false); | ||
expect(EventEmitter.checkListener('ddd')).toBe(false); | ||
expect(EventEmitter.listeners('bbb').length).toBe(0); | ||
expect(EventEmitter.listeners('ccc').length).toBe(0); | ||
expect(EventEmitter.listeners('ddd').length).toBe(0); | ||
expect(EventEmitter.listeners('eee').length).toBe(0); | ||
}); | ||
@@ -188,11 +96,2 @@ | ||
it('binds to third argument', function () { | ||
var that = jasmine.createSpy('foo'); | ||
EventEmitter.on('aaa', function () { | ||
expect(this.identity).toBe(that.identity); | ||
}, that); | ||
EventEmitter.emit('aaa'); | ||
}); | ||
it('binds to the emitter by default', function () { | ||
@@ -210,19 +109,8 @@ EventEmitter.on('aaa', function () { | ||
beforeEach(function () { | ||
EventEmitter.initEvents(setD); | ||
spy = jasmine.createSpy('callback'); | ||
}); | ||
it('refuses unknown events', function () { | ||
function bad() { | ||
EventEmitter.once('zed', function () {}); | ||
} | ||
expect(bad).toThrow(); | ||
expect(EventEmitter.checkEvent('zed')).toBe(false); | ||
expect(EventEmitter.checkListener('zed')).toBe(false); | ||
}); | ||
it('adds a listener', function () { | ||
EventEmitter.once('aaa', spy); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.listeners('aaa').length).toBe(1); | ||
}); | ||
@@ -247,11 +135,2 @@ | ||
it('binds to third argument', function () { | ||
var that = jasmine.createSpy('foo'); | ||
EventEmitter.once('aaa', function () { | ||
expect(this.identity).toBe(that.identity); | ||
}, that); | ||
EventEmitter.emit('aaa'); | ||
}); | ||
it('binds to the emitter by default', function () { | ||
@@ -278,3 +157,2 @@ EventEmitter.once('aaa', function () { | ||
beforeEach(function () { | ||
EventEmitter.initEvents(setD); | ||
foo = jasmine.createSpy('foo'); | ||
@@ -285,26 +163,8 @@ bar = jasmine.createSpy('bar'); | ||
it('refuses unknown events', function () { | ||
function bad() { | ||
EventEmitter.off('zed'); | ||
} | ||
function badTwo() { | ||
EventEmitter.off('zed', function () {}); | ||
} | ||
function badThree() { | ||
EventEmitter.off('zed', function () {}, foo); | ||
} | ||
expect(bad).toThrow(); | ||
expect(badTwo).toThrow(); | ||
expect(badThree).toThrow(); | ||
}); | ||
it('removes the matching listener', function () { | ||
EventEmitter.on('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.listeners('aaa').length).toBe(1); | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
@@ -315,7 +175,8 @@ EventEmitter.on('aaa', foo); | ||
EventEmitter.off('aaa', baz); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.events.aaa.length).toBe(2); | ||
expect(EventEmitter.listeners('aaa').length).toBe(2); | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
// remove twice since it was added twice above | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
}); | ||
@@ -325,6 +186,6 @@ | ||
EventEmitter.on('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.listeners('aaa').length).toBe(1); | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
@@ -335,7 +196,8 @@ EventEmitter.on('aaa', foo); | ||
EventEmitter.off('aaa', baz); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.events.aaa.length).toBe(2); | ||
expect(EventEmitter.listeners('aaa').length).toBe(2); | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
// remove twice since it was added twice above | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
@@ -348,25 +210,2 @@ EventEmitter.emit('aaa'); | ||
it('tries to match third argument', function () { | ||
var that = jasmine.createSpy('that'); | ||
EventEmitter.on('aaa', foo, that); | ||
EventEmitter.off('aaa', foo, that); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
EventEmitter.on('aaa', foo); | ||
EventEmitter.off('aaa', foo, that); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
EventEmitter.on('aaa', foo, that); | ||
EventEmitter.off('aaa', foo); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
EventEmitter.on('aaa', foo, that); | ||
EventEmitter.off('aaa', foo, bar); | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
}); | ||
it('can remove all listeners for an event', function () { | ||
@@ -384,3 +223,3 @@ var that = jasmine.createSpy('that'); | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
}); | ||
@@ -396,5 +235,5 @@ | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
expect(EventEmitter.checkListener('bbb')).toBe(true); | ||
expect(EventEmitter.checkListener('ccc')).toBe(true); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
expect(EventEmitter.listeners('bbb').length).toBe(1); | ||
expect(EventEmitter.listeners('ccc').length).toBe(1); | ||
}); | ||
@@ -412,6 +251,6 @@ | ||
expect(EventEmitter.checkListener('aaa')).toBe(false); | ||
expect(EventEmitter.checkListener('bbb')).toBe(false); | ||
expect(EventEmitter.checkListener('ccc')).toBe(false); | ||
expect(EventEmitter.checkListener('ddd')).toBe(false); | ||
expect(EventEmitter.listeners('aaa').length).toBe(0); | ||
expect(EventEmitter.listeners('bbb').length).toBe(0); | ||
expect(EventEmitter.listeners('ccc').length).toBe(0); | ||
expect(EventEmitter.listeners('ddd').length).toBe(0); | ||
}); | ||
@@ -431,33 +270,2 @@ | ||
/* Max Listeners */ | ||
it('prevents too many listeners', function () { | ||
var i, n = 1000; | ||
EventEmitter.initEvents(setD); | ||
for (i = 0; i < n; i++) { | ||
EventEmitter.on('aaa', function () {}); | ||
} | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.events.aaa.length).toBe(SIP.EventEmitter.C.MAX_LISTENERS); | ||
}); | ||
it('can have configurable max listeners', function () { | ||
var i, n = 1000; | ||
EventEmitter. | ||
initEvents(setD). | ||
setMaxListeners(723); | ||
for (i = 0; i < n; i++) { | ||
EventEmitter.on('aaa', function () {}); | ||
} | ||
expect(EventEmitter.checkListener('aaa')).toBe(true); | ||
expect(EventEmitter.events.aaa.length).toBe(723); | ||
}); | ||
it('returns this from setMaxListeners', function () { | ||
expect(EventEmitter.setMaxListeners(100)).toBe(EventEmitter); | ||
}); | ||
/* EMIT */ | ||
@@ -467,3 +275,3 @@ describe('.emit', function () { | ||
function removeSelf () { | ||
EventEmitter.off('aaa', removeSelf); | ||
EventEmitter.removeListener('aaa', removeSelf); | ||
} | ||
@@ -476,3 +284,2 @@ | ||
EventEmitter.initEvents(setD); | ||
EventEmitter.on('aaa', removeSelf); | ||
@@ -483,10 +290,2 @@ EventEmitter.on('aaa', foo); | ||
it('refuses to emit bad events', function () { | ||
function bad() { | ||
EventEmitter.emit('zed'); | ||
}; | ||
expect(bad).toThrow(); | ||
}); | ||
it('handles no listeners gracefully', function () { | ||
@@ -515,13 +314,4 @@ function good() { | ||
it('binds to the third argument', function () { | ||
bar.and.callFake(function () { | ||
expect(this).toBe(that); | ||
}); | ||
EventEmitter.on('bbb', bar, that); | ||
EventEmitter.emit('bbb'); | ||
expect(bar).toHaveBeenCalled(); | ||
}); | ||
it('ignores off listeners', function () { | ||
EventEmitter.off('aaa', foo); | ||
EventEmitter.removeListener('aaa', foo); | ||
EventEmitter.emit('aaa'); | ||
@@ -528,0 +318,0 @@ expect(foo).not.toHaveBeenCalled(); |
@@ -243,2 +243,62 @@ describe('Grammar', function () { | ||
}); | ||
describe('Replaces', function () { | ||
var goods = [ | ||
'98732@sip.example.com\r\n' + | ||
' ;from-tag=r33th4x0r\r\n' + | ||
' ;to-tag=ff87ff', | ||
'12adf2f34456gs5;to-tag=12345;from-tag=54321;early-only', // early | ||
'12adf2f34456gs5;baz;to-tag=12345;early-only;from-tag=54321', //early | ||
'87134@171.161.34.23;to-tag=24796;from-tag=0', | ||
'87134@171.161.34.23;to-tag=24796;from-tag=0;foo=bar' | ||
]; | ||
var goodIds = [ | ||
'98732@sip.example.com', | ||
'12adf2f34456gs5', | ||
'12adf2f34456gs5', | ||
'87134@171.161.34.23', | ||
'87134@171.161.34.23', | ||
]; | ||
var goodFroms = [ | ||
'r33th4x0r', | ||
'54321', | ||
'54321', | ||
'0', | ||
'0' | ||
]; | ||
var goodTos = [ | ||
'ff87ff', | ||
'12345', | ||
'12345', | ||
'24796', | ||
'24796' | ||
]; | ||
var bads = [ | ||
'12adf2f34456gs5;to-tag1=12345;from-tag=54321;early-only', | ||
'12adf2f34456gs5;baz;to-tag=12345;from-tag=54321;', | ||
'87134@171.161.34.23;to-tag=24796;from-tag', | ||
'87134@171.161.34.23;to-tag;from-tag=0;foo=bar' | ||
]; | ||
it('parses the good examples', function () { | ||
for (var i = 0; i < goods.length; i++) { | ||
var parsed = SIP.Grammar.parse(goods[i], 'Replaces'); | ||
expect(parsed).not.toEqual(-1); | ||
expect(parsed.call_id).toEqual(goodIds[i]); | ||
expect(parsed.replaces_from_tag).toEqual(goodFroms[i]); | ||
expect(parsed.replaces_to_tag).toEqual(goodTos[i]); | ||
expect(parsed.early_only || false).toEqual(i == 1 || i == 2); | ||
} | ||
}); | ||
it('rejects the bad examples', function () { | ||
for (var i = 0; i < bads.length; i++) { | ||
expect(SIP.Grammar.parse(bads[i], 'Replaces')).toEqual(-1); | ||
} | ||
}); | ||
}); | ||
}); |
@@ -21,3 +21,3 @@ describe('RegisterContext', function() { | ||
normalizeTarget: function (target) { return target; }, | ||
checkListener: function () { return true; } | ||
listeners: function () { return [1]; } | ||
}; | ||
@@ -141,8 +141,11 @@ RegisterContext = new SIP.RegisterContext(ua); | ||
it('calls register', function() { | ||
var options = { traceSip: true, extraHeaders: [ 'X-Foo: foo', 'X-Bar: bar' ] }; | ||
RegisterContext.options = options; | ||
spyOn(RegisterContext, 'register').and.returnValue('register'); | ||
expect(RegisterContext.register).not.toHaveBeenCalled(); | ||
RegisterContext.onTransportConnected(); | ||
expect(RegisterContext.register).toHaveBeenCalledWith(); | ||
expect(RegisterContext.register).toHaveBeenCalledWith(options); | ||
}); | ||
@@ -149,0 +152,0 @@ }); |
@@ -23,6 +23,8 @@ describe('SanityCheck', function () { | ||
ua = new SIP.UA(); | ||
ua.transport = transport; | ||
h.via = h.via.replace('MYHOST', ua.configuration.viaHost); | ||
h.v = h.v.replace('MYHOST', ua.configuration.viaHost); | ||
beforeAll(function () { | ||
ua = new SIP.UA(); | ||
ua.transport = transport; | ||
h.via = h.via.replace('MYHOST', ua.configuration.viaHost); | ||
h.v = h.v.replace('MYHOST', ua.configuration.viaHost); | ||
}); | ||
@@ -29,0 +31,0 @@ function p(data) { |
@@ -11,3 +11,18 @@ describe('ServerContext', function() { | ||
request = SIP.Parser.parseMessage('REFER sip:gled5gsn@hk95bautgaa7.invalid;transport=ws;aor=james%40onsnip.onsip.com SIP/2.0\r\nMax-Forwards: 65\r\nTo: <sip:james@onsnip.onsip.com>\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=rto5ib4052\r\nCall-ID: grj0liun879lfj35evfq\r\nCSeq: 1798 INVITE\r\nContact: <sip:e55r35u3@kgu78r4e1e6j.invalid;transport=ws;ob>\r\nAllow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE\r\nContent-Type: application/sdp\r\nSupported: outbound\r\nUser-Agent: SIP.js 0.5.0-devel\r\nContent-Length: 10\r\n\r\na=sendrecv\r\n', ua); | ||
request = SIP.Parser.parseMessage([ | ||
'REFER sip:gled5gsn@hk95bautgaa7.invalid;transport=ws;aor=james%40onsnip.onsip.com SIP/2.0', | ||
'Max-Forwards: 65', | ||
'To: <sip:james@onsnip.onsip.com>', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=rto5ib4052', | ||
'Call-ID: grj0liun879lfj35evfq', | ||
'CSeq: 1798 INVITE', | ||
'Contact: <sip:e55r35u3@kgu78r4e1e6j.invalid;transport=ws;ob>', | ||
'Allow: ACK,CANCEL,BYE,OPTIONS,INVITE,MESSAGE', | ||
'Content-Type: application/sdp', | ||
'Supported: outbound', | ||
'User-Agent: SIP.js 0.5.0-devel', | ||
'Content-Length: 10', | ||
'', | ||
'a=sendrecv', | ||
''].join('\r\n'), ua); | ||
@@ -61,9 +76,2 @@ spyOn(SIP.Transactions, 'InviteServerTransaction'); | ||
it('initializes events', function() { | ||
expect(ServerContext.checkEvent('progress')).toBeTruthy(); | ||
expect(ServerContext.checkEvent('accepted')).toBeTruthy(); | ||
expect(ServerContext.checkEvent('rejected')).toBeTruthy(); | ||
expect(ServerContext.checkEvent('failed')).toBeTruthy(); | ||
}); | ||
describe('.progress', function() { | ||
@@ -213,4 +221,4 @@ beforeEach(function() { | ||
it('passs along the status code, reason phrase, header, and body as is to request reply', function() { | ||
for( var i = 1; i < 700; i++) { | ||
it('passes along the status code, reason phrase, header, and body as is to request reply', function() { | ||
for( var i = 100; i < 700; i++) { | ||
var options={statusCode : i , | ||
@@ -217,0 +225,0 @@ reasonPhrase : 'reason' , |
@@ -121,3 +121,18 @@ describe('Subscription', function() { | ||
beforeEach(function() { | ||
response = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nEvent: dialog\r\nExpires: 3600\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', ua); | ||
response = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Event: dialog', | ||
'Expires: 3600', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
''].join('\r\n'), ua); | ||
}); | ||
@@ -174,3 +189,17 @@ | ||
response = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nEvent: dialog\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', ua); | ||
response = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Event: dialog', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
''].join('\r\n'), ua); | ||
@@ -187,3 +216,18 @@ Subscription.receiveResponse(response); | ||
response = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nEvent: dialog\r\nExpires: 777777\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', ua); | ||
response = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Event: dialog', | ||
'Expires: 777777', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
''].join('\r\n'), ua); | ||
@@ -266,4 +310,4 @@ Subscription.receiveResponse(response); | ||
it('calls subscribe for all other states (active, init)', function() { | ||
spyOn(Subscription, 'subscribe'); | ||
it('calls refresh for all other states (active, init)', function() { | ||
spyOn(Subscription, 'refresh'); | ||
Subscription.state = 'active'; | ||
@@ -273,5 +317,5 @@ | ||
expect(Subscription.subscribe).toHaveBeenCalled(); | ||
expect(Subscription.refresh).toHaveBeenCalled(); | ||
Subscription.subscribe.calls.reset(); | ||
Subscription.refresh.calls.reset(); | ||
Subscription.state = 'init'; //Note: there's no way this can be called with a state of init | ||
@@ -281,3 +325,3 @@ | ||
expect(Subscription.subscribe).toHaveBeenCalled(); | ||
expect(Subscription.refresh).toHaveBeenCalled(); | ||
}); | ||
@@ -329,3 +373,18 @@ }); | ||
it('creates a dialog, sets it to the subscription, and returns true on success', function() { | ||
response = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nEvent: dialog\r\nExpires: 3600\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', ua); | ||
response = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Event: dialog', | ||
'Expires: 3600', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
''].join('\r\n'), ua); | ||
@@ -339,3 +398,16 @@ expect(Subscription.createConfirmedDialog(response, 'UAC')).toBe(true); | ||
it('returns false, doesn\'t set the dialog on dialog creation failure', function() { | ||
response = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nEvent: dialog\r\nExpires: 3600\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', ua); | ||
response = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Event: dialog', | ||
'Expires: 3600', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
''].join('\r\n'), ua); | ||
//no contact header, will be false | ||
@@ -351,3 +423,18 @@ | ||
it('terminates and deletes the subscription\'s dialog if it exists', function() { | ||
var response = SIP.Parser.parseMessage('SIP/2.0 200 OK\r\nTo: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411\r\nFrom: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2\r\nCall-ID: upfrf7jpeb3rmc0gnnq1\r\nCSeq: 9059 INVITE\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nEvent: dialog\r\nExpires: 3600\r\nContact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>\r\nSupported: outbound\r\nContent-Type: application/sdp\r\nContent-Length: 11\r\n\r\na= sendrecv\r\n', ua); | ||
var response = SIP.Parser.parseMessage([ | ||
'SIP/2.0 200 OK', | ||
'To: <sip:james@onsnip.onsip.com>;tag=1ma2ki9411', | ||
'From: "test1" <sip:test1@onsnip.onsip.com>;tag=58312p20s2', | ||
'Call-ID: upfrf7jpeb3rmc0gnnq1', | ||
'CSeq: 9059 INVITE', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Event: dialog', | ||
'Expires: 3600', | ||
'Contact: <sip:gusgt9j8@vk3dj582vbu9.invalid;transport=ws>', | ||
'Supported: outbound', | ||
'Content-Type: application/sdp', | ||
'Content-Length: 11', | ||
'', | ||
'a= sendrecv', | ||
'',].join('\r\n'), ua); | ||
@@ -369,3 +456,23 @@ Subscription.createConfirmedDialog(response, 'UAC') | ||
beforeEach(function() { | ||
request = SIP.Parser.parseMessage('NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0\r\nRecord-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>\r\nRecord-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>\r\nVia: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport\r\nVia: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0\r\nTo: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo\r\nFrom: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992\r\nCSeq: 1 NOTIFY\r\nCall-ID: 8fe1v8j577pj9bakcpbs\r\nMax-Forwards: 69\r\nContent-Length: 160\r\nUser-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))\r\nEvent: dialog\r\nContact: <sip:199.7.175.102:5060>\r\nSubscription-State: active;expires=3600\r\nContent-Type: application/dialog-info+xml\r\n\r\n<?xml version="1.0"?>\r\n<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"\r\nstate="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>', ua); | ||
request = SIP.Parser.parseMessage([ | ||
'NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0', | ||
'Record-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>', | ||
'Record-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>', | ||
'Via: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport', | ||
'Via: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0', | ||
'To: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo', | ||
'From: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992', | ||
'CSeq: 1 NOTIFY', | ||
'Call-ID: 8fe1v8j577pj9bakcpbs', | ||
'Max-Forwards: 69', | ||
'Content-Length: 160', | ||
'User-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))', | ||
'Event: dialog', | ||
'Contact: <sip:199.7.175.102:5060>', | ||
'Subscription-State: active;expires=3600', | ||
'Content-Type: application/dialog-info+xml', | ||
'', | ||
'<?xml version="1.0"?>', | ||
'<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"', | ||
'state="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>'].join('\r\n'), ua); | ||
@@ -411,3 +518,3 @@ spyOn(request, 'reply'); //takes care of an error | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(3600000); | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(3600000 * .9); | ||
expect(Subscription.timers.sub_duration).not.toBeNull(); | ||
@@ -423,3 +530,3 @@ expect(Subscription.timers.sub_duration).toBeDefined(); | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(700000); | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(700000 * .9); | ||
expect(Subscription.timers.sub_duration).not.toBeNull(); | ||
@@ -435,3 +542,3 @@ expect(Subscription.timers.sub_duration).toBeDefined(); | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(3600000); | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(3600000 * .9); | ||
expect(Subscription.timers.sub_duration).not.toBeNull(); | ||
@@ -448,3 +555,3 @@ expect(Subscription.timers.sub_duration).toBeDefined(); | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(3600000); | ||
expect(SIP.Timers.setTimeout.calls.argsFor(0)[1]).toBe(3600000 * .9); | ||
expect(Subscription.timers.sub_duration).not.toBeNull(); | ||
@@ -532,3 +639,22 @@ expect(Subscription.timers.sub_duration).toBeDefined(); | ||
it('logs a warning and returns false if Event header is missing', function() { | ||
request = SIP.Parser.parseMessage('NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0\r\nRecord-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>\r\nRecord-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>\r\nVia: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport\r\nVia: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0\r\nTo: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo\r\nFrom: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992\r\nCSeq: 1 NOTIFY\r\nCall-ID: 8fe1v8j577pj9bakcpbs\r\nMax-Forwards: 69\r\nContent-Length: 160\r\nUser-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))\r\nContact: <sip:199.7.175.102:5060>\r\nSubscription-State: active;expires=3600\r\nContent-Type: application/dialog-info+xml\r\n\r\n<?xml version="1.0"?>\r\n<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"\r\nstate="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>', ua); | ||
request = SIP.Parser.parseMessage([ | ||
'NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0', | ||
'Record-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>', | ||
'Record-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>', | ||
'Via: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport', | ||
'Via: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0', | ||
'To: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo', | ||
'From: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992', | ||
'CSeq: 1 NOTIFY', | ||
'Call-ID: 8fe1v8j577pj9bakcpbs', | ||
'Max-Forwards: 69', | ||
'Content-Length: 160', | ||
'User-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))', | ||
'Contact: <sip:199.7.175.102:5060>', | ||
'Subscription-State: active;expires=3600', | ||
'Content-Type: application/dialog-info+xml', | ||
'', | ||
'<?xml version="1.0"?>', | ||
'<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"', | ||
'state="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>'].join('\r\n'), ua); | ||
@@ -542,3 +668,22 @@ spyOn(Subscription.logger, 'warn'); | ||
it('logs a warning and returns false if Subscription-State header is missing', function() { | ||
request = SIP.Parser.parseMessage('NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0\r\nRecord-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>\r\nRecord-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>\r\nVia: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport\r\nVia: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0\r\nTo: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo\r\nFrom: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992\r\nCSeq: 1 NOTIFY\r\nCall-ID: 8fe1v8j577pj9bakcpbs\r\nMax-Forwards: 69\r\nContent-Length: 160\r\nUser-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))\r\nEvent: dialog\r\nContact: <sip:199.7.175.102:5060>\r\nContent-Type: application/dialog-info+xml\r\n\r\n<?xml version="1.0"?>\r\n<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"\r\nstate="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>', ua); | ||
request = SIP.Parser.parseMessage([ | ||
'NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0', | ||
'Record-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>', | ||
'Record-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>', | ||
'Via: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport', | ||
'Via: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0', | ||
'To: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo', | ||
'From: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992', | ||
'CSeq: 1 NOTIFY', | ||
'Call-ID: 8fe1v8j577pj9bakcpbs', | ||
'Max-Forwards: 69', | ||
'Content-Length: 160', | ||
'User-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))', | ||
'Event: dialog', | ||
'Contact: <sip:199.7.175.102:5060>', | ||
'Content-Type: application/dialog-info+xml', | ||
'', | ||
'<?xml version="1.0"?>', | ||
'<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"', | ||
'state="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>'].join('\r\n'), ua); | ||
@@ -552,3 +697,23 @@ spyOn(Subscription.logger, 'warn'); | ||
it('logs a warning, replies 481, and returns false if the events don\'t match', function() { | ||
request = SIP.Parser.parseMessage('NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0\r\nRecord-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>\r\nRecord-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>\r\nVia: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport\r\nVia: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0\r\nTo: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo\r\nFrom: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992\r\nCSeq: 1 NOTIFY\r\nCall-ID: 8fe1v8j577pj9bakcpbs\r\nMax-Forwards: 69\r\nContent-Length: 160\r\nUser-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))\r\nEvent: WRONG\r\nContact: <sip:199.7.175.102:5060>\r\nSubscription-State: active;expires=3600\r\nContent-Type: application/dialog-info+xml\r\n\r\n<?xml version="1.0"?>\r\n<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"\r\nstate="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>', ua); | ||
request = SIP.Parser.parseMessage([ | ||
'NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0', | ||
'Record-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>', | ||
'Record-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>', | ||
'Via: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport', | ||
'Via: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0', | ||
'To: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo', | ||
'From: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992', | ||
'CSeq: 1 NOTIFY', | ||
'Call-ID: 8fe1v8j577pj9bakcpbs', | ||
'Max-Forwards: 69', | ||
'Content-Length: 160', | ||
'User-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))', | ||
'Event: WRONG', | ||
'Contact: <sip:199.7.175.102:5060>', | ||
'Subscription-State: active;expires=3600', | ||
'Content-Type: application/dialog-info+xml', | ||
'', | ||
'<?xml version="1.0"?>', | ||
'<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"', | ||
'state="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>'].join('\r\n'), ua); | ||
@@ -564,3 +729,23 @@ spyOn(Subscription.logger, 'warn'); | ||
it('returns true if none of the above happens', function() { | ||
request = SIP.Parser.parseMessage('NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0\r\nRecord-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>\r\nRecord-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>\r\nVia: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport\r\nVia: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0\r\nTo: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo\r\nFrom: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992\r\nCSeq: 1 NOTIFY\r\nCall-ID: 8fe1v8j577pj9bakcpbs\r\nMax-Forwards: 69\r\nContent-Length: 160\r\nUser-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))\r\nEvent: dialog\r\nContact: <sip:199.7.175.102:5060>\r\nSubscription-State: active;expires=3600\r\nContent-Type: application/dialog-info+xml\r\n\r\n<?xml version="1.0"?>\r\n<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"\r\nstate="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>', ua); | ||
request = SIP.Parser.parseMessage([ | ||
'NOTIFY sip:5sik1gqu@ue55h9a6i4s5.invalid;transport=ws SIP/2.0', | ||
'Record-Route: <sip:1c2a4a345a@199.7.175.182:443;transport=wss;lr;ovid=7cb85a5c>', | ||
'Record-Route: <sip:199.7.175.182:5060;transport=udp;lr;ovid=7cb85a5c>', | ||
'Via: SIP/2.0/WSS 199.7.175.182:443;branch=z9hG4bK1b3f97d3d51a36142c17f2e35d31c93d0376cecc;rport', | ||
'Via: SIP/2.0/UDP 199.7.175.102:5060;branch=z9hG4bK5aef.40dfee72.0', | ||
'To: <sip:james@onsnip.onsip.com>;tag=c4pa0cc2uo', | ||
'From: <sip:sip:test1@onsnip.onsip.com>;tag=2b2fcef4d83711ffd986a7db00d29d1d.7992', | ||
'CSeq: 1 NOTIFY', | ||
'Call-ID: 8fe1v8j577pj9bakcpbs', | ||
'Max-Forwards: 69', | ||
'Content-Length: 160', | ||
'User-Agent: OpenSIPS (1.10.0-notls (x86_64/linux))', | ||
'Event: dialog', | ||
'Contact: <sip:199.7.175.102:5060>', | ||
'Subscription-State: active;expires=3600', | ||
'Content-Type: application/dialog-info+xml', | ||
'', | ||
'<?xml version="1.0"?>', | ||
'<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="0"', | ||
'state="full" entity="sip:sip%3btest1@onsnip.onsip.com"/>'].join('\r\n'), ua); | ||
@@ -567,0 +752,0 @@ expect(Subscription.matchEvent(request)).toBe(true); |
@@ -11,3 +11,2 @@ describe('WebRTC.MediaHandler', function() { | ||
Session = new SIP.EventEmitter(); | ||
Session.initEvents(['progress','accepted','rejected','failed']); | ||
SIP.Utils.augment(Session, SIP.Session, []); | ||
@@ -65,3 +64,3 @@ | ||
it("doesn't throw if renderHint and this.mediaHint are missing", function () { | ||
expect(MediaHandler.render).not.toThrow(); | ||
expect(MediaHandler.render.bind(MediaHandler)).not.toThrow(); | ||
}); | ||
@@ -318,2 +317,25 @@ }); | ||
}); | ||
describe('.getReferMedia', function () { | ||
function fakeStreamArray (hasAudioTracks, hasVideoTracks) { | ||
function fakeTracks (hasTracks) { | ||
return hasTracks ? [ 1 ] : []; | ||
} | ||
return [{ | ||
getAudioTracks: fakeTracks.bind(null, hasAudioTracks), | ||
getVideoTracks: fakeTracks.bind(null, hasVideoTracks) | ||
}]; | ||
} | ||
it('returns audio-only constraints if local audio/video and remote audio', function () { | ||
spyOn(MediaHandler, 'getLocalStreams').and.returnValue(fakeStreamArray(true, true)); | ||
spyOn(MediaHandler, 'getRemoteStreams').and.returnValue(fakeStreamArray(true, false)); | ||
var referMedia = MediaHandler.getReferMedia(); | ||
expect(referMedia.constraints.audio).toBe(true); | ||
expect(referMedia.constraints.video).toBe(false); | ||
}); | ||
}); | ||
}); |
@@ -8,2 +8,9 @@ THANKS | ||
* The [LiveNinja](https://www.liveninja.com) Team ([GitHub](https://github.com/liveninja)) | ||
* [Philipp Weissensteiner](https://github.com/wpp) | ||
* [Rob Wu](https://github.com/Rob--W) | ||
* [Sam Metson](https://github.com/Bat-o-matic) | ||
* [Mike Chacon](https://github.com/sicdigital) | ||
* [Sean Bright](https://github.com/seanbright) | ||
* [Carlos Ruiz Diaz](https://github.com/caruizdiaz) | ||
* [Destreyf](https://github.com/Destreyf) | ||
@@ -10,0 +17,0 @@ Much credit goes to the original authors of the JsSIP project. Thank you to all. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
776703
11
77
18138
1
0
2
+ Addedws@^0.6.4
+ Addednan@1.4.3(transitive)
+ Addedoptions@0.0.6(transitive)
+ Addedpromiscuous@0.6.0(transitive)
+ Addedultron@1.0.2(transitive)
+ Addedws@0.6.5(transitive)