Comparing version 0.7.2 to 0.7.3
{ | ||
"name": "sip.js", | ||
"version": "0.7.2", | ||
"version": "0.7.3", | ||
"authors": [ | ||
@@ -5,0 +5,0 @@ "Will Mitchell <will@onsip.com>", |
@@ -55,3 +55,3 @@ /*jshint multistr:true, devel:true*/ | ||
options: { | ||
bundleOptions: { | ||
browserifyOptions: { | ||
standalone: 'SIP' | ||
@@ -58,0 +58,0 @@ }, |
@@ -5,3 +5,3 @@ { | ||
"description": "A simple, intuitive, and powerful JavaScript signaling library", | ||
"version": "0.7.2", | ||
"version": "0.7.3", | ||
"main": "src/index.js", | ||
@@ -33,3 +33,3 @@ "browser": { | ||
"grunt": "~0.4.0", | ||
"grunt-browserify": "^2.1.0", | ||
"grunt-browserify": "^4.0.1", | ||
"grunt-cli": "~0.1.6", | ||
@@ -50,2 +50,3 @@ "grunt-contrib-copy": "^0.5.0", | ||
"repl": "beefy test/repl.js --open", | ||
"build": "grunt build", | ||
"prepublish": "cd src/Grammar && mkdir -p dist && pegjs --extra-options-file peg.json src/Grammar.pegjs dist/Grammar.js", | ||
@@ -52,0 +53,0 @@ "test": "grunt travis --verbose" |
@@ -22,2 +22,3 @@ # How To version release SIP.js | ||
* add new dist files (git add -f if it complains) | ||
* commit | ||
* test npm: | ||
@@ -24,0 +25,0 @@ |
@@ -11,2 +11,6 @@ "use strict"; | ||
module.exports = function (SIP) { | ||
//keep to quiet jshint, and remain consistent with other files | ||
SIP = SIP; | ||
var Hacks = { | ||
@@ -84,15 +88,2 @@ AllBrowsers: { | ||
}, | ||
hasIncompatibleCLineWithSomeSIPEndpoints: function(sdp) { | ||
/* | ||
* Firefox appears to be following https://tools.ietf.org/html/rfc5245#section-9.1.1.1 | ||
* and using a c line IP address of 0.0.0.0. This is completely valid, however it is | ||
* causing some endpoints (such as FreeSWITCH) to interpret the SDP as being on hold | ||
* https://freeswitch.org/jira/browse/FS-6955. To get around this issue we pull the | ||
* replace the c line with 1.1.1.1 which SIP clients do not interpret as hold. | ||
* This makes the other endpoint believe that the call is not on hold and audio flows | ||
* because ICE determines the media pathway (not the c line). | ||
*/ | ||
return sdp.replace(/(0\.0\.0\.0)/gmi, SIP.Utils.getRandomTestNetIP()); | ||
} | ||
}, | ||
@@ -99,0 +90,0 @@ |
@@ -29,2 +29,3 @@ "use strict"; | ||
params.to_uri = this.to_uri; | ||
params.to_displayName = ua.configuration.displayName; | ||
params.call_id = this.call_id; | ||
@@ -53,3 +54,3 @@ params.cseq = this.cseq; | ||
extraHeaders.push('Contact: ' + this.contact + ';expires=' + this.expires); | ||
extraHeaders.push('Allow: ' + SIP.Utils.getAllowedMethods(this.ua)); | ||
extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString()); | ||
@@ -56,0 +57,0 @@ // Save original extraHeaders to be used in .close |
@@ -23,2 +23,29 @@ "use strict"; | ||
// Reply | ||
function reply(status_code) { | ||
var to, | ||
response = SIP.Utils.buildStatusLine(status_code), | ||
vias = message.getHeaders('via'), | ||
length = vias.length, | ||
idx = 0; | ||
for(idx; idx < length; idx++) { | ||
response += "Via: " + vias[idx] + "\r\n"; | ||
} | ||
to = message.getHeader('To'); | ||
if(!message.to_tag) { | ||
to += ';tag=' + SIP.Utils.newTag(); | ||
} | ||
response += "To: " + to + "\r\n"; | ||
response += "From: " + message.getHeader('From') + "\r\n"; | ||
response += "Call-ID: " + message.call_id + "\r\n"; | ||
response += "CSeq: " + message.cseq + " " + message.method + "\r\n"; | ||
response += "\r\n"; | ||
transport.send(response); | ||
} | ||
/* | ||
@@ -149,29 +176,2 @@ * Sanity Check for incoming Messages | ||
// Reply | ||
function reply(status_code) { | ||
var to, | ||
response = SIP.Utils.buildStatusLine(status_code), | ||
vias = message.getHeaders('via'), | ||
length = vias.length, | ||
idx = 0; | ||
for(idx; idx < length; idx++) { | ||
response += "Via: " + vias[idx] + "\r\n"; | ||
} | ||
to = message.getHeader('To'); | ||
if(!message.to_tag) { | ||
to += ';tag=' + SIP.Utils.newTag(); | ||
} | ||
response += "To: " + to + "\r\n"; | ||
response += "From: " + message.getHeader('From') + "\r\n"; | ||
response += "Call-ID: " + message.call_id + "\r\n"; | ||
response += "CSeq: " + message.cseq + " " + message.method + "\r\n"; | ||
response += "\r\n"; | ||
transport.send(response); | ||
} | ||
requests.push(rfc3261_8_2_2_1); | ||
@@ -178,0 +178,0 @@ requests.push(rfc3261_16_3_4); |
@@ -44,3 +44,3 @@ "use strict"; | ||
options.extraHeaders.push('Contact: '+ this.contact); | ||
options.extraHeaders.push('Allow: '+ SIP.Utils.getAllowedMethods(ua)); | ||
options.extraHeaders.push('Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString()); | ||
@@ -60,2 +60,10 @@ SIP.Utils.augment(this, SIP.ClientContext, [ua, SIP.C.SUBSCRIBE, target, options]); | ||
//these states point to an existing subscription, no subscribe is necessary | ||
if (this.state === 'active') { | ||
this.refresh(); | ||
return this; | ||
} else if (this.state === 'notify_wait') { | ||
return this; | ||
} | ||
SIP.Timers.clearTimeout(this.timers.sub_duration); | ||
@@ -87,3 +95,4 @@ SIP.Timers.clearTimeout(this.timers.N); | ||
if (this.errorCodes.indexOf(response.status_code) !== -1) { | ||
if ((this.state === 'notify_wait' && response.status_code >= 300) || | ||
(this.state !== 'notify_wait' && this.errorCodes.indexOf(response.status_code) !== -1)) { | ||
this.failed(response, null); | ||
@@ -126,14 +135,15 @@ } else if (/^2[0-9]{2}$/.test(response.status_code)){ | ||
extraHeaders.push('Contact: '+ this.contact); | ||
extraHeaders.push('Allow: '+ SIP.Utils.getAllowedMethods(this.ua)); | ||
extraHeaders.push('Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString()); | ||
this.request = new SIP.OutgoingRequest(this.method, this.request.to.uri.toString(), this.ua, null, extraHeaders); | ||
//MAYBE, may want to see state | ||
//makes sure expires isn't set, and other typical resubscribe behavior | ||
this.receiveResponse = function(){}; | ||
this.dialog.sendRequest(this, this.method, { | ||
extraHeaders: extraHeaders, | ||
body: this.body | ||
}); | ||
SIP.Timers.clearTimeout(this.timers.sub_duration); | ||
SIP.Timers.clearTimeout(this.timers.N); | ||
this.timers.N = SIP.Timers.setTimeout(sub.timer_fire.bind(sub), SIP.Timers.TIMER_N); | ||
this.send(); | ||
}, | ||
@@ -146,5 +156,8 @@ | ||
if (this.state === 'terminated') { | ||
this.close(); | ||
this.terminateDialog(); | ||
SIP.Timers.clearTimeout(this.timers.N); | ||
SIP.Timers.clearTimeout(this.timers.sub_duration); | ||
delete this.ua.subscriptions[this.id]; | ||
} else if (this.state === 'pending' || this.state === 'notify_wait') { | ||
this.state = 'terminated'; | ||
this.close(); | ||
@@ -160,11 +173,5 @@ } else { | ||
close: function() { | ||
if(this.state !== 'terminated') { | ||
if(this.state !== 'notify_wait' && this.state !== 'terminated') { | ||
this.unsubscribe(); | ||
} | ||
this.terminateDialog(); | ||
SIP.Timers.clearTimeout(this.timers.N); | ||
SIP.Timers.clearTimeout(this.timers.sub_duration); | ||
delete this.ua.subscriptions[this.id]; | ||
}, | ||
@@ -231,2 +238,15 @@ | ||
// if we've set state to terminated, no further processing should take place | ||
// and we are only interested in cleaning up after the appropriate NOTIFY | ||
if (this.state === 'terminated') { | ||
if (sub_state.state === 'terminated') { | ||
this.terminateDialog(); | ||
SIP.Timers.clearTimeout(this.timers.N); | ||
SIP.Timers.clearTimeout(this.timers.sub_duration); | ||
delete this.ua.subscriptions[this.id]; | ||
} | ||
return; | ||
} | ||
switch (sub_state.state) { | ||
@@ -277,2 +297,6 @@ case 'active': | ||
onDialogError: function(response) { | ||
this.failed(response, SIP.C.causes.DIALOG_ERROR); | ||
}, | ||
/** | ||
@@ -279,0 +303,0 @@ * @private |
@@ -21,2 +21,12 @@ "use strict"; | ||
/** | ||
* Compute an amount of time in seconds to wait before sending another | ||
* keep-alive. | ||
* @returns {Number} | ||
*/ | ||
function computeKeepAliveTimeout(upperBound) { | ||
var lowerBound = upperBound * 0.8; | ||
return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound); | ||
} | ||
Transport = function(ua, server) { | ||
@@ -360,14 +370,4 @@ | ||
/** | ||
* Compute an amount of time in seconds to wait before sending another | ||
* keep-alive. | ||
* @returns {Number} | ||
*/ | ||
function computeKeepAliveTimeout(upperBound) { | ||
var lowerBound = upperBound * 0.8; | ||
return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound); | ||
} | ||
Transport.C = C; | ||
return Transport; | ||
}; |
@@ -25,14 +25,7 @@ "use strict"; | ||
/* UA events and corresponding SIP Methods. | ||
* Dynamically added to 'Allow' header field if the | ||
* corresponding event handler is set. | ||
*/ | ||
EVENT_METHODS: { | ||
'invite': 'INVITE', | ||
'message': 'MESSAGE' | ||
}, | ||
ALLOWED_METHODS: [ | ||
'ACK', | ||
'CANCEL', | ||
'INVITE', | ||
'MESSAGE', | ||
'BYE', | ||
@@ -586,3 +579,2 @@ 'OPTIONS', | ||
replacedDialog, | ||
methodLower = request.method.toLowerCase(), | ||
self = this; | ||
@@ -625,12 +617,6 @@ | ||
request.reply(200, null, [ | ||
'Allow: '+ SIP.Utils.getAllowedMethods(this), | ||
'Allow: '+ SIP.UA.C.ALLOWED_METHODS.toString(), | ||
'Accept: '+ C.ACCEPTED_BODY_TYPES | ||
]); | ||
} else if (method === SIP.C.MESSAGE) { | ||
if (!this.listeners(methodLower).length) { | ||
// UA is not listening for this. Reject immediately. | ||
new SIP.Transactions.NonInviteServerTransaction(request, this); | ||
request.reply(405, null, ['Allow: '+ SIP.Utils.getAllowedMethods(this)]); | ||
return; | ||
} | ||
message = new SIP.ServerContext(this, request); | ||
@@ -637,0 +623,0 @@ message.body = request.body; |
@@ -284,15 +284,2 @@ "use strict"; | ||
getAllowedMethods: function(ua) { | ||
var event, | ||
allowed = SIP.UA.C.ALLOWED_METHODS.toString(); | ||
for (event in SIP.UA.C.EVENT_METHODS) { | ||
if (ua.listeners(event).length) { | ||
allowed += ','+ SIP.UA.C.EVENT_METHODS[event]; | ||
} | ||
} | ||
return allowed; | ||
}, | ||
// MD5 (Message-Digest Algorithm) http://www.webtoolkit.info | ||
@@ -299,0 +286,0 @@ calculateMD5: function(string) { |
@@ -28,146 +28,7 @@ "use strict"; | ||
// old init() from here on | ||
var idx, jdx, length, server, | ||
self = this, | ||
servers = [], | ||
stunServers = options.stunServers || null, | ||
turnServers = options.turnServers || null, | ||
config = this.session.ua.configuration; | ||
var servers = this.prepareIceServers(options.stunServers, options.turnServers); | ||
this.RTCConstraints = options.RTCConstraints || {}; | ||
if (!stunServers) { | ||
stunServers = config.stunServers; | ||
} | ||
this.initPeerConnection(servers, this.RTCConstraints); | ||
if(!turnServers) { | ||
turnServers = config.turnServers; | ||
} | ||
/* Change 'url' to 'urls' whenever this issue is solved: | ||
* https://code.google.com/p/webrtc/issues/detail?id=2096 | ||
*/ | ||
[].concat(stunServers).forEach(function (server) { | ||
servers.push({'url': server}); | ||
}); | ||
length = turnServers.length; | ||
for (idx = 0; idx < length; idx++) { | ||
server = turnServers[idx]; | ||
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(); | ||
self.emit('addStream', e); | ||
}; | ||
this.peerConnection.onremovestream = function(e) { | ||
self.logger.log('stream removed: '+ e.stream.id); | ||
}; | ||
this.startIceCheckingTimer = function () { | ||
if (!self.iceCheckingTimer) { | ||
self.iceCheckingTimer = SIP.Timers.setTimeout(function() { | ||
self.logger.log('RTCIceChecking Timeout Triggered after '+config.iceCheckingTimeout+' milliseconds'); | ||
self.onIceCompleted.resolve(this); | ||
}.bind(this.peerConnection), config.iceCheckingTimeout); | ||
} | ||
}; | ||
this.peerConnection.onicecandidate = function(e) { | ||
self.emit('iceCandidate', e); | ||
if (e.candidate) { | ||
self.logger.log('ICE candidate received: '+ (e.candidate.candidate === null ? null : e.candidate.candidate.trim())); | ||
self.startIceCheckingTimer(); | ||
} else { | ||
self.onIceCompleted.resolve(this); | ||
} | ||
}; | ||
this.peerConnection.onicegatheringstatechange = function () { | ||
self.logger.log('RTCIceGatheringState changed: ' + this.iceGatheringState); | ||
if (this.iceGatheringState === 'gathering') { | ||
self.emit('iceGathering', this); | ||
} | ||
if (this.iceGatheringState === 'complete') { | ||
self.onIceCompleted.resolve(this); | ||
} | ||
}; | ||
this.peerConnection.oniceconnectionstatechange = function() { //need e for commented out case | ||
var stateEvent; | ||
if (this.iceConnectionState === 'checking') { | ||
self.startIceCheckingTimer(); | ||
} | ||
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 | ||
//normal calls switch from failed to connected in some cases, so checking for failed and terminated | ||
/*if (this.iceConnectionState === 'failed') { | ||
self.session.terminate({ | ||
cause: SIP.C.causes.RTP_TIMEOUT, | ||
status_code: 200, | ||
reason_phrase: SIP.C.causes.RTP_TIMEOUT | ||
}); | ||
} else if (e.currentTarget.iceGatheringState === 'complete' && this.iceConnectionState !== 'closed') { | ||
self.onIceCompleted(this); | ||
}*/ | ||
}; | ||
this.peerConnection.onstatechange = function() { | ||
self.logger.log('PeerConnection state changed to "'+ this.readyState +'"'); | ||
}; | ||
function selfEmit(mh, event) { | ||
@@ -320,2 +181,18 @@ if (mh.mediaStreamManager.on) { | ||
updateIceServers: {writeable:true, value: function (options) { | ||
var servers = this.prepareIceServers(options.stunServers, options.turnServers); | ||
this.RTCConstraints = options.RTCConstraints || this.RTCConstraints; | ||
this.initPeerConnection(servers, this.RTCConstraints); | ||
/* once updateIce is implemented correctly, this is better than above | ||
//no op if browser does not support this | ||
if (!this.peerConnection.updateIce) { | ||
return; | ||
} | ||
this.peerConnection.updateIce({'iceServers': servers}, this.RTCConstraints); | ||
*/ | ||
}}, | ||
// Functions the session can use, but only because it's convenient for the application | ||
@@ -464,2 +341,157 @@ isMuted: {writable: true, value: function isMuted () { | ||
prepareIceServers: {writable: true, value: function prepareIceServers (stunServers, turnServers) { | ||
var idx, jdx, length, server, | ||
servers = [], | ||
config = this.session.ua.configuration; | ||
stunServers = stunServers || null; | ||
turnServers = turnServers || null; | ||
if (!stunServers) { | ||
stunServers = config.stunServers; | ||
} | ||
if(!turnServers) { | ||
turnServers = config.turnServers; | ||
} | ||
/* Change 'url' to 'urls' whenever this issue is solved: | ||
* https://code.google.com/p/webrtc/issues/detail?id=2096 | ||
*/ | ||
[].concat(stunServers).forEach(function (server) { | ||
servers.push({'url': server}); | ||
}); | ||
length = turnServers.length; | ||
for (idx = 0; idx < length; idx++) { | ||
server = turnServers[idx]; | ||
for (jdx = 0; jdx < server.urls.length; jdx++) { | ||
servers.push({ | ||
'url': server.urls[jdx], | ||
'username': server.username, | ||
'credential': server.password | ||
}); | ||
} | ||
} | ||
return servers; | ||
}}, | ||
initPeerConnection: {writable: true, value: function initPeerConnection(servers, RTCConstraints) { | ||
var self = this, | ||
config = this.session.ua.configuration; | ||
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; | ||
} | ||
}); | ||
if (this.peerConnection) { | ||
this.peerConnection.close(); | ||
} | ||
this.peerConnection = new SIP.WebRTC.RTCPeerConnection({'iceServers': servers}, 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(); | ||
self.emit('addStream', e); | ||
}; | ||
this.peerConnection.onremovestream = function(e) { | ||
self.logger.log('stream removed: '+ e.stream.id); | ||
}; | ||
this.startIceCheckingTimer = function () { | ||
if (!self.iceCheckingTimer) { | ||
self.iceCheckingTimer = SIP.Timers.setTimeout(function() { | ||
self.logger.log('RTCIceChecking Timeout Triggered after '+config.iceCheckingTimeout+' milliseconds'); | ||
self.onIceCompleted.resolve(this); | ||
}.bind(this.peerConnection), config.iceCheckingTimeout); | ||
} | ||
}; | ||
this.peerConnection.onicecandidate = function(e) { | ||
self.emit('iceCandidate', e); | ||
if (e.candidate) { | ||
self.logger.log('ICE candidate received: '+ (e.candidate.candidate === null ? null : e.candidate.candidate.trim())); | ||
self.startIceCheckingTimer(); | ||
} else { | ||
self.onIceCompleted.resolve(this); | ||
} | ||
}; | ||
this.peerConnection.onicegatheringstatechange = function () { | ||
self.logger.log('RTCIceGatheringState changed: ' + this.iceGatheringState); | ||
if (this.iceGatheringState === 'gathering') { | ||
self.emit('iceGathering', this); | ||
} | ||
if (this.iceGatheringState === 'complete') { | ||
self.onIceCompleted.resolve(this); | ||
} | ||
}; | ||
this.peerConnection.oniceconnectionstatechange = function() { //need e for commented out case | ||
var stateEvent; | ||
if (this.iceConnectionState === 'checking') { | ||
self.startIceCheckingTimer(); | ||
} | ||
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 | ||
//normal calls switch from failed to connected in some cases, so checking for failed and terminated | ||
/*if (this.iceConnectionState === 'failed') { | ||
self.session.terminate({ | ||
cause: SIP.C.causes.RTP_TIMEOUT, | ||
status_code: 200, | ||
reason_phrase: SIP.C.causes.RTP_TIMEOUT | ||
}); | ||
} else if (e.currentTarget.iceGatheringState === 'complete' && this.iceConnectionState !== 'closed') { | ||
self.onIceCompleted(this); | ||
}*/ | ||
}; | ||
this.peerConnection.onstatechange = function() { | ||
self.logger.log('PeerConnection state changed to "'+ this.readyState +'"'); | ||
}; | ||
}}, | ||
createOfferOrAnswer: {writable: true, value: function createOfferOrAnswer (constraints) { | ||
@@ -489,3 +521,2 @@ var self = this; | ||
sdp = SIP.Hacks.AllBrowsers.unmaskDtls(sdp); | ||
sdp = SIP.Hacks.Firefox.hasIncompatibleCLineWithSomeSIPEndpoints(sdp); | ||
@@ -492,0 +523,0 @@ var sdpWrapper = { |
@@ -52,11 +52,2 @@ "use strict"; | ||
function attachAndPlay (elements, stream, index) { | ||
if (typeof elements === 'function') { | ||
elements = elements(); | ||
} | ||
var element = elements[index % elements.length]; | ||
(environment.attachMediaStream || attachMediaStream)(element, stream); | ||
ensureMediaPlaying(element); | ||
} | ||
function attachMediaStream(element, stream) { | ||
@@ -87,2 +78,11 @@ if (typeof element.src !== 'undefined') { | ||
function attachAndPlay (elements, stream, index) { | ||
if (typeof elements === 'function') { | ||
elements = elements(); | ||
} | ||
var element = elements[index % elements.length]; | ||
(environment.attachMediaStream || attachMediaStream)(element, stream); | ||
ensureMediaPlaying(element); | ||
} | ||
// [].concat "casts" `elements` into an array | ||
@@ -134,9 +134,14 @@ // so forEach works even if `elements` was a single element | ||
deferred.resolve( | ||
SIP.WebRTC.getUserMedia(constraints) | ||
.then( | ||
emitThenCall.bind(this, 'userMedia', saveSuccess.bind(null, false)), | ||
emitThenCall.bind(this, 'userMediaFailed', function(e){throw e;}) | ||
) | ||
); | ||
if (constraints.audio || constraints.video) { | ||
deferred.resolve( | ||
SIP.WebRTC.getUserMedia(constraints) | ||
.then( | ||
emitThenCall.bind(this, 'userMedia', saveSuccess.bind(null, false)), | ||
emitThenCall.bind(this, 'userMediaFailed', function(e){throw e;}) | ||
) | ||
); | ||
} else { | ||
// Local streams were explicitly excluded. | ||
deferred.resolve([]); | ||
} | ||
}.bind(this), 0); | ||
@@ -143,0 +148,0 @@ |
@@ -112,2 +112,28 @@ describe('MediaStreamManager', function () { | ||
}); | ||
it('does not require local streams', function(done) { | ||
var success, failure, onUM, onUMF; | ||
success = jasmine.createSpy('success'); | ||
failure = jasmine.createSpy('failure'); | ||
onUM = jasmine.createSpy('userMediaFailed'); | ||
onUMF = jasmine.createSpy('userMediaFailed'); | ||
mediaStreamManager.on('userMedia', onUM); | ||
mediaStreamManager.on('userMediaFailed', onUMF); | ||
mediaStreamManager.acquire({ | ||
constraints: { | ||
audio: false, | ||
video: false | ||
} | ||
}).then(success, failure) | ||
.then(function () { | ||
expect(onUM).not.toHaveBeenCalled(); | ||
expect(onUMF).not.toHaveBeenCalled(); | ||
expect(success).toHaveBeenCalledWith([]); | ||
expect(failure).not.toHaveBeenCalled(); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -114,0 +140,0 @@ |
@@ -242,3 +242,3 @@ describe('Dialogs', function() { | ||
beforeEach(function() { | ||
request = new SIP.OutgoingRequest('INVITE', 'bob@example.com', owner.ua, {from: 'abcdefg'}, ['Contact: ' + owner.contact, 'Allow: ' + SIP.Utils.getAllowedMethods(owner.ua)]); | ||
request = new SIP.OutgoingRequest('INVITE', 'bob@example.com', owner.ua, {from: 'abcdefg'}, ['Contact: ' + owner.contact, 'Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString()]); | ||
@@ -348,3 +348,3 @@ request.server_transaction = {on: jasmine.createSpy('on')}; | ||
beforeEach(function() { | ||
request = new SIP.OutgoingRequest('INVITE', 'bob@example.com', owner.ua, {from: 'abcdefg'}, ['Contact: ' + owner.contact, 'Allow: ' + SIP.Utils.getAllowedMethods(owner.ua)]); | ||
request = new SIP.OutgoingRequest('INVITE', 'bob@example.com', owner.ua, {from: 'abcdefg'}, ['Contact: ' + owner.contact, 'Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString()]); | ||
@@ -351,0 +351,0 @@ spyOn(owner, 'receiveRequest'); |
@@ -239,2 +239,8 @@ describe('Subscription', function() { | ||
describe('.unsubscribe', function() { | ||
beforeEach(function() { | ||
Subscription.dialog = { | ||
sendRequest: function() {} | ||
}; | ||
}); | ||
it('sets the state to terminated', function() { | ||
@@ -246,6 +252,7 @@ Subscription.unsubscribe(); | ||
it('creates a new request with an Expires header of 0', function() { | ||
it('sends a request using the same dialog as the subscription', function() { | ||
spyOn(Subscription.dialog, 'sendRequest'); | ||
Subscription.unsubscribe(); | ||
expect(Subscription.request.getHeader('Expires')).toBe('0'); | ||
expect(Subscription.dialog.sendRequest).toHaveBeenCalled(); | ||
}); | ||
@@ -271,15 +278,16 @@ | ||
}); | ||
}); | ||
it('calls send', function() { | ||
spyOn(Subscription, 'send'); | ||
describe('.timer_fire', function() { | ||
it('calls terminateDialog if state is terminated', function() { | ||
spyOn(Subscription, 'terminateDialog'); | ||
Subscription.state = 'terminated'; | ||
Subscription.unsubscribe(); | ||
Subscription.timer_fire(); | ||
expect(Subscription.send).toHaveBeenCalled(); | ||
expect(Subscription.terminateDialog).toHaveBeenCalled(); | ||
}); | ||
}); | ||
describe('.timer_fire', function() { | ||
it('calls close if state is terminated', function() { | ||
spyOn(Subscription, 'close'); | ||
it('calls clearTimeout on both timers if state is terminated', function() { | ||
spyOn(SIP.Timers, 'clearTimeout'); | ||
Subscription.state = 'terminated'; | ||
@@ -289,6 +297,17 @@ | ||
expect(Subscription.close).toHaveBeenCalled(); | ||
expect(SIP.Timers.clearTimeout.calls.count()).toBe(2); | ||
}); | ||
it('switches the state to terminated and calls close if the state is pending or notify_wait', function() { | ||
it('deletes the subscription from the UA if state is terminated', function() { | ||
Subscription.id = 'fake'; | ||
ua.subscriptions[Subscription.id] = Subscription; | ||
Subscription.state = 'terminated'; | ||
Subscription.timer_fire(); | ||
expect(ua.subscriptions[Subscription.id]).toBeUndefined(); | ||
}); | ||
it('calls close if the state is pending or notify_wait', function() { | ||
spyOn(Subscription, 'close'); | ||
@@ -300,3 +319,2 @@ Subscription.state = 'pending'; | ||
expect(Subscription.close).toHaveBeenCalled(); | ||
expect(Subscription.state).toBe('terminated'); | ||
@@ -309,3 +327,2 @@ Subscription.close.calls.reset(); | ||
expect(Subscription.close).toHaveBeenCalled(); | ||
expect(Subscription.state).toBe('terminated'); | ||
}); | ||
@@ -331,10 +348,12 @@ | ||
describe('.close', function() { | ||
it('calls unsubscribe if the state is not terminated', function() { | ||
Subscription.state = 'terminated'; | ||
it('calls unsubscribe if the state is not terminated or notify_wait', function() { | ||
spyOn(Subscription, 'unsubscribe'); | ||
Subscription.state = 'terminated'; | ||
Subscription.close(); | ||
Subscription.state = 'notify_wait'; | ||
Subscription.close(); | ||
Subscription.state = 'pending'; | ||
Subscription.close(); | ||
@@ -344,28 +363,2 @@ | ||
}); | ||
it('calls terminateDialog', function() { | ||
spyOn(Subscription, 'terminateDialog'); | ||
Subscription.close(); | ||
expect(Subscription.terminateDialog).toHaveBeenCalled(); | ||
}); | ||
it('calls clearTimeout on both timers', function() { | ||
spyOn(SIP.Timers, 'clearTimeout'); | ||
Subscription.state = 'terminated'; //to ensure number of calls to clearTimeout | ||
Subscription.close(); | ||
expect(SIP.Timers.clearTimeout.calls.count()).toBe(2); | ||
}); | ||
it('deletes the subscription from ua.subscriptions', function() { | ||
Subscription.id = 'fake'; | ||
ua.subscriptions[Subscription.id] = Subscription; | ||
Subscription.close(); | ||
expect(ua.subscriptions[Subscription.id]).toBeUndefined(); | ||
}); | ||
}); | ||
@@ -563,3 +556,3 @@ | ||
it('if sub_state.state is terminated with reason deactivated or timeout, subscribe will be called without close (always a log)', function() { | ||
it('if sub_state.state is terminated with reason deactivated or timeout and state is not terminated, subscribe will be called without close (always a log)', function() { | ||
request.setHeader('Subscription-State', 'terminated;expires=3600;reason=deactivated'); | ||
@@ -570,2 +563,4 @@ spyOn(Subscription, 'subscribe'); | ||
Subscription.state = 'pending'; | ||
Subscription.receiveRequest(request); | ||
@@ -585,2 +580,17 @@ | ||
it('if sub_state.state is terminated with reason deactivated or timeout and state is not terminated, the Subscription will be gracefully shut down', function() { | ||
spyOn(Subscription, 'terminateDialog'); | ||
spyOn(SIP.Timers, 'clearTimeout'); | ||
Subscription.id = 'fake'; | ||
ua.subscriptions[Subscription.id] = Subscription; | ||
Subscription.state = 'terminated'; | ||
Subscription.timer_fire(); | ||
expect(SIP.Timers.clearTimeout.calls.count()).toBe(2); | ||
expect(Subscription.terminateDialog).toHaveBeenCalled(); | ||
expect(ua.subscriptions[Subscription.id]).toBeUndefined(); | ||
}); | ||
it('if sub_state.state is terminated with reason probation or giveup, subscribe will be called or the sub_duration timer will be set if retry-after is present, both without close', function() { | ||
@@ -632,2 +642,16 @@ request.setHeader('Subscription-State', 'terminated;retry-after=3600;reason=probation'); | ||
describe('.onDialogError', function() { | ||
it('does not throw an exception', function() { | ||
Subscription.dialog = { | ||
sendRequest: function() {} | ||
}; | ||
function errant () { | ||
Subscription.onDialogError(); | ||
} | ||
expect(errant).not.toThrow(); | ||
}); | ||
}); | ||
describe('.matchEvent', function() { | ||
@@ -634,0 +658,0 @@ var request; |
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
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
794780
18546
1