openvidu/openvidu-browser/src/main/resources/static/js/OpenVidu.js

17382 lines
1.5 MiB
JavaScript
Raw Normal View History

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*
* (C) Copyright 2014-2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var freeice = require('freeice')
var inherits = require('inherits')
var UAParser = require('ua-parser-js')
var uuid = require('uuid')
var hark = require('hark')
var EventEmitter = require('events').EventEmitter
var recursive = require('merge').recursive.bind(undefined, true)
var sdpTranslator = require('sdp-translator')
var logger = window.Logger || console
// var gUM = navigator.mediaDevices.getUserMedia || function (constraints) {
// return new Promise(navigator.getUserMedia(constraints, function (stream) {
// videoStream = stream
// start()
// }).eror(callback));
// }
try {
require('kurento-browser-extensions')
} catch (error) {
if (typeof getScreenConstraints === 'undefined') {
logger.warn('screen sharing is not available')
getScreenConstraints = function getScreenConstraints(sendSource, callback) {
callback(new Error("This library is not enabled for screen sharing"))
}
}
}
var MEDIA_CONSTRAINTS = {
audio: true,
video: {
width: 640,
framerate: 15
}
}
// Somehow, the UAParser constructor gets an empty window object.
// We need to pass the user agent string in order to get information
var ua = (window && window.navigator) ? window.navigator.userAgent : ''
var parser = new UAParser(ua)
var browser = parser.getBrowser()
var usePlanB = false
if (browser.name === 'Chrome' || browser.name === 'Chromium') {
logger.info(browser.name + ": using SDP PlanB")
usePlanB = true
}
function noop(error) {
if (error) logger.error(error)
}
function trackStop(track) {
track.stop && track.stop()
}
function streamStop(stream) {
stream.getTracks().forEach(trackStop)
}
/**
* Returns a string representation of a SessionDescription object.
*/
var dumpSDP = function (description) {
if (typeof description === 'undefined' || description === null) {
return ''
}
return 'type: ' + description.type + '\r\n' + description.sdp
}
function bufferizeCandidates(pc, onerror) {
var candidatesQueue = []
pc.addEventListener('signalingstatechange', function () {
if (this.signalingState === 'stable') {
while (candidatesQueue.length) {
var entry = candidatesQueue.shift()
this.addIceCandidate(entry.candidate, entry.callback, entry.callback)
}
}
})
return function (candidate, callback) {
callback = callback || onerror
switch (pc.signalingState) {
case 'closed':
callback(new Error('PeerConnection object is closed'))
break
case 'stable':
if (pc.remoteDescription) {
pc.addIceCandidate(candidate, callback, callback)
break
}
default:
candidatesQueue.push({
candidate: candidate,
callback: callback
})
}
}
}
/* Simulcast utilities */
function removeFIDFromOffer(sdp) {
var n = sdp.indexOf("a=ssrc-group:FID");
if (n > 0) {
return sdp.slice(0, n);
} else {
return sdp;
}
}
function getSimulcastInfo(videoStream) {
var videoTracks = videoStream.getVideoTracks();
if (!videoTracks.length) {
logger.warn('No video tracks available in the video stream')
return ''
}
var lines = [
'a=x-google-flag:conference',
'a=ssrc-group:SIM 1 2 3',
'a=ssrc:1 cname:localVideo',
'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:1 mslabel:' + videoStream.id,
'a=ssrc:1 label:' + videoTracks[0].id,
'a=ssrc:2 cname:localVideo',
'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:2 mslabel:' + videoStream.id,
'a=ssrc:2 label:' + videoTracks[0].id,
'a=ssrc:3 cname:localVideo',
'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:3 mslabel:' + videoStream.id,
'a=ssrc:3 label:' + videoTracks[0].id
];
lines.push('');
return lines.join('\n');
}
/**
* Wrapper object of an RTCPeerConnection. This object is aimed to simplify the
* development of WebRTC-based applications.
*
* @constructor module:kurentoUtils.WebRtcPeer
*
* @param {String} mode Mode in which the PeerConnection will be configured.
* Valid values are: 'recv', 'send', and 'sendRecv'
* @param localVideo Video tag for the local stream
* @param remoteVideo Video tag for the remote stream
* @param {MediaStream} videoStream Stream to be used as primary source
* (typically video and audio, or only video if combined with audioStream) for
* localVideo and to be added as stream to the RTCPeerConnection
* @param {MediaStream} audioStream Stream to be used as second source
* (typically for audio) for localVideo and to be added as stream to the
* RTCPeerConnection
*/
function WebRtcPeer(mode, options, callback) {
if (!(this instanceof WebRtcPeer)) {
return new WebRtcPeer(mode, options, callback)
}
WebRtcPeer.super_.call(this)
if (options instanceof Function) {
callback = options
options = undefined
}
options = options || {}
callback = (callback || noop).bind(this)
var self = this
var localVideo = options.localVideo
var remoteVideo = options.remoteVideo
var videoStream = options.videoStream
var audioStream = options.audioStream
var mediaConstraints = options.mediaConstraints
var connectionConstraints = options.connectionConstraints
var pc = options.peerConnection
var sendSource = options.sendSource || 'webcam'
var dataChannelConfig = options.dataChannelConfig
var useDataChannels = options.dataChannels || false
var dataChannel
var guid = uuid.v4()
var configuration = recursive({
iceServers: freeice()
},
options.configuration)
var onicecandidate = options.onicecandidate
if (onicecandidate) this.on('icecandidate', onicecandidate)
var oncandidategatheringdone = options.oncandidategatheringdone
if (oncandidategatheringdone) {
this.on('candidategatheringdone', oncandidategatheringdone)
}
var simulcast = options.simulcast
var multistream = options.multistream
var interop = new sdpTranslator.Interop()
var candidatesQueueOut = []
var candidategatheringdone = false
Object.defineProperties(this, {
'peerConnection': {
get: function () {
return pc
}
},
'id': {
value: options.id || guid,
writable: false
},
'remoteVideo': {
get: function () {
return remoteVideo
}
},
'localVideo': {
get: function () {
return localVideo
}
},
'dataChannel': {
get: function () {
return dataChannel
}
},
/**
* @member {(external:ImageData|undefined)} currentFrame
*/
'currentFrame': {
get: function () {
// [ToDo] Find solution when we have a remote stream but we didn't set
// a remoteVideo tag
if (!remoteVideo) return;
if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
throw new Error('No video stream data available')
var canvas = document.createElement('canvas')
canvas.width = remoteVideo.videoWidth
canvas.height = remoteVideo.videoHeight
canvas.getContext('2d').drawImage(remoteVideo, 0, 0)
return canvas
}
}
})
// Init PeerConnection
if (!pc) {
pc = new RTCPeerConnection(configuration);
if (useDataChannels && !dataChannel) {
var dcId = 'WebRtcPeer-' + self.id
var dcOptions = undefined
if (dataChannelConfig) {
dcId = dataChannelConfig.id || dcId
dcOptions = dataChannelConfig.options
}
dataChannel = pc.createDataChannel(dcId, dcOptions);
if (dataChannelConfig) {
dataChannel.onopen = dataChannelConfig.onopen;
dataChannel.onclose = dataChannelConfig.onclose;
dataChannel.onmessage = dataChannelConfig.onmessage;
dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
dataChannel.onerror = dataChannelConfig.onerror || noop;
}
}
}
pc.addEventListener('icecandidate', function (event) {
var candidate = event.candidate
if (EventEmitter.listenerCount(self, 'icecandidate') ||
EventEmitter.listenerCount(
self, 'candidategatheringdone')) {
if (candidate) {
var cand
if (multistream && usePlanB) {
cand = interop.candidateToUnifiedPlan(candidate)
} else {
cand = candidate
}
self.emit('icecandidate', cand)
candidategatheringdone = false
} else if (!candidategatheringdone) {
self.emit('candidategatheringdone')
candidategatheringdone = true
}
} else if (!candidategatheringdone) {
// Not listening to 'icecandidate' or 'candidategatheringdone' events, queue
// the candidate until one of them is listened
candidatesQueueOut.push(candidate)
if (!candidate) candidategatheringdone = true
}
})
pc.ontrack = options.onaddstream
pc.onnegotiationneeded = options.onnegotiationneeded
this.on('newListener', function (event, listener) {
if (event === 'icecandidate' || event === 'candidategatheringdone') {
while (candidatesQueueOut.length) {
var candidate = candidatesQueueOut.shift()
if (!candidate === (event === 'candidategatheringdone')) {
listener(candidate)
}
}
}
})
var addIceCandidate = bufferizeCandidates(pc)
/**
* Callback function invoked when an ICE candidate is received. Developers are
* expected to invoke this function in order to complete the SDP negotiation.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.addIceCandidate
*
* @param iceCandidate - Literal object with the ICE candidate description
* @param callback - Called when the ICE candidate has been added.
*/
this.addIceCandidate = function (iceCandidate, callback) {
var candidate
if (multistream && usePlanB) {
candidate = interop.candidateToPlanB(iceCandidate)
} else {
candidate = new RTCIceCandidate(iceCandidate)
}
logger.debug('Remote ICE candidate received', iceCandidate)
callback = (callback || noop).bind(this)
addIceCandidate(candidate, callback)
}
this.generateOffer = function (callback) {
callback = callback.bind(this)
var offerAudio = true
var offerVideo = true
// Constraints must have both blocks
if (mediaConstraints) {
offerAudio = (typeof mediaConstraints.audio === 'boolean') ?
mediaConstraints.audio : true
offerVideo = (typeof mediaConstraints.video === 'boolean') ?
mediaConstraints.video : true
}
var browserDependantConstraints = {
offerToReceiveAudio: (mode !== 'sendonly' && offerAudio),
offerToReceiveVideo: (mode !== 'sendonly' && offerVideo)
}
//FIXME: clarify possible constraints passed to createOffer()
/*var constraints = recursive(browserDependantConstraints,
connectionConstraints)*/
var constraints = browserDependantConstraints;
logger.info('constraints: ' + JSON.stringify(constraints))
pc.createOffer(constraints).then(function (offer) {
logger.info('Created SDP offer')
offer = mangleSdpToAddSimulcast(offer)
return pc.setLocalDescription(offer)
}).then(function () {
var localDescription = pc.localDescription
logger.info('Local description set', localDescription.sdp)
if (multistream && usePlanB) {
localDescription = interop.toUnifiedPlan(localDescription)
logger.info('offer::origPlanB->UnifiedPlan', dumpSDP(
localDescription))
}
callback(null, localDescription.sdp, self.processAnswer.bind(
self))
}).catch(callback)
}
this.getLocalSessionDescriptor = function () {
return pc.localDescription
}
this.getRemoteSessionDescriptor = function () {
return pc.remoteDescription
}
function setRemoteVideo() {
if (remoteVideo) {
var stream = pc.getRemoteStreams()[0]
var url = stream ? URL.createObjectURL(stream) : ''
remoteVideo.pause()
remoteVideo.src = url
remoteVideo.load()
logger.info('Remote URL:', url)
}
}
this.showLocalVideo = function () {
localVideo.src = URL.createObjectURL(videoStream)
localVideo.muted = true
}
this.send = function (data) {
if (dataChannel && dataChannel.readyState === 'open') {
dataChannel.send(data)
} else {
logger.warn(
'Trying to send data over a non-existing or closed data channel')
}
}
/**
* Callback function invoked when a SDP answer is received. Developers are
* expected to invoke this function in order to complete the SDP negotiation.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.processAnswer
*
* @param sdpAnswer - Description of sdpAnswer
* @param callback -
* Invoked after the SDP answer is processed, or there is an error.
*/
this.processAnswer = function (sdpAnswer, callback) {
callback = (callback || noop).bind(this)
var answer = new RTCSessionDescription({
type: 'answer',
sdp: sdpAnswer
})
if (multistream && usePlanB) {
var planBAnswer = interop.toPlanB(answer)
logger.info('asnwer::planB', dumpSDP(planBAnswer))
answer = planBAnswer
}
logger.info('SDP answer received, setting remote description')
if (pc.signalingState === 'closed') {
return callback('PeerConnection is closed')
}
pc.setRemoteDescription(answer, function () {
setRemoteVideo()
callback()
},
callback)
}
/**
* Callback function invoked when a SDP offer is received. Developers are
* expected to invoke this function in order to complete the SDP negotiation.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.processOffer
*
* @param sdpOffer - Description of sdpOffer
* @param callback - Called when the remote description has been set
* successfully.
*/
this.processOffer = function (sdpOffer, callback) {
callback = callback.bind(this)
var offer = new RTCSessionDescription({
type: 'offer',
sdp: sdpOffer
})
if (multistream && usePlanB) {
var planBOffer = interop.toPlanB(offer)
logger.info('offer::planB', dumpSDP(planBOffer))
offer = planBOffer
}
logger.info('SDP offer received, setting remote description')
if (pc.signalingState === 'closed') {
return callback('PeerConnection is closed')
}
pc.setRemoteDescription(offer).then(function () {
return setRemoteVideo()
}).then(function () {
return pc.createAnswer()
}).then(function (answer) {
answer = mangleSdpToAddSimulcast(answer)
logger.info('Created SDP answer')
return pc.setLocalDescription(answer)
}).then(function () {
var localDescription = pc.localDescription
if (multistream && usePlanB) {
localDescription = interop.toUnifiedPlan(localDescription)
logger.info('answer::origPlanB->UnifiedPlan', dumpSDP(
localDescription))
}
logger.info('Local description set', localDescription.sdp)
callback(null, localDescription.sdp)
}).catch(callback)
}
function mangleSdpToAddSimulcast(answer) {
if (simulcast) {
if (browser.name === 'Chrome' || browser.name === 'Chromium') {
logger.info('Adding multicast info')
answer = new RTCSessionDescription({
'type': answer.type,
'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(
videoStream)
})
} else {
logger.warn('Simulcast is only available in Chrome browser.')
}
}
return answer
}
/**
* This function creates the RTCPeerConnection object taking into account the
* properties received in the constructor. It starts the SDP negotiation
* process: generates the SDP offer and invokes the onsdpoffer callback. This
* callback is expected to send the SDP offer, in order to obtain an SDP
* answer from another peer.
*/
function start() {
if (pc.signalingState === 'closed') {
callback(
'The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'
)
}
if (videoStream && localVideo) {
self.showLocalVideo()
}
if (videoStream) {
pc.addStream(videoStream)
}
if (audioStream) {
pc.addStream(audioStream)
}
// [Hack] https://code.google.com/p/chromium/issues/detail?id=443558
var browser = parser.getBrowser()
if (mode === 'sendonly' &&
(browser.name === 'Chrome' || browser.name === 'Chromium') &&
browser.major === 39) {
mode = 'sendrecv'
}
callback()
}
if (mode !== 'recvonly' && !videoStream && !audioStream) {
function getMedia(constraints) {
if (constraints === undefined) {
constraints = MEDIA_CONSTRAINTS
}
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
videoStream = stream
start()
}).catch(callback);
}
if (sendSource === 'webcam') {
getMedia(mediaConstraints)
} else {
getScreenConstraints(sendSource, function (error, constraints_) {
if (error)
return callback(error)
constraints = [mediaConstraints]
constraints.unshift(constraints_)
getMedia(recursive.apply(undefined, constraints))
}, guid)
}
} else {
setTimeout(start, 0)
}
this.on('_dispose', function () {
if (localVideo) {
localVideo.pause()
localVideo.src = ''
localVideo.load()
//Unmute local video in case the video tag is later used for remote video
localVideo.muted = false
}
if (remoteVideo) {
remoteVideo.pause()
remoteVideo.src = ''
remoteVideo.load()
}
self.removeAllListeners()
if (window.cancelChooseDesktopMedia !== undefined) {
window.cancelChooseDesktopMedia(guid)
}
})
}
inherits(WebRtcPeer, EventEmitter)
function createEnableDescriptor(type) {
var method = 'get' + type + 'Tracks'
return {
enumerable: true,
get: function () {
// [ToDo] Should return undefined if not all tracks have the same value?
if (!this.peerConnection) return
var streams = this.peerConnection.getLocalStreams()
if (!streams.length) return
for (var i = 0, stream; stream = streams[i]; i++) {
var tracks = stream[method]()
for (var j = 0, track; track = tracks[j]; j++)
if (!track.enabled) return false
}
return true
},
set: function (value) {
function trackSetEnable(track) {
track.enabled = value
}
this.peerConnection.getLocalStreams().forEach(function (stream) {
stream[method]().forEach(trackSetEnable)
})
}
}
}
Object.defineProperties(WebRtcPeer.prototype, {
'enabled': {
enumerable: true,
get: function () {
return this.audioEnabled && this.videoEnabled
},
set: function (value) {
this.audioEnabled = this.videoEnabled = value
}
},
'audioEnabled': createEnableDescriptor('Audio'),
'videoEnabled': createEnableDescriptor('Video')
})
WebRtcPeer.prototype.getLocalStream = function (index) {
if (this.peerConnection) {
return this.peerConnection.getLocalStreams()[index || 0]
}
}
WebRtcPeer.prototype.getRemoteStream = function (index) {
if (this.peerConnection) {
return this.peerConnection.getRemoteStreams()[index || 0]
}
}
/**
* @description This method frees the resources used by WebRtcPeer.
*
* @function module:kurentoUtils.WebRtcPeer.prototype.dispose
*/
WebRtcPeer.prototype.dispose = function () {
logger.info('Disposing WebRtcPeer')
var pc = this.peerConnection
var dc = this.dataChannel
try {
if (dc) {
if (dc.signalingState === 'closed') return
dc.close()
}
if (pc) {
if (pc.signalingState === 'closed') return
pc.getLocalStreams().forEach(streamStop)
// FIXME This is not yet implemented in firefox
// if(videoStream) pc.removeStream(videoStream);
// if(audioStream) pc.removeStream(audioStream);
pc.close()
}
} catch (err) {
logger.warn('Exception disposing webrtc peer ' + err)
}
this.emit('_dispose')
}
//
// Specialized child classes
//
function WebRtcPeerRecvonly(options, callback) {
if (!(this instanceof WebRtcPeerRecvonly)) {
return new WebRtcPeerRecvonly(options, callback)
}
WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback)
}
inherits(WebRtcPeerRecvonly, WebRtcPeer)
function WebRtcPeerSendonly(options, callback) {
if (!(this instanceof WebRtcPeerSendonly)) {
return new WebRtcPeerSendonly(options, callback)
}
WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback)
}
inherits(WebRtcPeerSendonly, WebRtcPeer)
function WebRtcPeerSendrecv(options, callback) {
if (!(this instanceof WebRtcPeerSendrecv)) {
return new WebRtcPeerSendrecv(options, callback)
}
WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback)
}
inherits(WebRtcPeerSendrecv, WebRtcPeer)
function harkUtils(stream, options) {
return hark(stream, options);
}
exports.bufferizeCandidates = bufferizeCandidates
exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly
exports.WebRtcPeerSendonly = WebRtcPeerSendonly
exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv
exports.hark = harkUtils
},{"events":114,"freeice":3,"hark":6,"inherits":7,"kurento-browser-extensions":undefined,"merge":8,"sdp-translator":15,"ua-parser-js":18,"uuid":20}],2:[function(require,module,exports){
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* This module contains a set of reusable components that have been found useful
* during the development of the WebRTC applications with Kurento.
*
* @module kurentoUtils
*
* @copyright 2014 Kurento (http://kurento.org/)
* @license ALv2
*/
var WebRtcPeer = require('./WebRtcPeer');
exports.WebRtcPeer = WebRtcPeer;
},{"./WebRtcPeer":1}],3:[function(require,module,exports){
/* jshint node: true */
'use strict';
var normalice = require('normalice');
/**
# freeice
The `freeice` module is a simple way of getting random STUN or TURN server
for your WebRTC application. The list of servers (just STUN at this stage)
were sourced from this [gist](https://gist.github.com/zziuni/3741933).
## Example Use
The following demonstrates how you can use `freeice` with
[rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect):
<<< examples/quickconnect.js
As the `freeice` module generates ice servers in a list compliant with the
WebRTC spec you will be able to use it with raw `RTCPeerConnection`
constructors and other WebRTC libraries.
## Hey, don't use my STUN/TURN server!
If for some reason your free STUN or TURN server ends up in the
list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or
[turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json))
that is used in this module, you can feel
free to open an issue on this repository and those servers will be removed
within 24 hours (or sooner). This is the quickest and probably the most
polite way to have something removed (and provides us some visibility
if someone opens a pull request requesting that a server is added).
## Please add my server!
If you have a server that you wish to add to the list, that's awesome! I'm
sure I speak on behalf of a whole pile of WebRTC developers who say thanks.
To get it into the list, feel free to either open a pull request or if you
find that process a bit daunting then just create an issue requesting
the addition of the server (make sure you provide all the details, and if
you have a Terms of Service then including that in the PR/issue would be
awesome).
## I know of a free server, can I add it?
Sure, if you do your homework and make sure it is ok to use (I'm currently
in the process of reviewing the terms of those STUN servers included from
the original list). If it's ok to go, then please see the previous entry
for how to add it.
## Current List of Servers
* current as at the time of last `README.md` file generation
### STUN
<<< stun.json
### TURN
<<< turn.json
**/
var freeice = module.exports = function(opts) {
// if a list of servers has been provided, then use it instead of defaults
var servers = {
stun: (opts || {}).stun || require('./stun.json'),
turn: (opts || {}).turn || require('./turn.json')
};
var stunCount = (opts || {}).stunCount || 2;
var turnCount = (opts || {}).turnCount || 0;
var selected;
function getServers(type, count) {
var out = [];
var input = [].concat(servers[type]);
var idx;
while (input.length && out.length < count) {
idx = (Math.random() * input.length) | 0;
out = out.concat(input.splice(idx, 1));
}
return out.map(function(url) {
//If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up
if ((typeof url !== 'string') && (! (url instanceof String))) {
return url;
} else {
return normalice(type + ':' + url);
}
});
}
// add stun servers
selected = [].concat(getServers('stun', stunCount));
if (turnCount) {
selected = selected.concat(getServers('turn', turnCount));
}
return selected;
};
},{"./stun.json":4,"./turn.json":5,"normalice":9}],4:[function(require,module,exports){
module.exports=[
"stun.l.google.com:19302",
"stun1.l.google.com:19302",
"stun2.l.google.com:19302",
"stun3.l.google.com:19302",
"stun4.l.google.com:19302",
"stun.ekiga.net",
"stun.ideasip.com",
"stun.schlund.de",
"stun.stunprotocol.org:3478",
"stun.voiparound.com",
"stun.voipbuster.com",
"stun.voipstunt.com",
"stun.voxgratia.org",
"stun.services.mozilla.com"
]
},{}],5:[function(require,module,exports){
module.exports=[]
},{}],6:[function(require,module,exports){
var WildEmitter = require('wildemitter');
function getMaxVolume (analyser, fftBins) {
var maxVolume = -Infinity;
analyser.getFloatFrequencyData(fftBins);
for(var i=4, ii=fftBins.length; i < ii; i++) {
if (fftBins[i] > maxVolume && fftBins[i] < 0) {
maxVolume = fftBins[i];
}
};
return maxVolume;
}
var audioContextType = window.AudioContext || window.webkitAudioContext;
// use a single audio context due to hardware limits
var audioContext = null;
module.exports = function(stream, options) {
var harker = new WildEmitter();
// make it not break in non-supported browsers
if (!audioContextType) return harker;
//Config
var options = options || {},
smoothing = (options.smoothing || 0.1),
interval = (options.interval || 50),
threshold = options.threshold,
play = options.play,
history = options.history || 10,
running = true;
//Setup Audio Context
if (!audioContext) {
audioContext = new audioContextType();
}
var sourceNode, fftBins, analyser;
analyser = audioContext.createAnalyser();
analyser.fftSize = 512;
analyser.smoothingTimeConstant = smoothing;
fftBins = new Float32Array(analyser.fftSize);
if (stream.jquery) stream = stream[0];
if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {
//Audio Tag
sourceNode = audioContext.createMediaElementSource(stream);
if (typeof play === 'undefined') play = true;
threshold = threshold || -50;
} else {
//WebRTC Stream
sourceNode = audioContext.createMediaStreamSource(stream);
threshold = threshold || -50;
}
sourceNode.connect(analyser);
if (play) analyser.connect(audioContext.destination);
harker.speaking = false;
harker.setThreshold = function(t) {
threshold = t;
};
harker.setInterval = function(i) {
interval = i;
};
harker.stop = function() {
running = false;
harker.emit('volume_change', -100, threshold);
if (harker.speaking) {
harker.speaking = false;
harker.emit('stopped_speaking');
}
};
harker.speakingHistory = [];
for (var i = 0; i < history; i++) {
harker.speakingHistory.push(0);
}
// Poll the analyser node to determine if speaking
// and emit events if changed
var looper = function() {
setTimeout(function() {
//check if stop has been called
if(!running) {
return;
}
var currentVolume = getMaxVolume(analyser, fftBins);
harker.emit('volume_change', currentVolume, threshold);
var history = 0;
if (currentVolume > threshold && !harker.speaking) {
// trigger quickly, short history
for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {
history += harker.speakingHistory[i];
}
if (history >= 2) {
harker.speaking = true;
harker.emit('speaking');
}
} else if (currentVolume < threshold && harker.speaking) {
for (var i = 0; i < harker.speakingHistory.length; i++) {
history += harker.speakingHistory[i];
}
if (history == 0) {
harker.speaking = false;
harker.emit('stopped_speaking');
}
}
harker.speakingHistory.shift();
harker.speakingHistory.push(0 + (currentVolume > threshold));
looper();
}, interval);
};
looper();
return harker;
}
},{"wildemitter":21}],7:[function(require,module,exports){
if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
} else {
// old school shim for old browsers
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
var TempCtor = function () {}
TempCtor.prototype = superCtor.prototype
ctor.prototype = new TempCtor()
ctor.prototype.constructor = ctor
}
}
},{}],8:[function(require,module,exports){
/*!
* @name JavaScript/NodeJS Merge v1.2.0
* @author yeikos
* @repository https://github.com/yeikos/js.merge
* Copyright 2014 yeikos - MIT license
* https://raw.github.com/yeikos/js.merge/master/LICENSE
*/
;(function(isNode) {
/**
* Merge one or more objects
* @param bool? clone
* @param mixed,... arguments
* @return object
*/
var Public = function(clone) {
return merge(clone === true, false, arguments);
}, publicName = 'merge';
/**
* Merge two or more objects recursively
* @param bool? clone
* @param mixed,... arguments
* @return object
*/
Public.recursive = function(clone) {
return merge(clone === true, true, arguments);
};
/**
* Clone the input removing any reference
* @param mixed input
* @return mixed
*/
Public.clone = function(input) {
var output = input,
type = typeOf(input),
index, size;
if (type === 'array') {
output = [];
size = input.length;
for (index=0;index<size;++index)
output[index] = Public.clone(input[index]);
} else if (type === 'object') {
output = {};
for (index in input)
output[index] = Public.clone(input[index]);
}
return output;
};
/**
* Merge two objects recursively
* @param mixed input
* @param mixed extend
* @return mixed
*/
function merge_recursive(base, extend) {
if (typeOf(base) !== 'object')
return extend;
for (var key in extend) {
if (typeOf(base[key]) === 'object' && typeOf(extend[key]) === 'object') {
base[key] = merge_recursive(base[key], extend[key]);
} else {
base[key] = extend[key];
}
}
return base;
}
/**
* Merge two or more objects
* @param bool clone
* @param bool recursive
* @param array argv
* @return object
*/
function merge(clone, recursive, argv) {
var result = argv[0],
size = argv.length;
if (clone || typeOf(result) !== 'object')
result = {};
for (var index=0;index<size;++index) {
var item = argv[index],
type = typeOf(item);
if (type !== 'object') continue;
for (var key in item) {
var sitem = clone ? Public.clone(item[key]) : item[key];
if (recursive) {
result[key] = merge_recursive(result[key], sitem);
} else {
result[key] = sitem;
}
}
}
return result;
}
/**
* Get type of variable
* @param mixed input
* @return string
*
* @see http://jsperf.com/typeofvar
*/
function typeOf(input) {
return ({}).toString.call(input).slice(8, -1).toLowerCase();
}
if (isNode) {
module.exports = Public;
} else {
window[publicName] = Public;
}
})(typeof module === 'object' && module && typeof module.exports === 'object' && module.exports);
},{}],9:[function(require,module,exports){
/**
# normalice
Normalize an ice server configuration object (or plain old string) into a format
that is usable in all browsers supporting WebRTC. Primarily this module is designed
to help with the transition of the `url` attribute of the configuration object to
the `urls` attribute.
## Example Usage
<<< examples/simple.js
**/
var protocols = [
'stun:',
'turn:'
];
module.exports = function(input) {
var url = (input || {}).url || input;
var protocol;
var parts;
var output = {};
// if we don't have a string url, then allow the input to passthrough
if (typeof url != 'string' && (! (url instanceof String))) {
return input;
}
// trim the url string, and convert to an array
url = url.trim();
// if the protocol is not known, then passthrough
protocol = protocols[protocols.indexOf(url.slice(0, 5))];
if (! protocol) {
return input;
}
// now let's attack the remaining url parts
url = url.slice(5);
parts = url.split('@');
output.username = input.username;
output.credential = input.credential;
// if we have an authentication part, then set the credentials
if (parts.length > 1) {
url = parts[1];
parts = parts[0].split(':');
// add the output credential and username
output.username = parts[0];
output.credential = (input || {}).credential || parts[1] || '';
}
output.url = protocol + url;
output.urls = [ output.url ];
return output;
};
},{}],10:[function(require,module,exports){
var grammar = module.exports = {
v: [{
name: 'version',
reg: /^(\d*)$/
}],
o: [{ //o=- 20518 0 IN IP4 203.0.113.1
// NB: sessionId will be a String in most cases because it is huge
name: 'origin',
reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
format: "%s %s %d %s IP%d %s"
}],
// default parsing of these only (though some of these feel outdated)
s: [{ name: 'name' }],
i: [{ name: 'description' }],
u: [{ name: 'uri' }],
e: [{ name: 'email' }],
p: [{ name: 'phone' }],
z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly
//k: [{}], // outdated thing ignored
t: [{ //t=0 0
name: 'timing',
reg: /^(\d*) (\d*)/,
names: ['start', 'stop'],
format: "%d %d"
}],
c: [{ //c=IN IP4 10.47.197.26
name: 'connection',
reg: /^IN IP(\d) (\S*)/,
names: ['version', 'ip'],
format: "IN IP%d %s"
}],
b: [{ //b=AS:4000
push: 'bandwidth',
reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
names: ['type', 'limit'],
format: "%s:%s"
}],
m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
// NB: special - pushes to session
// TODO: rtp/fmtp should be filtered by the payloads found here?
reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
names: ['type', 'port', 'protocol', 'payloads'],
format: "%s %d %s %s"
}],
a: [
{ //a=rtpmap:110 opus/48000/2
push: 'rtp',
reg: /^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
names: ['payload', 'codec', 'rate', 'encoding'],
format: function (o) {
return (o.encoding) ?
"rtpmap:%d %s/%s/%s":
o.rate ?
"rtpmap:%d %s/%s":
"rtpmap:%d %s";
}
},
{
//a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
//a=fmtp:111 minptime=10; useinbandfec=1
push: 'fmtp',
reg: /^fmtp:(\d*) ([\S| ]*)/,
names: ['payload', 'config'],
format: "fmtp:%d %s"
},
{ //a=control:streamid=0
name: 'control',
reg: /^control:(.*)/,
format: "control:%s"
},
{ //a=rtcp:65179 IN IP4 193.84.77.194
name: 'rtcp',
reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
names: ['port', 'netType', 'ipVer', 'address'],
format: function (o) {
return (o.address != null) ?
"rtcp:%d %s IP%d %s":
"rtcp:%d";
}
},
{ //a=rtcp-fb:98 trr-int 100
push: 'rtcpFbTrrInt',
reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
names: ['payload', 'value'],
format: "rtcp-fb:%d trr-int %d"
},
{ //a=rtcp-fb:98 nack rpsi
push: 'rtcpFb',
reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
names: ['payload', 'type', 'subtype'],
format: function (o) {
return (o.subtype != null) ?
"rtcp-fb:%s %s %s":
"rtcp-fb:%s %s";
}
},
{ //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
//a=extmap:1/recvonly URI-gps-string
push: 'ext',
reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,
names: ['value', 'uri', 'config'], // value may include "/direction" suffix
format: function (o) {
return (o.config != null) ?
"extmap:%s %s %s":
"extmap:%s %s";
}
},
{
//a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
push: 'crypto',
reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
names: ['id', 'suite', 'config', 'sessionConfig'],
format: function (o) {
return (o.sessionConfig != null) ?
"crypto:%d %s %s %s":
"crypto:%d %s %s";
}
},
{ //a=setup:actpass
name: 'setup',
reg: /^setup:(\w*)/,
format: "setup:%s"
},
{ //a=mid:1
name: 'mid',
reg: /^mid:([^\s]*)/,
format: "mid:%s"
},
{ //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
name: 'msid',
reg: /^msid:(.*)/,
format: "msid:%s"
},
{ //a=ptime:20
name: 'ptime',
reg: /^ptime:(\d*)/,
format: "ptime:%d"
},
{ //a=maxptime:60
name: 'maxptime',
reg: /^maxptime:(\d*)/,
format: "maxptime:%d"
},
{ //a=sendrecv
name: 'direction',
reg: /^(sendrecv|recvonly|sendonly|inactive)/
},
{ //a=ice-lite
name: 'icelite',
reg: /^(ice-lite)/
},
{ //a=ice-ufrag:F7gI
name: 'iceUfrag',
reg: /^ice-ufrag:(\S*)/,
format: "ice-ufrag:%s"
},
{ //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
name: 'icePwd',
reg: /^ice-pwd:(\S*)/,
format: "ice-pwd:%s"
},
{ //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
name: 'fingerprint',
reg: /^fingerprint:(\S*) (\S*)/,
names: ['type', 'hash'],
format: "fingerprint:%s %s"
},
{
//a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
//a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
//a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
//a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0
//a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0
push:'candidates',
reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/,
names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation'],
format: function (o) {
var str = "candidate:%s %d %s %d %s %d typ %s";
str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v";
// NB: candidate has three optional chunks, so %void middles one if it's missing
str += (o.tcptype != null) ? " tcptype %s" : "%v";
if (o.generation != null) {
str += " generation %d";
}
return str;
}
},
{ //a=end-of-candidates (keep after the candidates line for readability)
name: 'endOfCandidates',
reg: /^(end-of-candidates)/
},
{ //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
name: 'remoteCandidates',
reg: /^remote-candidates:(.*)/,
format: "remote-candidates:%s"
},
{ //a=ice-options:google-ice
name: 'iceOptions',
reg: /^ice-options:(\S*)/,
format: "ice-options:%s"
},
{ //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
push: "ssrcs",
reg: /^ssrc:(\d*) ([\w_]*):(.*)/,
names: ['id', 'attribute', 'value'],
format: "ssrc:%d %s:%s"
},
{ //a=ssrc-group:FEC 1 2
push: "ssrcGroups",
reg: /^ssrc-group:(\w*) (.*)/,
names: ['semantics', 'ssrcs'],
format: "ssrc-group:%s %s"
},
{ //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
name: "msidSemantic",
reg: /^msid-semantic:\s?(\w*) (\S*)/,
names: ['semantic', 'token'],
format: "msid-semantic: %s %s" // space after ":" is not accidental
},
{ //a=group:BUNDLE audio video
push: 'groups',
reg: /^group:(\w*) (.*)/,
names: ['type', 'mids'],
format: "group:%s %s"
},
{ //a=rtcp-mux
name: 'rtcpMux',
reg: /^(rtcp-mux)/
},
{ //a=rtcp-rsize
name: 'rtcpRsize',
reg: /^(rtcp-rsize)/
},
{ // any a= that we don't understand is kepts verbatim on media.invalid
push: 'invalid',
names: ["value"]
}
]
};
// set sensible defaults to avoid polluting the grammar with boring details
Object.keys(grammar).forEach(function (key) {
var objs = grammar[key];
objs.forEach(function (obj) {
if (!obj.reg) {
obj.reg = /(.*)/;
}
if (!obj.format) {
obj.format = "%s";
}
});
});
},{}],11:[function(require,module,exports){
var parser = require('./parser');
var writer = require('./writer');
exports.write = writer;
exports.parse = parser.parse;
exports.parseFmtpConfig = parser.parseFmtpConfig;
exports.parsePayloads = parser.parsePayloads;
exports.parseRemoteCandidates = parser.parseRemoteCandidates;
},{"./parser":12,"./writer":13}],12:[function(require,module,exports){
var toIntIfInt = function (v) {
return String(Number(v)) === v ? Number(v) : v;
};
var attachProperties = function (match, location, names, rawName) {
if (rawName && !names) {
location[rawName] = toIntIfInt(match[1]);
}
else {
for (var i = 0; i < names.length; i += 1) {
if (match[i+1] != null) {
location[names[i]] = toIntIfInt(match[i+1]);
}
}
}
};
var parseReg = function (obj, location, content) {
var needsBlank = obj.name && obj.names;
if (obj.push && !location[obj.push]) {
location[obj.push] = [];
}
else if (needsBlank && !location[obj.name]) {
location[obj.name] = {};
}
var keyLocation = obj.push ?
{} : // blank object that will be pushed
needsBlank ? location[obj.name] : location; // otherwise, named location or root
attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
if (obj.push) {
location[obj.push].push(keyLocation);
}
};
var grammar = require('./grammar');
var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
exports.parse = function (sdp) {
var session = {}
, media = []
, location = session; // points at where properties go under (one of the above)
// parse lines we understand
sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
var type = l[0];
var content = l.slice(2);
if (type === 'm') {
media.push({rtp: [], fmtp: []});
location = media[media.length-1]; // point at latest media line
}
for (var j = 0; j < (grammar[type] || []).length; j += 1) {
var obj = grammar[type][j];
if (obj.reg.test(content)) {
return parseReg(obj, location, content);
}
}
});
session.media = media; // link it up
return session;
};
var fmtpReducer = function (acc, expr) {
var s = expr.split('=');
if (s.length === 2) {
acc[s[0]] = toIntIfInt(s[1]);
}
return acc;
};
exports.parseFmtpConfig = function (str) {
return str.split(/\;\s?/).reduce(fmtpReducer, {});
};
exports.parsePayloads = function (str) {
return str.split(' ').map(Number);
};
exports.parseRemoteCandidates = function (str) {
var candidates = [];
var parts = str.split(' ').map(toIntIfInt);
for (var i = 0; i < parts.length; i += 3) {
candidates.push({
component: parts[i],
ip: parts[i + 1],
port: parts[i + 2]
});
}
return candidates;
};
},{"./grammar":10}],13:[function(require,module,exports){
var grammar = require('./grammar');
// customized util.format - discards excess arguments and can void middle ones
var formatRegExp = /%[sdv%]/g;
var format = function (formatStr) {
var i = 1;
var args = arguments;
var len = args.length;
return formatStr.replace(formatRegExp, function (x) {
if (i >= len) {
return x; // missing argument
}
var arg = args[i];
i += 1;
switch (x) {
case '%%':
return '%';
case '%s':
return String(arg);
case '%d':
return Number(arg);
case '%v':
return '';
}
});
// NB: we discard excess arguments - they are typically undefined from makeLine
};
var makeLine = function (type, obj, location) {
var str = obj.format instanceof Function ?
(obj.format(obj.push ? location : location[obj.name])) :
obj.format;
var args = [type + '=' + str];
if (obj.names) {
for (var i = 0; i < obj.names.length; i += 1) {
var n = obj.names[i];
if (obj.name) {
args.push(location[obj.name][n]);
}
else { // for mLine and push attributes
args.push(location[obj.names[i]]);
}
}
}
else {
args.push(location[obj.name]);
}
return format.apply(null, args);
};
// RFC specified order
// TODO: extend this with all the rest
var defaultOuterOrder = [
'v', 'o', 's', 'i',
'u', 'e', 'p', 'c',
'b', 't', 'r', 'z', 'a'
];
var defaultInnerOrder = ['i', 'c', 'b', 'a'];
module.exports = function (session, opts) {
opts = opts || {};
// ensure certain properties exist
if (session.version == null) {
session.version = 0; // "v=0" must be there (only defined version atm)
}
if (session.name == null) {
session.name = " "; // "s= " must be there if no meaningful name set
}
session.media.forEach(function (mLine) {
if (mLine.payloads == null) {
mLine.payloads = "";
}
});
var outerOrder = opts.outerOrder || defaultOuterOrder;
var innerOrder = opts.innerOrder || defaultInnerOrder;
var sdp = [];
// loop through outerOrder for matching properties on session
outerOrder.forEach(function (type) {
grammar[type].forEach(function (obj) {
if (obj.name in session && session[obj.name] != null) {
sdp.push(makeLine(type, obj, session));
}
else if (obj.push in session && session[obj.push] != null) {
session[obj.push].forEach(function (el) {
sdp.push(makeLine(type, obj, el));
});
}
});
});
// then for each media line, follow the innerOrder
session.media.forEach(function (mLine) {
sdp.push(makeLine('m', grammar.m[0], mLine));
innerOrder.forEach(function (type) {
grammar[type].forEach(function (obj) {
if (obj.name in mLine && mLine[obj.name] != null) {
sdp.push(makeLine(type, obj, mLine));
}
else if (obj.push in mLine && mLine[obj.push] != null) {
mLine[obj.push].forEach(function (el) {
sdp.push(makeLine(type, obj, el));
});
}
});
});
});
return sdp.join('\r\n') + '\r\n';
};
},{"./grammar":10}],14:[function(require,module,exports){
/* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = function arrayEquals(array) {
// if the other array is a falsy value, return
if (!array)
return false;
// compare lengths - can save a lot of time
if (this.length != array.length)
return false;
for (var i = 0, l = this.length; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!arrayEquals.apply(this[i], [array[i]]))
return false;
} else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal:
// {x:20} != {x:20}
return false;
}
}
return true;
};
},{}],15:[function(require,module,exports){
/* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.Interop = require('./interop');
},{"./interop":16}],16:[function(require,module,exports){
/* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* global RTCSessionDescription */
/* global RTCIceCandidate */
/* jshint -W097 */
"use strict";
var transform = require('./transform');
var arrayEquals = require('./array-equals');
function Interop() {
/**
* This map holds the most recent Unified Plan offer/answer SDP that was
* converted to Plan B, with the SDP type ('offer' or 'answer') as keys and
* the SDP string as values.
*
* @type {{}}
*/
this.cache = {
mlB2UMap : {},
mlU2BMap : {}
};
}
module.exports = Interop;
/**
* Changes the candidate args to match with the related Unified Plan
*/
Interop.prototype.candidateToUnifiedPlan = function(candidate) {
var cand = new RTCIceCandidate(candidate);
cand.sdpMLineIndex = this.cache.mlB2UMap[cand.sdpMLineIndex];
/* TODO: change sdpMid to (audio|video)-SSRC */
return cand;
};
/**
* Changes the candidate args to match with the related Plan B
*/
Interop.prototype.candidateToPlanB = function(candidate) {
var cand = new RTCIceCandidate(candidate);
if (cand.sdpMid.indexOf('audio') === 0) {
cand.sdpMid = 'audio';
} else if (cand.sdpMid.indexOf('video') === 0) {
cand.sdpMid = 'video';
} else {
throw new Error('candidate with ' + cand.sdpMid + ' not allowed');
}
cand.sdpMLineIndex = this.cache.mlU2BMap[cand.sdpMLineIndex];
return cand;
};
/**
* Returns the index of the first m-line with the given media type and with a
* direction which allows sending, in the last Unified Plan description with
* type "answer" converted to Plan B. Returns {null} if there is no saved
* answer, or if none of its m-lines with the given type allow sending.
* @param type the media type ("audio" or "video").
* @returns {*}
*/
Interop.prototype.getFirstSendingIndexFromAnswer = function(type) {
if (!this.cache.answer) {
return null;
}
var session = transform.parse(this.cache.answer);
if (session && session.media && Array.isArray(session.media)){
for (var i = 0; i < session.media.length; i++) {
if (session.media[i].type == type &&
(!session.media[i].direction /* default to sendrecv */ ||
session.media[i].direction === 'sendrecv' ||
session.media[i].direction === 'sendonly')){
return i;
}
}
}
return null;
};
/**
* This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A
* PeerConnection wrapper transforms the SDP to Plan B before passing it to the
* application.
*
* @param desc
* @returns {*}
*/
Interop.prototype.toPlanB = function(desc) {
var self = this;
//#region Preliminary input validation.
if (typeof desc !== 'object' || desc === null ||
typeof desc.sdp !== 'string') {
console.warn('An empty description was passed as an argument.');
return desc;
}
// Objectify the SDP for easier manipulation.
var session = transform.parse(desc.sdp);
// If the SDP contains no media, there's nothing to transform.
if (typeof session.media === 'undefined' ||
!Array.isArray(session.media) || session.media.length === 0) {
console.warn('The description has no media.');
return desc;
}
// Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B
// SDP has a video, an audio and a data "channel" at most.
if (session.media.length <= 3 && session.media.every(function(m) {
return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
})) {
console.warn('This description does not look like Unified Plan.');
return desc;
}
//#endregion
// HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443
var sdp = desc.sdp;
var rewrite = false;
for (var i = 0; i < session.media.length; i++) {
var uLine = session.media[i];
uLine.rtp.forEach(function(rtp) {
if (rtp.codec === 'NULL')
{
rewrite = true;
var offer = transform.parse(self.cache.offer);
rtp.codec = offer.media[i].rtp[0].codec;
}
});
}
if (rewrite) {
sdp = transform.write(session);
}
// Unified Plan SDP is our "precious". Cache it for later use in the Plan B
// -> Unified Plan transformation.
this.cache[desc.type] = sdp;
//#region Convert from Unified Plan to Plan B.
// We rebuild the session.media array.
var media = session.media;
session.media = [];
// Associative array that maps channel types to channel objects for fast
// access to channel objects by their type, e.g. type2bl['audio']->channel
// obj.
var type2bl = {};
// Used to build the group:BUNDLE value after the channels construction
// loop.
var types = [];
media.forEach(function(uLine) {
// rtcp-mux is required in the Plan B SDP.
if ((typeof uLine.rtcpMux !== 'string' ||
uLine.rtcpMux !== 'rtcp-mux') &&
uLine.direction !== 'inactive') {
throw new Error('Cannot convert to Plan B because m-lines ' +
'without the rtcp-mux attribute were found.');
}
// If we don't have a channel for this uLine.type OR the selected is
// inactive, then select this uLine as the channel basis.
if (typeof type2bl[uLine.type] === 'undefined' ||
type2bl[uLine.type].direction === 'inactive') {
type2bl[uLine.type] = uLine;
}
if (uLine.protocol != type2bl[uLine.type].protocol) {
throw new Error('Cannot convert to Plan B because m-lines ' +
'have different protocols and this library does not have ' +
'support for that');
}
if (uLine.payloads != type2bl[uLine.type].payloads) {
throw new Error('Cannot convert to Plan B because m-lines ' +
'have different payloads and this library does not have ' +
'support for that');
}
});
// Implode the Unified Plan m-lines/tracks into Plan B channels.
media.forEach(function(uLine) {
if (uLine.type === 'application') {
session.media.push(uLine);
types.push(uLine.mid);
return;
}
// Add sources to the channel and handle a=msid.
if (typeof uLine.sources === 'object') {
Object.keys(uLine.sources).forEach(function(ssrc) {
if (typeof type2bl[uLine.type].sources !== 'object')
type2bl[uLine.type].sources = {};
// Assign the sources to the channel.
type2bl[uLine.type].sources[ssrc] =
uLine.sources[ssrc];
if (typeof uLine.msid !== 'undefined') {
// In Plan B the msid is an SSRC attribute. Also, we don't
// care about the obsolete label and mslabel attributes.
//
// Note that it is not guaranteed that the uLine will
// have an msid. recvonly channels in particular don't have
// one.
type2bl[uLine.type].sources[ssrc].msid =
uLine.msid;
}
// NOTE ssrcs in ssrc groups will share msids, as
// draft-uberti-rtcweb-plan-00 mandates.
});
}
// Add ssrc groups to the channel.
if (typeof uLine.ssrcGroups !== 'undefined' &&
Array.isArray(uLine.ssrcGroups)) {
// Create the ssrcGroups array, if it's not defined.
if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' ||
!Array.isArray(type2bl[uLine.type].ssrcGroups)) {
type2bl[uLine.type].ssrcGroups = [];
}
type2bl[uLine.type].ssrcGroups =
type2bl[uLine.type].ssrcGroups.concat(
uLine.ssrcGroups);
}
if (type2bl[uLine.type] === uLine) {
// Plan B mids are in ['audio', 'video', 'data']
uLine.mid = uLine.type;
// Plan B doesn't support/need the bundle-only attribute.
delete uLine.bundleOnly;
// In Plan B the msid is an SSRC attribute.
delete uLine.msid;
if (uLine.type == media[0].type) {
types.unshift(uLine.type);
// Add the channel to the new media array.
session.media.unshift(uLine);
} else {
types.push(uLine.type);
// Add the channel to the new media array.
session.media.push(uLine);
}
}
});
if (typeof session.groups !== 'undefined') {
// We regenerate the BUNDLE group with the new mids.
session.groups.some(function(group) {
if (group.type === 'BUNDLE') {
group.mids = types.join(' ');
return true;
}
});
}
// msid semantic
session.msidSemantic = {
semantic: 'WMS',
token: '*'
};
var resStr = transform.write(session);
return new RTCSessionDescription({
type: desc.type,
sdp: resStr
});
//#endregion
};
/* follow rules defined in RFC4145 */
function addSetupAttr(uLine) {
if (typeof uLine.setup === 'undefined') {
return;
}
if (uLine.setup === "active") {
uLine.setup = "passive";
} else if (uLine.setup === "passive") {
uLine.setup = "active";
}
}
/**
* This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A
* PeerConnection wrapper transforms the SDP to Unified Plan before passing it
* to FF.
*
* @param desc
* @returns {*}
*/
Interop.prototype.toUnifiedPlan = function(desc) {
var self = this;
//#region Preliminary input validation.
if (typeof desc !== 'object' || desc === null ||
typeof desc.sdp !== 'string') {
console.warn('An empty description was passed as an argument.');
return desc;
}
var session = transform.parse(desc.sdp);
// If the SDP contains no media, there's nothing to transform.
if (typeof session.media === 'undefined' ||
!Array.isArray(session.media) || session.media.length === 0) {
console.warn('The description has no media.');
return desc;
}
// Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has
// a video, an audio and a data "channel" at most.
if (session.media.length > 3 || !session.media.every(function(m) {
return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
})) {
console.warn('This description does not look like Plan B.');
return desc;
}
// Make sure this Plan B SDP can be converted to a Unified Plan SDP.
var mids = [];
session.media.forEach(function(m) {
mids.push(m.mid);
});
var hasBundle = false;
if (typeof session.groups !== 'undefined' &&
Array.isArray(session.groups)) {
hasBundle = session.groups.every(function(g) {
return g.type !== 'BUNDLE' ||
arrayEquals.apply(g.mids.sort(), [mids.sort()]);
});
}
if (!hasBundle) {
var mustBeBundle = false;
session.media.forEach(function(m) {
if (m.direction !== 'inactive') {
mustBeBundle = true;
}
});
if (mustBeBundle) {
throw new Error("Cannot convert to Unified Plan because m-lines that" +
" are not bundled were found.");
}
}
//#endregion
//#region Convert from Plan B to Unified Plan.
// Unfortunately, a Plan B offer/answer doesn't have enough information to
// rebuild an equivalent Unified Plan offer/answer.
//
// For example, if this is a local answer (in Unified Plan style) that we
// convert to Plan B prior to handing it over to the application (the
// PeerConnection wrapper called us, for instance, after a successful
// createAnswer), we want to remember the m-line at which we've seen the
// (local) SSRC. That's because when the application wants to do call the
// SLD method, forcing us to do the inverse transformation (from Plan B to
// Unified Plan), we need to know to which m-line to assign the (local)
// SSRC. We also need to know all the other m-lines that the original
// answer had and include them in the transformed answer as well.
//
// Another example is if this is a remote offer that we convert to Plan B
// prior to giving it to the application, we want to remember the mid at
// which we've seen the (remote) SSRC.
//
// In the iteration that follows, we use the cached Unified Plan (if it
// exists) to assign mids to ssrcs.
var type;
if (desc.type === 'answer') {
type = 'offer';
} else if (desc.type === 'offer') {
type = 'answer';
} else {
throw new Error("Type '" + desc.type + "' not supported.");
}
var cached;
if (typeof this.cache[type] !== 'undefined') {
cached = transform.parse(this.cache[type]);
}
var recvonlySsrcs = {
audio: {},
video: {}
};
// A helper map that sends mids to m-line objects. We use it later to
// rebuild the Unified Plan style session.media array.
var mid2ul = {};
var bIdx = 0;
var uIdx = 0;
var sources2ul = {};
var candidates;
var iceUfrag;
var icePwd;
var fingerprint;
var payloads = {};
var rtcpFb = {};
var rtp = {};
session.media.forEach(function(bLine) {
if ((typeof bLine.rtcpMux !== 'string' ||
bLine.rtcpMux !== 'rtcp-mux') &&
bLine.direction !== 'inactive') {
throw new Error("Cannot convert to Unified Plan because m-lines " +
"without the rtcp-mux attribute were found.");
}
if (bLine.type === 'application') {
mid2ul[bLine.mid] = bLine;
return;
}
// With rtcp-mux and bundle all the channels should have the same ICE
// stuff.
var sources = bLine.sources;
var ssrcGroups = bLine.ssrcGroups;
var port = bLine.port;
/* Chrome adds different candidates even using bundle, so we concat the candidates list */
if (typeof bLine.candidates != 'undefined') {
if (typeof candidates != 'undefined') {
candidates = candidates.concat(bLine.candidates);
} else {
candidates = bLine.candidates;
}
}
if ((typeof iceUfrag != 'undefined') && (typeof bLine.iceUfrag != 'undefined') && (iceUfrag != bLine.iceUfrag)) {
throw new Error("Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n" +
"\tLast iceUfrag: " + iceUfrag + "\n" +
"\tNew iceUfrag: " + bLine.iceUfrag
);
}
if (typeof bLine.iceUfrag != 'undefined') {
iceUfrag = bLine.iceUfrag;
}
if ((typeof icePwd != 'undefined') && (typeof bLine.icePwd != 'undefined') && (icePwd != bLine.icePwd)) {
throw new Error("Only BUNDLE supported, icePwd must be the same for all m-lines.\n" +
"\tLast icePwd: " + icePwd + "\n" +
"\tNew icePwd: " + bLine.icePwd
);
}
if (typeof bLine.icePwd != 'undefined') {
icePwd = bLine.icePwd;
}
if ((typeof fingerprint != 'undefined') && (typeof bLine.fingerprint != 'undefined') &&
(fingerprint.type != bLine.fingerprint.type || fingerprint.hash != bLine.fingerprint.hash)) {
throw new Error("Only BUNDLE supported, fingerprint must be the same for all m-lines.\n" +
"\tLast fingerprint: " + JSON.stringify(fingerprint) + "\n" +
"\tNew fingerprint: " + JSON.stringify(bLine.fingerprint)
);
}
if (typeof bLine.fingerprint != 'undefined') {
fingerprint = bLine.fingerprint;
}
payloads[bLine.type] = bLine.payloads;
rtcpFb[bLine.type] = bLine.rtcpFb;
rtp[bLine.type] = bLine.rtp;
// inverted ssrc group map
var ssrc2group = {};
if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) {
ssrcGroups.forEach(function (ssrcGroup) {
// XXX This might brake if an SSRC is in more than one group
// for some reason.
if (typeof ssrcGroup.ssrcs !== 'undefined' &&
Array.isArray(ssrcGroup.ssrcs)) {
ssrcGroup.ssrcs.forEach(function (ssrc) {
if (typeof ssrc2group[ssrc] === 'undefined') {
ssrc2group[ssrc] = [];
}
ssrc2group[ssrc].push(ssrcGroup);
});
}
});
}
// ssrc to m-line index.
var ssrc2ml = {};
if (typeof sources === 'object') {
// We'll use the "bLine" object as a prototype for each new "mLine"
// that we create, but first we need to clean it up a bit.
delete bLine.sources;
delete bLine.ssrcGroups;
delete bLine.candidates;
delete bLine.iceUfrag;
delete bLine.icePwd;
delete bLine.fingerprint;
delete bLine.port;
delete bLine.mid;
// Explode the Plan B channel sources with one m-line per source.
Object.keys(sources).forEach(function(ssrc) {
// The (unified) m-line for this SSRC. We either create it from
// scratch or, if it's a grouped SSRC, we re-use a related
// mline. In other words, if the source is grouped with another
// source, put the two together in the same m-line.
var uLine;
// We assume here that we are the answerer in the O/A, so any
// offers which we translate come from the remote side, while
// answers are local. So the check below is to make that we
// handle receive-only SSRCs in a special way only if they come
// from the remote side.
if (desc.type==='offer') {
// We want to detect SSRCs which are used by a remote peer
// in an m-line with direction=recvonly (i.e. they are
// being used for RTCP only).
// This information would have gotten lost if the remote
// peer used Unified Plan and their local description was
// translated to Plan B. So we use the lack of an MSID
// attribute to deduce a "receive only" SSRC.
if (!sources[ssrc].msid) {
recvonlySsrcs[bLine.type][ssrc] = sources[ssrc];
// Receive-only SSRCs must not create new m-lines. We
// will assign them to an existing m-line later.
return;
}
}
if (typeof ssrc2group[ssrc] !== 'undefined' &&
Array.isArray(ssrc2group[ssrc])) {
ssrc2group[ssrc].some(function (ssrcGroup) {
// ssrcGroup.ssrcs *is* an Array, no need to check
// again here.
return ssrcGroup.ssrcs.some(function (related) {
if (typeof ssrc2ml[related] === 'object') {
uLine = ssrc2ml[related];
return true;
}
});
});
}
if (typeof uLine === 'object') {
// the m-line already exists. Just add the source.
uLine.sources[ssrc] = sources[ssrc];
delete sources[ssrc].msid;
} else {
// Use the "bLine" as a prototype for the "uLine".
uLine = Object.create(bLine);
ssrc2ml[ssrc] = uLine;
if (typeof sources[ssrc].msid !== 'undefined') {
// Assign the msid of the source to the m-line. Note
// that it is not guaranteed that the source will have
// msid. In particular "recvonly" sources don't have an
// msid. Note that "recvonly" is a term only defined
// for m-lines.
uLine.msid = sources[ssrc].msid;
delete sources[ssrc].msid;
}
// We assign one SSRC per media line.
uLine.sources = {};
uLine.sources[ssrc] = sources[ssrc];
uLine.ssrcGroups = ssrc2group[ssrc];
// Use the cached Unified Plan SDP (if it exists) to assign
// SSRCs to mids.
if (typeof cached !== 'undefined' &&
typeof cached.media !== 'undefined' &&
Array.isArray(cached.media)) {
cached.media.forEach(function (m) {
if (typeof m.sources === 'object') {
Object.keys(m.sources).forEach(function (s) {
if (s === ssrc) {
uLine.mid = m.mid;
}
});
}
});
}
if (typeof uLine.mid === 'undefined') {
// If this is an SSRC that we see for the first time
// assign it a new mid. This is typically the case when
// this method is called to transform a remote
// description for the first time or when there is a
// new SSRC in the remote description because a new
// peer has joined the conference. Local SSRCs should
// have already been added to the map in the toPlanB
// method.
//
// Because FF generates answers in Unified Plan style,
// we MUST already have a cached answer with all the
// local SSRCs mapped to some m-line/mid.
uLine.mid = [bLine.type, '-', ssrc].join('');
}
// Include the candidates in the 1st media line.
uLine.candidates = candidates;
uLine.iceUfrag = iceUfrag;
uLine.icePwd = icePwd;
uLine.fingerprint = fingerprint;
uLine.port = port;
mid2ul[uLine.mid] = uLine;
sources2ul[uIdx] = uLine.sources;
self.cache.mlU2BMap[uIdx] = bIdx;
if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
self.cache.mlB2UMap[bIdx] = uIdx;
}
uIdx++;
}
});
} else {
var uLine = bLine;
uLine.candidates = candidates;
uLine.iceUfrag = iceUfrag;
uLine.icePwd = icePwd;
uLine.fingerprint = fingerprint;
uLine.port = port;
mid2ul[uLine.mid] = uLine;
self.cache.mlU2BMap[uIdx] = bIdx;
if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
self.cache.mlB2UMap[bIdx] = uIdx;
}
}
bIdx++;
});
// Rebuild the media array in the right order and add the missing mLines
// (missing from the Plan B SDP).
session.media = [];
mids = []; // reuse
if (desc.type === 'answer') {
// The media lines in the answer must match the media lines in the
// offer. The order is important too. Here we assume that Firefox is
// the answerer, so we merely have to use the reconstructed (unified)
// answer to update the cached (unified) answer accordingly.
//
// In the general case, one would have to use the cached (unified)
// offer to find the m-lines that are missing from the reconstructed
// answer, potentially grabbing them from the cached (unified) answer.
// One has to be careful with this approach because inactive m-lines do
// not always have an mid, making it tricky (impossible?) to find where
// exactly and which m-lines are missing from the reconstructed answer.
for (var i = 0; i < cached.media.length; i++) {
var uLine = cached.media[i];
delete uLine.msid;
delete uLine.sources;
delete uLine.ssrcGroups;
if (typeof sources2ul[i] === 'undefined') {
if (!uLine.direction
|| uLine.direction === 'sendrecv')
uLine.direction = 'recvonly';
else if (uLine.direction === 'sendonly')
uLine.direction = 'inactive';
} else {
if (!uLine.direction
|| uLine.direction === 'sendrecv')
uLine.direction = 'sendrecv';
else if (uLine.direction === 'recvonly')
uLine.direction = 'sendonly';
}
uLine.sources = sources2ul[i];
uLine.candidates = candidates;
uLine.iceUfrag = iceUfrag;
uLine.icePwd = icePwd;
uLine.fingerprint = fingerprint;
uLine.rtp = rtp[uLine.type];
uLine.payloads = payloads[uLine.type];
uLine.rtcpFb = rtcpFb[uLine.type];
session.media.push(uLine);
if (typeof uLine.mid === 'string') {
// inactive lines don't/may not have an mid.
mids.push(uLine.mid);
}
}
} else {
// SDP offer/answer (and the JSEP spec) forbids removing an m-section
// under any circumstances. If we are no longer interested in sending a
// track, we just remove the msid and ssrc attributes and set it to
// either a=recvonly (as the reofferer, we must use recvonly if the
// other side was previously sending on the m-section, but we can also
// leave the possibility open if it wasn't previously in use), or
// a=inactive.
if (typeof cached !== 'undefined' &&
typeof cached.media !== 'undefined' &&
Array.isArray(cached.media)) {
cached.media.forEach(function(uLine) {
mids.push(uLine.mid);
if (typeof mid2ul[uLine.mid] !== 'undefined') {
session.media.push(mid2ul[uLine.mid]);
} else {
delete uLine.msid;
delete uLine.sources;
delete uLine.ssrcGroups;
if (!uLine.direction
|| uLine.direction === 'sendrecv') {
uLine.direction = 'sendonly';
}
if (!uLine.direction
|| uLine.direction === 'recvonly') {
uLine.direction = 'inactive';
}
addSetupAttr (uLine);
session.media.push(uLine);
}
});
}
// Add all the remaining (new) m-lines of the transformed SDP.
Object.keys(mid2ul).forEach(function(mid) {
if (mids.indexOf(mid) === -1) {
mids.push(mid);
if (mid2ul[mid].direction === 'recvonly') {
// This is a remote recvonly channel. Add its SSRC to the
// appropriate sendrecv or sendonly channel.
// TODO(gp) what if we don't have sendrecv/sendonly
// channel?
var done = false;
session.media.some(function (uLine) {
if ((uLine.direction === 'sendrecv' ||
uLine.direction === 'sendonly') &&
uLine.type === mid2ul[mid].type) {
// mid2ul[mid] shouldn't have any ssrc-groups
Object.keys(mid2ul[mid].sources).forEach(
function (ssrc) {
uLine.sources[ssrc] =
mid2ul[mid].sources[ssrc];
});
done = true;
return true;
}
});
if (!done) {
session.media.push(mid2ul[mid]);
}
} else {
session.media.push(mid2ul[mid]);
}
}
});
}
// After we have constructed the Plan Unified m-lines we can figure out
// where (in which m-line) to place the 'recvonly SSRCs'.
// Note: we assume here that we are the answerer in the O/A, so any offers
// which we translate come from the remote side, while answers are local
// (and so our last local description is cached as an 'answer').
["audio", "video"].forEach(function (type) {
if (!session || !session.media || !Array.isArray(session.media))
return;
var idx = null;
if (Object.keys(recvonlySsrcs[type]).length > 0) {
idx = self.getFirstSendingIndexFromAnswer(type);
if (idx === null){
// If this is the first offer we receive, we don't have a
// cached answer. Assume that we will be sending media using
// the first m-line for each media type.
for (var i = 0; i < session.media.length; i++) {
if (session.media[i].type === type) {
idx = i;
break;
}
}
}
}
if (idx && session.media.length > idx) {
var mLine = session.media[idx];
Object.keys(recvonlySsrcs[type]).forEach(function(ssrc) {
if (mLine.sources && mLine.sources[ssrc]) {
console.warn("Replacing an existing SSRC.");
}
if (!mLine.sources) {
mLine.sources = {};
}
mLine.sources[ssrc] = recvonlySsrcs[type][ssrc];
});
}
});
if (typeof session.groups !== 'undefined') {
// We regenerate the BUNDLE group (since we regenerated the mids)
session.groups.some(function(group) {
if (group.type === 'BUNDLE') {
group.mids = mids.join(' ');
return true;
}
});
}
// msid semantic
session.msidSemantic = {
semantic: 'WMS',
token: '*'
};
var resStr = transform.write(session);
// Cache the transformed SDP (Unified Plan) for later re-use in this
// function.
this.cache[desc.type] = resStr;
return new RTCSessionDescription({
type: desc.type,
sdp: resStr
});
//#endregion
};
},{"./array-equals":14,"./transform":17}],17:[function(require,module,exports){
/* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var transform = require('sdp-transform');
exports.write = function(session, opts) {
if (typeof session !== 'undefined' &&
typeof session.media !== 'undefined' &&
Array.isArray(session.media)) {
session.media.forEach(function (mLine) {
// expand sources to ssrcs
if (typeof mLine.sources !== 'undefined' &&
Object.keys(mLine.sources).length !== 0) {
mLine.ssrcs = [];
Object.keys(mLine.sources).forEach(function (ssrc) {
var source = mLine.sources[ssrc];
Object.keys(source).forEach(function (attribute) {
mLine.ssrcs.push({
id: ssrc,
attribute: attribute,
value: source[attribute]
});
});
});
delete mLine.sources;
}
// join ssrcs in ssrc groups
if (typeof mLine.ssrcGroups !== 'undefined' &&
Array.isArray(mLine.ssrcGroups)) {
mLine.ssrcGroups.forEach(function (ssrcGroup) {
if (typeof ssrcGroup.ssrcs !== 'undefined' &&
Array.isArray(ssrcGroup.ssrcs)) {
ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');
}
});
}
});
}
// join group mids
if (typeof session !== 'undefined' &&
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
session.groups.forEach(function (g) {
if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {
g.mids = g.mids.join(' ');
}
});
}
return transform.write(session, opts);
};
exports.parse = function(sdp) {
var session = transform.parse(sdp);
if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
Array.isArray(session.media)) {
session.media.forEach(function (mLine) {
// group sources attributes by ssrc
if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
mLine.sources = {};
mLine.ssrcs.forEach(function (ssrc) {
if (!mLine.sources[ssrc.id])
mLine.sources[ssrc.id] = {};
mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;
});
delete mLine.ssrcs;
}
// split ssrcs in ssrc groups
if (typeof mLine.ssrcGroups !== 'undefined' &&
Array.isArray(mLine.ssrcGroups)) {
mLine.ssrcGroups.forEach(function (ssrcGroup) {
if (typeof ssrcGroup.ssrcs === 'string') {
ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');
}
});
}
});
}
// split group mids
if (typeof session !== 'undefined' &&
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
session.groups.forEach(function (g) {
if (typeof g.mids === 'string') {
g.mids = g.mids.split(' ');
}
});
}
return session;
};
},{"sdp-transform":11}],18:[function(require,module,exports){
/**
* UAParser.js v0.7.12
* Lightweight JavaScript-based User-Agent string parser
* https://github.com/faisalman/ua-parser-js
*
* Copyright © 2012-2016 Faisal Salman <fyzlman@gmail.com>
* Dual licensed under GPLv2 & MIT
*/
(function (window, undefined) {
'use strict';
//////////////
// Constants
/////////////
var LIBVERSION = '0.7.12',
EMPTY = '',
UNKNOWN = '?',
FUNC_TYPE = 'function',
UNDEF_TYPE = 'undefined',
OBJ_TYPE = 'object',
STR_TYPE = 'string',
MAJOR = 'major', // deprecated
MODEL = 'model',
NAME = 'name',
TYPE = 'type',
VENDOR = 'vendor',
VERSION = 'version',
ARCHITECTURE= 'architecture',
CONSOLE = 'console',
MOBILE = 'mobile',
TABLET = 'tablet',
SMARTTV = 'smarttv',
WEARABLE = 'wearable',
EMBEDDED = 'embedded';
///////////
// Helper
//////////
var util = {
extend : function (regexes, extensions) {
var margedRegexes = {};
for (var i in regexes) {
if (extensions[i] && extensions[i].length % 2 === 0) {
margedRegexes[i] = extensions[i].concat(regexes[i]);
} else {
margedRegexes[i] = regexes[i];
}
}
return margedRegexes;
},
has : function (str1, str2) {
if (typeof str1 === "string") {
return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1;
} else {
return false;
}
},
lowerize : function (str) {
return str.toLowerCase();
},
major : function (version) {
return typeof(version) === STR_TYPE ? version.replace(/[^\d\.]/g,'').split(".")[0] : undefined;
},
trim : function (str) {
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
};
///////////////
// Map helper
//////////////
var mapper = {
rgx : function () {
var result, i = 0, j, k, p, q, matches, match, args = arguments;
// loop through all regexes maps
while (i < args.length && !matches) {
var regex = args[i], // even sequence (0,2,4,..)
props = args[i + 1]; // odd sequence (1,3,5,..)
// construct object barebones
if (typeof result === UNDEF_TYPE) {
result = {};
for (p in props) {
if (props.hasOwnProperty(p)){
q = props[p];
if (typeof q === OBJ_TYPE) {
result[q[0]] = undefined;
} else {
result[q] = undefined;
}
}
}
}
// try matching uastring with regexes
j = k = 0;
while (j < regex.length && !matches) {
matches = regex[j++].exec(this.getUA());
if (!!matches) {
for (p = 0; p < props.length; p++) {
match = matches[++k];
q = props[p];
// check if given property is actually array
if (typeof q === OBJ_TYPE && q.length > 0) {
if (q.length == 2) {
if (typeof q[1] == FUNC_TYPE) {
// assign modified match
result[q[0]] = q[1].call(this, match);
} else {
// assign given value, ignore regex match
result[q[0]] = q[1];
}
} else if (q.length == 3) {
// check whether function or regex
if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
// call function (usually string mapper)
result[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
} else {
// sanitize match using given regex
result[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
}
} else if (q.length == 4) {
result[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
}
} else {
result[q] = match ? match : undefined;
}
}
}
}
i += 2;
}
return result;
},
str : function (str, map) {
for (var i in map) {
// check if array
if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
for (var j = 0; j < map[i].length; j++) {
if (util.has(map[i][j], str)) {
return (i === UNKNOWN) ? undefined : i;
}
}
} else if (util.has(map[i], str)) {
return (i === UNKNOWN) ? undefined : i;
}
}
return str;
}
};
///////////////
// String map
//////////////
var maps = {
browser : {
oldsafari : {
version : {
'1.0' : '/8',
'1.2' : '/1',
'1.3' : '/3',
'2.0' : '/412',
'2.0.2' : '/416',
'2.0.3' : '/417',
'2.0.4' : '/419',
'?' : '/'
}
}
},
device : {
amazon : {
model : {
'Fire Phone' : ['SD', 'KF']
}
},
sprint : {
model : {
'Evo Shift 4G' : '7373KT'
},
vendor : {
'HTC' : 'APA',
'Sprint' : 'Sprint'
}
}
},
os : {
windows : {
version : {
'ME' : '4.90',
'NT 3.11' : 'NT3.51',
'NT 4.0' : 'NT4.0',
'2000' : 'NT 5.0',
'XP' : ['NT 5.1', 'NT 5.2'],
'Vista' : 'NT 6.0',
'7' : 'NT 6.1',
'8' : 'NT 6.2',
'8.1' : 'NT 6.3',
'10' : ['NT 6.4', 'NT 10.0'],
'RT' : 'ARM'
}
}
}
};
//////////////
// Regex map
/////////////
var regexes = {
browser : [[
// Presto based
/(opera\smini)\/([\w\.-]+)/i, // Opera Mini
/(opera\s[mobiletab]+).+version\/([\w\.-]+)/i, // Opera Mobi/Tablet
/(opera).+version\/([\w\.]+)/i, // Opera > 9.80
/(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80
], [NAME, VERSION], [
/(opios)[\/\s]+([\w\.]+)/i // Opera mini on iphone >= 8.0
], [[NAME, 'Opera Mini'], VERSION], [
/\s(opr)\/([\w\.]+)/i // Opera Webkit
], [[NAME, 'Opera'], VERSION], [
// Mixed
/(kindle)\/([\w\.]+)/i, // Kindle
/(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i,
// Lunascape/Maxthon/Netfront/Jasmine/Blazer
// Trident based
/(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i,
// Avant/IEMobile/SlimBrowser/Baidu
/(?:ms|\()(ie)\s([\w\.]+)/i, // Internet Explorer
// Webkit/KHTML based
/(rekonq)\/([\w\.]+)*/i, // Rekonq
/(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs)\/([\w\.-]+)/i
// Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS
], [NAME, VERSION], [
/(trident).+rv[:\s]([\w\.]+).+like\sgecko/i // IE11
], [[NAME, 'IE'], VERSION], [
/(edge)\/((\d+)?[\w\.]+)/i // Microsoft Edge
], [NAME, VERSION], [
/(yabrowser)\/([\w\.]+)/i // Yandex
], [[NAME, 'Yandex'], VERSION], [
/(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon
], [[NAME, /_/g, ' '], VERSION], [
/(micromessenger)\/([\w\.]+)/i // WeChat
], [[NAME, 'WeChat'], VERSION], [
/xiaomi\/miuibrowser\/([\w\.]+)/i // MIUI Browser
], [VERSION, [NAME, 'MIUI Browser']], [
/\swv\).+(chrome)\/([\w\.]+)/i // Chrome WebView
], [[NAME, /(.+)/, '$1 WebView'], VERSION], [
/android.+samsungbrowser\/([\w\.]+)/i,
/android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)*/i // Android Browser
], [VERSION, [NAME, 'Android Browser']], [
/(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i,
// Chrome/OmniWeb/Arora/Tizen/Nokia
/(qqbrowser)[\/\s]?([\w\.]+)/i
// QQBrowser
], [NAME, VERSION], [
/(uc\s?browser)[\/\s]?([\w\.]+)/i,
/ucweb.+(ucbrowser)[\/\s]?([\w\.]+)/i,
/juc.+(ucweb)[\/\s]?([\w\.]+)/i
// UCBrowser
], [[NAME, 'UCBrowser'], VERSION], [
/(dolfin)\/([\w\.]+)/i // Dolphin
], [[NAME, 'Dolphin'], VERSION], [
/((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
], [[NAME, 'Chrome'], VERSION], [
/;fbav\/([\w\.]+);/i // Facebook App for iOS
], [VERSION, [NAME, 'Facebook']], [
/fxios\/([\w\.-]+)/i // Firefox for iOS
], [VERSION, [NAME, 'Firefox']], [
/version\/([\w\.]+).+?mobile\/\w+\s(safari)/i // Mobile Safari
], [VERSION, [NAME, 'Mobile Safari']], [
/version\/([\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile
], [VERSION, NAME], [
/webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [
/(konqueror)\/([\w\.]+)/i, // Konqueror
/(webkit|khtml)\/([\w\.]+)/i
], [NAME, VERSION], [
// Gecko based
/(navigator|netscape)\/([\w\.-]+)/i // Netscape
], [[NAME, 'Netscape'], VERSION], [
/(swiftfox)/i, // Swiftfox
/(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i,
// IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
/(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i,
// Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
/(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i, // Mozilla
// Other
/(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir)[\/\s]?([\w\.]+)/i,
// Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf/Sleipnir
/(links)\s\(([\w\.]+)/i, // Links
/(gobrowser)\/?([\w\.]+)*/i, // GoBrowser
/(ice\s?browser)\/v?([\w\._]+)/i, // ICE Browser
/(mosaic)[\/\s]([\w\.]+)/i // Mosaic
], [NAME, VERSION]
/* /////////////////////
// Media players BEGIN
////////////////////////
, [
/(apple(?:coremedia|))\/((\d+)[\w\._]+)/i, // Generic Apple CoreMedia
/(coremedia) v((\d+)[\w\._]+)/i
], [NAME, VERSION], [
/(aqualung|lyssna|bsplayer)\/((\d+)?[\w\.-]+)/i // Aqualung/Lyssna/BSPlayer
], [NAME, VERSION], [
/(ares|ossproxy)\s((\d+)[\w\.-]+)/i // Ares/OSSProxy
], [NAME, VERSION], [
/(audacious|audimusicstream|amarok|bass|core|dalvik|gnomemplayer|music on console|nsplayer|psp-internetradioplayer|videos)\/((\d+)[\w\.-]+)/i,
// Audacious/AudiMusicStream/Amarok/BASS/OpenCORE/Dalvik/GnomeMplayer/MoC
// NSPlayer/PSP-InternetRadioPlayer/Videos
/(clementine|music player daemon)\s((\d+)[\w\.-]+)/i, // Clementine/MPD
/(lg player|nexplayer)\s((\d+)[\d\.]+)/i,
/player\/(nexplayer|lg player)\s((\d+)[\w\.-]+)/i // NexPlayer/LG Player
], [NAME, VERSION], [
/(nexplayer)\s((\d+)[\w\.-]+)/i // Nexplayer
], [NAME, VERSION], [
/(flrp)\/((\d+)[\w\.-]+)/i // Flip Player
], [[NAME, 'Flip Player'], VERSION], [
/(fstream|nativehost|queryseekspider|ia-archiver|facebookexternalhit)/i
// FStream/NativeHost/QuerySeekSpider/IA Archiver/facebookexternalhit
], [NAME], [
/(gstreamer) souphttpsrc (?:\([^\)]+\)){0,1} libsoup\/((\d+)[\w\.-]+)/i
// Gstreamer
], [NAME, VERSION], [
/(htc streaming player)\s[\w_]+\s\/\s((\d+)[\d\.]+)/i, // HTC Streaming Player
/(java|python-urllib|python-requests|wget|libcurl)\/((\d+)[\w\.-_]+)/i,
// Java/urllib/requests/wget/cURL
/(lavf)((\d+)[\d\.]+)/i // Lavf (FFMPEG)
], [NAME, VERSION], [
/(htc_one_s)\/((\d+)[\d\.]+)/i // HTC One S
], [[NAME, /_/g, ' '], VERSION], [
/(mplayer)(?:\s|\/)(?:(?:sherpya-){0,1}svn)(?:-|\s)(r\d+(?:-\d+[\w\.-]+){0,1})/i
// MPlayer SVN
], [NAME, VERSION], [
/(mplayer)(?:\s|\/|[unkow-]+)((\d+)[\w\.-]+)/i // MPlayer
], [NAME, VERSION], [
/(mplayer)/i, // MPlayer (no other info)
/(yourmuze)/i, // YourMuze
/(media player classic|nero showtime)/i // Media Player Classic/Nero ShowTime
], [NAME], [
/(nero (?:home|scout))\/((\d+)[\w\.-]+)/i // Nero Home/Nero Scout
], [NAME, VERSION], [
/(nokia\d+)\/((\d+)[\w\.-]+)/i // Nokia
], [NAME, VERSION], [
/\s(songbird)\/((\d+)[\w\.-]+)/i // Songbird/Philips-Songbird
], [NAME, VERSION], [
/(winamp)3 version ((\d+)[\w\.-]+)/i, // Winamp
/(winamp)\s((\d+)[\w\.-]+)/i,
/(winamp)mpeg\/((\d+)[\w\.-]+)/i
], [NAME, VERSION], [
/(ocms-bot|tapinradio|tunein radio|unknown|winamp|inlight radio)/i // OCMS-bot/tap in radio/tunein/unknown/winamp (no other info)
// inlight radio
], [NAME], [
/(quicktime|rma|radioapp|radioclientapplication|soundtap|totem|stagefright|streamium)\/((\d+)[\w\.-]+)/i
// QuickTime/RealMedia/RadioApp/RadioClientApplication/
// SoundTap/Totem/Stagefright/Streamium
], [NAME, VERSION], [
/(smp)((\d+)[\d\.]+)/i // SMP
], [NAME, VERSION], [
/(vlc) media player - version ((\d+)[\w\.]+)/i, // VLC Videolan
/(vlc)\/((\d+)[\w\.-]+)/i,
/(xbmc|gvfs|xine|xmms|irapp)\/((\d+)[\w\.-]+)/i, // XBMC/gvfs/Xine/XMMS/irapp
/(foobar2000)\/((\d+)[\d\.]+)/i, // Foobar2000
/(itunes)\/((\d+)[\d\.]+)/i // iTunes
], [NAME, VERSION], [
/(wmplayer)\/((\d+)[\w\.-]+)/i, // Windows Media Player
/(windows-media-player)\/((\d+)[\w\.-]+)/i
], [[NAME, /-/g, ' '], VERSION], [
/windows\/((\d+)[\w\.-]+) upnp\/[\d\.]+ dlnadoc\/[\d\.]+ (home media server)/i
// Windows Media Server
], [VERSION, [NAME, 'Windows']], [
/(com\.riseupradioalarm)\/((\d+)[\d\.]*)/i // RiseUP Radio Alarm
], [NAME, VERSION], [
/(rad.io)\s((\d+)[\d\.]+)/i, // Rad.io
/(radio.(?:de|at|fr))\s((\d+)[\d\.]+)/i
], [[NAME, 'rad.io'], VERSION]
//////////////////////
// Media players END
////////////////////*/
],
cpu : [[
/(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\)]/i // AMD64
], [[ARCHITECTURE, 'amd64']], [
/(ia32(?=;))/i // IA32 (quicktime)
], [[ARCHITECTURE, util.lowerize]], [
/((?:i[346]|x)86)[;\)]/i // IA32
], [[ARCHITECTURE, 'ia32']], [
// PocketPC mistakenly identified as PowerPC
/windows\s(ce|mobile);\sppc;/i
], [[ARCHITECTURE, 'arm']], [
/((?:ppc|powerpc)(?:64)?)(?:\smac|;|\))/i // PowerPC
], [[ARCHITECTURE, /ower/, '', util.lowerize]], [
/(sun4\w)[;\)]/i // SPARC
], [[ARCHITECTURE, 'sparc']], [
/((?:avr32|ia64(?=;))|68k(?=\))|arm(?:64|(?=v\d+;))|(?=atmel\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i
// IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
], [[ARCHITECTURE, util.lowerize]]
],
device : [[
/\((ipad|playbook);[\w\s\);-]+(rim|apple)/i // iPad/PlayBook
], [MODEL, VENDOR, [TYPE, TABLET]], [
/applecoremedia\/[\w\.]+ \((ipad)/ // iPad
], [MODEL, [VENDOR, 'Apple'], [TYPE, TABLET]], [
/(apple\s{0,1}tv)/i // Apple TV
], [[MODEL, 'Apple TV'], [VENDOR, 'Apple']], [
/(archos)\s(gamepad2?)/i, // Archos
/(hp).+(touchpad)/i, // HP TouchPad
/(hp).+(tablet)/i, // HP Tablet
/(kindle)\/([\w\.]+)/i, // Kindle
/\s(nook)[\w\s]+build\/(\w+)/i, // Nook
/(dell)\s(strea[kpr\s\d]*[\dko])/i // Dell Streak
], [VENDOR, MODEL, [TYPE, TABLET]], [
/(kf[A-z]+)\sbuild\/[\w\.]+.*silk\//i // Kindle Fire HD
], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [
/(sd|kf)[0349hijorstuw]+\sbuild\/[\w\.]+.*silk\//i // Fire Phone
], [[MODEL, mapper.str, maps.device.amazon.model], [VENDOR, 'Amazon'], [TYPE, MOBILE]], [
/\((ip[honed|\s\w*]+);.+(apple)/i // iPod/iPhone
], [MODEL, VENDOR, [TYPE, MOBILE]], [
/\((ip[honed|\s\w*]+);/i // iPod/iPhone
], [MODEL, [VENDOR, 'Apple'], [TYPE, MOBILE]], [
/(blackberry)[\s-]?(\w+)/i, // BlackBerry
/(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|huawei|meizu|motorola|polytron)[\s_-]?([\w-]+)*/i,
// BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Huawei/Meizu/Motorola/Polytron
/(hp)\s([\w\s]+\w)/i, // HP iPAQ
/(asus)-?(\w+)/i // Asus
], [VENDOR, MODEL, [TYPE, MOBILE]], [
/\(bb10;\s(\w+)/i // BlackBerry 10
], [MODEL, [VENDOR, 'BlackBerry'], [TYPE, MOBILE]], [
// Asus Tablets
/android.+(transfo[prime\s]{4,10}\s\w+|eeepc|slider\s\w+|nexus 7|padfone)/i
], [MODEL, [VENDOR, 'Asus'], [TYPE, TABLET]], [
/(sony)\s(tablet\s[ps])\sbuild\//i, // Sony
/(sony)?(?:sgp.+)\sbuild\//i
], [[VENDOR, 'Sony'], [MODEL, 'Xperia Tablet'], [TYPE, TABLET]], [
/(?:sony)?(?:(?:(?:c|d)\d{4})|(?:so[-l].+))\sbuild\//i
], [[VENDOR, 'Sony'], [MODEL, 'Xperia Phone'], [TYPE, MOBILE]], [
/\s(ouya)\s/i, // Ouya
/(nintendo)\s([wids3u]+)/i // Nintendo
], [VENDOR, MODEL, [TYPE, CONSOLE]], [
/android.+;\s(shield)\sbuild/i // Nvidia
], [MODEL, [VENDOR, 'Nvidia'], [TYPE, CONSOLE]], [
/(playstation\s[34portablevi]+)/i // Playstation
], [MODEL, [VENDOR, 'Sony'], [TYPE, CONSOLE]], [
/(sprint\s(\w+))/i // Sprint Phones
], [[VENDOR, mapper.str, maps.device.sprint.vendor], [MODEL, mapper.str, maps.device.sprint.model], [TYPE, MOBILE]], [
/(lenovo)\s?(S(?:5000|6000)+(?:[-][\w+]))/i // Lenovo tablets
], [VENDOR, MODEL, [TYPE, TABLET]], [
/(htc)[;_\s-]+([\w\s]+(?=\))|\w+)*/i, // HTC
/(zte)-(\w+)*/i, // ZTE
/(alcatel|geeksphone|huawei|lenovo|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]+)*/i
// Alcatel/GeeksPhone/Huawei/Lenovo/Nexian/Panasonic/Sony
], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [
/(nexus\s9)/i // HTC Nexus 9
], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [
/(nexus\s6p)/i // Huawei Nexus 6P
], [MODEL, [VENDOR, 'Huawei'], [TYPE, MOBILE]], [
/(microsoft);\s(lumia[\s\w]+)/i // Microsoft Lumia
], [VENDOR, MODEL, [TYPE, MOBILE]], [
/[\s\(;](xbox(?:\sone)?)[\s\);]/i // Microsoft Xbox
], [MODEL, [VENDOR, 'Microsoft'], [TYPE, CONSOLE]], [
/(kin\.[onetw]{3})/i // Microsoft Kin
], [[MODEL, /\./g, ' '], [VENDOR, 'Microsoft'], [TYPE, MOBILE]], [
// Motorola
/\s(milestone|droid(?:[2-4x]|\s(?:bionic|x2|pro|razr))?(:?\s4g)?)[\w\s]+build\//i,
/mot[\s-]?(\w+)*/i,
/(XT\d{3,4}) build\//i,
/(nexus\s6)/i
], [MODEL, [VENDOR, 'Motorola'], [TYPE, MOBILE]], [
/android.+\s(mz60\d|xoom[\s2]{0,2})\sbuild\//i
], [MODEL, [VENDOR, 'Motorola'], [TYPE, TABLET]], [
/hbbtv\/\d+\.\d+\.\d+\s+\([\w\s]*;\s*(\w[^;]*);([^;]*)/i // HbbTV devices
], [[VENDOR, util.trim], [MODEL, util.trim], [TYPE, SMARTTV]], [
/hbbtv.+maple;(\d+)/i
], [[MODEL, /^/, 'SmartTV'], [VENDOR, 'Samsung'], [TYPE, SMARTTV]], [
/\(dtv[\);].+(aquos)/i // Sharp
], [MODEL, [VENDOR, 'Sharp'], [TYPE, SMARTTV]], [
/android.+((sch-i[89]0\d|shw-m380s|gt-p\d{4}|gt-n\d+|sgh-t8[56]9|nexus 10))/i,
/((SM-T\w+))/i
], [[VENDOR, 'Samsung'], MODEL, [TYPE, TABLET]], [ // Samsung
/smart-tv.+(samsung)/i
], [VENDOR, [TYPE, SMARTTV], MODEL], [
/((s[cgp]h-\w+|gt-\w+|galaxy\snexus|sm-\w[\w\d]+))/i,
/(sam[sung]*)[\s-]*(\w+-?[\w-]*)*/i,
/sec-((sgh\w+))/i
], [[VENDOR, 'Samsung'], MODEL, [TYPE, MOBILE]], [
/sie-(\w+)*/i // Siemens
], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [
/(maemo|nokia).*(n900|lumia\s\d+)/i, // Nokia
/(nokia)[\s_-]?([\w-]+)*/i
], [[VENDOR, 'Nokia'], MODEL, [TYPE, MOBILE]], [
/android\s3\.[\s\w;-]{10}(a\d{3})/i // Acer
], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [
/android\s3\.[\s\w;-]{10}(lg?)-([06cv9]{3,4})/i // LG Tablet
], [[VENDOR, 'LG'], MODEL, [TYPE, TABLET]], [
/(lg) netcast\.tv/i // LG SmartTV
], [VENDOR, MODEL, [TYPE, SMARTTV]], [
/(nexus\s[45])/i, // LG
/lg[e;\s\/-]+(\w+)*/i
], [MODEL, [VENDOR, 'LG'], [TYPE, MOBILE]], [
/android.+(ideatab[a-z0-9\-\s]+)/i // Lenovo
], [MODEL, [VENDOR, 'Lenovo'], [TYPE, TABLET]], [
/linux;.+((jolla));/i // Jolla
], [VENDOR, MODEL, [TYPE, MOBILE]], [
/((pebble))app\/[\d\.]+\s/i // Pebble
], [VENDOR, MODEL, [TYPE, WEARABLE]], [
/android.+;\s(glass)\s\d/i // Google Glass
], [MODEL, [VENDOR, 'Google'], [TYPE, WEARABLE]], [
/android.+(\w+)\s+build\/hm\1/i, // Xiaomi Hongmi 'numeric' models
/android.+(hm[\s\-_]*note?[\s_]*(?:\d\w)?)\s+build/i, // Xiaomi Hongmi
/android.+(mi[\s\-_]*(?:one|one[\s_]plus|note lte)?[\s_]*(?:\d\w)?)\s+build/i // Xiaomi Mi
], [[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, MOBILE]], [
/android.+a000(1)\s+build/i // OnePlus
], [MODEL, [VENDOR, 'OnePlus'], [TYPE, MOBILE]], [
/\s(tablet)[;\/]/i, // Unidentifiable Tablet
/\s(mobile)(?:[;\/]|\ssafari)/i // Unidentifiable Mobile
], [[TYPE, util.lowerize], VENDOR, MODEL]
/*//////////////////////////
// TODO: move to string map
////////////////////////////
/(C6603)/i // Sony Xperia Z C6603
], [[MODEL, 'Xperia Z C6603'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [
/(C6903)/i // Sony Xperia Z 1
], [[MODEL, 'Xperia Z 1'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [
/(SM-G900[F|H])/i // Samsung Galaxy S5
], [[MODEL, 'Galaxy S5'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
/(SM-G7102)/i // Samsung Galaxy Grand 2
], [[MODEL, 'Galaxy Grand 2'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
/(SM-G530H)/i // Samsung Galaxy Grand Prime
], [[MODEL, 'Galaxy Grand Prime'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
/(SM-G313HZ)/i // Samsung Galaxy V
], [[MODEL, 'Galaxy V'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
/(SM-T805)/i // Samsung Galaxy Tab S 10.5
], [[MODEL, 'Galaxy Tab S 10.5'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [
/(SM-G800F)/i // Samsung Galaxy S5 Mini
], [[MODEL, 'Galaxy S5 Mini'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
/(SM-T311)/i // Samsung Galaxy Tab 3 8.0
], [[MODEL, 'Galaxy Tab 3 8.0'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [
/(R1001)/i // Oppo R1001
], [MODEL, [VENDOR, 'OPPO'], [TYPE, MOBILE]], [
/(X9006)/i // Oppo Find 7a
], [[MODEL, 'Find 7a'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
/(R2001)/i // Oppo YOYO R2001
], [[MODEL, 'Yoyo R2001'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
/(R815)/i // Oppo Clover R815
], [[MODEL, 'Clover R815'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
/(U707)/i // Oppo Find Way S
], [[MODEL, 'Find Way S'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
/(T3C)/i // Advan Vandroid T3C
], [MODEL, [VENDOR, 'Advan'], [TYPE, TABLET]], [
/(ADVAN T1J\+)/i // Advan Vandroid T1J+
], [[MODEL, 'Vandroid T1J+'], [VENDOR, 'Advan'], [TYPE, TABLET]], [
/(ADVAN S4A)/i // Advan Vandroid S4A
], [[MODEL, 'Vandroid S4A'], [VENDOR, 'Advan'], [TYPE, MOBILE]], [
/(V972M)/i // ZTE V972M
], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [
/(i-mobile)\s(IQ\s[\d\.]+)/i // i-mobile IQ
], [VENDOR, MODEL, [TYPE, MOBILE]], [
/(IQ6.3)/i // i-mobile IQ IQ 6.3
], [[MODEL, 'IQ 6.3'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [
/(i-mobile)\s(i-style\s[\d\.]+)/i // i-mobile i-STYLE
], [VENDOR, MODEL, [TYPE, MOBILE]], [
/(i-STYLE2.1)/i // i-mobile i-STYLE 2.1
], [[MODEL, 'i-STYLE 2.1'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [
/(mobiistar touch LAI 512)/i // mobiistar touch LAI 512
], [[MODEL, 'Touch LAI 512'], [VENDOR, 'mobiistar'], [TYPE, MOBILE]], [
/////////////
// END TODO
///////////*/
],
engine : [[
/windows.+\sedge\/([\w\.]+)/i // EdgeHTML
], [VERSION, [NAME, 'EdgeHTML']], [
/(presto)\/([\w\.]+)/i, // Presto
/(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m
/(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links
/(icab)[\/\s]([23]\.[\d\.]+)/i // iCab
], [NAME, VERSION], [
/rv\:([\w\.]+).*(gecko)/i // Gecko
], [VERSION, NAME]
],
os : [[
// Windows based
/microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes)
], [NAME, VERSION], [
/(windows)\snt\s6\.2;\s(arm)/i, // Windows RT
/(windows\sphone(?:\sos)*)[\s\/]?([\d\.\s]+\w)*/i, // Windows Phone
/(windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i
], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [
/(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i
], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [
// Mobile/Embedded OS
/\((bb)(10);/i // BlackBerry 10
], [[NAME, 'BlackBerry'], VERSION], [
/(blackberry)\w*\/?([\w\.]+)*/i, // Blackberry
/(tizen)[\/\s]([\w\.]+)/i, // Tizen
/(android|webos|palm\sos|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i,
// Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki
/linux;.+(sailfish);/i // Sailfish OS
], [NAME, VERSION], [
/(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i // Symbian
], [[NAME, 'Symbian'], VERSION], [
/\((series40);/i // Series 40
], [NAME], [
/mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS
], [[NAME, 'Firefox OS'], VERSION], [
// Console
/(nintendo|playstation)\s([wids34portablevu]+)/i, // Nintendo/Playstation
// GNU/Linux based
/(mint)[\/\s\(]?(\w+)*/i, // Mint
/(mageia|vectorlinux)[;\s]/i, // Mageia/VectorLinux
/(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|(?=\s)arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?(?!chrom)([\w\.-]+)*/i,
// Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
// Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
/(hurd|linux)\s?([\w\.]+)*/i, // Hurd/Linux
/(gnu)\s?([\w\.]+)*/i // GNU
], [NAME, VERSION], [
/(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS
], [[NAME, 'Chromium OS'], VERSION],[
// Solaris
/(sunos)\s?([\w\.]+\d)*/i // Solaris
], [[NAME, 'Solaris'], VERSION], [
// BSD based
/\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
], [NAME, VERSION],[
/(haiku)\s(\w+)/i // Haiku
], [NAME, VERSION],[
/(ip[honead]+)(?:.*os\s([\w]+)*\slike\smac|;\sopera)/i // iOS
], [[NAME, 'iOS'], [VERSION, /_/g, '.']], [
/(mac\sos\sx)\s?([\w\s\.]+\w)*/i,
/(macintosh|mac(?=_powerpc)\s)/i // Mac OS
], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [
// Other
/((?:open)?solaris)[\/\s-]?([\w\.]+)*/i, // Solaris
/(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i, // AIX
/(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i,
// Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS
/(unix)\s?([\w\.]+)*/i // UNIX
], [NAME, VERSION]
]
};
/////////////////
// Constructor
////////////////
var UAParser = function (uastring, extensions) {
if (!(this instanceof UAParser)) {
return new UAParser(uastring, extensions).getResult();
}
var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY);
var rgxmap = extensions ? util.extend(regexes, extensions) : regexes;
this.getBrowser = function () {
var browser = mapper.rgx.apply(this, rgxmap.browser);
browser.major = util.major(browser.version);
return browser;
};
this.getCPU = function () {
return mapper.rgx.apply(this, rgxmap.cpu);
};
this.getDevice = function () {
return mapper.rgx.apply(this, rgxmap.device);
};
this.getEngine = function () {
return mapper.rgx.apply(this, rgxmap.engine);
};
this.getOS = function () {
return mapper.rgx.apply(this, rgxmap.os);
};
this.getResult = function() {
return {
ua : this.getUA(),
browser : this.getBrowser(),
engine : this.getEngine(),
os : this.getOS(),
device : this.getDevice(),
cpu : this.getCPU()
};
};
this.getUA = function () {
return ua;
};
this.setUA = function (uastring) {
ua = uastring;
return this;
};
return this;
};
UAParser.VERSION = LIBVERSION;
UAParser.BROWSER = {
NAME : NAME,
MAJOR : MAJOR, // deprecated
VERSION : VERSION
};
UAParser.CPU = {
ARCHITECTURE : ARCHITECTURE
};
UAParser.DEVICE = {
MODEL : MODEL,
VENDOR : VENDOR,
TYPE : TYPE,
CONSOLE : CONSOLE,
MOBILE : MOBILE,
SMARTTV : SMARTTV,
TABLET : TABLET,
WEARABLE: WEARABLE,
EMBEDDED: EMBEDDED
};
UAParser.ENGINE = {
NAME : NAME,
VERSION : VERSION
};
UAParser.OS = {
NAME : NAME,
VERSION : VERSION
};
///////////
// Export
//////////
// check js environment
if (typeof(exports) !== UNDEF_TYPE) {
// nodejs env
if (typeof module !== UNDEF_TYPE && module.exports) {
exports = module.exports = UAParser;
}
exports.UAParser = UAParser;
} else {
// requirejs env (optional)
if (typeof(define) === FUNC_TYPE && define.amd) {
define(function () {
return UAParser;
});
} else {
// browser env
window.UAParser = UAParser;
}
}
// jQuery/Zepto specific (optional)
// Note:
// In AMD env the global scope should be kept clean, but jQuery is an exception.
// jQuery always exports to global scope, unless jQuery.noConflict(true) is used,
// and we should catch that.
var $ = window.jQuery || window.Zepto;
if (typeof $ !== UNDEF_TYPE) {
var parser = new UAParser();
$.ua = parser.getResult();
$.ua.get = function() {
return parser.getUA();
};
$.ua.set = function (uastring) {
parser.setUA(uastring);
var result = parser.getResult();
for (var prop in result) {
$.ua[prop] = result[prop];
}
};
}
})(typeof window === 'object' ? window : this);
},{}],19:[function(require,module,exports){
(function (global){
var rng;
var crypto = global.crypto || global.msCrypto; // for IE 11
if (crypto && crypto.getRandomValues) {
// WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
// Moderately fast, high quality
var _rnds8 = new Uint8Array(16);
rng = function whatwgRNG() {
crypto.getRandomValues(_rnds8);
return _rnds8;
};
}
if (!rng) {
// Math.random()-based (RNG)
//
// If all else fails, use Math.random(). It's fast, but is of unspecified
// quality.
var _rnds = new Array(16);
rng = function() {
for (var i = 0, r; i < 16; i++) {
if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
_rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
}
return _rnds;
};
}
module.exports = rng;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],20:[function(require,module,exports){
// uuid.js
//
// Copyright (c) 2010-2012 Robert Kieffer
// MIT License - http://opensource.org/licenses/mit-license.php
// Unique ID creation requires a high quality random # generator. We feature
// detect to determine the best RNG source, normalizing to a function that
// returns 128-bits of randomness, since that's what's usually required
var _rng = require('./rng');
// Maps for number <-> hex string conversion
var _byteToHex = [];
var _hexToByte = {};
for (var i = 0; i < 256; i++) {
_byteToHex[i] = (i + 0x100).toString(16).substr(1);
_hexToByte[_byteToHex[i]] = i;
}
// **`parse()` - Parse a UUID into it's component bytes**
function parse(s, buf, offset) {
var i = (buf && offset) || 0, ii = 0;
buf = buf || [];
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
if (ii < 16) { // Don't overflow!
buf[i + ii++] = _hexToByte[oct];
}
});
// Zero out remaining bytes if string was short
while (ii < 16) {
buf[i + ii++] = 0;
}
return buf;
}
// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
function unparse(buf, offset) {
var i = offset || 0, bth = _byteToHex;
return bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]];
}
// **`v1()` - Generate time-based UUID**
//
// Inspired by https://github.com/LiosK/UUID.js
// and http://docs.python.org/library/uuid.html
// random #'s we need to init node and clockseq
var _seedBytes = _rng();
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
var _nodeId = [
_seedBytes[0] | 0x01,
_seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
];
// Per 4.2.2, randomize (14 bit) clockseq
var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
// Previous uuid creation time
var _lastMSecs = 0, _lastNSecs = 0;
// See https://github.com/broofa/node-uuid for API details
function v1(options, buf, offset) {
var i = buf && offset || 0;
var b = buf || [];
options = options || {};
var clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq;
// UUID timestamps are 100 nano-second units since the Gregorian epoch,
// (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
// time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
// (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
var msecs = options.msecs !== undefined ? options.msecs : new Date().getTime();
// Per 4.2.1.2, use count of uuid's generated during the current clock
// cycle to simulate higher resolution clock
var nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;
// Time since last uuid creation (in msecs)
var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;
// Per 4.2.1.2, Bump clockseq on clock regression
if (dt < 0 && options.clockseq === undefined) {
clockseq = clockseq + 1 & 0x3fff;
}
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
// time interval
if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
nsecs = 0;
}
// Per 4.2.1.2 Throw error if too many uuids are requested
if (nsecs >= 10000) {
throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
}
_lastMSecs = msecs;
_lastNSecs = nsecs;
_clockseq = clockseq;
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
msecs += 12219292800000;
// `time_low`
var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
b[i++] = tl >>> 24 & 0xff;
b[i++] = tl >>> 16 & 0xff;
b[i++] = tl >>> 8 & 0xff;
b[i++] = tl & 0xff;
// `time_mid`
var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
b[i++] = tmh >>> 8 & 0xff;
b[i++] = tmh & 0xff;
// `time_high_and_version`
b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
b[i++] = tmh >>> 16 & 0xff;
// `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
b[i++] = clockseq >>> 8 | 0x80;
// `clock_seq_low`
b[i++] = clockseq & 0xff;
// `node`
var node = options.node || _nodeId;
for (var n = 0; n < 6; n++) {
b[i + n] = node[n];
}
return buf ? buf : unparse(b);
}
// **`v4()` - Generate random UUID**
// See https://github.com/broofa/node-uuid for API details
function v4(options, buf, offset) {
// Deprecated - 'format' argument, as supported in v1.2
var i = buf && offset || 0;
if (typeof(options) == 'string') {
buf = options == 'binary' ? new Array(16) : null;
options = null;
}
options = options || {};
var rnds = options.random || (options.rng || _rng)();
// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
// Copy bytes to buffer, if provided
if (buf) {
for (var ii = 0; ii < 16; ii++) {
buf[i + ii] = rnds[ii];
}
}
return buf || unparse(rnds);
}
// Export public API
var uuid = v4;
uuid.v1 = v1;
uuid.v4 = v4;
uuid.parse = parse;
uuid.unparse = unparse;
module.exports = uuid;
},{"./rng":19}],21:[function(require,module,exports){
/*
WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based
on @visionmedia's Emitter from UI Kit.
Why? I wanted it standalone.
I also wanted support for wildcard emitters like this:
emitter.on('*', function (eventName, other, event, payloads) {
});
emitter.on('somenamespace*', function (eventName, payloads) {
});
Please note that callbacks triggered by wildcard registered events also get
the event name as the first argument.
*/
module.exports = WildEmitter;
function WildEmitter() { }
WildEmitter.mixin = function (constructor) {
var prototype = constructor.prototype || constructor;
prototype.isWildEmitter= true;
// Listen on the given `event` with `fn`. Store a group name if present.
prototype.on = function (event, groupName, fn) {
this.callbacks = this.callbacks || {};
var hasGroup = (arguments.length === 3),
group = hasGroup ? arguments[1] : undefined,
func = hasGroup ? arguments[2] : arguments[1];
func._groupName = group;
(this.callbacks[event] = this.callbacks[event] || []).push(func);
return this;
};
// Adds an `event` listener that will be invoked a single
// time then automatically removed.
prototype.once = function (event, groupName, fn) {
var self = this,
hasGroup = (arguments.length === 3),
group = hasGroup ? arguments[1] : undefined,
func = hasGroup ? arguments[2] : arguments[1];
function on() {
self.off(event, on);
func.apply(this, arguments);
}
this.on(event, group, on);
return this;
};
// Unbinds an entire group
prototype.releaseGroup = function (groupName) {
this.callbacks = this.callbacks || {};
var item, i, len, handlers;
for (item in this.callbacks) {
handlers = this.callbacks[item];
for (i = 0, len = handlers.length; i < len; i++) {
if (handlers[i]._groupName === groupName) {
//console.log('removing');
// remove it and shorten the array we're looping through
handlers.splice(i, 1);
i--;
len--;
}
}
}
return this;
};
// Remove the given callback for `event` or all
// registered callbacks.
prototype.off = function (event, fn) {
this.callbacks = this.callbacks || {};
var callbacks = this.callbacks[event],
i;
if (!callbacks) return this;
// remove all handlers
if (arguments.length === 1) {
delete this.callbacks[event];
return this;
}
// remove specific handler
i = callbacks.indexOf(fn);
callbacks.splice(i, 1);
if (callbacks.length === 0) {
delete this.callbacks[event];
}
return this;
};
/// Emit `event` with the given args.
// also calls any `*` handlers
prototype.emit = function (event) {
this.callbacks = this.callbacks || {};
var args = [].slice.call(arguments, 1),
callbacks = this.callbacks[event],
specialCallbacks = this.getWildcardCallbacks(event),
i,
len,
item,
listeners;
if (callbacks) {
listeners = callbacks.slice();
for (i = 0, len = listeners.length; i < len; ++i) {
if (!listeners[i]) {
break;
}
listeners[i].apply(this, args);
}
}
if (specialCallbacks) {
len = specialCallbacks.length;
listeners = specialCallbacks.slice();
for (i = 0, len = listeners.length; i < len; ++i) {
if (!listeners[i]) {
break;
}
listeners[i].apply(this, [event].concat(args));
}
}
return this;
};
// Helper for for finding special wildcard event handlers that match the event
prototype.getWildcardCallbacks = function (eventName) {
this.callbacks = this.callbacks || {};
var item,
split,
result = [];
for (item in this.callbacks) {
split = item.split('*');
if (item === '*' || (split.length === 2 && eventName.slice(0, split[0].length) === split[0])) {
result = result.concat(this.callbacks[item]);
}
}
return result;
};
};
WildEmitter.mixin(WildEmitter);
},{}],22:[function(require,module,exports){
(function (process){
/**
* This is the web browser implementation of `debug()`.
*
* Expose `debug()` as the module.
*/
exports = module.exports = require('./debug');
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = 'undefined' != typeof chrome
&& 'undefined' != typeof chrome.storage
? chrome.storage.local
: localstorage();
/**
* Colors.
*/
exports.colors = [
'lightseagreen',
'forestgreen',
'goldenrod',
'dodgerblue',
'darkorchid',
'crimson'
];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
* and the Firebug extension (any Firefox version) are known
* to support "%c" CSS customizations.
*
* TODO: add a `localStorage` variable to explicitly enable/disable colors
*/
function useColors() {
// NB: In an Electron preload script, document will be defined but not fully
// initialized. Since we know we're in Chrome, we'll just detect this case
// explicitly
if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {
return true;
}
// is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
return (typeof document !== 'undefined' && document && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
// is firebug? http://stackoverflow.com/a/398120/376773
(typeof window !== 'undefined' && window && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
// is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
(typeof navigator !== 'undefined' && navigator && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
// double check webkit in userAgent just in case we are in a worker
(typeof navigator !== 'undefined' && navigator && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
}
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
exports.formatters.j = function(v) {
try {
return JSON.stringify(v);
} catch (err) {
return '[UnexpectedJSONParseError]: ' + err.message;
}
};
/**
* Colorize log arguments if enabled.
*
* @api public
*/
function formatArgs(args) {
var useColors = this.useColors;
args[0] = (useColors ? '%c' : '')
+ this.namespace
+ (useColors ? ' %c' : ' ')
+ args[0]
+ (useColors ? '%c ' : ' ')
+ '+' + exports.humanize(this.diff);
if (!useColors) return;
var c = 'color: ' + this.color;
args.splice(1, 0, c, 'color: inherit')
// the final "%c" is somewhat tricky, because there could be other
// arguments passed either before or after the %c, so we need to
// figure out the correct index to insert the CSS into
var index = 0;
var lastC = 0;
args[0].replace(/%[a-zA-Z%]/g, function(match) {
if ('%%' === match) return;
index++;
if ('%c' === match) {
// we only are interested in the *last* %c
// (the user may have provided their own)
lastC = index;
}
});
args.splice(lastC, 0, c);
}
/**
* Invokes `console.log()` when available.
* No-op when `console.log` is not a "function".
*
* @api public
*/
function log() {
// this hackery is required for IE8/9, where
// the `console.log` function doesn't have 'apply'
return 'object' === typeof console
&& console.log
&& Function.prototype.apply.call(console.log, console, arguments);
}
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
try {
if (null == namespaces) {
exports.storage.removeItem('debug');
} else {
exports.storage.debug = namespaces;
}
} catch(e) {}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
var r;
try {
r = exports.storage.debug;
} catch(e) {}
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
if (!r && typeof process !== 'undefined' && 'env' in process) {
r = process.env.DEBUG;
}
return r;
}
/**
* Enable namespaces listed in `localStorage.debug` initially.
*/
exports.enable(load());
/**
* Localstorage attempts to return the localstorage.
*
* This is necessary because safari throws
* when a user disables cookies/localstorage
* and you attempt to access it.
*
* @return {LocalStorage}
* @api private
*/
function localstorage() {
try {
return window.localStorage;
} catch (e) {}
}
}).call(this,require('_process'))
},{"./debug":23,"_process":115}],23:[function(require,module,exports){
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*
* Expose `debug()` as the module.
*/
exports = module.exports = createDebug.debug = createDebug['default'] = createDebug;
exports.coerce = coerce;
exports.disable = disable;
exports.enable = enable;
exports.enabled = enabled;
exports.humanize = require('ms');
/**
* The currently active debug mode names, and names to skip.
*/
exports.names = [];
exports.skips = [];
/**
* Map of special "%n" handling functions, for the debug "format" argument.
*
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
*/
exports.formatters = {};
/**
* Previous log timestamp.
*/
var prevTime;
/**
* Select a color.
* @param {String} namespace
* @return {Number}
* @api private
*/
function selectColor(namespace) {
var hash = 0, i;
for (i in namespace) {
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return exports.colors[Math.abs(hash) % exports.colors.length];
}
/**
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function createDebug(namespace) {
function debug() {
// disabled?
if (!debug.enabled) return;
var self = debug;
// set `diff` timestamp
var curr = +new Date();
var ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
// turn the `arguments` into a proper Array
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
args[0] = exports.coerce(args[0]);
if ('string' !== typeof args[0]) {
// anything else let's inspect with %O
args.unshift('%O');
}
// apply any `formatters` transformations
var index = 0;
args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {
// if we encounter an escaped % then don't increase the array index
if (match === '%%') return match;
index++;
var formatter = exports.formatters[format];
if ('function' === typeof formatter) {
var val = args[index];
match = formatter.call(self, val);
// now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});
// apply env-specific formatting (colors, etc.)
exports.formatArgs.call(self, args);
var logFn = debug.log || exports.log || console.log.bind(console);
logFn.apply(self, args);
}
debug.namespace = namespace;
debug.enabled = exports.enabled(namespace);
debug.useColors = exports.useColors();
debug.color = selectColor(namespace);
// env-specific initialization logic for debug instances
if ('function' === typeof exports.init) {
exports.init(debug);
}
return debug;
}
/**
* Enables a debug mode by namespaces. This can include modes
* separated by a colon and wildcards.
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
exports.save(namespaces);
exports.names = [];
exports.skips = [];
var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
var len = split.length;
for (var i = 0; i < len; i++) {
if (!split[i]) continue; // ignore empty strings
namespaces = split[i].replace(/\*/g, '.*?');
if (namespaces[0] === '-') {
exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
} else {
exports.names.push(new RegExp('^' + namespaces + '$'));
}
}
}
/**
* Disable debug output.
*
* @api public
*/
function disable() {
exports.enable('');
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
var i, len;
for (i = 0, len = exports.skips.length; i < len; i++) {
if (exports.skips[i].test(name)) {
return false;
}
}
for (i = 0, len = exports.names.length; i < len; i++) {
if (exports.names[i].test(name)) {
return true;
}
}
return false;
}
/**
* Coerce `val`.
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) return val.stack || val.message;
return val;
}
},{"ms":35}],24:[function(require,module,exports){
arguments[4][7][0].apply(exports,arguments)
},{"dup":7}],25:[function(require,module,exports){
(function (global){
/*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */
;(function () {
// Detect the `define` function exposed by asynchronous module loaders. The
// strict `define` check is necessary for compatibility with `r.js`.
var isLoader = typeof define === "function" && define.amd;
// A set of types used to distinguish objects from primitives.
var objectTypes = {
"function": true,
"object": true
};
// Detect the `exports` object exposed by CommonJS implementations.
var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
// Use the `global` object exposed by Node (including Browserify via
// `insert-module-globals`), Narwhal, and Ringo as the default context,
// and the `window` object in browsers. Rhino exports a `global` function
// instead.
var root = objectTypes[typeof window] && window || this,
freeGlobal = freeExports && objectTypes[typeof module] && module && !module.nodeType && typeof global == "object" && global;
if (freeGlobal && (freeGlobal["global"] === freeGlobal || freeGlobal["window"] === freeGlobal || freeGlobal["self"] === freeGlobal)) {
root = freeGlobal;
}
// Public: Initializes JSON 3 using the given `context` object, attaching the
// `stringify` and `parse` functions to the specified `exports` object.
function runInContext(context, exports) {
context || (context = root["Object"]());
exports || (exports = root["Object"]());
// Native constructor aliases.
var Number = context["Number"] || root["Number"],
String = context["String"] || root["String"],
Object = context["Object"] || root["Object"],
Date = context["Date"] || root["Date"],
SyntaxError = context["SyntaxError"] || root["SyntaxError"],
TypeError = context["TypeError"] || root["TypeError"],
Math = context["Math"] || root["Math"],
nativeJSON = context["JSON"] || root["JSON"];
// Delegate to the native `stringify` and `parse` implementations.
if (typeof nativeJSON == "object" && nativeJSON) {
exports.stringify = nativeJSON.stringify;
exports.parse = nativeJSON.parse;
}
// Convenience aliases.
var objectProto = Object.prototype,
getClass = objectProto.toString,
isProperty, forEach, undef;
// Test the `Date#getUTC*` methods. Based on work by @Yaffle.
var isExtended = new Date(-3509827334573292);
try {
// The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
// results for certain dates in Opera >= 10.53.
isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 &&
// Safari < 2.0.2 stores the internal millisecond time value correctly,
// but clips the values returned by the date methods to the range of
// signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
} catch (exception) {}
// Internal: Determines whether the native `JSON.stringify` and `parse`
// implementations are spec-compliant. Based on work by Ken Snyder.
function has(name) {
if (has[name] !== undef) {
// Return cached feature test result.
return has[name];
}
var isSupported;
if (name == "bug-string-char-index") {
// IE <= 7 doesn't support accessing string characters using square
// bracket notation. IE 8 only supports this for primitives.
isSupported = "a"[0] != "a";
} else if (name == "json") {
// Indicates whether both `JSON.stringify` and `JSON.parse` are
// supported.
isSupported = has("json-stringify") && has("json-parse");
} else {
var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
// Test `JSON.stringify`.
if (name == "json-stringify") {
var stringify = exports.stringify, stringifySupported = typeof stringify == "function" && isExtended;
if (stringifySupported) {
// A test function object with a custom `toJSON` method.
(value = function () {
return 1;
}).toJSON = value;
try {
stringifySupported =
// Firefox 3.1b1 and b2 serialize string, number, and boolean
// primitives as object literals.
stringify(0) === "0" &&
// FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
// literals.
stringify(new Number()) === "0" &&
stringify(new String()) == '""' &&
// FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
// does not define a canonical JSON representation (this applies to
// objects with `toJSON` properties as well, *unless* they are nested
// within an object or array).
stringify(getClass) === undef &&
// IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
// FF 3.1b3 pass this test.
stringify(undef) === undef &&
// Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
// respectively, if the value is omitted entirely.
stringify() === undef &&
// FF 3.1b1, 2 throw an error if the given value is not a number,
// string, array, object, Boolean, or `null` literal. This applies to
// objects with custom `toJSON` methods as well, unless they are nested
// inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
// methods entirely.
stringify(value) === "1" &&
stringify([value]) == "[1]" &&
// Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
// `"[null]"`.
stringify([undef]) == "[null]" &&
// YUI 3.0.0b1 fails to serialize `null` literals.
stringify(null) == "null" &&
// FF 3.1b1, 2 halts serialization if an array contains a function:
// `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3
// elides non-JSON values from objects and arrays, unless they
// define custom `toJSON` methods.
stringify([undef, getClass, null]) == "[null,null,null]" &&
// Simple serialization test. FF 3.1b1 uses Unicode escape sequences
// where character escape codes are expected (e.g., `\b` => `\u0008`).
stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized &&
// FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
stringify(null, value) === "1" &&
stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
// JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
// serialize extended years.
stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
// The milliseconds are optional in ES 5, but required in 5.1.
stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
// Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
// four-digit years instead of six-digit years. Credits: @Yaffle.
stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
// Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
// values less than 1000. Credits: @Yaffle.
stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
} catch (exception) {
stringifySupported = false;
}
}
isSupported = stringifySupported;
}
// Test `JSON.parse`.
if (name == "json-parse") {
var parse = exports.parse;
if (typeof parse == "function") {
try {
// FF 3.1b1, b2 will throw an exception if a bare literal is provided.
// Conforming implementations should also coerce the initial argument to
// a string prior to parsing.
if (parse("0") === 0 && !parse(false)) {
// Simple parsing test.
value = parse(serialized);
var parseSupported = value["a"].length == 5 && value["a"][0] === 1;
if (parseSupported) {
try {
// Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
parseSupported = !parse('"\t"');
} catch (exception) {}
if (parseSupported) {
try {
// FF 4.0 and 4.0.1 allow leading `+` signs and leading
// decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
// certain octal literals.
parseSupported = parse("01") !== 1;
} catch (exception) {}
}
if (parseSupported) {
try {
// FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
// points. These environments, along with FF 3.1b1 and 2,
// also allow trailing commas in JSON objects and arrays.
parseSupported = parse("1.") !== 1;
} catch (exception) {}
}
}
}
} catch (exception) {
parseSupported = false;
}
}
isSupported = parseSupported;
}
}
return has[name] = !!isSupported;
}
if (!has("json")) {
// Common `[[Class]]` name aliases.
var functionClass = "[object Function]",
dateClass = "[object Date]",
numberClass = "[object Number]",
stringClass = "[object String]",
arrayClass = "[object Array]",
booleanClass = "[object Boolean]";
// Detect incomplete support for accessing string characters by index.
var charIndexBuggy = has("bug-string-char-index");
// Define additional utility methods if the `Date` methods are buggy.
if (!isExtended) {
var floor = Math.floor;
// A mapping between the months of the year and the number of days between
// January 1st and the first of the respective month.
var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
// Internal: Calculates the number of days between the Unix epoch and the
// first day of the given month.
var getDay = function (year, month) {
return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
};
}
// Internal: Determines if a property is a direct property of the given
// object. Delegates to the native `Object#hasOwnProperty` method.
if (!(isProperty = objectProto.hasOwnProperty)) {
isProperty = function (property) {
var members = {}, constructor;
if ((members.__proto__ = null, members.__proto__ = {
// The *proto* property cannot be set multiple times in recent
// versions of Firefox and SeaMonkey.
"toString": 1
}, members).toString != getClass) {
// Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
// supports the mutable *proto* property.
isProperty = function (property) {
// Capture and break the object's prototype chain (see section 8.6.2
// of the ES 5.1 spec). The parenthesized expression prevents an
// unsafe transformation by the Closure Compiler.
var original = this.__proto__, result = property in (this.__proto__ = null, this);
// Restore the original prototype chain.
this.__proto__ = original;
return result;
};
} else {
// Capture a reference to the top-level `Object` constructor.
constructor = members.constructor;
// Use the `constructor` property to simulate `Object#hasOwnProperty` in
// other environments.
isProperty = function (property) {
var parent = (this.constructor || constructor).prototype;
return property in this && !(property in parent && this[property] === parent[property]);
};
}
members = null;
return isProperty.call(this, property);
};
}
// Internal: Normalizes the `for...in` iteration algorithm across
// environments. Each enumerated key is yielded to a `callback` function.
forEach = function (object, callback) {
var size = 0, Properties, members, property;
// Tests for bugs in the current environment's `for...in` algorithm. The
// `valueOf` property inherits the non-enumerable flag from
// `Object.prototype` in older versions of IE, Netscape, and Mozilla.
(Properties = function () {
this.valueOf = 0;
}).prototype.valueOf = 0;
// Iterate over a new instance of the `Properties` class.
members = new Properties();
for (property in members) {
// Ignore all properties inherited from `Object.prototype`.
if (isProperty.call(members, property)) {
size++;
}
}
Properties = members = null;
// Normalize the iteration algorithm.
if (!size) {
// A list of non-enumerable properties inherited from `Object.prototype`.
members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
// IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
// properties.
forEach = function (object, callback) {
var isFunction = getClass.call(object) == functionClass, property, length;
var hasProperty = !isFunction && typeof object.constructor != "function" && objectTypes[typeof object.hasOwnProperty] && object.hasOwnProperty || isProperty;
for (property in object) {
// Gecko <= 1.0 enumerates the `prototype` property of functions under
// certain conditions; IE does not.
if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) {
callback(property);
}
}
// Manually invoke the callback for each non-enumerable property.
for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property));
};
} else if (size == 2) {
// Safari <= 2.0.4 enumerates shadowed properties twice.
forEach = function (object, callback) {
// Create a set of iterated properties.
var members = {}, isFunction = getClass.call(object) == functionClass, property;
for (property in object) {
// Store each property name to prevent double enumeration. The
// `prototype` property of functions is not enumerated due to cross-
// environment inconsistencies.
if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
callback(property);
}
}
};
} else {
// No bugs detected; use the standard `for...in` algorithm.
forEach = function (object, callback) {
var isFunction = getClass.call(object) == functionClass, property, isConstructor;
for (property in object) {
if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
callback(property);
}
}
// Manually invoke the callback for the `constructor` property due to
// cross-environment inconsistencies.
if (isConstructor || isProperty.call(object, (property = "constructor"))) {
callback(property);
}
};
}
return forEach(object, callback);
};
// Public: Serializes a JavaScript `value` as a JSON string. The optional
// `filter` argument may specify either a function that alters how object and
// array members are serialized, or an array of strings and numbers that
// indicates which properties should be serialized. The optional `width`
// argument may be either a string or number that specifies the indentation
// level of the output.
if (!has("json-stringify")) {
// Internal: A map of control characters and their escaped equivalents.
var Escapes = {
92: "\\\\",
34: '\\"',
8: "\\b",
12: "\\f",
10: "\\n",
13: "\\r",
9: "\\t"
};
// Internal: Converts `value` into a zero-padded string such that its
// length is at least equal to `width`. The `width` must be <= 6.
var leadingZeroes = "000000";
var toPaddedString = function (width, value) {
// The `|| 0` expression is necessary to work around a bug in
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
return (leadingZeroes + (value || 0)).slice(-width);
};
// Internal: Double-quotes a string `value`, replacing all ASCII control
// characters (characters with code unit values between 0 and 31) with
// their escaped equivalents. This is an implementation of the
// `Quote(value)` operation defined in ES 5.1 section 15.12.3.
var unicodePrefix = "\\u00";
var quote = function (value) {
var result = '"', index = 0, length = value.length, useCharIndex = !charIndexBuggy || length > 10;
var symbols = useCharIndex && (charIndexBuggy ? value.split("") : value);
for (; index < length; index++) {
var charCode = value.charCodeAt(index);
// If the character is a control character, append its Unicode or
// shorthand escape sequence; otherwise, append the character as-is.
switch (charCode) {
case 8: case 9: case 10: case 12: case 13: case 34: case 92:
result += Escapes[charCode];
break;
default:
if (charCode < 32) {
result += unicodePrefix + toPaddedString(2, charCode.toString(16));
break;
}
result += useCharIndex ? symbols[index] : value.charAt(index);
}
}
return result + '"';
};
// Internal: Recursively serializes an object. Implements the
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
var serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result;
try {
// Necessary for host object support.
value = object[property];
} catch (exception) {}
if (typeof value == "object" && value) {
className = getClass.call(value);
if (className == dateClass && !isProperty.call(value, "toJSON")) {
if (value > -1 / 0 && value < 1 / 0) {
// Dates are serialized according to the `Date#toJSON` method
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
// for the ISO 8601 date time string format.
if (getDay) {
// Manually compute the year, month, date, hours, minutes,
// seconds, and milliseconds if the `getUTC*` methods are
// buggy. Adapted from @Yaffle's `date-shim` project.
date = floor(value / 864e5);
for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
date = 1 + date - getDay(year, month);
// The `time` value specifies the time within the day (see ES
// 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
// to compute `A modulo B`, as the `%` operator does not
// correspond to the `modulo` operation for negative numbers.
time = (value % 864e5 + 864e5) % 864e5;
// The hours, minutes, seconds, and milliseconds are obtained by
// decomposing the time within the day. See section 15.9.1.10.
hours = floor(time / 36e5) % 24;
minutes = floor(time / 6e4) % 60;
seconds = floor(time / 1e3) % 60;
milliseconds = time % 1e3;
} else {
year = value.getUTCFullYear();
month = value.getUTCMonth();
date = value.getUTCDate();
hours = value.getUTCHours();
minutes = value.getUTCMinutes();
seconds = value.getUTCSeconds();
milliseconds = value.getUTCMilliseconds();
}
// Serialize extended years correctly.
value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
"-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
// Months, dates, hours, minutes, and seconds should have two
// digits; milliseconds should have three.
"T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
// Milliseconds are optional in ES 5.0, but required in 5.1.
"." + toPaddedString(3, milliseconds) + "Z";
} else {
value = null;
}
} else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) {
// Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
// `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
// ignores all `toJSON` methods on these objects unless they are
// defined directly on an instance.
value = value.toJSON(property);
}
}
if (callback) {
// If a replacement function was provided, call it to obtain the value
// for serialization.
value = callback.call(object, property, value);
}
if (value === null) {
return "null";
}
className = getClass.call(value);
if (className == booleanClass) {
// Booleans are represented literally.
return "" + value;
} else if (className == numberClass) {
// JSON numbers must be finite. `Infinity` and `NaN` are serialized as
// `"null"`.
return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
} else if (className == stringClass) {
// Strings are double-quoted and escaped.
return quote("" + value);
}
// Recursively serialize objects and arrays.
if (typeof value == "object") {
// Check for cyclic structures. This is a linear search; performance
// is inversely proportional to the number of unique nested objects.
for (length = stack.length; length--;) {
if (stack[length] === value) {
// Cyclic structures cannot be serialized by `JSON.stringify`.
throw TypeError();
}
}
// Add the object to the stack of traversed objects.
stack.push(value);
results = [];
// Save the current indentation level and indent one additional level.
prefix = indentation;
indentation += whitespace;
if (className == arrayClass) {
// Recursively serialize array elements.
for (index = 0, length = value.length; index < length; index++) {
element = serialize(index, value, callback, properties, whitespace, indentation, stack);
results.push(element === undef ? "null" : element);
}
result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
} else {
// Recursively serialize object members. Members are selected from
// either a user-specified list of property names, or the object
// itself.
forEach(properties || value, function (property) {
var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
if (element !== undef) {
// According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
// is not the empty string, let `member` {quote(property) + ":"}
// be the concatenation of `member` and the `space` character."
// The "`space` character" refers to the literal space
// character, not the `space` {width} argument provided to
// `JSON.stringify`.
results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
}
});
result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
}
// Remove the object from the traversed object stack.
stack.pop();
return result;
}
};
// Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
exports.stringify = function (source, filter, width) {
var whitespace, callback, properties, className;
if (objectTypes[typeof filter] && filter) {
if ((className = getClass.call(filter)) == functionClass) {
callback = filter;
} else if (className == arrayClass) {
// Convert the property names array into a makeshift set.
properties = {};
for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1));
}
}
if (width) {
if ((className = getClass.call(width)) == numberClass) {
// Convert the `width` to an integer and create a string containing
// `width` number of space characters.
if ((width -= width % 1) > 0) {
for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
}
} else if (className == stringClass) {
whitespace = width.length <= 10 ? width : width.slice(0, 10);
}
}
// Opera <= 7.54u2 discards the values associated with empty string keys
// (`""`) only if they are used directly within an object member list
// (e.g., `!("" in { "": 1})`).
return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
};
}
// Public: Parses a JSON source string.
if (!has("json-parse")) {
var fromCharCode = String.fromCharCode;
// Internal: A map of escaped control characters and their unescaped
// equivalents.
var Unescapes = {
92: "\\",
34: '"',
47: "/",
98: "\b",
116: "\t",
110: "\n",
102: "\f",
114: "\r"
};
// Internal: Stores the parser state.
var Index, Source;
// Internal: Resets the parser state and throws a `SyntaxError`.
var abort = function () {
Index = Source = null;
throw SyntaxError();
};
// Internal: Returns the next token, or `"$"` if the parser has reached
// the end of the source string. A token may be a string, number, `null`
// literal, or Boolean literal.
var lex = function () {
var source = Source, length = source.length, value, begin, position, isSigned, charCode;
while (Index < length) {
charCode = source.charCodeAt(Index);
switch (charCode) {
case 9: case 10: case 13: case 32:
// Skip whitespace tokens, including tabs, carriage returns, line
// feeds, and space characters.
Index++;
break;
case 123: case 125: case 91: case 93: case 58: case 44:
// Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at
// the current position.
value = charIndexBuggy ? source.charAt(Index) : source[Index];
Index++;
return value;
case 34:
// `"` delimits a JSON string; advance to the next character and
// begin parsing the string. String tokens are prefixed with the
// sentinel `@` character to distinguish them from punctuators and
// end-of-string tokens.
for (value = "@", Index++; Index < length;) {
charCode = source.charCodeAt(Index);
if (charCode < 32) {
// Unescaped ASCII control characters (those with a code unit
// less than the space character) are not permitted.
abort();
} else if (charCode == 92) {
// A reverse solidus (`\`) marks the beginning of an escaped
// control character (including `"`, `\`, and `/`) or Unicode
// escape sequence.
charCode = source.charCodeAt(++Index);
switch (charCode) {
case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114:
// Revive escaped control characters.
value += Unescapes[charCode];
Index++;
break;
case 117:
// `\u` marks the beginning of a Unicode escape sequence.
// Advance to the first character and validate the
// four-digit code point.
begin = ++Index;
for (position = Index + 4; Index < position; Index++) {
charCode = source.charCodeAt(Index);
// A valid sequence comprises four hexdigits (case-
// insensitive) that form a single hexadecimal value.
if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) {
// Invalid Unicode escape sequence.
abort();
}
}
// Revive the escaped character.
value += fromCharCode("0x" + source.slice(begin, Index));
break;
default:
// Invalid escape sequence.
abort();
}
} else {
if (charCode == 34) {
// An unescaped double-quote character marks the end of the
// string.
break;
}
charCode = source.charCodeAt(Index);
begin = Index;
// Optimize for the common case where a string is valid.
while (charCode >= 32 && charCode != 92 && charCode != 34) {
charCode = source.charCodeAt(++Index);
}
// Append the string as-is.
value += source.slice(begin, Index);
}
}
if (source.charCodeAt(Index) == 34) {
// Advance to the next character and return the revived string.
Index++;
return value;
}
// Unterminated string.
abort();
default:
// Parse numbers and literals.
begin = Index;
// Advance past the negative sign, if one is specified.
if (charCode == 45) {
isSigned = true;
charCode = source.charCodeAt(++Index);
}
// Parse an integer or floating-point value.
if (charCode >= 48 && charCode <= 57) {
// Leading zeroes are interpreted as octal literals.
if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) {
// Illegal octal literal.
abort();
}
isSigned = false;
// Parse the integer component.
for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++);
// Floats cannot contain a leading decimal point; however, this
// case is already accounted for by the parser.
if (source.charCodeAt(Index) == 46) {
position = ++Index;
// Parse the decimal component.
for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
if (position == Index) {
// Illegal trailing decimal.
abort();
}
Index = position;
}
// Parse exponents. The `e` denoting the exponent is
// case-insensitive.
charCode = source.charCodeAt(Index);
if (charCode == 101 || charCode == 69) {
charCode = source.charCodeAt(++Index);
// Skip past the sign following the exponent, if one is
// specified.
if (charCode == 43 || charCode == 45) {
Index++;
}
// Parse the exponential component.
for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
if (position == Index) {
// Illegal empty exponent.
abort();
}
Index = position;
}
// Coerce the parsed value to a JavaScript number.
return +source.slice(begin, Index);
}
// A negative sign may only precede numbers.
if (isSigned) {
abort();
}
// `true`, `false`, and `null` literals.
if (source.slice(Index, Index + 4) == "true") {
Index += 4;
return true;
} else if (source.slice(Index, Index + 5) == "false") {
Index += 5;
return false;
} else if (source.slice(Index, Index + 4) == "null") {
Index += 4;
return null;
}
// Unrecognized token.
abort();
}
}
// Return the sentinel `$` character if the parser has reached the end
// of the source string.
return "$";
};
// Internal: Parses a JSON `value` token.
var get = function (value) {
var results, hasMembers;
if (value == "$") {
// Unexpected end of input.
abort();
}
if (typeof value == "string") {
if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") {
// Remove the sentinel `@` character.
return value.slice(1);
}
// Parse object and array literals.
if (value == "[") {
// Parses a JSON array, returning a new JavaScript array.
results = [];
for (;; hasMembers || (hasMembers = true)) {
value = lex();
// A closing square bracket marks the end of the array literal.
if (value == "]") {
break;
}
// If the array literal contains elements, the current token
// should be a comma separating the previous element from the
// next.
if (hasMembers) {
if (value == ",") {
value = lex();
if (value == "]") {
// Unexpected trailing `,` in array literal.
abort();
}
} else {
// A `,` must separate each array element.
abort();
}
}
// Elisions and leading commas are not permitted.
if (value == ",") {
abort();
}
results.push(get(value));
}
return results;
} else if (value == "{") {
// Parses a JSON object, returning a new JavaScript object.
results = {};
for (;; hasMembers || (hasMembers = true)) {
value = lex();
// A closing curly brace marks the end of the object literal.
if (value == "}") {
break;
}
// If the object literal contains members, the current token
// should be a comma separator.
if (hasMembers) {
if (value == ",") {
value = lex();
if (value == "}") {
// Unexpected trailing `,` in object literal.
abort();
}
} else {
// A `,` must separate each object member.
abort();
}
}
// Leading commas are not permitted, object property names must be
// double-quoted strings, and a `:` must separate each property
// name and value.
if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") {
abort();
}
results[value.slice(1)] = get(lex());
}
return results;
}
// Unexpected token encountered.
abort();
}
return value;
};
// Internal: Updates a traversed object member.
var update = function (source, property, callback) {
var element = walk(source, property, callback);
if (element === undef) {
delete source[property];
} else {
source[property] = element;
}
};
// Internal: Recursively traverses a parsed JSON object, invoking the
// `callback` function for each value. This is an implementation of the
// `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
var walk = function (source, property, callback) {
var value = source[property], length;
if (typeof value == "object" && value) {
// `forEach` can't be used to traverse an array in Opera <= 8.54
// because its `Object#hasOwnProperty` implementation returns `false`
// for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`).
if (getClass.call(value) == arrayClass) {
for (length = value.length; length--;) {
update(value, length, callback);
}
} else {
forEach(value, function (property) {
update(value, property, callback);
});
}
}
return callback.call(source, property, value);
};
// Public: `JSON.parse`. See ES 5.1 section 15.12.2.
exports.parse = function (source, callback) {
var result, value;
Index = 0;
Source = "" + source;
result = get(lex());
// If a JSON string contains multiple tokens, it is invalid.
if (lex() != "$") {
abort();
}
// Reset the parser state.
Index = Source = null;
return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result;
};
}
}
exports["runInContext"] = runInContext;
return exports;
}
if (freeExports && !isLoader) {
// Export for CommonJS environments.
runInContext(root, freeExports);
} else {
// Export for web browsers and JavaScript engines.
var nativeJSON = root.JSON,
previousJSON = root["JSON3"],
isRestored = false;
var JSON3 = runInContext(root, (root["JSON3"] = {
// Public: Restores the original value of the global `JSON` object and
// returns a reference to the `JSON3` object.
"noConflict": function () {
if (!isRestored) {
isRestored = true;
root.JSON = nativeJSON;
root["JSON3"] = previousJSON;
nativeJSON = previousJSON = null;
}
return JSON3;
}
}));
root.JSON = {
"parse": JSON3.parse,
"stringify": JSON3.stringify
};
}
// Export for asynchronous module loaders.
if (isLoader) {
define(function () {
return JSON3;
});
}
}).call(this);
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],26:[function(require,module,exports){
function Mapper()
{
var sources = {};
this.forEach = function(callback)
{
for(var key in sources)
{
var source = sources[key];
for(var key2 in source)
callback(source[key2]);
};
};
this.get = function(id, source)
{
var ids = sources[source];
if(ids == undefined)
return undefined;
return ids[id];
};
this.remove = function(id, source)
{
var ids = sources[source];
if(ids == undefined)
return;
delete ids[id];
// Check it's empty
for(var i in ids){return false}
delete sources[source];
};
this.set = function(value, id, source)
{
if(value == undefined)
return this.remove(id, source);
var ids = sources[source];
if(ids == undefined)
sources[source] = ids = {};
ids[id] = value;
};
};
Mapper.prototype.pop = function(id, source)
{
var value = this.get(id, source);
if(value == undefined)
return undefined;
this.remove(id, source);
return value;
};
module.exports = Mapper;
},{}],27:[function(require,module,exports){
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var JsonRpcClient = require('./jsonrpcclient');
exports.JsonRpcClient = JsonRpcClient;
},{"./jsonrpcclient":28}],28:[function(require,module,exports){
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var RpcBuilder = require('../..');
var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
Date.now = Date.now || function() {
return +new Date;
};
var PING_INTERVAL = 5000;
var RECONNECTING = 'RECONNECTING';
var CONNECTED = 'CONNECTED';
var DISCONNECTED = 'DISCONNECTED';
var RECONNECTING = "RECONNECTING";
var CONNECTED = "CONNECTED";
var DISCONNECTED = "DISCONNECTED";
/**
*
* heartbeat: interval in ms for each heartbeat message,
* sendCloseMessage : true / false, before closing the connection, it sends a closeSession message
* <pre>
* ws : {
* uri : URI to conntect to,
* useSockJS : true (use SockJS) / false (use WebSocket) by default,
* onconnected : callback method to invoke when connection is successful,
* ondisconnect : callback method to invoke when the connection is lost,
* onreconnecting : callback method to invoke when the client is reconnecting,
* onreconnected : callback method to invoke when the client succesfully reconnects,
* },
* rpc : {
* requestTimeout : timeout for a request,
* sessionStatusChanged: callback method for changes in session status,
* mediaRenegotiation: mediaRenegotiation
* }
* </pre>
*/
function JsonRpcClient(configuration) {
var self = this;
var wsConfig = configuration.ws;
var notReconnectIfNumLessThan = -1;
var pingNextNum = 0;
var enabledPings = true;
var pingPongStarted = false;
var pingInterval;
var status = DISCONNECTED;
var onreconnecting = wsConfig.onreconnecting;
var onreconnected = wsConfig.onreconnected;
var onconnected = wsConfig.onconnected;
configuration.rpc.pull = function(params, request) {
request.reply(null, "push");
}
wsConfig.onreconnecting = function() {
console.log("--------- ONRECONNECTING -----------");
if (status === RECONNECTING) {
console.error("Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it");
return;
}
status = RECONNECTING;
if (onreconnecting) {
onreconnecting();
}
}
wsConfig.onreconnected = function() {
console.log("--------- ONRECONNECTED -----------");
if (status === CONNECTED) {
console.error("Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it");
return;
}
status = CONNECTED;
enabledPings = true;
updateNotReconnectIfLessThan();
usePing();
if (onreconnected) {
onreconnected();
}
}
wsConfig.onconnected = function() {
console.log("--------- ONCONNECTED -----------");
if (status === CONNECTED) {
console.error("Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it");
return;
}
status = CONNECTED;
enabledPings = true;
usePing();
if (onconnected) {
onconnected();
}
}
var ws = new WebSocketWithReconnection(wsConfig);
console.log('Connecting websocket to URI: ' + wsConfig.uri);
var rpcBuilderOptions = {
request_timeout: configuration.rpc.requestTimeout
};
var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws,
function(request) {
console.log('Received request: ' + JSON.stringify(request));
try {
var func = configuration.rpc[request.method];
if (func === undefined) {
console.error("Method " + request.method + " not registered in client");
} else {
func(request.params, request);
}
} catch (err) {
console.error('Exception processing request: ' + JSON.stringify(request));
console.error(err);
}
});
this.send = function(method, params, callback) {
if (method !== 'ping') {
console.log('Request: method:' + method + " params:" + JSON.stringify(params));
}
var requestTime = Date.now();
rpc.encode(method, params, function(error, result) {
if (error) {
try {
console.error("ERROR:" + error.message + " in Request: method:" + method + " params:" + JSON.stringify(params));
if (error.data) {
console.error("ERROR DATA:" + JSON.stringify(error.data));
}
} catch (e) {}
error.requestTime = requestTime;
}
if (callback) {
if (result != undefined && result.value !== 'pong') {
console.log('Response: ' + JSON.stringify(result));
}
callback(error, result);
}
});
}
function updateNotReconnectIfLessThan() {
notReconnectIfNumLessThan = pingNextNum;
console.log("notReconnectIfNumLessThan = " + notReconnectIfNumLessThan);
}
function sendPing() {
if (enabledPings) {
var params = null;
if (pingNextNum == 0 || pingNextNum == notReconnectIfNumLessThan) {
params = {
interval: PING_INTERVAL
};
}
pingNextNum++;
self.send('ping', params, (function(pingNum) {
return function(error, result) {
if (error) {
if (pingNum > notReconnectIfNumLessThan) {
enabledPings = false;
updateNotReconnectIfLessThan();
console.log("DSS did not respond to ping message " + pingNum + ". Reconnecting... ");
ws.reconnectWs();
}
}
}
})(pingNextNum));
} else {
console.log("Trying to send ping, but ping is not enabled");
}
}
/*
* If configuration.hearbeat has any value, the ping-pong will work with the interval
* of configuration.hearbeat
*/
function usePing() {
if (!pingPongStarted) {
console.log("Starting ping (if configured)")
pingPongStarted = true;
if (configuration.heartbeat != undefined) {
pingInterval = setInterval(sendPing, configuration.heartbeat);
sendPing();
}
}
}
this.close = function() {
console.log("Closing jsonRpcClient explicitely by client");
if (pingInterval != undefined) {
clearInterval(pingInterval);
}
pingPongStarted = false;
enabledPings = false;
if (configuration.sendCloseMessage) {
this.send('closeSession', null, function(error, result) {
if (error) {
console.error("Error sending close message: " + JSON.stringify(error));
}
ws.close();
});
} else {
ws.close();
}
}
// This method is only for testing
this.forceClose = function(millis) {
ws.forceClose(millis);
}
this.reconnect = function() {
ws.reconnectWs();
}
}
module.exports = JsonRpcClient;
},{"../..":31,"./transports/webSocketWithReconnection":30}],29:[function(require,module,exports){
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var WebSocketWithReconnection = require('./webSocketWithReconnection');
exports.WebSocketWithReconnection = WebSocketWithReconnection;
},{"./webSocketWithReconnection":30}],30:[function(require,module,exports){
/*
* (C) Copyright 2013-2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";
var WebSocket = require('ws');
var SockJS = require('sockjs-client');
var MAX_RETRIES = 2000; // Forever...
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...
var PING_INTERVAL = 5000;
var PING_MSG = JSON.stringify({
'method': 'ping'
});
var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;
/*
config = {
uri : wsUri,
useSockJS : true (use SockJS) / false (use WebSocket) by default,
onconnected : callback method to invoke when connection is successful,
ondisconnect : callback method to invoke when the connection is lost,
onreconnecting : callback method to invoke when the client is reconnecting,
onreconnected : callback method to invoke when the client succesfully reconnects,
};
*/
function WebSocketWithReconnection(config) {
var closing = false;
var registerMessageHandler;
var wsUri = config.uri;
var useSockJS = config.useSockJS;
var reconnecting = false;
var forcingDisconnection = false;
var ws;
if (useSockJS) {
ws = new SockJS(wsUri);
} else {
ws = new WebSocket(wsUri);
}
ws.onopen = function() {
logConnected(ws, wsUri);
config.onconnected();
};
ws.onerror = function(evt) {
config.onconnected(evt.data);
};
function logConnected(ws, wsUri) {
try {
console.log("WebSocket connected to " + wsUri);
} catch (e) {
console.error(e);
}
}
var reconnectionOnClose = function() {
if (ws.readyState === CLOSED) {
if (closing) {
console.log("Connection Closed by user");
} else {
console.log("Connection closed unexpectecly. Reconnecting...");
reconnectInNewUri(MAX_RETRIES, 1);
}
} else {
console.log("Close callback from previous websocket. Ignoring it");
}
};
ws.onclose = reconnectionOnClose;
function reconnectInNewUri(maxRetries, numRetries) {
console.log("reconnectInNewUri");
if (numRetries === 1) {
if (reconnecting) {
console
.warn("Trying to reconnect when reconnecting... Ignoring this reconnection.")
return;
} else {
reconnecting = true;
}
if (config.onreconnecting) {
config.onreconnecting();
}
}
if (forcingDisconnection) {
reconnect(maxRetries, numRetries, wsUri);
} else {
if (config.newWsUriOnReconnection) {
config.newWsUriOnReconnection(function(error, newWsUri) {
if (error) {
console.log(error);
setTimeout(function() {
reconnectInNewUri(maxRetries, numRetries + 1);
}, RETRY_TIME_MS);
} else {
reconnect(maxRetries, numRetries, newWsUri);
}
})
} else {
reconnect(maxRetries, numRetries, wsUri);
}
}
}
// TODO Test retries. How to force not connection?
function reconnect(maxRetries, numRetries, reconnectWsUri) {
console.log("Trying to reconnect " + numRetries + " times");
var newWs;
if (useSockJS) {
newWs = new SockJS(wsUri);
} else {
newWs = new WebSocket(wsUri);
}
newWs.onopen = function() {
console.log("Reconnected in " + numRetries + " retries...");
logConnected(newWs, reconnectWsUri);
reconnecting = false;
registerMessageHandler();
if (config.onreconnected()) {
config.onreconnected();
}
newWs.onclose = reconnectionOnClose;
};
var onErrorOrClose = function(error) {
console.log("Reconnection error: ", error);
if (numRetries === maxRetries) {
if (config.ondisconnect) {
config.ondisconnect();
}
} else {
setTimeout(function() {
reconnectInNewUri(maxRetries, numRetries + 1);
}, RETRY_TIME_MS);
}
};
newWs.onerror = onErrorOrClose;
ws = newWs;
}
this.close = function() {
closing = true;
ws.close();
};
// This method is only for testing
this.forceClose = function(millis) {
console.log("Testing: Force WebSocket close");
if (millis) {
console.log("Testing: Change wsUri for " + millis + " millis to simulate net failure");
var goodWsUri = wsUri;
wsUri = "wss://21.234.12.34.4:443/";
forcingDisconnection = true;
setTimeout(function() {
console.log("Testing: Recover good wsUri " + goodWsUri);
wsUri = goodWsUri;
forcingDisconnection = false;
}, millis);
}
ws.close();
};
this.reconnectWs = function() {
console.log("reconnectWs");
reconnectInNewUri(MAX_RETRIES, 1, wsUri);
};
this.send = function(message) {
ws.send(message);
};
this.addEventListener = function(type, callback) {
registerMessageHandler = function() {
ws.addEventListener(type, callback);
};
registerMessageHandler();
};
}
module.exports = WebSocketWithReconnection;
},{"sockjs-client":38,"ws":104}],31:[function(require,module,exports){
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var defineProperty_IE8 = false
if(Object.defineProperty)
{
try
{
Object.defineProperty({}, "x", {});
}
catch(e)
{
defineProperty_IE8 = true
}
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits');
var packers = require('./packers');
var Mapper = require('./Mapper');
var BASE_TIMEOUT = 5000;
function unifyResponseMethods(responseMethods)
{
if(!responseMethods) return {};
for(var key in responseMethods)
{
var value = responseMethods[key];
if(typeof value == 'string')
responseMethods[key] =
{
response: value
}
};
return responseMethods;
};
function unifyTransport(transport)
{
if(!transport) return;
// Transport as a function
if(transport instanceof Function)
return {send: transport};
// WebSocket & DataChannel
if(transport.send instanceof Function)
return transport;
// Message API (Inter-window & WebWorker)
if(transport.postMessage instanceof Function)
{
transport.send = transport.postMessage;
return transport;
}
// Stream API
if(transport.write instanceof Function)
{
transport.send = transport.write;
return transport;
}
// Transports that only can receive messages, but not send
if(transport.onmessage !== undefined) return;
if(transport.pause instanceof Function) return;
throw new SyntaxError("Transport is not a function nor a valid object");
};
/**
* Representation of a RPC notification
*
* @class
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
*/
function RpcNotification(method, params)
{
if(defineProperty_IE8)
{
this.method = method
this.params = params
}
else
{
Object.defineProperty(this, 'method', {value: method, enumerable: true});
Object.defineProperty(this, 'params', {value: params, enumerable: true});
}
};
/**
* @class
*
* @constructor
*
* @param {object} packer
*
* @param {object} [options]
*
* @param {object} [transport]
*
* @param {Function} [onRequest]
*/
function RpcBuilder(packer, options, transport, onRequest)
{
var self = this;
if(!packer)
throw new SyntaxError('Packer is not defined');
if(!packer.pack || !packer.unpack)
throw new SyntaxError('Packer is invalid');
var responseMethods = unifyResponseMethods(packer.responseMethods);
if(options instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = options;
transport = undefined;
options = undefined;
};
if(options && options.send instanceof Function)
{
if(transport && !(transport instanceof Function))
throw new SyntaxError("Only a function can be after transport");
onRequest = transport;
transport = options;
options = undefined;
};
if(transport instanceof Function)
{
if(onRequest != undefined)
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = transport;
transport = undefined;
};
if(transport && transport.send instanceof Function)
if(onRequest && !(onRequest instanceof Function))
throw new SyntaxError("Only a function can be after transport");
options = options || {};
EventEmitter.call(this);
if(onRequest)
this.on('request', onRequest);
if(defineProperty_IE8)
this.peerID = options.peerID
else
Object.defineProperty(this, 'peerID', {value: options.peerID});
var max_retries = options.max_retries || 0;
function transportMessage(event)
{
self.decode(event.data || event);
};
this.getTransport = function()
{
return transport;
}
this.setTransport = function(value)
{
// Remove listener from old transport
if(transport)
{
// W3C transports
if(transport.removeEventListener)
transport.removeEventListener('message', transportMessage);
// Node.js Streams API
else if(transport.removeListener)
transport.removeListener('data', transportMessage);
};
// Set listener on new transport
if(value)
{
// W3C transports
if(value.addEventListener)
value.addEventListener('message', transportMessage);
// Node.js Streams API
else if(value.addListener)
value.addListener('data', transportMessage);
};
transport = unifyTransport(value);
}
if(!defineProperty_IE8)
Object.defineProperty(this, 'transport',
{
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
})
this.setTransport(transport);
var request_timeout = options.request_timeout || BASE_TIMEOUT;
var response_timeout = options.response_timeout || BASE_TIMEOUT;
var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
var requestID = 0;
var requests = new Mapper();
var responses = new Mapper();
var processedResponses = new Mapper();
var message2Key = {};
/**
* Store the response to prevent to process duplicate request later
*/
function storeResponse(message, id, dest)
{
var response =
{
message: message,
/** Timeout to auto-clean old responses */
timeout: setTimeout(function()
{
responses.remove(id, dest);
},
response_timeout)
};
responses.set(response, id, dest);
};
/**
* Store the response to ignore duplicated messages later
*/
function storeProcessedResponse(ack, from)
{
var timeout = setTimeout(function()
{
processedResponses.remove(ack, from);
},
duplicates_timeout);
processedResponses.set(timeout, ack, from);
};
/**
* Representation of a RPC request
*
* @class
* @extends RpcNotification
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param {Integer} id - identifier of the request
* @param [from] - source of the notification
*/
function RpcRequest(method, params, id, from, transport)
{
RpcNotification.call(this, method, params);
this.getTransport = function()
{
return transport;
}
this.setTransport = function(value)
{
transport = unifyTransport(value);
}
if(!defineProperty_IE8)
Object.defineProperty(this, 'transport',
{
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
})
var response = responses.get(id, from);
/**
* @constant {Boolean} duplicated
*/
if(!(transport || self.getTransport()))
{
if(defineProperty_IE8)
this.duplicated = Boolean(response)
else
Object.defineProperty(this, 'duplicated',
{
value: Boolean(response)
});
}
var responseMethod = responseMethods[method];
this.pack = packer.pack.bind(packer, this, id)
/**
* Generate a response to this request
*
* @param {Error} [error]
* @param {*} [result]
*
* @returns {string}
*/
this.reply = function(error, result, transport)
{
// Fix optional parameters
if(error instanceof Function || error && error.send instanceof Function)
{
if(result != undefined)
throw new SyntaxError("There can't be parameters after callback");
transport = error;
result = null;
error = undefined;
}
else if(result instanceof Function
|| result && result.send instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback");
transport = result;
result = null;
};
transport = unifyTransport(transport);
// Duplicated request, remove old response timeout
if(response)
clearTimeout(response.timeout);
if(from != undefined)
{
if(error)
error.dest = from;
if(result)
result.dest = from;
};
var message;
// New request or overriden one, create new response with provided data
if(error || result != undefined)
{
if(self.peerID != undefined)
{
if(error)
error.from = self.peerID;
else
result.from = self.peerID;
}
// Protocol indicates that responses has own request methods
if(responseMethod)
{
if(responseMethod.error == undefined && error)
message =
{
error: error
};
else
{
var method = error
? responseMethod.error
: responseMethod.response;
message =
{
method: method,
params: error || result
};
}
}
else
message =
{
error: error,
result: result
};
message = packer.pack(message, id);
}
// Duplicate & not-overriden request, re-send old response
else if(response)
message = response.message;
// New empty reply, response null value
else
message = packer.pack({result: null}, id);
// Store the response to prevent to process a duplicated request later
storeResponse(message, id, from);
// Return the stored response so it can be directly send back
transport = transport || this.getTransport() || self.getTransport();
if(transport)
return transport.send(message);
return message;
}
};
inherits(RpcRequest, RpcNotification);
function cancel(message)
{
var key = message2Key[message];
if(!key) return;
delete message2Key[message];
var request = requests.pop(key.id, key.dest);
if(!request) return;
clearTimeout(request.timeout);
// Start duplicated responses timeout
storeProcessedResponse(key.id, key.dest);
};
/**
* Allow to cancel a request and don't wait for a response
*
* If `message` is not given, cancel all the request
*/
this.cancel = function(message)
{
if(message) return cancel(message);
for(var message in message2Key)
cancel(message);
};
this.close = function()
{
// Prevent to receive new messages
var transport = this.getTransport();
if(transport && transport.close)
transport.close();
// Request & processed responses
this.cancel();
processedResponses.forEach(clearTimeout);
// Responses
responses.forEach(function(response)
{
clearTimeout(response.timeout);
});
};
/**
* Generates and encode a JsonRPC 2.0 message
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param [dest] - destination of the notification
* @param {object} [transport] - transport where to send the message
* @param [callback] - function called when a response to this request is
* received. If not defined, a notification will be send instead
*
* @returns {string} A raw JsonRPC 2.0 request or notification string
*/
this.encode = function(method, params, dest, transport, callback)
{
// Fix optional parameters
if(params instanceof Function)
{
if(dest != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = params;
transport = undefined;
dest = undefined;
params = undefined;
}
else if(dest instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = dest;
transport = undefined;
dest = undefined;
}
else if(transport instanceof Function)
{
if(callback != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = transport;
transport = undefined;
};
if(self.peerID != undefined)
{
params = params || {};
params.from = self.peerID;
};
if(dest != undefined)
{
params = params || {};
params.dest = dest;
};
// Encode message
var message =
{
method: method,
params: params
};
if(callback)
{
var id = requestID++;
var retried = 0;
message = packer.pack(message, id);
function dispatchCallback(error, result)
{
self.cancel(message);
callback(error, result);
};
var request =
{
message: message,
callback: dispatchCallback,
responseMethods: responseMethods[method] || {}
};
var encode_transport = unifyTransport(transport);
function sendRequest(transport)
{
request.timeout = setTimeout(timeout,
request_timeout*Math.pow(2, retried++));
message2Key[message] = {id: id, dest: dest};
requests.set(request, id, dest);
transport = transport || encode_transport || self.getTransport();
if(transport)
return transport.send(message);
return message;
};
function retry(transport)
{
transport = unifyTransport(transport);
console.warn(retried+' retry for request message:',message);
var timeout = processedResponses.pop(id, dest);
clearTimeout(timeout);
return sendRequest(transport);
};
function timeout()
{
if(retried < max_retries)
return retry(transport);
var error = new Error('Request has timed out');
error.request = message;
error.retry = retry;
dispatchCallback(error)
};
return sendRequest(transport);
};
// Return the packed message
message = packer.pack(message);
transport = transport || this.getTransport();
if(transport)
return transport.send(message);
return message;
};
/**
* Decode and process a JsonRPC 2.0 message
*
* @param {string} message - string with the content of the message
*
* @returns {RpcNotification|RpcRequest|undefined} - the representation of the
* notification or the request. If a response was processed, it will return
* `undefined` to notify that it was processed
*
* @throws {TypeError} - Message is not defined
*/
this.decode = function(message, transport)
{
if(!message)
throw new TypeError("Message is not defined");
try
{
message = packer.unpack(message);
}
catch(e)
{
// Ignore invalid messages
return console.log(e, message);
};
var id = message.id;
var ack = message.ack;
var method = message.method;
var params = message.params || {};
var from = params.from;
var dest = params.dest;
// Ignore messages send by us
if(self.peerID != undefined && from == self.peerID) return;
// Notification
if(id == undefined && ack == undefined)
{
var notification = new RpcNotification(method, params);
if(self.emit('request', notification)) return;
return notification;
};
function processRequest()
{
// If we have a transport and it's a duplicated request, reply inmediatly
transport = unifyTransport(transport) || self.getTransport();
if(transport)
{
var response = responses.get(id, from);
if(response)
return transport.send(response.message);
};
var idAck = (id != undefined) ? id : ack;
var request = new RpcRequest(method, params, idAck, from, transport);
if(self.emit('request', request)) return;
return request;
};
function processResponse(request, error, result)
{
request.callback(error, result);
};
function duplicatedResponse(timeout)
{
console.warn("Response already processed", message);
// Update duplicated responses timeout
clearTimeout(timeout);
storeProcessedResponse(ack, from);
};
// Request, or response with own method
if(method)
{
// Check if it's a response with own method
if(dest == undefined || dest == self.peerID)
{
var request = requests.get(ack, from);
if(request)
{
var responseMethods = request.responseMethods;
if(method == responseMethods.error)
return processResponse(request, params);
if(method == responseMethods.response)
return processResponse(request, null, params);
return processRequest();
}
var processed = processedResponses.get(ack, from);
if(processed)
return duplicatedResponse(processed);
}
// Request
return processRequest();
};
var error = message.error;
var result = message.result;
// Ignore responses not send to us
if(error && error.dest && error.dest != self.peerID) return;
if(result && result.dest && result.dest != self.peerID) return;
// Response
var request = requests.get(ack, from);
if(!request)
{
var processed = processedResponses.get(ack, from);
if(processed)
return duplicatedResponse(processed);
return console.warn("No callback was defined for this message", message);
};
// Process response
processResponse(request, error, result);
};
};
inherits(RpcBuilder, EventEmitter);
RpcBuilder.RpcNotification = RpcNotification;
module.exports = RpcBuilder;
var clients = require('./clients');
var transports = require('./clients/transports');
RpcBuilder.clients = clients;
RpcBuilder.clients.transports = transports;
RpcBuilder.packers = packers;
},{"./Mapper":26,"./clients":27,"./clients/transports":29,"./packers":34,"events":114,"inherits":24}],32:[function(require,module,exports){
/**
* JsonRPC 2.0 packer
*/
/**
* Pack a JsonRPC 2.0 message
*
* @param {Object} message - object to be packaged. It requires to have all the
* fields needed by the JsonRPC 2.0 message that it's going to be generated
*
* @return {String} - the stringified JsonRPC 2.0 message
*/
function pack(message, id)
{
var result =
{
jsonrpc: "2.0"
};
// Request
if(message.method)
{
result.method = message.method;
if(message.params)
result.params = message.params;
// Request is a notification
if(id != undefined)
result.id = id;
}
// Response
else if(id != undefined)
{
if(message.error)
{
if(message.result !== undefined)
throw new TypeError("Both result and error are defined");
result.error = message.error;
}
else if(message.result !== undefined)
result.result = message.result;
else
throw new TypeError("No result or error is defined");
result.id = id;
};
return JSON.stringify(result);
};
/**
* Unpack a JsonRPC 2.0 message
*
* @param {String} message - string with the content of the JsonRPC 2.0 message
*
* @throws {TypeError} - Invalid JsonRPC version
*
* @return {Object} - object filled with the JsonRPC 2.0 message content
*/
function unpack(message)
{
var result = message;
if(typeof message === 'string' || message instanceof String)
result = JSON.parse(message);
// Check if it's a valid message
var version = result.jsonrpc;
if(version !== '2.0')
throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
// Response
if(result.method == undefined)
{
if(result.id == undefined)
throw new TypeError("Invalid message: "+message);
var result_defined = result.result !== undefined;
var error_defined = result.error !== undefined;
// Check only result or error is defined, not both or none
if(result_defined && error_defined)
throw new TypeError("Both result and error are defined: "+message);
if(!result_defined && !error_defined)
throw new TypeError("No result or error is defined: "+message);
result.ack = result.id;
delete result.id;
}
// Return unpacked message
return result;
};
exports.pack = pack;
exports.unpack = unpack;
},{}],33:[function(require,module,exports){
function pack(message)
{
throw new TypeError("Not yet implemented");
};
function unpack(message)
{
throw new TypeError("Not yet implemented");
};
exports.pack = pack;
exports.unpack = unpack;
},{}],34:[function(require,module,exports){
var JsonRPC = require('./JsonRPC');
var XmlRPC = require('./XmlRPC');
exports.JsonRPC = JsonRPC;
exports.XmlRPC = XmlRPC;
},{"./JsonRPC":32,"./XmlRPC":33}],35:[function(require,module,exports){
/**
* Helpers.
*/
var s = 1000
var m = s * 60
var h = m * 60
var d = h * 24
var y = d * 365.25
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @param {String|Number} val
* @param {Object} [options]
* @throws {Error} throw an error if val is not a non-empty string or a number
* @return {String|Number}
* @api public
*/
module.exports = function (val, options) {
options = options || {}
var type = typeof val
if (type === 'string' && val.length > 0) {
return parse(val)
} else if (type === 'number' && isNaN(val) === false) {
return options.long ?
fmtLong(val) :
fmtShort(val)
}
throw new Error('val is not a non-empty string or a valid number. val=' + JSON.stringify(val))
}
/**
* Parse the given `str` and return milliseconds.
*
* @param {String} str
* @return {Number}
* @api private
*/
function parse(str) {
str = String(str)
if (str.length > 10000) {
return
}
var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str)
if (!match) {
return
}
var n = parseFloat(match[1])
var type = (match[2] || 'ms').toLowerCase()
switch (type) {
case 'years':
case 'year':
case 'yrs':
case 'yr':
case 'y':
return n * y
case 'days':
case 'day':
case 'd':
return n * d
case 'hours':
case 'hour':
case 'hrs':
case 'hr':
case 'h':
return n * h
case 'minutes':
case 'minute':
case 'mins':
case 'min':
case 'm':
return n * m
case 'seconds':
case 'second':
case 'secs':
case 'sec':
case 's':
return n * s
case 'milliseconds':
case 'millisecond':
case 'msecs':
case 'msec':
case 'ms':
return n
default:
return undefined
}
}
/**
* Short format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtShort(ms) {
if (ms >= d) {
return Math.round(ms / d) + 'd'
}
if (ms >= h) {
return Math.round(ms / h) + 'h'
}
if (ms >= m) {
return Math.round(ms / m) + 'm'
}
if (ms >= s) {
return Math.round(ms / s) + 's'
}
return ms + 'ms'
}
/**
* Long format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtLong(ms) {
return plural(ms, d, 'day') ||
plural(ms, h, 'hour') ||
plural(ms, m, 'minute') ||
plural(ms, s, 'second') ||
ms + ' ms'
}
/**
* Pluralization helper.
*/
function plural(ms, n, name) {
if (ms < n) {
return
}
if (ms < n * 1.5) {
return Math.floor(ms / n) + ' ' + name
}
return Math.ceil(ms / n) + ' ' + name + 's'
}
},{}],36:[function(require,module,exports){
'use strict';
/**
* Check if we're required to add a port number.
*
* @see https://url.spec.whatwg.org/#default-port
* @param {Number|String} port Port number we need to check
* @param {String} protocol Protocol we need to check against.
* @returns {Boolean} Is it a default port for the given protocol
* @api private
*/
module.exports = function required(port, protocol) {
protocol = protocol.split(':')[0];
port = +port;
if (!port) return false;
switch (protocol) {
case 'http':
case 'ws':
return port !== 80;
case 'https':
case 'wss':
return port !== 443;
case 'ftp':
return port !== 21;
case 'gopher':
return port !== 70;
case 'file':
return false;
}
return port !== 0;
};
},{}],37:[function(require,module,exports){
/* eslint-env node */
'use strict';
// SDP helpers.
var SDPUtils = {};
// Generate an alphanumeric identifier for cname or mids.
// TODO: use UUIDs instead? https://gist.github.com/jed/982883
SDPUtils.generateIdentifier = function() {
return Math.random().toString(36).substr(2, 10);
};
// The RTCP CNAME used by all peerconnections from the same JS.
SDPUtils.localCName = SDPUtils.generateIdentifier();
// Splits SDP into lines, dealing with both CRLF and LF.
SDPUtils.splitLines = function(blob) {
return blob.trim().split('\n').map(function(line) {
return line.trim();
});
};
// Splits SDP into sessionpart and mediasections. Ensures CRLF.
SDPUtils.splitSections = function(blob) {
var parts = blob.split('\nm=');
return parts.map(function(part, index) {
return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
});
};
// Returns lines that start with a certain prefix.
SDPUtils.matchPrefix = function(blob, prefix) {
return SDPUtils.splitLines(blob).filter(function(line) {
return line.indexOf(prefix) === 0;
});
};
// Parses an ICE candidate line. Sample input:
// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
// rport 55996"
SDPUtils.parseCandidate = function(line) {
var parts;
// Parse both variants.
if (line.indexOf('a=candidate:') === 0) {
parts = line.substring(12).split(' ');
} else {
parts = line.substring(10).split(' ');
}
var candidate = {
foundation: parts[0],
component: parseInt(parts[1], 10),
protocol: parts[2].toLowerCase(),
priority: parseInt(parts[3], 10),
ip: parts[4],
port: parseInt(parts[5], 10),
// skip parts[6] == 'typ'
type: parts[7]
};
for (var i = 8; i < parts.length; i += 2) {
switch (parts[i]) {
case 'raddr':
candidate.relatedAddress = parts[i + 1];
break;
case 'rport':
candidate.relatedPort = parseInt(parts[i + 1], 10);
break;
case 'tcptype':
candidate.tcpType = parts[i + 1];
break;
default: // extension handling, in particular ufrag
candidate[parts[i]] = parts[i + 1];
break;
}
}
return candidate;
};
// Translates a candidate object into SDP candidate attribute.
SDPUtils.writeCandidate = function(candidate) {
var sdp = [];
sdp.push(candidate.foundation);
sdp.push(candidate.component);
sdp.push(candidate.protocol.toUpperCase());
sdp.push(candidate.priority);
sdp.push(candidate.ip);
sdp.push(candidate.port);
var type = candidate.type;
sdp.push('typ');
sdp.push(type);
if (type !== 'host' && candidate.relatedAddress &&
candidate.relatedPort) {
sdp.push('raddr');
sdp.push(candidate.relatedAddress); // was: relAddr
sdp.push('rport');
sdp.push(candidate.relatedPort); // was: relPort
}
if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
sdp.push('tcptype');
sdp.push(candidate.tcpType);
}
return 'candidate:' + sdp.join(' ');
};
// Parses an ice-options line, returns an array of option tags.
// a=ice-options:foo bar
SDPUtils.parseIceOptions = function(line) {
return line.substr(14).split(' ');
}
// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
// a=rtpmap:111 opus/48000/2
SDPUtils.parseRtpMap = function(line) {
var parts = line.substr(9).split(' ');
var parsed = {
payloadType: parseInt(parts.shift(), 10) // was: id
};
parts = parts[0].split('/');
parsed.name = parts[0];
parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
// was: channels
parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
return parsed;
};
// Generate an a=rtpmap line from RTCRtpCodecCapability or
// RTCRtpCodecParameters.
SDPUtils.writeRtpMap = function(codec) {
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
(codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
};
// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
SDPUtils.parseExtmap = function(line) {
var parts = line.substr(9).split(' ');
return {
id: parseInt(parts[0], 10),
direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
uri: parts[1]
};
};
// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
// RTCRtpHeaderExtension.
SDPUtils.writeExtmap = function(headerExtension) {
return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
(headerExtension.direction && headerExtension.direction !== 'sendrecv'
? '/' + headerExtension.direction
: '') +
' ' + headerExtension.uri + '\r\n';
};
// Parses an ftmp line, returns dictionary. Sample input:
// a=fmtp:96 vbr=on;cng=on
// Also deals with vbr=on; cng=on
SDPUtils.parseFmtp = function(line) {
var parsed = {};
var kv;
var parts = line.substr(line.indexOf(' ') + 1).split(';');
for (var j = 0; j < parts.length; j++) {
kv = parts[j].trim().split('=');
parsed[kv[0].trim()] = kv[1];
}
return parsed;
};
// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeFmtp = function(codec) {
var line = '';
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.parameters && Object.keys(codec.parameters).length) {
var params = [];
Object.keys(codec.parameters).forEach(function(param) {
params.push(param + '=' + codec.parameters[param]);
});
line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
}
return line;
};
// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
// a=rtcp-fb:98 nack rpsi
SDPUtils.parseRtcpFb = function(line) {
var parts = line.substr(line.indexOf(' ') + 1).split(' ');
return {
type: parts.shift(),
parameter: parts.join(' ')
};
};
// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
SDPUtils.writeRtcpFb = function(codec) {
var lines = '';
var pt = codec.payloadType;
if (codec.preferredPayloadType !== undefined) {
pt = codec.preferredPayloadType;
}
if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
// FIXME: special handling for trr-int?
codec.rtcpFeedback.forEach(function(fb) {
lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
(fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
'\r\n';
});
}
return lines;
};
// Parses an RFC 5576 ssrc media attribute. Sample input:
// a=ssrc:3735928559 cname:something
SDPUtils.parseSsrcMedia = function(line) {
var sp = line.indexOf(' ');
var parts = {
ssrc: parseInt(line.substr(7, sp - 7), 10)
};
var colon = line.indexOf(':', sp);
if (colon > -1) {
parts.attribute = line.substr(sp + 1, colon - sp - 1);
parts.value = line.substr(colon + 1);
} else {
parts.attribute = line.substr(sp + 1);
}
return parts;
};
// Extracts the MID (RFC 5888) from a media section.
// returns the MID or undefined if no mid line was found.
SDPUtils.getMid = function(mediaSection) {
var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
if (mid) {
return mid.substr(6);
}
}
SDPUtils.parseFingerprint = function(line) {
var parts = line.substr(14).split(' ');
return {
algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
value: parts[1]
};
};
// Extracts DTLS parameters from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the fingerprint line as input. See also getIceParameters.
SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
'a=fingerprint:');
// Note: a=setup line is ignored since we use the 'auto' role.
// Note2: 'algorithm' is not case sensitive except in Edge.
return {
role: 'auto',
fingerprints: lines.map(SDPUtils.parseFingerprint)
};
};
// Serializes DTLS parameters to SDP.
SDPUtils.writeDtlsParameters = function(params, setupType) {
var sdp = 'a=setup:' + setupType + '\r\n';
params.fingerprints.forEach(function(fp) {
sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
});
return sdp;
};
// Parses ICE information from SDP media section or sessionpart.
// FIXME: for consistency with other functions this should only
// get the ice-ufrag and ice-pwd lines as input.
SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
var lines = SDPUtils.splitLines(mediaSection);
// Search in session part, too.
lines = lines.concat(SDPUtils.splitLines(sessionpart));
var iceParameters = {
usernameFragment: lines.filter(function(line) {
return line.indexOf('a=ice-ufrag:') === 0;
})[0].substr(12),
password: lines.filter(function(line) {
return line.indexOf('a=ice-pwd:') === 0;
})[0].substr(10)
};
return iceParameters;
};
// Serializes ICE parameters to SDP.
SDPUtils.writeIceParameters = function(params) {
return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
'a=ice-pwd:' + params.password + '\r\n';
};
// Parses the SDP media section and returns RTCRtpParameters.
SDPUtils.parseRtpParameters = function(mediaSection) {
var description = {
codecs: [],
headerExtensions: [],
fecMechanisms: [],
rtcp: []
};
var lines = SDPUtils.splitLines(mediaSection);
var mline = lines[0].split(' ');
for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
var pt = mline[i];
var rtpmapline = SDPUtils.matchPrefix(
mediaSection, 'a=rtpmap:' + pt + ' ')[0];
if (rtpmapline) {
var codec = SDPUtils.parseRtpMap(rtpmapline);
var fmtps = SDPUtils.matchPrefix(
mediaSection, 'a=fmtp:' + pt + ' ');
// Only the first a=fmtp:<pt> is considered.
codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
codec.rtcpFeedback = SDPUtils.matchPrefix(
mediaSection, 'a=rtcp-fb:' + pt + ' ')
.map(SDPUtils.parseRtcpFb);
description.codecs.push(codec);
// parse FEC mechanisms from rtpmap lines.
switch (codec.name.toUpperCase()) {
case 'RED':
case 'ULPFEC':
description.fecMechanisms.push(codec.name.toUpperCase());
break;
default: // only RED and ULPFEC are recognized as FEC mechanisms.
break;
}
}
}
SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
description.headerExtensions.push(SDPUtils.parseExtmap(line));
});
// FIXME: parse rtcp.
return description;
};
// Generates parts of the SDP media section describing the capabilities /
// parameters.
SDPUtils.writeRtpDescription = function(kind, caps) {
var sdp = '';
// Build the mline.
sdp += 'm=' + kind + ' ';
sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
sdp += ' UDP/TLS/RTP/SAVPF ';
sdp += caps.codecs.map(function(codec) {
if (codec.preferredPayloadType !== undefined) {
return codec.preferredPayloadType;
}
return codec.payloadType;
}).join(' ') + '\r\n';
sdp += 'c=IN IP4 0.0.0.0\r\n';
sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
// Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
caps.codecs.forEach(function(codec) {
sdp += SDPUtils.writeRtpMap(codec);
sdp += SDPUtils.writeFmtp(codec);
sdp += SDPUtils.writeRtcpFb(codec);
});
var maxptime = 0;
caps.codecs.forEach(function(codec) {
if (codec.maxptime > maxptime) {
maxptime = codec.maxptime;
}
});
if (maxptime > 0) {
sdp += 'a=maxptime:' + maxptime + '\r\n';
}
sdp += 'a=rtcp-mux\r\n';
caps.headerExtensions.forEach(function(extension) {
sdp += SDPUtils.writeExtmap(extension);
});
// FIXME: write fecMechanisms.
return sdp;
};
// Parses the SDP media section and returns an array of
// RTCRtpEncodingParameters.
SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
var encodingParameters = [];
var description = SDPUtils.parseRtpParameters(mediaSection);
var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
// filter a=ssrc:... cname:, ignore PlanB-msid
var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(parts) {
return parts.attribute === 'cname';
});
var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
var secondarySsrc;
var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
.map(function(line) {
var parts = line.split(' ');
parts.shift();
return parts.map(function(part) {
return parseInt(part, 10);
});
});
if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
secondarySsrc = flows[0][1];
}
description.codecs.forEach(function(codec) {
if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
var encParam = {
ssrc: primarySsrc,
codecPayloadType: parseInt(codec.parameters.apt, 10),
rtx: {
ssrc: secondarySsrc
}
};
encodingParameters.push(encParam);
if (hasRed) {
encParam = JSON.parse(JSON.stringify(encParam));
encParam.fec = {
ssrc: secondarySsrc,
mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
};
encodingParameters.push(encParam);
}
}
});
if (encodingParameters.length === 0 && primarySsrc) {
encodingParameters.push({
ssrc: primarySsrc
});
}
// we support both b=AS and b=TIAS but interpret AS as TIAS.
var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
if (bandwidth.length) {
if (bandwidth[0].indexOf('b=TIAS:') === 0) {
bandwidth = parseInt(bandwidth[0].substr(7), 10);
} else if (bandwidth[0].indexOf('b=AS:') === 0) {
bandwidth = parseInt(bandwidth[0].substr(5), 10);
}
encodingParameters.forEach(function(params) {
params.maxBitrate = bandwidth;
});
}
return encodingParameters;
};
// parses http://draft.ortc.org/#rtcrtcpparameters*
SDPUtils.parseRtcpParameters = function(mediaSection) {
var rtcpParameters = {};
var cname;
// Gets the first SSRC. Note that with RTX there might be multiple
// SSRCs.
var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(obj) {
return obj.attribute === 'cname';
})[0];
if (remoteSsrc) {
rtcpParameters.cname = remoteSsrc.value;
rtcpParameters.ssrc = remoteSsrc.ssrc;
}
// Edge uses the compound attribute instead of reducedSize
// compound is !reducedSize
var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
rtcpParameters.reducedSize = rsize.length > 0;
rtcpParameters.compound = rsize.length === 0;
// parses the rtcp-mux attrіbute.
// Note that Edge does not support unmuxed RTCP.
var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
rtcpParameters.mux = mux.length > 0;
return rtcpParameters;
};
// parses either a=msid: or a=ssrc:... msid lines an returns
// the id of the MediaStream and MediaStreamTrack.
SDPUtils.parseMsid = function(mediaSection) {
var parts;
var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
if (spec.length === 1) {
parts = spec[0].substr(7).split(' ');
return {stream: parts[0], track: parts[1]};
}
var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
.map(function(line) {
return SDPUtils.parseSsrcMedia(line);
})
.filter(function(parts) {
return parts.attribute === 'msid';
});
if (planB.length > 0) {
parts = planB[0].value.split(' ');
return {stream: parts[0], track: parts[1]};
}
};
SDPUtils.writeSessionBoilerplate = function() {
// FIXME: sess-id should be an NTP timestamp.
return 'v=0\r\n' +
'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
's=-\r\n' +
't=0 0\r\n';
};
SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
// Map ICE parameters (ufrag, pwd) to SDP.
sdp += SDPUtils.writeIceParameters(
transceiver.iceGatherer.getLocalParameters());
// Map DTLS parameters to SDP.
sdp += SDPUtils.writeDtlsParameters(
transceiver.dtlsTransport.getLocalParameters(),
type === 'offer' ? 'actpass' : 'active');
sdp += 'a=mid:' + transceiver.mid + '\r\n';
if (transceiver.direction) {
sdp += 'a=' + transceiver.direction + '\r\n';
} else if (transceiver.rtpSender && transceiver.rtpReceiver) {
sdp += 'a=sendrecv\r\n';
} else if (transceiver.rtpSender) {
sdp += 'a=sendonly\r\n';
} else if (transceiver.rtpReceiver) {
sdp += 'a=recvonly\r\n';
} else {
sdp += 'a=inactive\r\n';
}
if (transceiver.rtpSender) {
// spec.
var msid = 'msid:' + stream.id + ' ' +
transceiver.rtpSender.track.id + '\r\n';
sdp += 'a=' + msid;
// for Chrome.
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
' ' + msid;
if (transceiver.sendEncodingParameters[0].rtx) {
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
' ' + msid;
sdp += 'a=ssrc-group:FID ' +
transceiver.sendEncodingParameters[0].ssrc + ' ' +
transceiver.sendEncodingParameters[0].rtx.ssrc +
'\r\n';
}
}
// FIXME: this should be written by writeRtpDescription.
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
' cname:' + SDPUtils.localCName + '\r\n';
if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
' cname:' + SDPUtils.localCName + '\r\n';
}
return sdp;
};
// Gets the direction from the mediaSection or the sessionpart.
SDPUtils.getDirection = function(mediaSection, sessionpart) {
// Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
var lines = SDPUtils.splitLines(mediaSection);
for (var i = 0; i < lines.length; i++) {
switch (lines[i]) {
case 'a=sendrecv':
case 'a=sendonly':
case 'a=recvonly':
case 'a=inactive':
return lines[i].substr(2);
default:
// FIXME: What should happen here?
}
}
if (sessionpart) {
return SDPUtils.getDirection(sessionpart);
}
return 'sendrecv';
};
SDPUtils.getKind = function(mediaSection) {
var lines = SDPUtils.splitLines(mediaSection);
var mline = lines[0].split(' ');
return mline[0].substr(2);
};
SDPUtils.isRejected = function(mediaSection) {
return mediaSection.split(' ', 2)[1] === '0';
};
// Expose public methods.
module.exports = SDPUtils;
},{}],38:[function(require,module,exports){
(function (global){
'use strict';
var transportList = require('./transport-list');
module.exports = require('./main')(transportList);
// TODO can't get rid of this until all servers do
if ('_sockjs_onload' in global) {
setTimeout(global._sockjs_onload, 1);
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./main":51,"./transport-list":53}],39:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, Event = require('./event')
;
function CloseEvent() {
Event.call(this);
this.initEvent('close', false, false);
this.wasClean = false;
this.code = 0;
this.reason = '';
}
inherits(CloseEvent, Event);
module.exports = CloseEvent;
},{"./event":41,"inherits":24}],40:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, EventTarget = require('./eventtarget')
;
function EventEmitter() {
EventTarget.call(this);
}
inherits(EventEmitter, EventTarget);
EventEmitter.prototype.removeAllListeners = function(type) {
if (type) {
delete this._listeners[type];
} else {
this._listeners = {};
}
};
EventEmitter.prototype.once = function(type, listener) {
var self = this
, fired = false;
function g() {
self.removeListener(type, g);
if (!fired) {
fired = true;
listener.apply(this, arguments);
}
}
this.on(type, g);
};
EventEmitter.prototype.emit = function() {
var type = arguments[0];
var listeners = this._listeners[type];
if (!listeners) {
return;
}
// equivalent of Array.prototype.slice.call(arguments, 1);
var l = arguments.length;
var args = new Array(l - 1);
for (var ai = 1; ai < l; ai++) {
args[ai - 1] = arguments[ai];
}
for (var i = 0; i < listeners.length; i++) {
listeners[i].apply(this, args);
}
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener = EventTarget.prototype.addEventListener;
EventEmitter.prototype.removeListener = EventTarget.prototype.removeEventListener;
module.exports.EventEmitter = EventEmitter;
},{"./eventtarget":42,"inherits":24}],41:[function(require,module,exports){
'use strict';
function Event(eventType) {
this.type = eventType;
}
Event.prototype.initEvent = function(eventType, canBubble, cancelable) {
this.type = eventType;
this.bubbles = canBubble;
this.cancelable = cancelable;
this.timeStamp = +new Date();
return this;
};
Event.prototype.stopPropagation = function() {};
Event.prototype.preventDefault = function() {};
Event.CAPTURING_PHASE = 1;
Event.AT_TARGET = 2;
Event.BUBBLING_PHASE = 3;
module.exports = Event;
},{}],42:[function(require,module,exports){
'use strict';
/* Simplified implementation of DOM2 EventTarget.
* http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
*/
function EventTarget() {
this._listeners = {};
}
EventTarget.prototype.addEventListener = function(eventType, listener) {
if (!(eventType in this._listeners)) {
this._listeners[eventType] = [];
}
var arr = this._listeners[eventType];
// #4
if (arr.indexOf(listener) === -1) {
// Make a copy so as not to interfere with a current dispatchEvent.
arr = arr.concat([listener]);
}
this._listeners[eventType] = arr;
};
EventTarget.prototype.removeEventListener = function(eventType, listener) {
var arr = this._listeners[eventType];
if (!arr) {
return;
}
var idx = arr.indexOf(listener);
if (idx !== -1) {
if (arr.length > 1) {
// Make a copy so as not to interfere with a current dispatchEvent.
this._listeners[eventType] = arr.slice(0, idx).concat(arr.slice(idx + 1));
} else {
delete this._listeners[eventType];
}
return;
}
};
EventTarget.prototype.dispatchEvent = function() {
var event = arguments[0];
var t = event.type;
// equivalent of Array.prototype.slice.call(arguments, 0);
var args = arguments.length === 1 ? [event] : Array.apply(null, arguments);
// TODO: This doesn't match the real behavior; per spec, onfoo get
// their place in line from the /first/ time they're set from
// non-null. Although WebKit bumps it to the end every time it's
// set.
if (this['on' + t]) {
this['on' + t].apply(this, args);
}
if (t in this._listeners) {
// Grab a reference to the listeners list. removeEventListener may alter the list.
var listeners = this._listeners[t];
for (var i = 0; i < listeners.length; i++) {
listeners[i].apply(this, args);
}
}
};
module.exports = EventTarget;
},{}],43:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, Event = require('./event')
;
function TransportMessageEvent(data) {
Event.call(this);
this.initEvent('message', false, false);
this.data = data;
}
inherits(TransportMessageEvent, Event);
module.exports = TransportMessageEvent;
},{"./event":41,"inherits":24}],44:[function(require,module,exports){
'use strict';
var JSON3 = require('json3')
, iframeUtils = require('./utils/iframe')
;
function FacadeJS(transport) {
this._transport = transport;
transport.on('message', this._transportMessage.bind(this));
transport.on('close', this._transportClose.bind(this));
}
FacadeJS.prototype._transportClose = function(code, reason) {
iframeUtils.postMessage('c', JSON3.stringify([code, reason]));
};
FacadeJS.prototype._transportMessage = function(frame) {
iframeUtils.postMessage('t', frame);
};
FacadeJS.prototype._send = function(data) {
this._transport.send(data);
};
FacadeJS.prototype._close = function() {
this._transport.close();
this._transport.removeAllListeners();
};
module.exports = FacadeJS;
},{"./utils/iframe":84,"json3":25}],45:[function(require,module,exports){
(function (process){
'use strict';
var urlUtils = require('./utils/url')
, eventUtils = require('./utils/event')
, JSON3 = require('json3')
, FacadeJS = require('./facade')
, InfoIframeReceiver = require('./info-iframe-receiver')
, iframeUtils = require('./utils/iframe')
, loc = require('./location')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:iframe-bootstrap');
}
module.exports = function(SockJS, availableTransports) {
var transportMap = {};
availableTransports.forEach(function(at) {
if (at.facadeTransport) {
transportMap[at.facadeTransport.transportName] = at.facadeTransport;
}
});
// hard-coded for the info iframe
// TODO see if we can make this more dynamic
transportMap[InfoIframeReceiver.transportName] = InfoIframeReceiver;
var parentOrigin;
/* eslint-disable camelcase */
SockJS.bootstrap_iframe = function() {
/* eslint-enable camelcase */
var facade;
iframeUtils.currentWindowId = loc.hash.slice(1);
var onMessage = function(e) {
if (e.source !== parent) {
return;
}
if (typeof parentOrigin === 'undefined') {
parentOrigin = e.origin;
}
if (e.origin !== parentOrigin) {
return;
}
var iframeMessage;
try {
iframeMessage = JSON3.parse(e.data);
} catch (ignored) {
debug('bad json', e.data);
return;
}
if (iframeMessage.windowId !== iframeUtils.currentWindowId) {
return;
}
switch (iframeMessage.type) {
case 's':
var p;
try {
p = JSON3.parse(iframeMessage.data);
} catch (ignored) {
debug('bad json', iframeMessage.data);
break;
}
var version = p[0];
var transport = p[1];
var transUrl = p[2];
var baseUrl = p[3];
debug(version, transport, transUrl, baseUrl);
// change this to semver logic
if (version !== SockJS.version) {
throw new Error('Incompatible SockJS! Main site uses:' +
' "' + version + '", the iframe:' +
' "' + SockJS.version + '".');
}
if (!urlUtils.isOriginEqual(transUrl, loc.href) ||
!urlUtils.isOriginEqual(baseUrl, loc.href)) {
throw new Error('Can\'t connect to different domain from within an ' +
'iframe. (' + loc.href + ', ' + transUrl + ', ' + baseUrl + ')');
}
facade = new FacadeJS(new transportMap[transport](transUrl, baseUrl));
break;
case 'm':
facade._send(iframeMessage.data);
break;
case 'c':
if (facade) {
facade._close();
}
facade = null;
break;
}
};
eventUtils.attachEvent('message', onMessage);
// Start
iframeUtils.postMessage('s');
};
};
}).call(this,require('_process'))
},{"./facade":44,"./info-iframe-receiver":47,"./location":50,"./utils/event":83,"./utils/iframe":84,"./utils/url":89,"_process":115,"debug":22,"json3":25}],46:[function(require,module,exports){
(function (process){
'use strict';
var EventEmitter = require('events').EventEmitter
, inherits = require('inherits')
, JSON3 = require('json3')
, objectUtils = require('./utils/object')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:info-ajax');
}
function InfoAjax(url, AjaxObject) {
EventEmitter.call(this);
var self = this;
var t0 = +new Date();
this.xo = new AjaxObject('GET', url);
this.xo.once('finish', function(status, text) {
var info, rtt;
if (status === 200) {
rtt = (+new Date()) - t0;
if (text) {
try {
info = JSON3.parse(text);
} catch (e) {
debug('bad json', text);
}
}
if (!objectUtils.isObject(info)) {
info = {};
}
}
self.emit('finish', info, rtt);
self.removeAllListeners();
});
}
inherits(InfoAjax, EventEmitter);
InfoAjax.prototype.close = function() {
this.removeAllListeners();
this.xo.close();
};
module.exports = InfoAjax;
}).call(this,require('_process'))
},{"./utils/object":86,"_process":115,"debug":22,"events":40,"inherits":24,"json3":25}],47:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, EventEmitter = require('events').EventEmitter
, JSON3 = require('json3')
, XHRLocalObject = require('./transport/sender/xhr-local')
, InfoAjax = require('./info-ajax')
;
function InfoReceiverIframe(transUrl) {
var self = this;
EventEmitter.call(this);
this.ir = new InfoAjax(transUrl, XHRLocalObject);
this.ir.once('finish', function(info, rtt) {
self.ir = null;
self.emit('message', JSON3.stringify([info, rtt]));
});
}
inherits(InfoReceiverIframe, EventEmitter);
InfoReceiverIframe.transportName = 'iframe-info-receiver';
InfoReceiverIframe.prototype.close = function() {
if (this.ir) {
this.ir.close();
this.ir = null;
}
this.removeAllListeners();
};
module.exports = InfoReceiverIframe;
},{"./info-ajax":46,"./transport/sender/xhr-local":74,"events":40,"inherits":24,"json3":25}],48:[function(require,module,exports){
(function (process,global){
'use strict';
var EventEmitter = require('events').EventEmitter
, inherits = require('inherits')
, JSON3 = require('json3')
, utils = require('./utils/event')
, IframeTransport = require('./transport/iframe')
, InfoReceiverIframe = require('./info-iframe-receiver')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:info-iframe');
}
function InfoIframe(baseUrl, url) {
var self = this;
EventEmitter.call(this);
var go = function() {
var ifr = self.ifr = new IframeTransport(InfoReceiverIframe.transportName, url, baseUrl);
ifr.once('message', function(msg) {
if (msg) {
var d;
try {
d = JSON3.parse(msg);
} catch (e) {
debug('bad json', msg);
self.emit('finish');
self.close();
return;
}
var info = d[0], rtt = d[1];
self.emit('finish', info, rtt);
}
self.close();
});
ifr.once('close', function() {
self.emit('finish');
self.close();
});
};
// TODO this seems the same as the 'needBody' from transports
if (!global.document.body) {
utils.attachEvent('load', go);
} else {
go();
}
}
inherits(InfoIframe, EventEmitter);
InfoIframe.enabled = function() {
return IframeTransport.enabled();
};
InfoIframe.prototype.close = function() {
if (this.ifr) {
this.ifr.close();
}
this.removeAllListeners();
this.ifr = null;
};
module.exports = InfoIframe;
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./info-iframe-receiver":47,"./transport/iframe":59,"./utils/event":83,"_process":115,"debug":22,"events":40,"inherits":24,"json3":25}],49:[function(require,module,exports){
(function (process){
'use strict';
var EventEmitter = require('events').EventEmitter
, inherits = require('inherits')
, urlUtils = require('./utils/url')
, XDR = require('./transport/sender/xdr')
, XHRCors = require('./transport/sender/xhr-cors')
, XHRLocal = require('./transport/sender/xhr-local')
, XHRFake = require('./transport/sender/xhr-fake')
, InfoIframe = require('./info-iframe')
, InfoAjax = require('./info-ajax')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:info-receiver');
}
function InfoReceiver(baseUrl, urlInfo) {
debug(baseUrl);
var self = this;
EventEmitter.call(this);
setTimeout(function() {
self.doXhr(baseUrl, urlInfo);
}, 0);
}
inherits(InfoReceiver, EventEmitter);
// TODO this is currently ignoring the list of available transports and the whitelist
InfoReceiver._getReceiver = function(baseUrl, url, urlInfo) {
// determine method of CORS support (if needed)
if (urlInfo.sameOrigin) {
return new InfoAjax(url, XHRLocal);
}
if (XHRCors.enabled) {
return new InfoAjax(url, XHRCors);
}
if (XDR.enabled && urlInfo.sameScheme) {
return new InfoAjax(url, XDR);
}
if (InfoIframe.enabled()) {
return new InfoIframe(baseUrl, url);
}
return new InfoAjax(url, XHRFake);
};
InfoReceiver.prototype.doXhr = function(baseUrl, urlInfo) {
var self = this
, url = urlUtils.addPath(baseUrl, '/info')
;
debug('doXhr', url);
this.xo = InfoReceiver._getReceiver(baseUrl, url, urlInfo);
this.timeoutRef = setTimeout(function() {
debug('timeout');
self._cleanup(false);
self.emit('finish');
}, InfoReceiver.timeout);
this.xo.once('finish', function(info, rtt) {
debug('finish', info, rtt);
self._cleanup(true);
self.emit('finish', info, rtt);
});
};
InfoReceiver.prototype._cleanup = function(wasClean) {
debug('_cleanup');
clearTimeout(this.timeoutRef);
this.timeoutRef = null;
if (!wasClean && this.xo) {
this.xo.close();
}
this.xo = null;
};
InfoReceiver.prototype.close = function() {
debug('close');
this.removeAllListeners();
this._cleanup(false);
};
InfoReceiver.timeout = 8000;
module.exports = InfoReceiver;
}).call(this,require('_process'))
},{"./info-ajax":46,"./info-iframe":48,"./transport/sender/xdr":71,"./transport/sender/xhr-cors":72,"./transport/sender/xhr-fake":73,"./transport/sender/xhr-local":74,"./utils/url":89,"_process":115,"debug":22,"events":40,"inherits":24}],50:[function(require,module,exports){
(function (global){
'use strict';
module.exports = global.location || {
origin: 'http://localhost:80'
, protocol: 'http'
, host: 'localhost'
, port: 80
, href: 'http://localhost/'
, hash: ''
};
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],51:[function(require,module,exports){
(function (process,global){
'use strict';
require('./shims');
var URL = require('url-parse')
, inherits = require('inherits')
, JSON3 = require('json3')
, random = require('./utils/random')
, escape = require('./utils/escape')
, urlUtils = require('./utils/url')
, eventUtils = require('./utils/event')
, transport = require('./utils/transport')
, objectUtils = require('./utils/object')
, browser = require('./utils/browser')
, log = require('./utils/log')
, Event = require('./event/event')
, EventTarget = require('./event/eventtarget')
, loc = require('./location')
, CloseEvent = require('./event/close')
, TransportMessageEvent = require('./event/trans-message')
, InfoReceiver = require('./info-receiver')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:main');
}
var transports;
// follow constructor steps defined at http://dev.w3.org/html5/websockets/#the-websocket-interface
function SockJS(url, protocols, options) {
if (!(this instanceof SockJS)) {
return new SockJS(url, protocols, options);
}
if (arguments.length < 1) {
throw new TypeError("Failed to construct 'SockJS: 1 argument required, but only 0 present");
}
EventTarget.call(this);
this.readyState = SockJS.CONNECTING;
this.extensions = '';
this.protocol = '';
// non-standard extension
options = options || {};
if (options.protocols_whitelist) {
log.warn("'protocols_whitelist' is DEPRECATED. Use 'transports' instead.");
}
this._transportsWhitelist = options.transports;
this._transportOptions = options.transportOptions || {};
var sessionId = options.sessionId || 8;
if (typeof sessionId === 'function') {
this._generateSessionId = sessionId;
} else if (typeof sessionId === 'number') {
this._generateSessionId = function() {
return random.string(sessionId);
};
} else {
throw new TypeError('If sessionId is used in the options, it needs to be a number or a function.');
}
this._server = options.server || random.numberString(1000);
// Step 1 of WS spec - parse and validate the url. Issue #8
var parsedUrl = new URL(url);
if (!parsedUrl.host || !parsedUrl.protocol) {
throw new SyntaxError("The URL '" + url + "' is invalid");
} else if (parsedUrl.hash) {
throw new SyntaxError('The URL must not contain a fragment');
} else if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
throw new SyntaxError("The URL's scheme must be either 'http:' or 'https:'. '" + parsedUrl.protocol + "' is not allowed.");
}
var secure = parsedUrl.protocol === 'https:';
// Step 2 - don't allow secure origin with an insecure protocol
if (loc.protocol === 'https' && !secure) {
throw new Error('SecurityError: An insecure SockJS connection may not be initiated from a page loaded over HTTPS');
}
// Step 3 - check port access - no need here
// Step 4 - parse protocols argument
if (!protocols) {
protocols = [];
} else if (!Array.isArray(protocols)) {
protocols = [protocols];
}
// Step 5 - check protocols argument
var sortedProtocols = protocols.sort();
sortedProtocols.forEach(function(proto, i) {
if (!proto) {
throw new SyntaxError("The protocols entry '" + proto + "' is invalid.");
}
if (i < (sortedProtocols.length - 1) && proto === sortedProtocols[i + 1]) {
throw new SyntaxError("The protocols entry '" + proto + "' is duplicated.");
}
});
// Step 6 - convert origin
var o = urlUtils.getOrigin(loc.href);
this._origin = o ? o.toLowerCase() : null;
// remove the trailing slash
parsedUrl.set('pathname', parsedUrl.pathname.replace(/\/+$/, ''));
// store the sanitized url
this.url = parsedUrl.href;
debug('using url', this.url);
// Step 7 - start connection in background
// obtain server info
// http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-26
this._urlInfo = {
nullOrigin: !browser.hasDomain()
, sameOrigin: urlUtils.isOriginEqual(this.url, loc.href)
, sameScheme: urlUtils.isSchemeEqual(this.url, loc.href)
};
this._ir = new InfoReceiver(this.url, this._urlInfo);
this._ir.once('finish', this._receiveInfo.bind(this));
}
inherits(SockJS, EventTarget);
function userSetCode(code) {
return code === 1000 || (code >= 3000 && code <= 4999);
}
SockJS.prototype.close = function(code, reason) {
// Step 1
if (code && !userSetCode(code)) {
throw new Error('InvalidAccessError: Invalid code');
}
// Step 2.4 states the max is 123 bytes, but we are just checking length
if (reason && reason.length > 123) {
throw new SyntaxError('reason argument has an invalid length');
}
// Step 3.1
if (this.readyState === SockJS.CLOSING || this.readyState === SockJS.CLOSED) {
return;
}
// TODO look at docs to determine how to set this
var wasClean = true;
this._close(code || 1000, reason || 'Normal closure', wasClean);
};
SockJS.prototype.send = function(data) {
// #13 - convert anything non-string to string
// TODO this currently turns objects into [object Object]
if (typeof data !== 'string') {
data = '' + data;
}
if (this.readyState === SockJS.CONNECTING) {
throw new Error('InvalidStateError: The connection has not been established yet');
}
if (this.readyState !== SockJS.OPEN) {
return;
}
this._transport.send(escape.quote(data));
};
SockJS.version = require('./version');
SockJS.CONNECTING = 0;
SockJS.OPEN = 1;
SockJS.CLOSING = 2;
SockJS.CLOSED = 3;
SockJS.prototype._receiveInfo = function(info, rtt) {
debug('_receiveInfo', rtt);
this._ir = null;
if (!info) {
this._close(1002, 'Cannot connect to server');
return;
}
// establish a round-trip timeout (RTO) based on the
// round-trip time (RTT)
this._rto = this.countRTO(rtt);
// allow server to override url used for the actual transport
this._transUrl = info.base_url ? info.base_url : this.url;
info = objectUtils.extend(info, this._urlInfo);
debug('info', info);
// determine list of desired and supported transports
var enabledTransports = transports.filterToEnabled(this._transportsWhitelist, info);
this._transports = enabledTransports.main;
debug(this._transports.length + ' enabled transports');
this._connect();
};
SockJS.prototype._connect = function() {
for (var Transport = this._transports.shift(); Transport; Transport = this._transports.shift()) {
debug('attempt', Transport.transportName);
if (Transport.needBody) {
if (!global.document.body ||
(typeof global.document.readyState !== 'undefined' &&
global.document.readyState !== 'complete' &&
global.document.readyState !== 'interactive')) {
debug('waiting for body');
this._transports.unshift(Transport);
eventUtils.attachEvent('load', this._connect.bind(this));
return;
}
}
// calculate timeout based on RTO and round trips. Default to 5s
var timeoutMs = (this._rto * Transport.roundTrips) || 5000;
this._transportTimeoutId = setTimeout(this._transportTimeout.bind(this), timeoutMs);
debug('using timeout', timeoutMs);
var transportUrl = urlUtils.addPath(this._transUrl, '/' + this._server + '/' + this._generateSessionId());
var options = this._transportOptions[Transport.transportName];
debug('transport url', transportUrl);
var transportObj = new Transport(transportUrl, this._transUrl, options);
transportObj.on('message', this._transportMessage.bind(this));
transportObj.once('close', this._transportClose.bind(this));
transportObj.transportName = Transport.transportName;
this._transport = transportObj;
return;
}
this._close(2000, 'All transports failed', false);
};
SockJS.prototype._transportTimeout = function() {
debug('_transportTimeout');
if (this.readyState === SockJS.CONNECTING) {
this._transportClose(2007, 'Transport timed out');
}
};
SockJS.prototype._transportMessage = function(msg) {
debug('_transportMessage', msg);
var self = this
, type = msg.slice(0, 1)
, content = msg.slice(1)
, payload
;
// first check for messages that don't need a payload
switch (type) {
case 'o':
this._open();
return;
case 'h':
this.dispatchEvent(new Event('heartbeat'));
debug('heartbeat', this.transport);
return;
}
if (content) {
try {
payload = JSON3.parse(content);
} catch (e) {
debug('bad json', content);
}
}
if (typeof payload === 'undefined') {
debug('empty payload', content);
return;
}
switch (type) {
case 'a':
if (Array.isArray(payload)) {
payload.forEach(function(p) {
debug('message', self.transport, p);
self.dispatchEvent(new TransportMessageEvent(p));
});
}
break;
case 'm':
debug('message', this.transport, payload);
this.dispatchEvent(new TransportMessageEvent(payload));
break;
case 'c':
if (Array.isArray(payload) && payload.length === 2) {
this._close(payload[0], payload[1], true);
}
break;
}
};
SockJS.prototype._transportClose = function(code, reason) {
debug('_transportClose', this.transport, code, reason);
if (this._transport) {
this._transport.removeAllListeners();
this._transport = null;
this.transport = null;
}
if (!userSetCode(code) && code !== 2000 && this.readyState === SockJS.CONNECTING) {
this._connect();
return;
}
this._close(code, reason);
};
SockJS.prototype._open = function() {
debug('_open', this._transport.transportName, this.readyState);
if (this.readyState === SockJS.CONNECTING) {
if (this._transportTimeoutId) {
clearTimeout(this._transportTimeoutId);
this._transportTimeoutId = null;
}
this.readyState = SockJS.OPEN;
this.transport = this._transport.transportName;
this.dispatchEvent(new Event('open'));
debug('connected', this.transport);
} else {
// The server might have been restarted, and lost track of our
// connection.
this._close(1006, 'Server lost session');
}
};
SockJS.prototype._close = function(code, reason, wasClean) {
debug('_close', this.transport, code, reason, wasClean, this.readyState);
var forceFail = false;
if (this._ir) {
forceFail = true;
this._ir.close();
this._ir = null;
}
if (this._transport) {
this._transport.close();
this._transport = null;
this.transport = null;
}
if (this.readyState === SockJS.CLOSED) {
throw new Error('InvalidStateError: SockJS has already been closed');
}
this.readyState = SockJS.CLOSING;
setTimeout(function() {
this.readyState = SockJS.CLOSED;
if (forceFail) {
this.dispatchEvent(new Event('error'));
}
var e = new CloseEvent('close');
e.wasClean = wasClean || false;
e.code = code || 1000;
e.reason = reason;
this.dispatchEvent(e);
this.onmessage = this.onclose = this.onerror = null;
debug('disconnected');
}.bind(this), 0);
};
// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/
// and RFC 2988.
SockJS.prototype.countRTO = function(rtt) {
// In a local environment, when using IE8/9 and the `jsonp-polling`
// transport the time needed to establish a connection (the time that pass
// from the opening of the transport to the call of `_dispatchOpen`) is
// around 200msec (the lower bound used in the article above) and this
// causes spurious timeouts. For this reason we calculate a value slightly
// larger than that used in the article.
if (rtt > 100) {
return 4 * rtt; // rto > 400msec
}
return 300 + rtt; // 300msec < rto <= 400msec
};
module.exports = function(availableTransports) {
transports = transport(availableTransports);
require('./iframe-bootstrap')(SockJS, availableTransports);
return SockJS;
};
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./event/close":39,"./event/event":41,"./event/eventtarget":42,"./event/trans-message":43,"./iframe-bootstrap":45,"./info-receiver":49,"./location":50,"./shims":52,"./utils/browser":81,"./utils/escape":82,"./utils/event":83,"./utils/log":85,"./utils/object":86,"./utils/random":87,"./utils/transport":88,"./utils/url":89,"./version":90,"_process":115,"debug":22,"inherits":24,"json3":25,"url-parse":91}],52:[function(require,module,exports){
/* eslint-disable */
/* jscs: disable */
'use strict';
// pulled specific shims from https://github.com/es-shims/es5-shim
var ArrayPrototype = Array.prototype;
var ObjectPrototype = Object.prototype;
var FunctionPrototype = Function.prototype;
var StringPrototype = String.prototype;
var array_slice = ArrayPrototype.slice;
var _toString = ObjectPrototype.toString;
var isFunction = function (val) {
return ObjectPrototype.toString.call(val) === '[object Function]';
};
var isArray = function isArray(obj) {
return _toString.call(obj) === '[object Array]';
};
var isString = function isString(obj) {
return _toString.call(obj) === '[object String]';
};
var supportsDescriptors = Object.defineProperty && (function () {
try {
Object.defineProperty({}, 'x', {});
return true;
} catch (e) { /* this is ES3 */
return false;
}
}());
// Define configurable, writable and non-enumerable props
// if they don't exist.
var defineProperty;
if (supportsDescriptors) {
defineProperty = function (object, name, method, forceAssign) {
if (!forceAssign && (name in object)) { return; }
Object.defineProperty(object, name, {
configurable: true,
enumerable: false,
writable: true,
value: method
});
};
} else {
defineProperty = function (object, name, method, forceAssign) {
if (!forceAssign && (name in object)) { return; }
object[name] = method;
};
}
var defineProperties = function (object, map, forceAssign) {
for (var name in map) {
if (ObjectPrototype.hasOwnProperty.call(map, name)) {
defineProperty(object, name, map[name], forceAssign);
}
}
};
var toObject = function (o) {
if (o == null) { // this matches both null and undefined
throw new TypeError("can't convert " + o + ' to object');
}
return Object(o);
};
//
// Util
// ======
//
// ES5 9.4
// http://es5.github.com/#x9.4
// http://jsperf.com/to-integer
function toInteger(num) {
var n = +num;
if (n !== n) { // isNaN
n = 0;
} else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
return n;
}
function ToUint32(x) {
return x >>> 0;
}
//
// Function
// ========
//
// ES-5 15.3.4.5
// http://es5.github.com/#x15.3.4.5
function Empty() {}
defineProperties(FunctionPrototype, {
bind: function bind(that) { // .length is 1
// 1. Let Target be the this value.
var target = this;
// 2. If IsCallable(Target) is false, throw a TypeError exception.
if (!isFunction(target)) {
throw new TypeError('Function.prototype.bind called on incompatible ' + target);
}
// 3. Let A be a new (possibly empty) internal list of all of the
// argument values provided after thisArg (arg1, arg2 etc), in order.
// XXX slicedArgs will stand in for "A" if used
var args = array_slice.call(arguments, 1); // for normal call
// 4. Let F be a new native ECMAScript object.
// 11. Set the [[Prototype]] internal property of F to the standard
// built-in Function prototype object as specified in 15.3.3.1.
// 12. Set the [[Call]] internal property of F as described in
// 15.3.4.5.1.
// 13. Set the [[Construct]] internal property of F as described in
// 15.3.4.5.2.
// 14. Set the [[HasInstance]] internal property of F as described in
// 15.3.4.5.3.
var binder = function () {
if (this instanceof bound) {
// 15.3.4.5.2 [[Construct]]
// When the [[Construct]] internal method of a function object,
// F that was created using the bind function is called with a
// list of arguments ExtraArgs, the following steps are taken:
// 1. Let target be the value of F's [[TargetFunction]]
// internal property.
// 2. If target has no [[Construct]] internal method, a
// TypeError exception is thrown.
// 3. Let boundArgs be the value of F's [[BoundArgs]] internal
// property.
// 4. Let args be a new list containing the same values as the
// list boundArgs in the same order followed by the same
// values as the list ExtraArgs in the same order.
// 5. Return the result of calling the [[Construct]] internal
// method of target providing args as the arguments.
var result = target.apply(
this,
args.concat(array_slice.call(arguments))
);
if (Object(result) === result) {
return result;
}
return this;
} else {
// 15.3.4.5.1 [[Call]]
// When the [[Call]] internal method of a function object, F,
// which was created using the bind function is called with a
// this value and a list of arguments ExtraArgs, the following
// steps are taken:
// 1. Let boundArgs be the value of F's [[BoundArgs]] internal
// property.
// 2. Let boundThis be the value of F's [[BoundThis]] internal
// property.
// 3. Let target be the value of F's [[TargetFunction]] internal
// property.
// 4. Let args be a new list containing the same values as the
// list boundArgs in the same order followed by the same
// values as the list ExtraArgs in the same order.
// 5. Return the result of calling the [[Call]] internal method
// of target providing boundThis as the this value and
// providing args as the arguments.
// equiv: target.call(this, ...boundArgs, ...args)
return target.apply(
that,
args.concat(array_slice.call(arguments))
);
}
};
// 15. If the [[Class]] internal property of Target is "Function", then
// a. Let L be the length property of Target minus the length of A.
// b. Set the length own property of F to either 0 or L, whichever is
// larger.
// 16. Else set the length own property of F to 0.
var boundLength = Math.max(0, target.length - args.length);
// 17. Set the attributes of the length own property of F to the values
// specified in 15.3.5.1.
var boundArgs = [];
for (var i = 0; i < boundLength; i++) {
boundArgs.push('$' + i);
}
// XXX Build a dynamic function with desired amount of arguments is the only
// way to set the length property of a function.
// In environments where Content Security Policies enabled (Chrome extensions,
// for ex.) all use of eval or Function costructor throws an exception.
// However in all of these environments Function.prototype.bind exists
// and so this code will never be executed.
var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
if (target.prototype) {
Empty.prototype = target.prototype;
bound.prototype = new Empty();
// Clean up dangling references.
Empty.prototype = null;
}
// TODO
// 18. Set the [[Extensible]] internal property of F to true.
// TODO
// 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
// 20. Call the [[DefineOwnProperty]] internal method of F with
// arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
// thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
// false.
// 21. Call the [[DefineOwnProperty]] internal method of F with
// arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
// [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
// and false.
// TODO
// NOTE Function objects created using Function.prototype.bind do not
// have a prototype property or the [[Code]], [[FormalParameters]], and
// [[Scope]] internal properties.
// XXX can't delete prototype in pure-js.
// 22. Return F.
return bound;
}
});
//
// Array
// =====
//
// ES5 15.4.3.2
// http://es5.github.com/#x15.4.3.2
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
defineProperties(Array, { isArray: isArray });
var boxedString = Object('a');
var splitString = boxedString[0] !== 'a' || !(0 in boxedString);
var properlyBoxesContext = function properlyBoxed(method) {
// Check node 0.6.21 bug where third parameter is not boxed
var properlyBoxesNonStrict = true;
var properlyBoxesStrict = true;
if (method) {
method.call('foo', function (_, __, context) {
if (typeof context !== 'object') { properlyBoxesNonStrict = false; }
});
method.call([1], function () {
'use strict';
properlyBoxesStrict = typeof this === 'string';
}, 'x');
}
return !!method && properlyBoxesNonStrict && properlyBoxesStrict;
};
defineProperties(ArrayPrototype, {
forEach: function forEach(fun /*, thisp*/) {
var object = toObject(this),
self = splitString && isString(this) ? this.split('') : object,
thisp = arguments[1],
i = -1,
length = self.length >>> 0;
// If no callback function or if callback is not a callable function
if (!isFunction(fun)) {
throw new TypeError(); // TODO message
}
while (++i < length) {
if (i in self) {
// Invoke the callback function with call, passing arguments:
// context, property value, property key, thisArg object
// context
fun.call(thisp, self[i], i, object);
}
}
}
}, !properlyBoxesContext(ArrayPrototype.forEach));
// ES5 15.4.4.14
// http://es5.github.com/#x15.4.4.14
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
var hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1;
defineProperties(ArrayPrototype, {
indexOf: function indexOf(sought /*, fromIndex */ ) {
var self = splitString && isString(this) ? this.split('') : toObject(this),
length = self.length >>> 0;
if (!length) {
return -1;
}
var i = 0;
if (arguments.length > 1) {
i = toInteger(arguments[1]);
}
// handle negative indices
i = i >= 0 ? i : Math.max(0, length + i);
for (; i < length; i++) {
if (i in self && self[i] === sought) {
return i;
}
}
return -1;
}
}, hasFirefox2IndexOfBug);
//
// String
// ======
//
// ES5 15.5.4.14
// http://es5.github.com/#x15.5.4.14
// [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]
// Many browsers do not split properly with regular expressions or they
// do not perform the split correctly under obscure conditions.
// See http://blog.stevenlevithan.com/archives/cross-browser-split
// I've tested in many browsers and this seems to cover the deviant ones:
// 'ab'.split(/(?:ab)*/) should be ["", ""], not [""]
// '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""]
// 'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not
// [undefined, "t", undefined, "e", ...]
// ''.split(/.?/) should be [], not [""]
// '.'.split(/()()/) should be ["."], not ["", "", "."]
var string_split = StringPrototype.split;
if (
'ab'.split(/(?:ab)*/).length !== 2 ||
'.'.split(/(.?)(.?)/).length !== 4 ||
'tesst'.split(/(s)*/)[1] === 't' ||
'test'.split(/(?:)/, -1).length !== 4 ||
''.split(/.?/).length ||
'.'.split(/()()/).length > 1
) {
(function () {
var compliantExecNpcg = /()??/.exec('')[1] === void 0; // NPCG: nonparticipating capturing group
StringPrototype.split = function (separator, limit) {
var string = this;
if (separator === void 0 && limit === 0) {
return [];
}
// If `separator` is not a regex, use native split
if (_toString.call(separator) !== '[object RegExp]') {
return string_split.call(this, separator, limit);
}
var output = [],
flags = (separator.ignoreCase ? 'i' : '') +
(separator.multiline ? 'm' : '') +
(separator.extended ? 'x' : '') + // Proposed for ES6
(separator.sticky ? 'y' : ''), // Firefox 3+
lastLastIndex = 0,
// Make `global` and avoid `lastIndex` issues by working with a copy
separator2, match, lastIndex, lastLength;
separator = new RegExp(separator.source, flags + 'g');
string += ''; // Type-convert
if (!compliantExecNpcg) {
// Doesn't need flags gy, but they don't hurt
separator2 = new RegExp('^' + separator.source + '$(?!\\s)', flags);
}
/* Values for `limit`, per the spec:
* If undefined: 4294967295 // Math.pow(2, 32) - 1
* If 0, Infinity, or NaN: 0
* If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
* If negative number: 4294967296 - Math.floor(Math.abs(limit))
* If other: Type-convert, then use the above rules
*/
limit = limit === void 0 ?
-1 >>> 0 : // Math.pow(2, 32) - 1
ToUint32(limit);
while (match = separator.exec(string)) {
// `separator.lastIndex` is not reliable cross-browser
lastIndex = match.index + match[0].length;
if (lastIndex > lastLastIndex) {
output.push(string.slice(lastLastIndex, match.index));
// Fix browsers whose `exec` methods don't consistently return `undefined` for
// nonparticipating capturing groups
if (!compliantExecNpcg && match.length > 1) {
match[0].replace(separator2, function () {
for (var i = 1; i < arguments.length - 2; i++) {
if (arguments[i] === void 0) {
match[i] = void 0;
}
}
});
}
if (match.length > 1 && match.index < string.length) {
ArrayPrototype.push.apply(output, match.slice(1));
}
lastLength = match[0].length;
lastLastIndex = lastIndex;
if (output.length >= limit) {
break;
}
}
if (separator.lastIndex === match.index) {
separator.lastIndex++; // Avoid an infinite loop
}
}
if (lastLastIndex === string.length) {
if (lastLength || !separator.test('')) {
output.push('');
}
} else {
output.push(string.slice(lastLastIndex));
}
return output.length > limit ? output.slice(0, limit) : output;
};
}());
// [bugfix, chrome]
// If separator is undefined, then the result array contains just one String,
// which is the this value (converted to a String). If limit is not undefined,
// then the output array is truncated so that it contains no more than limit
// elements.
// "0".split(undefined, 0) -> []
} else if ('0'.split(void 0, 0).length) {
StringPrototype.split = function split(separator, limit) {
if (separator === void 0 && limit === 0) { return []; }
return string_split.call(this, separator, limit);
};
}
// ES5 15.5.4.20
// whitespace from: http://es5.github.io/#x15.5.4.20
var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' +
'\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' +
'\u2029\uFEFF';
var zeroWidth = '\u200b';
var wsRegexChars = '[' + ws + ']';
var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*');
var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$');
var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim());
defineProperties(StringPrototype, {
// http://blog.stevenlevithan.com/archives/faster-trim-javascript
// http://perfectionkills.com/whitespace-deviations/
trim: function trim() {
if (this === void 0 || this === null) {
throw new TypeError("can't convert " + this + ' to object');
}
return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '');
}
}, hasTrimWhitespaceBug);
// ECMA-262, 3rd B.2.3
// Not an ECMAScript standard, although ECMAScript 3rd Edition has a
// non-normative section suggesting uniform semantics and it should be
// normalized across all browsers
// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE
var string_substr = StringPrototype.substr;
var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';
defineProperties(StringPrototype, {
substr: function substr(start, length) {
return string_substr.call(
this,
start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start,
length
);
}
}, hasNegativeSubstrBug);
},{}],53:[function(require,module,exports){
'use strict';
module.exports = [
// streaming transports
require('./transport/websocket')
, require('./transport/xhr-streaming')
, require('./transport/xdr-streaming')
, require('./transport/eventsource')
, require('./transport/lib/iframe-wrap')(require('./transport/eventsource'))
// polling transports
, require('./transport/htmlfile')
, require('./transport/lib/iframe-wrap')(require('./transport/htmlfile'))
, require('./transport/xhr-polling')
, require('./transport/xdr-polling')
, require('./transport/lib/iframe-wrap')(require('./transport/xhr-polling'))
, require('./transport/jsonp-polling')
];
},{"./transport/eventsource":57,"./transport/htmlfile":58,"./transport/jsonp-polling":60,"./transport/lib/iframe-wrap":63,"./transport/websocket":75,"./transport/xdr-polling":76,"./transport/xdr-streaming":77,"./transport/xhr-polling":78,"./transport/xhr-streaming":79}],54:[function(require,module,exports){
(function (process,global){
'use strict';
var EventEmitter = require('events').EventEmitter
, inherits = require('inherits')
, utils = require('../../utils/event')
, urlUtils = require('../../utils/url')
, XHR = global.XMLHttpRequest
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:browser:xhr');
}
function AbstractXHRObject(method, url, payload, opts) {
debug(method, url);
var self = this;
EventEmitter.call(this);
setTimeout(function () {
self._start(method, url, payload, opts);
}, 0);
}
inherits(AbstractXHRObject, EventEmitter);
AbstractXHRObject.prototype._start = function(method, url, payload, opts) {
var self = this;
try {
this.xhr = new XHR();
} catch (x) {
// intentionally empty
}
if (!this.xhr) {
debug('no xhr');
this.emit('finish', 0, 'no xhr support');
this._cleanup();
return;
}
// several browsers cache POSTs
url = urlUtils.addQuery(url, 't=' + (+new Date()));
// Explorer tends to keep connection open, even after the
// tab gets closed: http://bugs.jquery.com/ticket/5280
this.unloadRef = utils.unloadAdd(function() {
debug('unload cleanup');
self._cleanup(true);
});
try {
this.xhr.open(method, url, true);
if (this.timeout && 'timeout' in this.xhr) {
this.xhr.timeout = this.timeout;
this.xhr.ontimeout = function() {
debug('xhr timeout');
self.emit('finish', 0, '');
self._cleanup(false);
};
}
} catch (e) {
debug('exception', e);
// IE raises an exception on wrong port.
this.emit('finish', 0, '');
this._cleanup(false);
return;
}
if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) {
debug('withCredentials');
// Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :
// "This never affects same-site requests."
this.xhr.withCredentials = 'true';
}
if (opts && opts.headers) {
for (var key in opts.headers) {
this.xhr.setRequestHeader(key, opts.headers[key]);
}
}
this.xhr.onreadystatechange = function() {
if (self.xhr) {
var x = self.xhr;
var text, status;
debug('readyState', x.readyState);
switch (x.readyState) {
case 3:
// IE doesn't like peeking into responseText or status
// on Microsoft.XMLHTTP and readystate=3
try {
status = x.status;
text = x.responseText;
} catch (e) {
// intentionally empty
}
debug('status', status);
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
if (status === 1223) {
status = 204;
}
// IE does return readystate == 3 for 404 answers.
if (status === 200 && text && text.length > 0) {
debug('chunk');
self.emit('chunk', status, text);
}
break;
case 4:
status = x.status;
debug('status', status);
// IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
if (status === 1223) {
status = 204;
}
// IE returns this for a bad port
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770(v=vs.85).aspx
if (status === 12005 || status === 12029) {
status = 0;
}
debug('finish', status, x.responseText);
self.emit('finish', status, x.responseText);
self._cleanup(false);
break;
}
}
};
try {
self.xhr.send(payload);
} catch (e) {
self.emit('finish', 0, '');
self._cleanup(false);
}
};
AbstractXHRObject.prototype._cleanup = function(abort) {
debug('cleanup');
if (!this.xhr) {
return;
}
this.removeAllListeners();
utils.unloadDel(this.unloadRef);
// IE needs this field to be a function
this.xhr.onreadystatechange = function() {};
if (this.xhr.ontimeout) {
this.xhr.ontimeout = null;
}
if (abort) {
try {
this.xhr.abort();
} catch (x) {
// intentionally empty
}
}
this.unloadRef = this.xhr = null;
};
AbstractXHRObject.prototype.close = function() {
debug('close');
this._cleanup(true);
};
AbstractXHRObject.enabled = !!XHR;
// override XMLHttpRequest for IE6/7
// obfuscate to avoid firewalls
var axo = ['Active'].concat('Object').join('X');
if (!AbstractXHRObject.enabled && (axo in global)) {
debug('overriding xmlhttprequest');
XHR = function() {
try {
return new global[axo]('Microsoft.XMLHTTP');
} catch (e) {
return null;
}
};
AbstractXHRObject.enabled = !!new XHR();
}
var cors = false;
try {
cors = 'withCredentials' in new XHR();
} catch (ignored) {
// intentionally empty
}
AbstractXHRObject.supportsCORS = cors;
module.exports = AbstractXHRObject;
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../../utils/event":83,"../../utils/url":89,"_process":115,"debug":22,"events":40,"inherits":24}],55:[function(require,module,exports){
(function (global){
module.exports = global.EventSource;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],56:[function(require,module,exports){
(function (global){
'use strict';
var Driver = global.WebSocket || global.MozWebSocket;
if (Driver) {
module.exports = function WebSocketBrowserDriver(url) {
return new Driver(url);
};
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],57:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, AjaxBasedTransport = require('./lib/ajax-based')
, EventSourceReceiver = require('./receiver/eventsource')
, XHRCorsObject = require('./sender/xhr-cors')
, EventSourceDriver = require('eventsource')
;
function EventSourceTransport(transUrl) {
if (!EventSourceTransport.enabled()) {
throw new Error('Transport created when disabled');
}
AjaxBasedTransport.call(this, transUrl, '/eventsource', EventSourceReceiver, XHRCorsObject);
}
inherits(EventSourceTransport, AjaxBasedTransport);
EventSourceTransport.enabled = function() {
return !!EventSourceDriver;
};
EventSourceTransport.transportName = 'eventsource';
EventSourceTransport.roundTrips = 2;
module.exports = EventSourceTransport;
},{"./lib/ajax-based":61,"./receiver/eventsource":66,"./sender/xhr-cors":72,"eventsource":55,"inherits":24}],58:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, HtmlfileReceiver = require('./receiver/htmlfile')
, XHRLocalObject = require('./sender/xhr-local')
, AjaxBasedTransport = require('./lib/ajax-based')
;
function HtmlFileTransport(transUrl) {
if (!HtmlfileReceiver.enabled) {
throw new Error('Transport created when disabled');
}
AjaxBasedTransport.call(this, transUrl, '/htmlfile', HtmlfileReceiver, XHRLocalObject);
}
inherits(HtmlFileTransport, AjaxBasedTransport);
HtmlFileTransport.enabled = function(info) {
return HtmlfileReceiver.enabled && info.sameOrigin;
};
HtmlFileTransport.transportName = 'htmlfile';
HtmlFileTransport.roundTrips = 2;
module.exports = HtmlFileTransport;
},{"./lib/ajax-based":61,"./receiver/htmlfile":67,"./sender/xhr-local":74,"inherits":24}],59:[function(require,module,exports){
(function (process){
'use strict';
// Few cool transports do work only for same-origin. In order to make
// them work cross-domain we shall use iframe, served from the
// remote domain. New browsers have capabilities to communicate with
// cross domain iframe using postMessage(). In IE it was implemented
// from IE 8+, but of course, IE got some details wrong:
// http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx
// http://stevesouders.com/misc/test-postmessage.php
var inherits = require('inherits')
, JSON3 = require('json3')
, EventEmitter = require('events').EventEmitter
, version = require('../version')
, urlUtils = require('../utils/url')
, iframeUtils = require('../utils/iframe')
, eventUtils = require('../utils/event')
, random = require('../utils/random')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:transport:iframe');
}
function IframeTransport(transport, transUrl, baseUrl) {
if (!IframeTransport.enabled()) {
throw new Error('Transport created when disabled');
}
EventEmitter.call(this);
var self = this;
this.origin = urlUtils.getOrigin(baseUrl);
this.baseUrl = baseUrl;
this.transUrl = transUrl;
this.transport = transport;
this.windowId = random.string(8);
var iframeUrl = urlUtils.addPath(baseUrl, '/iframe.html') + '#' + this.windowId;
debug(transport, transUrl, iframeUrl);
this.iframeObj = iframeUtils.createIframe(iframeUrl, function(r) {
debug('err callback');
self.emit('close', 1006, 'Unable to load an iframe (' + r + ')');
self.close();
});
this.onmessageCallback = this._message.bind(this);
eventUtils.attachEvent('message', this.onmessageCallback);
}
inherits(IframeTransport, EventEmitter);
IframeTransport.prototype.close = function() {
debug('close');
this.removeAllListeners();
if (this.iframeObj) {
eventUtils.detachEvent('message', this.onmessageCallback);
try {
// When the iframe is not loaded, IE raises an exception
// on 'contentWindow'.
this.postMessage('c');
} catch (x) {
// intentionally empty
}
this.iframeObj.cleanup();
this.iframeObj = null;
this.onmessageCallback = this.iframeObj = null;
}
};
IframeTransport.prototype._message = function(e) {
debug('message', e.data);
if (!urlUtils.isOriginEqual(e.origin, this.origin)) {
debug('not same origin', e.origin, this.origin);
return;
}
var iframeMessage;
try {
iframeMessage = JSON3.parse(e.data);
} catch (ignored) {
debug('bad json', e.data);
return;
}
if (iframeMessage.windowId !== this.windowId) {
debug('mismatched window id', iframeMessage.windowId, this.windowId);
return;
}
switch (iframeMessage.type) {
case 's':
this.iframeObj.loaded();
// window global dependency
this.postMessage('s', JSON3.stringify([
version
, this.transport
, this.transUrl
, this.baseUrl
]));
break;
case 't':
this.emit('message', iframeMessage.data);
break;
case 'c':
var cdata;
try {
cdata = JSON3.parse(iframeMessage.data);
} catch (ignored) {
debug('bad json', iframeMessage.data);
return;
}
this.emit('close', cdata[0], cdata[1]);
this.close();
break;
}
};
IframeTransport.prototype.postMessage = function(type, data) {
debug('postMessage', type, data);
this.iframeObj.post(JSON3.stringify({
windowId: this.windowId
, type: type
, data: data || ''
}), this.origin);
};
IframeTransport.prototype.send = function(message) {
debug('send', message);
this.postMessage('m', message);
};
IframeTransport.enabled = function() {
return iframeUtils.iframeEnabled;
};
IframeTransport.transportName = 'iframe';
IframeTransport.roundTrips = 2;
module.exports = IframeTransport;
}).call(this,require('_process'))
},{"../utils/event":83,"../utils/iframe":84,"../utils/random":87,"../utils/url":89,"../version":90,"_process":115,"debug":22,"events":40,"inherits":24,"json3":25}],60:[function(require,module,exports){
(function (global){
'use strict';
// The simplest and most robust transport, using the well-know cross
// domain hack - JSONP. This transport is quite inefficient - one
// message could use up to one http request. But at least it works almost
// everywhere.
// Known limitations:
// o you will get a spinning cursor
// o for Konqueror a dumb timer is needed to detect errors
var inherits = require('inherits')
, SenderReceiver = require('./lib/sender-receiver')
, JsonpReceiver = require('./receiver/jsonp')
, jsonpSender = require('./sender/jsonp')
;
function JsonPTransport(transUrl) {
if (!JsonPTransport.enabled()) {
throw new Error('Transport created when disabled');
}
SenderReceiver.call(this, transUrl, '/jsonp', jsonpSender, JsonpReceiver);
}
inherits(JsonPTransport, SenderReceiver);
JsonPTransport.enabled = function() {
return !!global.document;
};
JsonPTransport.transportName = 'jsonp-polling';
JsonPTransport.roundTrips = 1;
JsonPTransport.needBody = true;
module.exports = JsonPTransport;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./lib/sender-receiver":65,"./receiver/jsonp":68,"./sender/jsonp":70,"inherits":24}],61:[function(require,module,exports){
(function (process){
'use strict';
var inherits = require('inherits')
, urlUtils = require('../../utils/url')
, SenderReceiver = require('./sender-receiver')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:ajax-based');
}
function createAjaxSender(AjaxObject) {
return function(url, payload, callback) {
debug('create ajax sender', url, payload);
var opt = {};
if (typeof payload === 'string') {
opt.headers = {'Content-type': 'text/plain'};
}
var ajaxUrl = urlUtils.addPath(url, '/xhr_send');
var xo = new AjaxObject('POST', ajaxUrl, payload, opt);
xo.once('finish', function(status) {
debug('finish', status);
xo = null;
if (status !== 200 && status !== 204) {
return callback(new Error('http status ' + status));
}
callback();
});
return function() {
debug('abort');
xo.close();
xo = null;
var err = new Error('Aborted');
err.code = 1000;
callback(err);
};
};
}
function AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject) {
SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject), Receiver, AjaxObject);
}
inherits(AjaxBasedTransport, SenderReceiver);
module.exports = AjaxBasedTransport;
}).call(this,require('_process'))
},{"../../utils/url":89,"./sender-receiver":65,"_process":115,"debug":22,"inherits":24}],62:[function(require,module,exports){
(function (process){
'use strict';
var inherits = require('inherits')
, EventEmitter = require('events').EventEmitter
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:buffered-sender');
}
function BufferedSender(url, sender) {
debug(url);
EventEmitter.call(this);
this.sendBuffer = [];
this.sender = sender;
this.url = url;
}
inherits(BufferedSender, EventEmitter);
BufferedSender.prototype.send = function(message) {
debug('send', message);
this.sendBuffer.push(message);
if (!this.sendStop) {
this.sendSchedule();
}
};
// For polling transports in a situation when in the message callback,
// new message is being send. If the sending connection was started
// before receiving one, it is possible to saturate the network and
// timeout due to the lack of receiving socket. To avoid that we delay
// sending messages by some small time, in order to let receiving
// connection be started beforehand. This is only a halfmeasure and
// does not fix the big problem, but it does make the tests go more
// stable on slow networks.
BufferedSender.prototype.sendScheduleWait = function() {
debug('sendScheduleWait');
var self = this;
var tref;
this.sendStop = function() {
debug('sendStop');
self.sendStop = null;
clearTimeout(tref);
};
tref = setTimeout(function() {
debug('timeout');
self.sendStop = null;
self.sendSchedule();
}, 25);
};
BufferedSender.prototype.sendSchedule = function() {
debug('sendSchedule', this.sendBuffer.length);
var self = this;
if (this.sendBuffer.length > 0) {
var payload = '[' + this.sendBuffer.join(',') + ']';
this.sendStop = this.sender(this.url, payload, function(err) {
self.sendStop = null;
if (err) {
debug('error', err);
self.emit('close', err.code || 1006, 'Sending error: ' + err);
self._cleanup();
} else {
self.sendScheduleWait();
}
});
this.sendBuffer = [];
}
};
BufferedSender.prototype._cleanup = function() {
debug('_cleanup');
this.removeAllListeners();
};
BufferedSender.prototype.stop = function() {
debug('stop');
this._cleanup();
if (this.sendStop) {
this.sendStop();
this.sendStop = null;
}
};
module.exports = BufferedSender;
}).call(this,require('_process'))
},{"_process":115,"debug":22,"events":40,"inherits":24}],63:[function(require,module,exports){
(function (global){
'use strict';
var inherits = require('inherits')
, IframeTransport = require('../iframe')
, objectUtils = require('../../utils/object')
;
module.exports = function(transport) {
function IframeWrapTransport(transUrl, baseUrl) {
IframeTransport.call(this, transport.transportName, transUrl, baseUrl);
}
inherits(IframeWrapTransport, IframeTransport);
IframeWrapTransport.enabled = function(url, info) {
if (!global.document) {
return false;
}
var iframeInfo = objectUtils.extend({}, info);
iframeInfo.sameOrigin = true;
return transport.enabled(iframeInfo) && IframeTransport.enabled();
};
IframeWrapTransport.transportName = 'iframe-' + transport.transportName;
IframeWrapTransport.needBody = true;
IframeWrapTransport.roundTrips = IframeTransport.roundTrips + transport.roundTrips - 1; // html, javascript (2) + transport - no CORS (1)
IframeWrapTransport.facadeTransport = transport;
return IframeWrapTransport;
};
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../../utils/object":86,"../iframe":59,"inherits":24}],64:[function(require,module,exports){
(function (process){
'use strict';
var inherits = require('inherits')
, EventEmitter = require('events').EventEmitter
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:polling');
}
function Polling(Receiver, receiveUrl, AjaxObject) {
debug(receiveUrl);
EventEmitter.call(this);
this.Receiver = Receiver;
this.receiveUrl = receiveUrl;
this.AjaxObject = AjaxObject;
this._scheduleReceiver();
}
inherits(Polling, EventEmitter);
Polling.prototype._scheduleReceiver = function() {
debug('_scheduleReceiver');
var self = this;
var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject);
poll.on('message', function(msg) {
debug('message', msg);
self.emit('message', msg);
});
poll.once('close', function(code, reason) {
debug('close', code, reason, self.pollIsClosing);
self.poll = poll = null;
if (!self.pollIsClosing) {
if (reason === 'network') {
self._scheduleReceiver();
} else {
self.emit('close', code || 1006, reason);
self.removeAllListeners();
}
}
});
};
Polling.prototype.abort = function() {
debug('abort');
this.removeAllListeners();
this.pollIsClosing = true;
if (this.poll) {
this.poll.abort();
}
};
module.exports = Polling;
}).call(this,require('_process'))
},{"_process":115,"debug":22,"events":40,"inherits":24}],65:[function(require,module,exports){
(function (process){
'use strict';
var inherits = require('inherits')
, urlUtils = require('../../utils/url')
, BufferedSender = require('./buffered-sender')
, Polling = require('./polling')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:sender-receiver');
}
function SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject) {
var pollUrl = urlUtils.addPath(transUrl, urlSuffix);
debug(pollUrl);
var self = this;
BufferedSender.call(this, transUrl, senderFunc);
this.poll = new Polling(Receiver, pollUrl, AjaxObject);
this.poll.on('message', function(msg) {
debug('poll message', msg);
self.emit('message', msg);
});
this.poll.once('close', function(code, reason) {
debug('poll close', code, reason);
self.poll = null;
self.emit('close', code, reason);
self.close();
});
}
inherits(SenderReceiver, BufferedSender);
SenderReceiver.prototype.close = function() {
debug('close');
this.removeAllListeners();
if (this.poll) {
this.poll.abort();
this.poll = null;
}
this.stop();
};
module.exports = SenderReceiver;
}).call(this,require('_process'))
},{"../../utils/url":89,"./buffered-sender":62,"./polling":64,"_process":115,"debug":22,"inherits":24}],66:[function(require,module,exports){
(function (process){
'use strict';
var inherits = require('inherits')
, EventEmitter = require('events').EventEmitter
, EventSourceDriver = require('eventsource')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:receiver:eventsource');
}
function EventSourceReceiver(url) {
debug(url);
EventEmitter.call(this);
var self = this;
var es = this.es = new EventSourceDriver(url);
es.onmessage = function(e) {
debug('message', e.data);
self.emit('message', decodeURI(e.data));
};
es.onerror = function(e) {
debug('error', es.readyState, e);
// ES on reconnection has readyState = 0 or 1.
// on network error it's CLOSED = 2
var reason = (es.readyState !== 2 ? 'network' : 'permanent');
self._cleanup();
self._close(reason);
};
}
inherits(EventSourceReceiver, EventEmitter);
EventSourceReceiver.prototype.abort = function() {
debug('abort');
this._cleanup();
this._close('user');
};
EventSourceReceiver.prototype._cleanup = function() {
debug('cleanup');
var es = this.es;
if (es) {
es.onmessage = es.onerror = null;
es.close();
this.es = null;
}
};
EventSourceReceiver.prototype._close = function(reason) {
debug('close', reason);
var self = this;
// Safari and chrome < 15 crash if we close window before
// waiting for ES cleanup. See:
// https://code.google.com/p/chromium/issues/detail?id=89155
setTimeout(function() {
self.emit('close', null, reason);
self.removeAllListeners();
}, 200);
};
module.exports = EventSourceReceiver;
}).call(this,require('_process'))
},{"_process":115,"debug":22,"events":40,"eventsource":55,"inherits":24}],67:[function(require,module,exports){
(function (process,global){
'use strict';
var inherits = require('inherits')
, iframeUtils = require('../../utils/iframe')
, urlUtils = require('../../utils/url')
, EventEmitter = require('events').EventEmitter
, random = require('../../utils/random')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:receiver:htmlfile');
}
function HtmlfileReceiver(url) {
debug(url);
EventEmitter.call(this);
var self = this;
iframeUtils.polluteGlobalNamespace();
this.id = 'a' + random.string(6);
url = urlUtils.addQuery(url, 'c=' + decodeURIComponent(iframeUtils.WPrefix + '.' + this.id));
debug('using htmlfile', HtmlfileReceiver.htmlfileEnabled);
var constructFunc = HtmlfileReceiver.htmlfileEnabled ?
iframeUtils.createHtmlfile : iframeUtils.createIframe;
global[iframeUtils.WPrefix][this.id] = {
start: function() {
debug('start');
self.iframeObj.loaded();
}
, message: function(data) {
debug('message', data);
self.emit('message', data);
}
, stop: function() {
debug('stop');
self._cleanup();
self._close('network');
}
};
this.iframeObj = constructFunc(url, function() {
debug('callback');
self._cleanup();
self._close('permanent');
});
}
inherits(HtmlfileReceiver, EventEmitter);
HtmlfileReceiver.prototype.abort = function() {
debug('abort');
this._cleanup();
this._close('user');
};
HtmlfileReceiver.prototype._cleanup = function() {
debug('_cleanup');
if (this.iframeObj) {
this.iframeObj.cleanup();
this.iframeObj = null;
}
delete global[iframeUtils.WPrefix][this.id];
};
HtmlfileReceiver.prototype._close = function(reason) {
debug('_close', reason);
this.emit('close', null, reason);
this.removeAllListeners();
};
HtmlfileReceiver.htmlfileEnabled = false;
// obfuscate to avoid firewalls
var axo = ['Active'].concat('Object').join('X');
if (axo in global) {
try {
HtmlfileReceiver.htmlfileEnabled = !!new global[axo]('htmlfile');
} catch (x) {
// intentionally empty
}
}
HtmlfileReceiver.enabled = HtmlfileReceiver.htmlfileEnabled || iframeUtils.iframeEnabled;
module.exports = HtmlfileReceiver;
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../../utils/iframe":84,"../../utils/random":87,"../../utils/url":89,"_process":115,"debug":22,"events":40,"inherits":24}],68:[function(require,module,exports){
(function (process,global){
'use strict';
var utils = require('../../utils/iframe')
, random = require('../../utils/random')
, browser = require('../../utils/browser')
, urlUtils = require('../../utils/url')
, inherits = require('inherits')
, EventEmitter = require('events').EventEmitter
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:receiver:jsonp');
}
function JsonpReceiver(url) {
debug(url);
var self = this;
EventEmitter.call(this);
utils.polluteGlobalNamespace();
this.id = 'a' + random.string(6);
var urlWithId = urlUtils.addQuery(url, 'c=' + encodeURIComponent(utils.WPrefix + '.' + this.id));
global[utils.WPrefix][this.id] = this._callback.bind(this);
this._createScript(urlWithId);
// Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty.
this.timeoutId = setTimeout(function() {
debug('timeout');
self._abort(new Error('JSONP script loaded abnormally (timeout)'));
}, JsonpReceiver.timeout);
}
inherits(JsonpReceiver, EventEmitter);
JsonpReceiver.prototype.abort = function() {
debug('abort');
if (global[utils.WPrefix][this.id]) {
var err = new Error('JSONP user aborted read');
err.code = 1000;
this._abort(err);
}
};
JsonpReceiver.timeout = 35000;
JsonpReceiver.scriptErrorTimeout = 1000;
JsonpReceiver.prototype._callback = function(data) {
debug('_callback', data);
this._cleanup();
if (this.aborting) {
return;
}
if (data) {
debug('message', data);
this.emit('message', data);
}
this.emit('close', null, 'network');
this.removeAllListeners();
};
JsonpReceiver.prototype._abort = function(err) {
debug('_abort', err);
this._cleanup();
this.aborting = true;
this.emit('close', err.code, err.message);
this.removeAllListeners();
};
JsonpReceiver.prototype._cleanup = function() {
debug('_cleanup');
clearTimeout(this.timeoutId);
if (this.script2) {
this.script2.parentNode.removeChild(this.script2);
this.script2 = null;
}
if (this.script) {
var script = this.script;
// Unfortunately, you can't really abort script loading of
// the script.
script.parentNode.removeChild(script);
script.onreadystatechange = script.onerror =
script.onload = script.onclick = null;
this.script = null;
}
delete global[utils.WPrefix][this.id];
};
JsonpReceiver.prototype._scriptError = function() {
debug('_scriptError');
var self = this;
if (this.errorTimer) {
return;
}
this.errorTimer = setTimeout(function() {
if (!self.loadedOkay) {
self._abort(new Error('JSONP script loaded abnormally (onerror)'));
}
}, JsonpReceiver.scriptErrorTimeout);
};
JsonpReceiver.prototype._createScript = function(url) {
debug('_createScript', url);
var self = this;
var script = this.script = global.document.createElement('script');
var script2; // Opera synchronous load trick.
script.id = 'a' + random.string(8);
script.src = url;
script.type = 'text/javascript';
script.charset = 'UTF-8';
script.onerror = this._scriptError.bind(this);
script.onload = function() {
debug('onload');
self._abort(new Error('JSONP script loaded abnormally (onload)'));
};
// IE9 fires 'error' event after onreadystatechange or before, in random order.
// Use loadedOkay to determine if actually errored
script.onreadystatechange = function() {
debug('onreadystatechange', script.readyState);
if (/loaded|closed/.test(script.readyState)) {
if (script && script.htmlFor && script.onclick) {
self.loadedOkay = true;
try {
// In IE, actually execute the script.
script.onclick();
} catch (x) {
// intentionally empty
}
}
if (script) {
self._abort(new Error('JSONP script loaded abnormally (onreadystatechange)'));
}
}
};
// IE: event/htmlFor/onclick trick.
// One can't rely on proper order for onreadystatechange. In order to
// make sure, set a 'htmlFor' and 'event' properties, so that
// script code will be installed as 'onclick' handler for the
// script object. Later, onreadystatechange, manually execute this
// code. FF and Chrome doesn't work with 'event' and 'htmlFor'
// set. For reference see:
// http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
// Also, read on that about script ordering:
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
if (typeof script.async === 'undefined' && global.document.attachEvent) {
// According to mozilla docs, in recent browsers script.async defaults
// to 'true', so we may use it to detect a good browser:
// https://developer.mozilla.org/en/HTML/Element/script
if (!browser.isOpera()) {
// Naively assume we're in IE
try {
script.htmlFor = script.id;
script.event = 'onclick';
} catch (x) {
// intentionally empty
}
script.async = true;
} else {
// Opera, second sync script hack
script2 = this.script2 = global.document.createElement('script');
script2.text = "try{var a = document.getElementById('" + script.id + "'); if(a)a.onerror();}catch(x){};";
script.async = script2.async = false;
}
}
if (typeof script.async !== 'undefined') {
script.async = true;
}
var head = global.document.getElementsByTagName('head')[0];
head.insertBefore(script, head.firstChild);
if (script2) {
head.insertBefore(script2, head.firstChild);
}
};
module.exports = JsonpReceiver;
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../../utils/browser":81,"../../utils/iframe":84,"../../utils/random":87,"../../utils/url":89,"_process":115,"debug":22,"events":40,"inherits":24}],69:[function(require,module,exports){
(function (process){
'use strict';
var inherits = require('inherits')
, EventEmitter = require('events').EventEmitter
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:receiver:xhr');
}
function XhrReceiver(url, AjaxObject) {
debug(url);
EventEmitter.call(this);
var self = this;
this.bufferPosition = 0;
this.xo = new AjaxObject('POST', url, null);
this.xo.on('chunk', this._chunkHandler.bind(this));
this.xo.once('finish', function(status, text) {
debug('finish', status, text);
self._chunkHandler(status, text);
self.xo = null;
var reason = status === 200 ? 'network' : 'permanent';
debug('close', reason);
self.emit('close', null, reason);
self._cleanup();
});
}
inherits(XhrReceiver, EventEmitter);
XhrReceiver.prototype._chunkHandler = function(status, text) {
debug('_chunkHandler', status);
if (status !== 200 || !text) {
return;
}
for (var idx = -1; ; this.bufferPosition += idx + 1) {
var buf = text.slice(this.bufferPosition);
idx = buf.indexOf('\n');
if (idx === -1) {
break;
}
var msg = buf.slice(0, idx);
if (msg) {
debug('message', msg);
this.emit('message', msg);
}
}
};
XhrReceiver.prototype._cleanup = function() {
debug('_cleanup');
this.removeAllListeners();
};
XhrReceiver.prototype.abort = function() {
debug('abort');
if (this.xo) {
this.xo.close();
debug('close');
this.emit('close', null, 'user');
this.xo = null;
}
this._cleanup();
};
module.exports = XhrReceiver;
}).call(this,require('_process'))
},{"_process":115,"debug":22,"events":40,"inherits":24}],70:[function(require,module,exports){
(function (process,global){
'use strict';
var random = require('../../utils/random')
, urlUtils = require('../../utils/url')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:sender:jsonp');
}
var form, area;
function createIframe(id) {
debug('createIframe', id);
try {
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
return global.document.createElement('<iframe name="' + id + '">');
} catch (x) {
var iframe = global.document.createElement('iframe');
iframe.name = id;
return iframe;
}
}
function createForm() {
debug('createForm');
form = global.document.createElement('form');
form.style.display = 'none';
form.style.position = 'absolute';
form.method = 'POST';
form.enctype = 'application/x-www-form-urlencoded';
form.acceptCharset = 'UTF-8';
area = global.document.createElement('textarea');
area.name = 'd';
form.appendChild(area);
global.document.body.appendChild(form);
}
module.exports = function(url, payload, callback) {
debug(url, payload);
if (!form) {
createForm();
}
var id = 'a' + random.string(8);
form.target = id;
form.action = urlUtils.addQuery(urlUtils.addPath(url, '/jsonp_send'), 'i=' + id);
var iframe = createIframe(id);
iframe.id = id;
iframe.style.display = 'none';
form.appendChild(iframe);
try {
area.value = payload;
} catch (e) {
// seriously broken browsers get here
}
form.submit();
var completed = function(err) {
debug('completed', id, err);
if (!iframe.onerror) {
return;
}
iframe.onreadystatechange = iframe.onerror = iframe.onload = null;
// Opera mini doesn't like if we GC iframe
// immediately, thus this timeout.
setTimeout(function() {
debug('cleaning up', id);
iframe.parentNode.removeChild(iframe);
iframe = null;
}, 500);
area.value = '';
// It is not possible to detect if the iframe succeeded or
// failed to submit our form.
callback(err);
};
iframe.onerror = function() {
debug('onerror', id);
completed();
};
iframe.onload = function() {
debug('onload', id);
completed();
};
iframe.onreadystatechange = function(e) {
debug('onreadystatechange', id, iframe.readyState, e);
if (iframe.readyState === 'complete') {
completed();
}
};
return function() {
debug('aborted', id);
completed(new Error('Aborted'));
};
};
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../../utils/random":87,"../../utils/url":89,"_process":115,"debug":22}],71:[function(require,module,exports){
(function (process,global){
'use strict';
var EventEmitter = require('events').EventEmitter
, inherits = require('inherits')
, eventUtils = require('../../utils/event')
, browser = require('../../utils/browser')
, urlUtils = require('../../utils/url')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:sender:xdr');
}
// References:
// http://ajaxian.com/archives/100-line-ajax-wrapper
// http://msdn.microsoft.com/en-us/library/cc288060(v=VS.85).aspx
function XDRObject(method, url, payload) {
debug(method, url);
var self = this;
EventEmitter.call(this);
setTimeout(function() {
self._start(method, url, payload);
}, 0);
}
inherits(XDRObject, EventEmitter);
XDRObject.prototype._start = function(method, url, payload) {
debug('_start');
var self = this;
var xdr = new global.XDomainRequest();
// IE caches even POSTs
url = urlUtils.addQuery(url, 't=' + (+new Date()));
xdr.onerror = function() {
debug('onerror');
self._error();
};
xdr.ontimeout = function() {
debug('ontimeout');
self._error();
};
xdr.onprogress = function() {
debug('progress', xdr.responseText);
self.emit('chunk', 200, xdr.responseText);
};
xdr.onload = function() {
debug('load');
self.emit('finish', 200, xdr.responseText);
self._cleanup(false);
};
this.xdr = xdr;
this.unloadRef = eventUtils.unloadAdd(function() {
self._cleanup(true);
});
try {
// Fails with AccessDenied if port number is bogus
this.xdr.open(method, url);
if (this.timeout) {
this.xdr.timeout = this.timeout;
}
this.xdr.send(payload);
} catch (x) {
this._error();
}
};
XDRObject.prototype._error = function() {
this.emit('finish', 0, '');
this._cleanup(false);
};
XDRObject.prototype._cleanup = function(abort) {
debug('cleanup', abort);
if (!this.xdr) {
return;
}
this.removeAllListeners();
eventUtils.unloadDel(this.unloadRef);
this.xdr.ontimeout = this.xdr.onerror = this.xdr.onprogress = this.xdr.onload = null;
if (abort) {
try {
this.xdr.abort();
} catch (x) {
// intentionally empty
}
}
this.unloadRef = this.xdr = null;
};
XDRObject.prototype.close = function() {
debug('close');
this._cleanup(true);
};
// IE 8/9 if the request target uses the same scheme - #79
XDRObject.enabled = !!(global.XDomainRequest && browser.hasDomain());
module.exports = XDRObject;
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../../utils/browser":81,"../../utils/event":83,"../../utils/url":89,"_process":115,"debug":22,"events":40,"inherits":24}],72:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, XhrDriver = require('../driver/xhr')
;
function XHRCorsObject(method, url, payload, opts) {
XhrDriver.call(this, method, url, payload, opts);
}
inherits(XHRCorsObject, XhrDriver);
XHRCorsObject.enabled = XhrDriver.enabled && XhrDriver.supportsCORS;
module.exports = XHRCorsObject;
},{"../driver/xhr":54,"inherits":24}],73:[function(require,module,exports){
'use strict';
var EventEmitter = require('events').EventEmitter
, inherits = require('inherits')
;
function XHRFake(/* method, url, payload, opts */) {
var self = this;
EventEmitter.call(this);
this.to = setTimeout(function() {
self.emit('finish', 200, '{}');
}, XHRFake.timeout);
}
inherits(XHRFake, EventEmitter);
XHRFake.prototype.close = function() {
clearTimeout(this.to);
};
XHRFake.timeout = 2000;
module.exports = XHRFake;
},{"events":40,"inherits":24}],74:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, XhrDriver = require('../driver/xhr')
;
function XHRLocalObject(method, url, payload /*, opts */) {
XhrDriver.call(this, method, url, payload, {
noCredentials: true
});
}
inherits(XHRLocalObject, XhrDriver);
XHRLocalObject.enabled = XhrDriver.enabled;
module.exports = XHRLocalObject;
},{"../driver/xhr":54,"inherits":24}],75:[function(require,module,exports){
(function (process){
'use strict';
var utils = require('../utils/event')
, urlUtils = require('../utils/url')
, inherits = require('inherits')
, EventEmitter = require('events').EventEmitter
, WebsocketDriver = require('./driver/websocket')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:websocket');
}
function WebSocketTransport(transUrl, ignore, options) {
if (!WebSocketTransport.enabled()) {
throw new Error('Transport created when disabled');
}
EventEmitter.call(this);
debug('constructor', transUrl);
var self = this;
var url = urlUtils.addPath(transUrl, '/websocket');
if (url.slice(0, 5) === 'https') {
url = 'wss' + url.slice(5);
} else {
url = 'ws' + url.slice(4);
}
this.url = url;
this.ws = new WebsocketDriver(this.url, [], options);
this.ws.onmessage = function(e) {
debug('message event', e.data);
self.emit('message', e.data);
};
// Firefox has an interesting bug. If a websocket connection is
// created after onunload, it stays alive even when user
// navigates away from the page. In such situation let's lie -
// let's not open the ws connection at all. See:
// https://github.com/sockjs/sockjs-client/issues/28
// https://bugzilla.mozilla.org/show_bug.cgi?id=696085
this.unloadRef = utils.unloadAdd(function() {
debug('unload');
self.ws.close();
});
this.ws.onclose = function(e) {
debug('close event', e.code, e.reason);
self.emit('close', e.code, e.reason);
self._cleanup();
};
this.ws.onerror = function(e) {
debug('error event', e);
self.emit('close', 1006, 'WebSocket connection broken');
self._cleanup();
};
}
inherits(WebSocketTransport, EventEmitter);
WebSocketTransport.prototype.send = function(data) {
var msg = '[' + data + ']';
debug('send', msg);
this.ws.send(msg);
};
WebSocketTransport.prototype.close = function() {
debug('close');
if (this.ws) {
this.ws.close();
}
this._cleanup();
};
WebSocketTransport.prototype._cleanup = function() {
debug('_cleanup');
var ws = this.ws;
if (ws) {
ws.onmessage = ws.onclose = ws.onerror = null;
}
utils.unloadDel(this.unloadRef);
this.unloadRef = this.ws = null;
this.removeAllListeners();
};
WebSocketTransport.enabled = function() {
debug('enabled');
return !!WebsocketDriver;
};
WebSocketTransport.transportName = 'websocket';
// In theory, ws should require 1 round trip. But in chrome, this is
// not very stable over SSL. Most likely a ws connection requires a
// separate SSL connection, in which case 2 round trips are an
// absolute minumum.
WebSocketTransport.roundTrips = 2;
module.exports = WebSocketTransport;
}).call(this,require('_process'))
},{"../utils/event":83,"../utils/url":89,"./driver/websocket":56,"_process":115,"debug":22,"events":40,"inherits":24}],76:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, AjaxBasedTransport = require('./lib/ajax-based')
, XdrStreamingTransport = require('./xdr-streaming')
, XhrReceiver = require('./receiver/xhr')
, XDRObject = require('./sender/xdr')
;
function XdrPollingTransport(transUrl) {
if (!XDRObject.enabled) {
throw new Error('Transport created when disabled');
}
AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XDRObject);
}
inherits(XdrPollingTransport, AjaxBasedTransport);
XdrPollingTransport.enabled = XdrStreamingTransport.enabled;
XdrPollingTransport.transportName = 'xdr-polling';
XdrPollingTransport.roundTrips = 2; // preflight, ajax
module.exports = XdrPollingTransport;
},{"./lib/ajax-based":61,"./receiver/xhr":69,"./sender/xdr":71,"./xdr-streaming":77,"inherits":24}],77:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, AjaxBasedTransport = require('./lib/ajax-based')
, XhrReceiver = require('./receiver/xhr')
, XDRObject = require('./sender/xdr')
;
// According to:
// http://stackoverflow.com/questions/1641507/detect-browser-support-for-cross-domain-xmlhttprequests
// http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
function XdrStreamingTransport(transUrl) {
if (!XDRObject.enabled) {
throw new Error('Transport created when disabled');
}
AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XDRObject);
}
inherits(XdrStreamingTransport, AjaxBasedTransport);
XdrStreamingTransport.enabled = function(info) {
if (info.cookie_needed || info.nullOrigin) {
return false;
}
return XDRObject.enabled && info.sameScheme;
};
XdrStreamingTransport.transportName = 'xdr-streaming';
XdrStreamingTransport.roundTrips = 2; // preflight, ajax
module.exports = XdrStreamingTransport;
},{"./lib/ajax-based":61,"./receiver/xhr":69,"./sender/xdr":71,"inherits":24}],78:[function(require,module,exports){
'use strict';
var inherits = require('inherits')
, AjaxBasedTransport = require('./lib/ajax-based')
, XhrReceiver = require('./receiver/xhr')
, XHRCorsObject = require('./sender/xhr-cors')
, XHRLocalObject = require('./sender/xhr-local')
;
function XhrPollingTransport(transUrl) {
if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {
throw new Error('Transport created when disabled');
}
AjaxBasedTransport.call(this, transUrl, '/xhr', XhrReceiver, XHRCorsObject);
}
inherits(XhrPollingTransport, AjaxBasedTransport);
XhrPollingTransport.enabled = function(info) {
if (info.nullOrigin) {
return false;
}
if (XHRLocalObject.enabled && info.sameOrigin) {
return true;
}
return XHRCorsObject.enabled;
};
XhrPollingTransport.transportName = 'xhr-polling';
XhrPollingTransport.roundTrips = 2; // preflight, ajax
module.exports = XhrPollingTransport;
},{"./lib/ajax-based":61,"./receiver/xhr":69,"./sender/xhr-cors":72,"./sender/xhr-local":74,"inherits":24}],79:[function(require,module,exports){
(function (global){
'use strict';
var inherits = require('inherits')
, AjaxBasedTransport = require('./lib/ajax-based')
, XhrReceiver = require('./receiver/xhr')
, XHRCorsObject = require('./sender/xhr-cors')
, XHRLocalObject = require('./sender/xhr-local')
, browser = require('../utils/browser')
;
function XhrStreamingTransport(transUrl) {
if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) {
throw new Error('Transport created when disabled');
}
AjaxBasedTransport.call(this, transUrl, '/xhr_streaming', XhrReceiver, XHRCorsObject);
}
inherits(XhrStreamingTransport, AjaxBasedTransport);
XhrStreamingTransport.enabled = function(info) {
if (info.nullOrigin) {
return false;
}
// Opera doesn't support xhr-streaming #60
// But it might be able to #92
if (browser.isOpera()) {
return false;
}
return XHRCorsObject.enabled;
};
XhrStreamingTransport.transportName = 'xhr-streaming';
XhrStreamingTransport.roundTrips = 2; // preflight, ajax
// Safari gets confused when a streaming ajax request is started
// before onload. This causes the load indicator to spin indefinetely.
// Only require body when used in a browser
XhrStreamingTransport.needBody = !!global.document;
module.exports = XhrStreamingTransport;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../utils/browser":81,"./lib/ajax-based":61,"./receiver/xhr":69,"./sender/xhr-cors":72,"./sender/xhr-local":74,"inherits":24}],80:[function(require,module,exports){
(function (global){
'use strict';
if (global.crypto && global.crypto.getRandomValues) {
module.exports.randomBytes = function(length) {
var bytes = new Uint8Array(length);
global.crypto.getRandomValues(bytes);
return bytes;
};
} else {
module.exports.randomBytes = function(length) {
var bytes = new Array(length);
for (var i = 0; i < length; i++) {
bytes[i] = Math.floor(Math.random() * 256);
}
return bytes;
};
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],81:[function(require,module,exports){
(function (global){
'use strict';
module.exports = {
isOpera: function() {
return global.navigator &&
/opera/i.test(global.navigator.userAgent);
}
, isKonqueror: function() {
return global.navigator &&
/konqueror/i.test(global.navigator.userAgent);
}
// #187 wrap document.domain in try/catch because of WP8 from file:///
, hasDomain: function () {
// non-browser client always has a domain
if (!global.document) {
return true;
}
try {
return !!global.document.domain;
} catch (e) {
return false;
}
}
};
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],82:[function(require,module,exports){
'use strict';
var JSON3 = require('json3');
// Some extra characters that Chrome gets wrong, and substitutes with
// something else on the wire.
var extraEscapable = /[\x00-\x1f\ud800-\udfff\ufffe\uffff\u0300-\u0333\u033d-\u0346\u034a-\u034c\u0350-\u0352\u0357-\u0358\u035c-\u0362\u0374\u037e\u0387\u0591-\u05af\u05c4\u0610-\u0617\u0653-\u0654\u0657-\u065b\u065d-\u065e\u06df-\u06e2\u06eb-\u06ec\u0730\u0732-\u0733\u0735-\u0736\u073a\u073d\u073f-\u0741\u0743\u0745\u0747\u07eb-\u07f1\u0951\u0958-\u095f\u09dc-\u09dd\u09df\u0a33\u0a36\u0a59-\u0a5b\u0a5e\u0b5c-\u0b5d\u0e38-\u0e39\u0f43\u0f4d\u0f52\u0f57\u0f5c\u0f69\u0f72-\u0f76\u0f78\u0f80-\u0f83\u0f93\u0f9d\u0fa2\u0fa7\u0fac\u0fb9\u1939-\u193a\u1a17\u1b6b\u1cda-\u1cdb\u1dc0-\u1dcf\u1dfc\u1dfe\u1f71\u1f73\u1f75\u1f77\u1f79\u1f7b\u1f7d\u1fbb\u1fbe\u1fc9\u1fcb\u1fd3\u1fdb\u1fe3\u1feb\u1fee-\u1fef\u1ff9\u1ffb\u1ffd\u2000-\u2001\u20d0-\u20d1\u20d4-\u20d7\u20e7-\u20e9\u2126\u212a-\u212b\u2329-\u232a\u2adc\u302b-\u302c\uaab2-\uaab3\uf900-\ufa0d\ufa10\ufa12\ufa15-\ufa1e\ufa20\ufa22\ufa25-\ufa26\ufa2a-\ufa2d\ufa30-\ufa6d\ufa70-\ufad9\ufb1d\ufb1f\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4e\ufff0-\uffff]/g
, extraLookup;
// This may be quite slow, so let's delay until user actually uses bad
// characters.
var unrollLookup = function(escapable) {
var i;
var unrolled = {};
var c = [];
for (i = 0; i < 65536; i++) {
c.push( String.fromCharCode(i) );
}
escapable.lastIndex = 0;
c.join('').replace(escapable, function(a) {
unrolled[ a ] = '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
return '';
});
escapable.lastIndex = 0;
return unrolled;
};
// Quote string, also taking care of unicode characters that browsers
// often break. Especially, take care of unicode surrogates:
// http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Surrogates
module.exports = {
quote: function(string) {
var quoted = JSON3.stringify(string);
// In most cases this should be very fast and good enough.
extraEscapable.lastIndex = 0;
if (!extraEscapable.test(quoted)) {
return quoted;
}
if (!extraLookup) {
extraLookup = unrollLookup(extraEscapable);
}
return quoted.replace(extraEscapable, function(a) {
return extraLookup[a];
});
}
};
},{"json3":25}],83:[function(require,module,exports){
(function (global){
'use strict';
var random = require('./random');
var onUnload = {}
, afterUnload = false
// detect google chrome packaged apps because they don't allow the 'unload' event
, isChromePackagedApp = global.chrome && global.chrome.app && global.chrome.app.runtime
;
module.exports = {
attachEvent: function(event, listener) {
if (typeof global.addEventListener !== 'undefined') {
global.addEventListener(event, listener, false);
} else if (global.document && global.attachEvent) {
// IE quirks.
// According to: http://stevesouders.com/misc/test-postmessage.php
// the message gets delivered only to 'document', not 'window'.
global.document.attachEvent('on' + event, listener);
// I get 'window' for ie8.
global.attachEvent('on' + event, listener);
}
}
, detachEvent: function(event, listener) {
if (typeof global.addEventListener !== 'undefined') {
global.removeEventListener(event, listener, false);
} else if (global.document && global.detachEvent) {
global.document.detachEvent('on' + event, listener);
global.detachEvent('on' + event, listener);
}
}
, unloadAdd: function(listener) {
if (isChromePackagedApp) {
return null;
}
var ref = random.string(8);
onUnload[ref] = listener;
if (afterUnload) {
setTimeout(this.triggerUnloadCallbacks, 0);
}
return ref;
}
, unloadDel: function(ref) {
if (ref in onUnload) {
delete onUnload[ref];
}
}
, triggerUnloadCallbacks: function() {
for (var ref in onUnload) {
onUnload[ref]();
delete onUnload[ref];
}
}
};
var unloadTriggered = function() {
if (afterUnload) {
return;
}
afterUnload = true;
module.exports.triggerUnloadCallbacks();
};
// 'unload' alone is not reliable in opera within an iframe, but we
// can't use `beforeunload` as IE fires it on javascript: links.
if (!isChromePackagedApp) {
module.exports.attachEvent('unload', unloadTriggered);
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./random":87}],84:[function(require,module,exports){
(function (process,global){
'use strict';
var eventUtils = require('./event')
, JSON3 = require('json3')
, browser = require('./browser')
;
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:utils:iframe');
}
module.exports = {
WPrefix: '_jp'
, currentWindowId: null
, polluteGlobalNamespace: function() {
if (!(module.exports.WPrefix in global)) {
global[module.exports.WPrefix] = {};
}
}
, postMessage: function(type, data) {
if (global.parent !== global) {
global.parent.postMessage(JSON3.stringify({
windowId: module.exports.currentWindowId
, type: type
, data: data || ''
}), '*');
} else {
debug('Cannot postMessage, no parent window.', type, data);
}
}
, createIframe: function(iframeUrl, errorCallback) {
var iframe = global.document.createElement('iframe');
var tref, unloadRef;
var unattach = function() {
debug('unattach');
clearTimeout(tref);
// Explorer had problems with that.
try {
iframe.onload = null;
} catch (x) {
// intentionally empty
}
iframe.onerror = null;
};
var cleanup = function() {
debug('cleanup');
if (iframe) {
unattach();
// This timeout makes chrome fire onbeforeunload event
// within iframe. Without the timeout it goes straight to
// onunload.
setTimeout(function() {
if (iframe) {
iframe.parentNode.removeChild(iframe);
}
iframe = null;
}, 0);
eventUtils.unloadDel(unloadRef);
}
};
var onerror = function(err) {
debug('onerror', err);
if (iframe) {
cleanup();
errorCallback(err);
}
};
var post = function(msg, origin) {
debug('post', msg, origin);
try {
// When the iframe is not loaded, IE raises an exception
// on 'contentWindow'.
setTimeout(function() {
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(msg, origin);
}
}, 0);
} catch (x) {
// intentionally empty
}
};
iframe.src = iframeUrl;
iframe.style.display = 'none';
iframe.style.position = 'absolute';
iframe.onerror = function() {
onerror('onerror');
};
iframe.onload = function() {
debug('onload');
// `onload` is triggered before scripts on the iframe are
// executed. Give it few seconds to actually load stuff.
clearTimeout(tref);
tref = setTimeout(function() {
onerror('onload timeout');
}, 2000);
};
global.document.body.appendChild(iframe);
tref = setTimeout(function() {
onerror('timeout');
}, 15000);
unloadRef = eventUtils.unloadAdd(cleanup);
return {
post: post
, cleanup: cleanup
, loaded: unattach
};
}
/* jshint undef: false, newcap: false */
/* eslint no-undef: 0, new-cap: 0 */
, createHtmlfile: function(iframeUrl, errorCallback) {
var axo = ['Active'].concat('Object').join('X');
var doc = new global[axo]('htmlfile');
var tref, unloadRef;
var iframe;
var unattach = function() {
clearTimeout(tref);
iframe.onerror = null;
};
var cleanup = function() {
if (doc) {
unattach();
eventUtils.unloadDel(unloadRef);
iframe.parentNode.removeChild(iframe);
iframe = doc = null;
CollectGarbage();
}
};
var onerror = function(r) {
debug('onerror', r);
if (doc) {
cleanup();
errorCallback(r);
}
};
var post = function(msg, origin) {
try {
// When the iframe is not loaded, IE raises an exception
// on 'contentWindow'.
setTimeout(function() {
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(msg, origin);
}
}, 0);
} catch (x) {
// intentionally empty
}
};
doc.open();
doc.write('<html><s' + 'cript>' +
'document.domain="' + global.document.domain + '";' +
'</s' + 'cript></html>');
doc.close();
doc.parentWindow[module.exports.WPrefix] = global[module.exports.WPrefix];
var c = doc.createElement('div');
doc.body.appendChild(c);
iframe = doc.createElement('iframe');
c.appendChild(iframe);
iframe.src = iframeUrl;
iframe.onerror = function() {
onerror('onerror');
};
tref = setTimeout(function() {
onerror('timeout');
}, 15000);
unloadRef = eventUtils.unloadAdd(cleanup);
return {
post: post
, cleanup: cleanup
, loaded: unattach
};
}
};
module.exports.iframeEnabled = false;
if (global.document) {
// postMessage misbehaves in konqueror 4.6.5 - the messages are delivered with
// huge delay, or not at all.
module.exports.iframeEnabled = (typeof global.postMessage === 'function' ||
typeof global.postMessage === 'object') && (!browser.isKonqueror());
}
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./browser":81,"./event":83,"_process":115,"debug":22,"json3":25}],85:[function(require,module,exports){
(function (global){
'use strict';
var logObject = {};
['log', 'debug', 'warn'].forEach(function (level) {
var levelExists;
try {
levelExists = global.console && global.console[level] && global.console[level].apply;
} catch(e) {
// do nothing
}
logObject[level] = levelExists ? function () {
return global.console[level].apply(global.console, arguments);
} : (level === 'log' ? function () {} : logObject.log);
});
module.exports = logObject;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],86:[function(require,module,exports){
'use strict';
module.exports = {
isObject: function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
}
, extend: function(obj) {
if (!this.isObject(obj)) {
return obj;
}
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
obj[prop] = source[prop];
}
}
}
return obj;
}
};
},{}],87:[function(require,module,exports){
'use strict';
/* global crypto:true */
var crypto = require('crypto');
// This string has length 32, a power of 2, so the modulus doesn't introduce a
// bias.
var _randomStringChars = 'abcdefghijklmnopqrstuvwxyz012345';
module.exports = {
string: function(length) {
var max = _randomStringChars.length;
var bytes = crypto.randomBytes(length);
var ret = [];
for (var i = 0; i < length; i++) {
ret.push(_randomStringChars.substr(bytes[i] % max, 1));
}
return ret.join('');
}
, number: function(max) {
return Math.floor(Math.random() * max);
}
, numberString: function(max) {
var t = ('' + (max - 1)).length;
var p = new Array(t + 1).join('0');
return (p + this.number(max)).slice(-t);
}
};
},{"crypto":80}],88:[function(require,module,exports){
(function (process){
'use strict';
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:utils:transport');
}
module.exports = function(availableTransports) {
return {
filterToEnabled: function(transportsWhitelist, info) {
var transports = {
main: []
, facade: []
};
if (!transportsWhitelist) {
transportsWhitelist = [];
} else if (typeof transportsWhitelist === 'string') {
transportsWhitelist = [transportsWhitelist];
}
availableTransports.forEach(function(trans) {
if (!trans) {
return;
}
if (trans.transportName === 'websocket' && info.websocket === false) {
debug('disabled from server', 'websocket');
return;
}
if (transportsWhitelist.length &&
transportsWhitelist.indexOf(trans.transportName) === -1) {
debug('not in whitelist', trans.transportName);
return;
}
if (trans.enabled(info)) {
debug('enabled', trans.transportName);
transports.main.push(trans);
if (trans.facadeTransport) {
transports.facade.push(trans.facadeTransport);
}
} else {
debug('disabled', trans.transportName);
}
});
return transports;
}
};
};
}).call(this,require('_process'))
},{"_process":115,"debug":22}],89:[function(require,module,exports){
(function (process){
'use strict';
var URL = require('url-parse');
var debug = function() {};
if (process.env.NODE_ENV !== 'production') {
debug = require('debug')('sockjs-client:utils:url');
}
module.exports = {
getOrigin: function(url) {
if (!url) {
return null;
}
var p = new URL(url);
if (p.protocol === 'file:') {
return null;
}
var port = p.port;
if (!port) {
port = (p.protocol === 'https:') ? '443' : '80';
}
return p.protocol + '//' + p.hostname + ':' + port;
}
, isOriginEqual: function(a, b) {
var res = this.getOrigin(a) === this.getOrigin(b);
debug('same', a, b, res);
return res;
}
, isSchemeEqual: function(a, b) {
return (a.split(':')[0] === b.split(':')[0]);
}
, addPath: function (url, path) {
var qs = url.split('?');
return qs[0] + path + (qs[1] ? '?' + qs[1] : '');
}
, addQuery: function (url, q) {
return url + (url.indexOf('?') === -1 ? ('?' + q) : ('&' + q));
}
};
}).call(this,require('_process'))
},{"_process":115,"debug":22,"url-parse":91}],90:[function(require,module,exports){
module.exports = '1.1.1';
},{}],91:[function(require,module,exports){
(function (global){
'use strict';
var required = require('requires-port')
, qs = require('querystringify')
, protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\S\s]*)/i
, slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//;
/**
* These are the parse rules for the URL parser, it informs the parser
* about:
*
* 0. The char it Needs to parse, if it's a string it should be done using
* indexOf, RegExp using exec and NaN means set as current value.
* 1. The property we should set when parsing this value.
* 2. Indication if it's backwards or forward parsing, when set as number it's
* the value of extra chars that should be split off.
* 3. Inherit from location if non existing in the parser.
* 4. `toLowerCase` the resulting value.
*/
var rules = [
['#', 'hash'], // Extract from the back.
['?', 'query'], // Extract from the back.
['/', 'pathname'], // Extract from the back.
['@', 'auth', 1], // Extract from the front.
[NaN, 'host', undefined, 1, 1], // Set left over value.
[/:(\d+)$/, 'port', undefined, 1], // RegExp the back.
[NaN, 'hostname', undefined, 1, 1] // Set left over.
];
/**
* These properties should not be copied or inherited from. This is only needed
* for all non blob URL's as a blob URL does not include a hash, only the
* origin.
*
* @type {Object}
* @private
*/
var ignore = { hash: 1, query: 1 };
/**
* The location object differs when your code is loaded through a normal page,
* Worker or through a worker using a blob. And with the blobble begins the
* trouble as the location object will contain the URL of the blob, not the
* location of the page where our code is loaded in. The actual origin is
* encoded in the `pathname` so we can thankfully generate a good "default"
* location from it so we can generate proper relative URL's again.
*
* @param {Object|String} loc Optional default location object.
* @returns {Object} lolcation object.
* @api public
*/
function lolcation(loc) {
loc = loc || global.location || {};
var finaldestination = {}
, type = typeof loc
, key;
if ('blob:' === loc.protocol) {
finaldestination = new URL(unescape(loc.pathname), {});
} else if ('string' === type) {
finaldestination = new URL(loc, {});
for (key in ignore) delete finaldestination[key];
} else if ('object' === type) {
for (key in loc) {
if (key in ignore) continue;
finaldestination[key] = loc[key];
}
if (finaldestination.slashes === undefined) {
finaldestination.slashes = slashes.test(loc.href);
}
}
return finaldestination;
}
/**
* @typedef ProtocolExtract
* @type Object
* @property {String} protocol Protocol matched in the URL, in lowercase.
* @property {Boolean} slashes `true` if protocol is followed by "//", else `false`.
* @property {String} rest Rest of the URL that is not part of the protocol.
*/
/**
* Extract protocol information from a URL with/without double slash ("//").
*
* @param {String} address URL we want to extract from.
* @return {ProtocolExtract} Extracted information.
* @api private
*/
function extractProtocol(address) {
var match = protocolre.exec(address);
return {
protocol: match[1] ? match[1].toLowerCase() : '',
slashes: !!match[2],
rest: match[3]
};
}
/**
* Resolve a relative URL pathname against a base URL pathname.
*
* @param {String} relative Pathname of the relative URL.
* @param {String} base Pathname of the base URL.
* @return {String} Resolved pathname.
* @api private
*/
function resolve(relative, base) {
var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/'))
, i = path.length
, last = path[i - 1]
, unshift = false
, up = 0;
while (i--) {
if (path[i] === '.') {
path.splice(i, 1);
} else if (path[i] === '..') {
path.splice(i, 1);
up++;
} else if (up) {
if (i === 0) unshift = true;
path.splice(i, 1);
up--;
}
}
if (unshift) path.unshift('');
if (last === '.' || last === '..') path.push('');
return path.join('/');
}
/**
* The actual URL instance. Instead of returning an object we've opted-in to
* create an actual constructor as it's much more memory efficient and
* faster and it pleases my OCD.
*
* @constructor
* @param {String} address URL we want to parse.
* @param {Object|String} location Location defaults for relative paths.
* @param {Boolean|Function} parser Parser for the query string.
* @api public
*/
function URL(address, location, parser) {
if (!(this instanceof URL)) {
return new URL(address, location, parser);
}
var relative, extracted, parse, instruction, index, key
, instructions = rules.slice()
, type = typeof location
, url = this
, i = 0;
//
// The following if statements allows this module two have compatibility with
// 2 different API:
//
// 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments
// where the boolean indicates that the query string should also be parsed.
//
// 2. The `URL` interface of the browser which accepts a URL, object as
// arguments. The supplied object will be used as default values / fall-back
// for relative paths.
//
if ('object' !== type && 'string' !== type) {
parser = location;
location = null;
}
if (parser && 'function' !== typeof parser) parser = qs.parse;
location = lolcation(location);
//
// Extract protocol information before running the instructions.
//
extracted = extractProtocol(address || '');
relative = !extracted.protocol && !extracted.slashes;
url.slashes = extracted.slashes || relative && location.slashes;
url.protocol = extracted.protocol || location.protocol || '';
address = extracted.rest;
//
// When the authority component is absent the URL starts with a path
// component.
//
if (!extracted.slashes) instructions[2] = [/(.*)/, 'pathname'];
for (; i < instructions.length; i++) {
instruction = instructions[i];
parse = instruction[0];
key = instruction[1];
if (parse !== parse) {
url[key] = address;
} else if ('string' === typeof parse) {
if (~(index = address.indexOf(parse))) {
if ('number' === typeof instruction[2]) {
url[key] = address.slice(0, index);
address = address.slice(index + instruction[2]);
} else {
url[key] = address.slice(index);
address = address.slice(0, index);
}
}
} else if ((index = parse.exec(address))) {
url[key] = index[1];
address = address.slice(0, index.index);
}
url[key] = url[key] || (
relative && instruction[3] ? location[key] || '' : ''
);
//
// Hostname, host and protocol should be lowercased so they can be used to
// create a proper `origin`.
//
if (instruction[4]) url[key] = url[key].toLowerCase();
}
//
// Also parse the supplied query string in to an object. If we're supplied
// with a custom parser as function use that instead of the default build-in
// parser.
//
if (parser) url.query = parser(url.query);
//
// If the URL is relative, resolve the pathname against the base URL.
//
if (
relative
&& location.slashes
&& url.pathname.charAt(0) !== '/'
&& (url.pathname !== '' || location.pathname !== '')
) {
url.pathname = resolve(url.pathname, location.pathname);
}
//
// We should not add port numbers if they are already the default port number
// for a given protocol. As the host also contains the port number we're going
// override it with the hostname which contains no port number.
//
if (!required(url.port, url.protocol)) {
url.host = url.hostname;
url.port = '';
}
//
// Parse down the `auth` for the username and password.
//
url.username = url.password = '';
if (url.auth) {
instruction = url.auth.split(':');
url.username = instruction[0] || '';
url.password = instruction[1] || '';
}
url.origin = url.protocol && url.host && url.protocol !== 'file:'
? url.protocol +'//'+ url.host
: 'null';
//
// The href is just the compiled result.
//
url.href = url.toString();
}
/**
* This is convenience method for changing properties in the URL instance to
* insure that they all propagate correctly.
*
* @param {String} part Property we need to adjust.
* @param {Mixed} value The newly assigned value.
* @param {Boolean|Function} fn When setting the query, it will be the function
* used to parse the query.
* When setting the protocol, double slash will be
* removed from the final url if it is true.
* @returns {URL}
* @api public
*/
function set(part, value, fn) {
var url = this;
switch (part) {
case 'query':
if ('string' === typeof value && value.length) {
value = (fn || qs.parse)(value);
}
url[part] = value;
break;
case 'port':
url[part] = value;
if (!required(value, url.protocol)) {
url.host = url.hostname;
url[part] = '';
} else if (value) {
url.host = url.hostname +':'+ value;
}
break;
case 'hostname':
url[part] = value;
if (url.port) value += ':'+ url.port;
url.host = value;
break;
case 'host':
url[part] = value;
if (/:\d+$/.test(value)) {
value = value.split(':');
url.port = value.pop();
url.hostname = value.join(':');
} else {
url.hostname = value;
url.port = '';
}
break;
case 'protocol':
url.protocol = value.toLowerCase();
url.slashes = !fn;
break;
case 'pathname':
url.pathname = value.length && value.charAt(0) !== '/' ? '/' + value : value;
break;
default:
url[part] = value;
}
for (var i = 0; i < rules.length; i++) {
var ins = rules[i];
if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase();
}
url.origin = url.protocol && url.host && url.protocol !== 'file:'
? url.protocol +'//'+ url.host
: 'null';
url.href = url.toString();
return url;
}
/**
* Transform the properties back in to a valid and full URL string.
*
* @param {Function} stringify Optional query stringify function.
* @returns {String}
* @api public
*/
function toString(stringify) {
if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;
var query
, url = this
, protocol = url.protocol;
if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':';
var result = protocol + (url.slashes ? '//' : '');
if (url.username) {
result += url.username;
if (url.password) result += ':'+ url.password;
result += '@';
}
result += url.host + url.pathname;
query = 'object' === typeof url.query ? stringify(url.query) : url.query;
if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;
if (url.hash) result += url.hash;
return result;
}
URL.prototype = { set: set, toString: toString };
//
// Expose the URL parser and some additional properties that might be useful for
// others or testing.
//
URL.extractProtocol = extractProtocol;
URL.location = lolcation;
URL.qs = qs;
module.exports = URL;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"querystringify":92,"requires-port":36}],92:[function(require,module,exports){
'use strict';
var has = Object.prototype.hasOwnProperty;
/**
* Decode a URI encoded string.
*
* @param {String} input The URI encoded string.
* @returns {String} The decoded string.
* @api private
*/
function decode(input) {
return decodeURIComponent(input.replace(/\+/g, ' '));
}
/**
* Simple query string parser.
*
* @param {String} query The query string that needs to be parsed.
* @returns {Object}
* @api public
*/
function querystring(query) {
var parser = /([^=?&]+)=?([^&]*)/g
, result = {}
, part;
//
// Little nifty parsing hack, leverage the fact that RegExp.exec increments
// the lastIndex property so we can continue executing this loop until we've
// parsed all results.
//
for (;
part = parser.exec(query);
result[decode(part[1])] = decode(part[2])
);
return result;
}
/**
* Transform a query string to an object.
*
* @param {Object} obj Object that should be transformed.
* @param {String} prefix Optional prefix.
* @returns {String}
* @api public
*/
function querystringify(obj, prefix) {
prefix = prefix || '';
var pairs = [];
//
// Optionally prefix with a '?' if needed
//
if ('string' !== typeof prefix) prefix = '?';
for (var key in obj) {
if (has.call(obj, key)) {
pairs.push(encodeURIComponent(key) +'='+ encodeURIComponent(obj[key]));
}
}
return pairs.length ? prefix + pairs.join('&') : '';
}
//
// Expose the module.
//
exports.stringify = querystringify;
exports.parse = querystring;
},{}],93:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
// Shimming starts here.
(function() {
// Utils.
var utils = require('./utils');
var logging = utils.log;
var browserDetails = utils.browserDetails;
// Export to the adapter global object visible in the browser.
module.exports.browserDetails = browserDetails;
module.exports.extractVersion = utils.extractVersion;
module.exports.disableLog = utils.disableLog;
// Uncomment the line below if you want logging to occur, including logging
// for the switch statement below. Can also be turned on in the browser via
// adapter.disableLog(false), but then logging from the switch statement below
// will not appear.
// require('./utils').disableLog(false);
// Browser shims.
var chromeShim = require('./chrome/chrome_shim') || null;
var edgeShim = require('./edge/edge_shim') || null;
var firefoxShim = require('./firefox/firefox_shim') || null;
var safariShim = require('./safari/safari_shim') || null;
// Shim browser if found.
switch (browserDetails.browser) {
case 'chrome':
if (!chromeShim || !chromeShim.shimPeerConnection) {
logging('Chrome shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming chrome.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = chromeShim;
chromeShim.shimGetUserMedia();
chromeShim.shimMediaStream();
utils.shimCreateObjectURL();
chromeShim.shimSourceObject();
chromeShim.shimPeerConnection();
chromeShim.shimOnTrack();
chromeShim.shimGetSendersWithDtmf();
break;
case 'firefox':
if (!firefoxShim || !firefoxShim.shimPeerConnection) {
logging('Firefox shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming firefox.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = firefoxShim;
firefoxShim.shimGetUserMedia();
utils.shimCreateObjectURL();
firefoxShim.shimSourceObject();
firefoxShim.shimPeerConnection();
firefoxShim.shimOnTrack();
break;
case 'edge':
if (!edgeShim || !edgeShim.shimPeerConnection) {
logging('MS edge shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming edge.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = edgeShim;
edgeShim.shimGetUserMedia();
utils.shimCreateObjectURL();
edgeShim.shimPeerConnection();
edgeShim.shimReplaceTrack();
break;
case 'safari':
if (!safariShim) {
logging('Safari shim is not included in this adapter release.');
return;
}
logging('adapter.js shimming safari.');
// Export to the adapter global object visible in the browser.
module.exports.browserShim = safariShim;
safariShim.shimOnAddStream();
safariShim.shimGetUserMedia();
break;
default:
logging('Unsupported browser!');
}
})();
},{"./chrome/chrome_shim":94,"./edge/edge_shim":96,"./firefox/firefox_shim":99,"./safari/safari_shim":101,"./utils":102}],94:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logging = require('../utils.js').log;
var browserDetails = require('../utils.js').browserDetails;
var chromeShim = {
shimMediaStream: function() {
window.MediaStream = window.MediaStream || window.webkitMediaStream;
},
shimOnTrack: function() {
if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
window.RTCPeerConnection.prototype)) {
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
get: function() {
return this._ontrack;
},
set: function(f) {
var self = this;
if (this._ontrack) {
this.removeEventListener('track', this._ontrack);
this.removeEventListener('addstream', this._ontrackpoly);
}
this.addEventListener('track', this._ontrack = f);
this.addEventListener('addstream', this._ontrackpoly = function(e) {
// onaddstream does not fire when a track is added to an existing
// stream. But stream.onaddtrack is implemented so we use that.
e.stream.addEventListener('addtrack', function(te) {
var event = new Event('track');
event.track = te.track;
event.receiver = {track: te.track};
event.streams = [e.stream];
self.dispatchEvent(event);
});
e.stream.getTracks().forEach(function(track) {
var event = new Event('track');
event.track = track;
event.receiver = {track: track};
event.streams = [e.stream];
this.dispatchEvent(event);
}.bind(this));
}.bind(this));
}
});
}
},
shimGetSendersWithDtmf: function() {
if (typeof window === 'object' && window.RTCPeerConnection &&
!('getSenders' in RTCPeerConnection.prototype) &&
'createDTMFSender' in RTCPeerConnection.prototype) {
RTCPeerConnection.prototype.getSenders = function() {
return this._senders;
};
var origAddStream = RTCPeerConnection.prototype.addStream;
var origRemoveStream = RTCPeerConnection.prototype.removeStream;
RTCPeerConnection.prototype.addStream = function(stream) {
var pc = this;
pc._senders = pc._senders || [];
origAddStream.apply(pc, [stream]);
stream.getTracks().forEach(function(track) {
pc._senders.push({
track: track,
get dtmf() {
if (this._dtmf === undefined) {
if (track.kind === 'audio') {
this._dtmf = pc.createDTMFSender(track);
} else {
this._dtmf = null;
}
}
return this._dtmf;
}
});
});
};
RTCPeerConnection.prototype.removeStream = function(stream) {
var pc = this;
pc._senders = pc._senders || [];
origRemoveStream.apply(pc, [stream]);
stream.getTracks().forEach(function(track) {
var sender = pc._senders.find(function(s) {
return s.track === track;
});
if (sender) {
pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender
}
});
};
}
},
shimSourceObject: function() {
if (typeof window === 'object') {
if (window.HTMLMediaElement &&
!('srcObject' in window.HTMLMediaElement.prototype)) {
// Shim the srcObject property, once, when HTMLMediaElement is found.
Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
get: function() {
return this._srcObject;
},
set: function(stream) {
var self = this;
// Use _srcObject as a private property for this shim
this._srcObject = stream;
if (this.src) {
URL.revokeObjectURL(this.src);
}
if (!stream) {
this.src = '';
return undefined;
}
this.src = URL.createObjectURL(stream);
// We need to recreate the blob url when a track is added or
// removed. Doing it manually since we want to avoid a recursion.
stream.addEventListener('addtrack', function() {
if (self.src) {
URL.revokeObjectURL(self.src);
}
self.src = URL.createObjectURL(stream);
});
stream.addEventListener('removetrack', function() {
if (self.src) {
URL.revokeObjectURL(self.src);
}
self.src = URL.createObjectURL(stream);
});
}
});
}
}
},
shimPeerConnection: function() {
// The RTCPeerConnection object.
if (!window.RTCPeerConnection) {
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
// Translate iceTransportPolicy to iceTransports,
// see https://code.google.com/p/webrtc/issues/detail?id=4869
// this was fixed in M56 along with unprefixing RTCPeerConnection.
logging('PeerConnection');
if (pcConfig && pcConfig.iceTransportPolicy) {
pcConfig.iceTransports = pcConfig.iceTransportPolicy;
}
return new webkitRTCPeerConnection(pcConfig, pcConstraints);
};
window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype;
// wrap static methods. Currently just generateCertificate.
if (webkitRTCPeerConnection.generateCertificate) {
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
get: function() {
return webkitRTCPeerConnection.generateCertificate;
}
});
}
} else {
// migrate from non-spec RTCIceServer.url to RTCIceServer.urls
var OrigPeerConnection = RTCPeerConnection;
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
if (pcConfig && pcConfig.iceServers) {
var newIceServers = [];
for (var i = 0; i < pcConfig.iceServers.length; i++) {
var server = pcConfig.iceServers[i];
if (!server.hasOwnProperty('urls') &&
server.hasOwnProperty('url')) {
console.warn('RTCIceServer.url is deprecated! Use urls instead.');
server = JSON.parse(JSON.stringify(server));
server.urls = server.url;
newIceServers.push(server);
} else {
newIceServers.push(pcConfig.iceServers[i]);
}
}
pcConfig.iceServers = newIceServers;
}
return new OrigPeerConnection(pcConfig, pcConstraints);
};
window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;
// wrap static methods. Currently just generateCertificate.
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
get: function() {
return OrigPeerConnection.generateCertificate;
}
});
}
var origGetStats = RTCPeerConnection.prototype.getStats;
RTCPeerConnection.prototype.getStats = function(selector,
successCallback, errorCallback) {
var self = this;
var args = arguments;
// If selector is a function then we are in the old style stats so just
// pass back the original getStats format to avoid breaking old users.
if (arguments.length > 0 && typeof selector === 'function') {
return origGetStats.apply(this, arguments);
}
// When spec-style getStats is supported, return those when called with
// either no arguments or the selector argument is null.
if (origGetStats.length === 0 && (arguments.length === 0 ||
typeof arguments[0] !== 'function')) {
return origGetStats.apply(this, []);
}
var fixChromeStats_ = function(response) {
var standardReport = {};
var reports = response.result();
reports.forEach(function(report) {
var standardStats = {
id: report.id,
timestamp: report.timestamp,
type: {
localcandidate: 'local-candidate',
remotecandidate: 'remote-candidate'
}[report.type] || report.type
};
report.names().forEach(function(name) {
standardStats[name] = report.stat(name);
});
standardReport[standardStats.id] = standardStats;
});
return standardReport;
};
// shim getStats with maplike support
var makeMapStats = function(stats) {
return new Map(Object.keys(stats).map(function(key) {
return[key, stats[key]];
}));
};
if (arguments.length >= 2) {
var successCallbackWrapper_ = function(response) {
args[1](makeMapStats(fixChromeStats_(response)));
};
return origGetStats.apply(this, [successCallbackWrapper_,
arguments[0]]);
}
// promise-support
return new Promise(function(resolve, reject) {
origGetStats.apply(self, [
function(response) {
resolve(makeMapStats(fixChromeStats_(response)));
}, reject]);
}).then(successCallback, errorCallback);
};
// add promise support -- natively available in Chrome 51
if (browserDetails.version < 51) {
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
.forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
var args = arguments;
var self = this;
var promise = new Promise(function(resolve, reject) {
nativeMethod.apply(self, [args[0], resolve, reject]);
});
if (args.length < 2) {
return promise;
}
return promise.then(function() {
args[1].apply(null, []);
},
function(err) {
if (args.length >= 3) {
args[2].apply(null, [err]);
}
});
};
});
}
// promise support for createOffer and createAnswer. Available (without
// bugs) since M52: crbug/619289
if (browserDetails.version < 52) {
['createOffer', 'createAnswer'].forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
var self = this;
if (arguments.length < 1 || (arguments.length === 1 &&
typeof arguments[0] === 'object')) {
var opts = arguments.length === 1 ? arguments[0] : undefined;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [resolve, reject, opts]);
});
}
return nativeMethod.apply(this, arguments);
};
});
}
// shim implicit creation of RTCSessionDescription/RTCIceCandidate
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
.forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
arguments[0] = new ((method === 'addIceCandidate') ?
RTCIceCandidate : RTCSessionDescription)(arguments[0]);
return nativeMethod.apply(this, arguments);
};
});
// support for addIceCandidate(null or undefined)
var nativeAddIceCandidate =
RTCPeerConnection.prototype.addIceCandidate;
RTCPeerConnection.prototype.addIceCandidate = function() {
if (!arguments[0]) {
if (arguments[1]) {
arguments[1].apply(null);
}
return Promise.resolve();
}
return nativeAddIceCandidate.apply(this, arguments);
};
}
};
// Expose public methods.
module.exports = {
shimMediaStream: chromeShim.shimMediaStream,
shimOnTrack: chromeShim.shimOnTrack,
shimGetSendersWithDtmf: chromeShim.shimGetSendersWithDtmf,
shimSourceObject: chromeShim.shimSourceObject,
shimPeerConnection: chromeShim.shimPeerConnection,
shimGetUserMedia: require('./getusermedia')
};
},{"../utils.js":102,"./getusermedia":95}],95:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logging = require('../utils.js').log;
var browserDetails = require('../utils.js').browserDetails;
// Expose public methods.
module.exports = function() {
var constraintsToChrome_ = function(c) {
if (typeof c !== 'object' || c.mandatory || c.optional) {
return c;
}
var cc = {};
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
return;
}
var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
if (r.exact !== undefined && typeof r.exact === 'number') {
r.min = r.max = r.exact;
}
var oldname_ = function(prefix, name) {
if (prefix) {
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
}
return (name === 'deviceId') ? 'sourceId' : name;
};
if (r.ideal !== undefined) {
cc.optional = cc.optional || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[oldname_('min', key)] = r.ideal;
cc.optional.push(oc);
oc = {};
oc[oldname_('max', key)] = r.ideal;
cc.optional.push(oc);
} else {
oc[oldname_('', key)] = r.ideal;
cc.optional.push(oc);
}
}
if (r.exact !== undefined && typeof r.exact !== 'number') {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname_('', key)] = r.exact;
} else {
['min', 'max'].forEach(function(mix) {
if (r[mix] !== undefined) {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname_(mix, key)] = r[mix];
}
});
}
});
if (c.advanced) {
cc.optional = (cc.optional || []).concat(c.advanced);
}
return cc;
};
var shimConstraints_ = function(constraints, func) {
constraints = JSON.parse(JSON.stringify(constraints));
if (constraints && constraints.audio) {
constraints.audio = constraintsToChrome_(constraints.audio);
}
if (constraints && typeof constraints.video === 'object') {
// Shim facingMode for mobile & surface pro.
var face = constraints.video.facingMode;
face = face && ((typeof face === 'object') ? face : {ideal: face});
var getSupportedFacingModeLies = browserDetails.version < 61;
if ((face && (face.exact === 'user' || face.exact === 'environment' ||
face.ideal === 'user' || face.ideal === 'environment')) &&
!(navigator.mediaDevices.getSupportedConstraints &&
navigator.mediaDevices.getSupportedConstraints().facingMode &&
!getSupportedFacingModeLies)) {
delete constraints.video.facingMode;
var matches;
if (face.exact === 'environment' || face.ideal === 'environment') {
matches = ['back', 'rear'];
} else if (face.exact === 'user' || face.ideal === 'user') {
matches = ['front'];
}
if (matches) {
// Look for matches in label, or use last cam for back (typical).
return navigator.mediaDevices.enumerateDevices()
.then(function(devices) {
devices = devices.filter(function(d) {
return d.kind === 'videoinput';
});
var dev = devices.find(function(d) {
return matches.some(function(match) {
return d.label.toLowerCase().indexOf(match) !== -1;
});
});
if (!dev && devices.length && matches.indexOf('back') !== -1) {
dev = devices[devices.length - 1]; // more likely the back cam
}
if (dev) {
constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :
{ideal: dev.deviceId};
}
constraints.video = constraintsToChrome_(constraints.video);
logging('chrome: ' + JSON.stringify(constraints));
return func(constraints);
});
}
}
constraints.video = constraintsToChrome_(constraints.video);
}
logging('chrome: ' + JSON.stringify(constraints));
return func(constraints);
};
var shimError_ = function(e) {
return {
name: {
PermissionDeniedError: 'NotAllowedError',
ConstraintNotSatisfiedError: 'OverconstrainedError'
}[e.name] || e.name,
message: e.message,
constraint: e.constraintName,
toString: function() {
return this.name + (this.message && ': ') + this.message;
}
};
};
var getUserMedia_ = function(constraints, onSuccess, onError) {
shimConstraints_(constraints, function(c) {
navigator.webkitGetUserMedia(c, onSuccess, function(e) {
onError(shimError_(e));
});
});
};
navigator.getUserMedia = getUserMedia_;
// Returns the result of getUserMedia as a Promise.
var getUserMediaPromise_ = function(constraints) {
return new Promise(function(resolve, reject) {
navigator.getUserMedia(constraints, resolve, reject);
});
};
if (!navigator.mediaDevices) {
navigator.mediaDevices = {
getUserMedia: getUserMediaPromise_,
enumerateDevices: function() {
return new Promise(function(resolve) {
var kinds = {audio: 'audioinput', video: 'videoinput'};
return MediaStreamTrack.getSources(function(devices) {
resolve(devices.map(function(device) {
return {label: device.label,
kind: kinds[device.kind],
deviceId: device.id,
groupId: ''};
}));
});
});
},
getSupportedConstraints: function() {
return {
deviceId: true, echoCancellation: true, facingMode: true,
frameRate: true, height: true, width: true
};
}
};
}
// A shim for getUserMedia method on the mediaDevices object.
// TODO(KaptenJansson) remove once implemented in Chrome stable.
if (!navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia = function(constraints) {
return getUserMediaPromise_(constraints);
};
} else {
// Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
// function which returns a Promise, it does not accept spec-style
// constraints.
var origGetUserMedia = navigator.mediaDevices.getUserMedia.
bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = function(cs) {
return shimConstraints_(cs, function(c) {
return origGetUserMedia(c).then(function(stream) {
if (c.audio && !stream.getAudioTracks().length ||
c.video && !stream.getVideoTracks().length) {
stream.getTracks().forEach(function(track) {
track.stop();
});
throw new DOMException('', 'NotFoundError');
}
return stream;
}, function(e) {
return Promise.reject(shimError_(e));
});
});
};
}
// Dummy devicechange event methods.
// TODO(KaptenJansson) remove once implemented in Chrome stable.
if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
navigator.mediaDevices.addEventListener = function() {
logging('Dummy mediaDevices.addEventListener called.');
};
}
if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
navigator.mediaDevices.removeEventListener = function() {
logging('Dummy mediaDevices.removeEventListener called.');
};
}
};
},{"../utils.js":102}],96:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var browserDetails = require('../utils').browserDetails;
var shimRTCPeerConnection = require('./rtcpeerconnection_shim');
module.exports = {
shimGetUserMedia: require('./getusermedia'),
shimPeerConnection: function() {
if (window.RTCIceGatherer) {
// ORTC defines an RTCIceCandidate object but no constructor.
// Not implemented in Edge.
if (!window.RTCIceCandidate) {
window.RTCIceCandidate = function(args) {
return args;
};
}
// ORTC does not have a session description object but
// other browsers (i.e. Chrome) that will support both PC and ORTC
// in the future might have this defined already.
if (!window.RTCSessionDescription) {
window.RTCSessionDescription = function(args) {
return args;
};
}
// this adds an additional event listener to MediaStrackTrack that signals
// when a tracks enabled property was changed. Workaround for a bug in
// addStream, see below. No longer required in 15025+
if (browserDetails.version < 15025) {
var origMSTEnabled = Object.getOwnPropertyDescriptor(
MediaStreamTrack.prototype, 'enabled');
Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
set: function(value) {
origMSTEnabled.set.call(this, value);
var ev = new Event('enabled');
ev.enabled = value;
this.dispatchEvent(ev);
}
});
}
}
window.RTCPeerConnection = shimRTCPeerConnection(browserDetails.version);
},
shimReplaceTrack: function() {
// ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614
if (window.RTCRtpSender && !('replaceTrack' in RTCRtpSender.prototype)) {
RTCRtpSender.prototype.replaceTrack = RTCRtpSender.prototype.setTrack;
}
}
};
},{"../utils":102,"./getusermedia":97,"./rtcpeerconnection_shim":98}],97:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
// Expose public methods.
module.exports = function() {
var shimError_ = function(e) {
return {
name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,
message: e.message,
constraint: e.constraint,
toString: function() {
return this.name;
}
};
};
// getUserMedia error shim.
var origGetUserMedia = navigator.mediaDevices.getUserMedia.
bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = function(c) {
return origGetUserMedia(c).catch(function(e) {
return Promise.reject(shimError_(e));
});
};
};
},{}],98:[function(require,module,exports){
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var SDPUtils = require('sdp');
// sort tracks such that they follow an a-v-a-v...
// pattern.
function sortTracks(tracks) {
var audioTracks = tracks.filter(function(track) {
return track.kind === 'audio';
});
var videoTracks = tracks.filter(function(track) {
return track.kind === 'video';
});
tracks = [];
while (audioTracks.length || videoTracks.length) {
if (audioTracks.length) {
tracks.push(audioTracks.shift());
}
if (videoTracks.length) {
tracks.push(videoTracks.shift());
}
}
return tracks;
}
// Edge does not like
// 1) stun:
// 2) turn: that does not have all of turn:host:port?transport=udp
// 3) turn: with ipv6 addresses
// 4) turn: occurring muliple times
function filterIceServers(iceServers, edgeVersion) {
var hasTurn = false;
iceServers = JSON.parse(JSON.stringify(iceServers));
return iceServers.filter(function(server) {
if (server && (server.urls || server.url)) {
var urls = server.urls || server.url;
if (server.url && !server.urls) {
console.warn('RTCIceServer.url is deprecated! Use urls instead.');
}
var isString = typeof urls === 'string';
if (isString) {
urls = [urls];
}
urls = urls.filter(function(url) {
var validTurn = url.indexOf('turn:') === 0 &&
url.indexOf('transport=udp') !== -1 &&
url.indexOf('turn:[') === -1 &&
!hasTurn;
if (validTurn) {
hasTurn = true;
return true;
}
return url.indexOf('stun:') === 0 && edgeVersion >= 14393;
});
delete server.url;
server.urls = isString ? urls[0] : urls;
return !!urls.length;
}
return false;
});
}
module.exports = function(edgeVersion) {
var RTCPeerConnection = function(config) {
var self = this;
var _eventTarget = document.createDocumentFragment();
['addEventListener', 'removeEventListener', 'dispatchEvent']
.forEach(function(method) {
self[method] = _eventTarget[method].bind(_eventTarget);
});
this.onicecandidate = null;
this.onaddstream = null;
this.ontrack = null;
this.onremovestream = null;
this.onsignalingstatechange = null;
this.oniceconnectionstatechange = null;
this.onicegatheringstatechange = null;
this.onnegotiationneeded = null;
this.ondatachannel = null;
this.canTrickleIceCandidates = null;
this.localStreams = [];
this.remoteStreams = [];
this.getLocalStreams = function() {
return self.localStreams;
};
this.getRemoteStreams = function() {
return self.remoteStreams;
};
this.localDescription = new RTCSessionDescription({
type: '',
sdp: ''
});
this.remoteDescription = new RTCSessionDescription({
type: '',
sdp: ''
});
this.signalingState = 'stable';
this.iceConnectionState = 'new';
this.iceGatheringState = 'new';
this.iceOptions = {
gatherPolicy: 'all',
iceServers: []
};
if (config && config.iceTransportPolicy) {
switch (config.iceTransportPolicy) {
case 'all':
case 'relay':
this.iceOptions.gatherPolicy = config.iceTransportPolicy;
break;
default:
// don't set iceTransportPolicy.
break;
}
}
this.usingBundle = config && config.bundlePolicy === 'max-bundle';
if (config && config.iceServers) {
this.iceOptions.iceServers = filterIceServers(config.iceServers,
edgeVersion);
}
this._config = config || {};
// per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
// everything that is needed to describe a SDP m-line.
this.transceivers = [];
// since the iceGatherer is currently created in createOffer but we
// must not emit candidates until after setLocalDescription we buffer
// them in this array.
this._localIceCandidatesBuffer = [];
};
RTCPeerConnection.prototype._emitGatheringStateChange = function() {
var event = new Event('icegatheringstatechange');
this.dispatchEvent(event);
if (this.onicegatheringstatechange !== null) {
this.onicegatheringstatechange(event);
}
};
RTCPeerConnection.prototype._emitBufferedCandidates = function() {
var self = this;
var sections = SDPUtils.splitSections(self.localDescription.sdp);
// FIXME: need to apply ice candidates in a way which is async but
// in-order
this._localIceCandidatesBuffer.forEach(function(event) {
var end = !event.candidate || Object.keys(event.candidate).length === 0;
if (end) {
for (var j = 1; j < sections.length; j++) {
if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
sections[j] += 'a=end-of-candidates\r\n';
}
}
} else {
sections[event.candidate.sdpMLineIndex + 1] +=
'a=' + event.candidate.candidate + '\r\n';
}
self.localDescription.sdp = sections.join('');
self.dispatchEvent(event);
if (self.onicecandidate !== null) {
self.onicecandidate(event);
}
if (!event.candidate && self.iceGatheringState !== 'complete') {
var complete = self.transceivers.every(function(transceiver) {
return transceiver.iceGatherer &&
transceiver.iceGatherer.state === 'completed';
});
if (complete && self.iceGatheringStateChange !== 'complete') {
self.iceGatheringState = 'complete';
self._emitGatheringStateChange();
}
}
});
this._localIceCandidatesBuffer = [];
};
RTCPeerConnection.prototype.getConfiguration = function() {
return this._config;
};
RTCPeerConnection.prototype.addStream = function(stream) {
if (edgeVersion >= 15025) {
this.localStreams.push(stream);
} else {
// Clone is necessary for local demos mostly, attaching directly
// to two different senders does not work (build 10547).
// Fixed in 15025 (or earlier)
var clonedStream = stream.clone();
stream.getTracks().forEach(function(track, idx) {
var clonedTrack = clonedStream.getTracks()[idx];
track.addEventListener('enabled', function(event) {
clonedTrack.enabled = event.enabled;
});
});
this.localStreams.push(clonedStream);
}
this._maybeFireNegotiationNeeded();
};
RTCPeerConnection.prototype.removeStream = function(stream) {
var idx = this.localStreams.indexOf(stream);
if (idx > -1) {
this.localStreams.splice(idx, 1);
this._maybeFireNegotiationNeeded();
}
};
RTCPeerConnection.prototype.getSenders = function() {
return this.transceivers.filter(function(transceiver) {
return !!transceiver.rtpSender;
})
.map(function(transceiver) {
return transceiver.rtpSender;
});
};
RTCPeerConnection.prototype.getReceivers = function() {
return this.transceivers.filter(function(transceiver) {
return !!transceiver.rtpReceiver;
})
.map(function(transceiver) {
return transceiver.rtpReceiver;
});
};
// Determines the intersection of local and remote capabilities.
RTCPeerConnection.prototype._getCommonCapabilities = function(
localCapabilities, remoteCapabilities) {
var commonCapabilities = {
codecs: [],
headerExtensions: [],
fecMechanisms: []
};
var findCodecByPayloadType = function(pt, codecs) {
pt = parseInt(pt, 10);
for (var i = 0; i < codecs.length; i++) {
if (codecs[i].payloadType === pt ||
codecs[i].preferredPayloadType === pt) {
return codecs[i];
}
}
};
var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {
var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);
var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);
return lCodec && rCodec &&
lCodec.name.toLowerCase() === rCodec.name.toLowerCase();
};
localCapabilities.codecs.forEach(function(lCodec) {
for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
var rCodec = remoteCapabilities.codecs[i];
if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
lCodec.clockRate === rCodec.clockRate) {
if (lCodec.name.toLowerCase() === 'rtx' &&
lCodec.parameters && rCodec.parameters.apt) {
// for RTX we need to find the local rtx that has a apt
// which points to the same local codec as the remote one.
if (!rtxCapabilityMatches(lCodec, rCodec,
localCapabilities.codecs, remoteCapabilities.codecs)) {
continue;
}
}
rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy
// number of channels is the highest common number of channels
rCodec.numChannels = Math.min(lCodec.numChannels,
rCodec.numChannels);
// push rCodec so we reply with offerer payload type
commonCapabilities.codecs.push(rCodec);
// determine common feedback mechanisms
rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {
for (var j = 0; j < lCodec.rtcpFeedback.length; j++) {
if (lCodec.rtcpFeedback[j].type === fb.type &&
lCodec.rtcpFeedback[j].parameter === fb.parameter) {
return true;
}
}
return false;
});
// FIXME: also need to determine .parameters
// see https://github.com/openpeer/ortc/issues/569
break;
}
}
});
localCapabilities.headerExtensions
.forEach(function(lHeaderExtension) {
for (var i = 0; i < remoteCapabilities.headerExtensions.length;
i++) {
var rHeaderExtension = remoteCapabilities.headerExtensions[i];
if (lHeaderExtension.uri === rHeaderExtension.uri) {
commonCapabilities.headerExtensions.push(rHeaderExtension);
break;
}
}
});
// FIXME: fecMechanisms
return commonCapabilities;
};
// Create ICE gatherer, ICE transport and DTLS transport.
RTCPeerConnection.prototype._createIceAndDtlsTransports = function(mid,
sdpMLineIndex) {
var self = this;
var iceGatherer = new RTCIceGatherer(self.iceOptions);
var iceTransport = new RTCIceTransport(iceGatherer);
iceGatherer.onlocalcandidate = function(evt) {
var event = new Event('icecandidate');
event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
var cand = evt.candidate;
var end = !cand || Object.keys(cand).length === 0;
// Edge emits an empty object for RTCIceCandidateComplete‥
if (end) {
// polyfill since RTCIceGatherer.state is not implemented in
// Edge 10547 yet.
if (iceGatherer.state === undefined) {
iceGatherer.state = 'completed';
}
} else {
// RTCIceCandidate doesn't have a component, needs to be added
cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
event.candidate.candidate = SDPUtils.writeCandidate(cand);
}
// update local description.
var sections = SDPUtils.splitSections(self.localDescription.sdp);
if (!end) {
sections[event.candidate.sdpMLineIndex + 1] +=
'a=' + event.candidate.candidate + '\r\n';
} else {
sections[event.candidate.sdpMLineIndex + 1] +=
'a=end-of-candidates\r\n';
}
self.localDescription.sdp = sections.join('');
var transceivers = self._pendingOffer ? self._pendingOffer :
self.transceivers;
var complete = transceivers.every(function(transceiver) {
return transceiver.iceGatherer &&
transceiver.iceGatherer.state === 'completed';
});
// Emit candidate if localDescription is set.
// Also emits null candidate when all gatherers are complete.
switch (self.iceGatheringState) {
case 'new':
if (!end) {
self._localIceCandidatesBuffer.push(event);
}
if (end && complete) {
self._localIceCandidatesBuffer.push(
new Event('icecandidate'));
}
break;
case 'gathering':
self._emitBufferedCandidates();
if (!end) {
self.dispatchEvent(event);
if (self.onicecandidate !== null) {
self.onicecandidate(event);
}
}
if (complete) {
self.dispatchEvent(new Event('icecandidate'));
if (self.onicecandidate !== null) {
self.onicecandidate(new Event('icecandidate'));
}
self.iceGatheringState = 'complete';
self._emitGatheringStateChange();
}
break;
case 'complete':
// should not happen... currently!
break;
default: // no-op.
break;
}
};
iceTransport.onicestatechange = function() {
self._updateConnectionState();
};
var dtlsTransport = new RTCDtlsTransport(iceTransport);
dtlsTransport.ondtlsstatechange = function() {
self._updateConnectionState();
};
dtlsTransport.onerror = function() {
// onerror does not set state to failed by itself.
dtlsTransport.state = 'failed';
self._updateConnectionState();
};
return {
iceGatherer: iceGatherer,
iceTransport: iceTransport,
dtlsTransport: dtlsTransport
};
};
// Destroy ICE gatherer, ICE transport and DTLS transport.
// Without triggering the callbacks.
RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(
sdpMLineIndex) {
var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;
if (iceGatherer) {
delete iceGatherer.onlocalcandidate;
delete this.transceivers[sdpMLineIndex].iceGatherer;
}
var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;
if (iceTransport) {
delete iceTransport.onicestatechange;
delete this.transceivers[sdpMLineIndex].iceTransport;
}
var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;
if (dtlsTransport) {
delete dtlsTransport.ondtlssttatechange;
delete dtlsTransport.onerror;
delete this.transceivers[sdpMLineIndex].dtlsTransport;
}
};
// Start the RTP Sender and Receiver for a transceiver.
RTCPeerConnection.prototype._transceive = function(transceiver,
send, recv) {
var params = this._getCommonCapabilities(transceiver.localCapabilities,
transceiver.remoteCapabilities);
if (send && transceiver.rtpSender) {
params.encodings = transceiver.sendEncodingParameters;
params.rtcp = {
cname: SDPUtils.localCName,
compound: transceiver.rtcpParameters.compound
};
if (transceiver.recvEncodingParameters.length) {
params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
}
transceiver.rtpSender.send(params);
}
if (recv && transceiver.rtpReceiver) {
// remove RTX field in Edge 14942
if (transceiver.kind === 'video'
&& transceiver.recvEncodingParameters
&& edgeVersion < 15019) {
transceiver.recvEncodingParameters.forEach(function(p) {
delete p.rtx;
});
}
params.encodings = transceiver.recvEncodingParameters;
params.rtcp = {
cname: transceiver.rtcpParameters.cname,
compound: transceiver.rtcpParameters.compound
};
if (transceiver.sendEncodingParameters.length) {
params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
}
transceiver.rtpReceiver.receive(params);
}
};
RTCPeerConnection.prototype.setLocalDescription = function(description) {
var self = this;
var sections;
var sessionpart;
if (description.type === 'offer') {
// FIXME: What was the purpose of this empty if statement?
// if (!this._pendingOffer) {
// } else {
if (this._pendingOffer) {
// VERY limited support for SDP munging. Limited to:
// * changing the order of codecs
sections = SDPUtils.splitSections(description.sdp);
sessionpart = sections.shift();
sections.forEach(function(mediaSection, sdpMLineIndex) {
var caps = SDPUtils.parseRtpParameters(mediaSection);
self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
});
this.transceivers = this._pendingOffer;
delete this._pendingOffer;
}
} else if (description.type === 'answer') {
sections = SDPUtils.splitSections(self.remoteDescription.sdp);
sessionpart = sections.shift();
var isIceLite = SDPUtils.matchPrefix(sessionpart,
'a=ice-lite').length > 0;
sections.forEach(function(mediaSection, sdpMLineIndex) {
var transceiver = self.transceivers[sdpMLineIndex];
var iceGatherer = transceiver.iceGatherer;
var iceTransport = transceiver.iceTransport;
var dtlsTransport = transceiver.dtlsTransport;
var localCapabilities = transceiver.localCapabilities;
var remoteCapabilities = transceiver.remoteCapabilities;
var rejected = mediaSection.split('\n', 1)[0]
.split(' ', 2)[1] === '0';
if (!rejected && !transceiver.isDatachannel) {
var remoteIceParameters = SDPUtils.getIceParameters(
mediaSection, sessionpart);
var remoteDtlsParameters = SDPUtils.getDtlsParameters(
mediaSection, sessionpart);
if (isIceLite) {
remoteDtlsParameters.role = 'server';
}
if (!self.usingBundle || sdpMLineIndex === 0) {
iceTransport.start(iceGatherer, remoteIceParameters,
isIceLite ? 'controlling' : 'controlled');
dtlsTransport.start(remoteDtlsParameters);
}
// Calculate intersection of capabilities.
var params = self._getCommonCapabilities(localCapabilities,
remoteCapabilities);
// Start the RTCRtpSender. The RTCRtpReceiver for this
// transceiver has already been started in setRemoteDescription.
self._transceive(transceiver,
params.codecs.length > 0,
false);
}
});
}
this.localDescription = {
type: description.type,
sdp: description.sdp
};
switch (description.type) {
case 'offer':
this._updateSignalingState('have-local-offer');
break;
case 'answer':
this._updateSignalingState('stable');
break;
default:
throw new TypeError('unsupported type "' + description.type +
'"');
}
// If a success callback was provided, emit ICE candidates after it
// has been executed. Otherwise, emit callback after the Promise is
// resolved.
var hasCallback = arguments.length > 1 &&
typeof arguments[1] === 'function';
if (hasCallback) {
var cb = arguments[1];
window.setTimeout(function() {
cb();
if (self.iceGatheringState === 'new') {
self.iceGatheringState = 'gathering';
self._emitGatheringStateChange();
}
self._emitBufferedCandidates();
}, 0);
}
var p = Promise.resolve();
p.then(function() {
if (!hasCallback) {
if (self.iceGatheringState === 'new') {
self.iceGatheringState = 'gathering';
self._emitGatheringStateChange();
}
// Usually candidates will be emitted earlier.
window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
}
});
return p;
};
RTCPeerConnection.prototype.setRemoteDescription = function(description) {
var self = this;
var streams = {};
var receiverList = [];
var sections = SDPUtils.splitSections(description.sdp);
var sessionpart = sections.shift();
var isIceLite = SDPUtils.matchPrefix(sessionpart,
'a=ice-lite').length > 0;
var usingBundle = SDPUtils.matchPrefix(sessionpart,
'a=group:BUNDLE ').length > 0;
var iceOptions = SDPUtils.matchPrefix(sessionpart,
'a=ice-options:')[0];
if (iceOptions) {
this.canTrickleIceCandidates = iceOptions.substr(14).split(' ')
.indexOf('trickle') >= 0;
} else {
this.canTrickleIceCandidates = false;
}
sections.forEach(function(mediaSection, sdpMLineIndex) {
var lines = SDPUtils.splitLines(mediaSection);
var mline = lines[0].substr(2).split(' ');
var kind = mline[0];
var rejected = mline[1] === '0';
var direction = SDPUtils.getDirection(mediaSection, sessionpart);
var remoteMsid = SDPUtils.parseMsid(mediaSection);
var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:');
if (mid.length) {
mid = mid[0].substr(6);
} else {
mid = SDPUtils.generateIdentifier();
}
// Reject datachannels which are not implemented yet.
if (kind === 'application' && mline[2] === 'DTLS/SCTP') {
self.transceivers[sdpMLineIndex] = {
mid: mid,
isDatachannel: true
};
return;
}
var transceiver;
var iceGatherer;
var iceTransport;
var dtlsTransport;
var rtpSender;
var rtpReceiver;
var sendEncodingParameters;
var recvEncodingParameters;
var localCapabilities;
var track;
// FIXME: ensure the mediaSection has rtcp-mux set.
var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
var remoteIceParameters;
var remoteDtlsParameters;
if (!rejected) {
remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
sessionpart);
remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
sessionpart);
remoteDtlsParameters.role = 'client';
}
recvEncodingParameters =
SDPUtils.parseRtpEncodingParameters(mediaSection);
var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection);
var isComplete = SDPUtils.matchPrefix(mediaSection,
'a=end-of-candidates', sessionpart).length > 0;
var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
.map(function(cand) {
return SDPUtils.parseCandidate(cand);
})
.filter(function(cand) {
return cand.component === '1';
});
if (description.type === 'offer' && !rejected) {
var transports = usingBundle && sdpMLineIndex > 0 ? {
iceGatherer: self.transceivers[0].iceGatherer,
iceTransport: self.transceivers[0].iceTransport,
dtlsTransport: self.transceivers[0].dtlsTransport
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {
transports.iceTransport.setRemoteCandidates(cands);
}
localCapabilities = RTCRtpReceiver.getCapabilities(kind);
// filter RTX until additional stuff needed for RTX is implemented
// in adapter.js
if (edgeVersion < 15019) {
localCapabilities.codecs = localCapabilities.codecs.filter(
function(codec) {
return codec.name !== 'rtx';
});
}
sendEncodingParameters = [{
ssrc: (2 * sdpMLineIndex + 2) * 1001
}];
if (direction === 'sendrecv' || direction === 'sendonly') {
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport,
kind);
track = rtpReceiver.track;
// FIXME: does not work with Plan B.
if (remoteMsid) {
if (!streams[remoteMsid.stream]) {
streams[remoteMsid.stream] = new MediaStream();
Object.defineProperty(streams[remoteMsid.stream], 'id', {
get: function() {
return remoteMsid.stream;
}
});
}
Object.defineProperty(track, 'id', {
get: function() {
return remoteMsid.track;
}
});
streams[remoteMsid.stream].addTrack(track);
receiverList.push([track, rtpReceiver,
streams[remoteMsid.stream]]);
} else {
if (!streams.default) {
streams.default = new MediaStream();
}
streams.default.addTrack(track);
receiverList.push([track, rtpReceiver, streams.default]);
}
}
// FIXME: look at direction.
if (self.localStreams.length > 0 &&
self.localStreams[0].getTracks().length >= sdpMLineIndex) {
var localTrack;
if (kind === 'audio') {
localTrack = self.localStreams[0].getAudioTracks()[0];
} else if (kind === 'video') {
localTrack = self.localStreams[0].getVideoTracks()[0];
}
if (localTrack) {
// add RTX
if (edgeVersion >= 15019 && kind === 'video') {
sendEncodingParameters[0].rtx = {
ssrc: (2 * sdpMLineIndex + 2) * 1001 + 1
};
}
rtpSender = new RTCRtpSender(localTrack,
transports.dtlsTransport);
}
}
self.transceivers[sdpMLineIndex] = {
iceGatherer: transports.iceGatherer,
iceTransport: transports.iceTransport,
dtlsTransport: transports.dtlsTransport,
localCapabilities: localCapabilities,
remoteCapabilities: remoteCapabilities,
rtpSender: rtpSender,
rtpReceiver: rtpReceiver,
kind: kind,
mid: mid,
rtcpParameters: rtcpParameters,
sendEncodingParameters: sendEncodingParameters,
recvEncodingParameters: recvEncodingParameters
};
// Start the RTCRtpReceiver now. The RTPSender is started in
// setLocalDescription.
self._transceive(self.transceivers[sdpMLineIndex],
false,
direction === 'sendrecv' || direction === 'sendonly');
} else if (description.type === 'answer' && !rejected) {
if (usingBundle && sdpMLineIndex > 0) {
self._disposeIceAndDtlsTransports(sdpMLineIndex);
self.transceivers[sdpMLineIndex].iceGatherer =
self.transceivers[0].iceGatherer;
self.transceivers[sdpMLineIndex].iceTransport =
self.transceivers[0].iceTransport;
self.transceivers[sdpMLineIndex].dtlsTransport =
self.transceivers[0].dtlsTransport;
if (self.transceivers[sdpMLineIndex].rtpSender) {
self.transceivers[sdpMLineIndex].rtpSender.setTransport(
self.transceivers[0].dtlsTransport);
}
if (self.transceivers[sdpMLineIndex].rtpReceiver) {
self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(
self.transceivers[0].dtlsTransport);
}
}
transceiver = self.transceivers[sdpMLineIndex];
iceGatherer = transceiver.iceGatherer;
iceTransport = transceiver.iceTransport;
dtlsTransport = transceiver.dtlsTransport;
rtpSender = transceiver.rtpSender;
rtpReceiver = transceiver.rtpReceiver;
sendEncodingParameters = transceiver.sendEncodingParameters;
localCapabilities = transceiver.localCapabilities;
self.transceivers[sdpMLineIndex].recvEncodingParameters =
recvEncodingParameters;
self.transceivers[sdpMLineIndex].remoteCapabilities =
remoteCapabilities;
self.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;
if ((isIceLite || isComplete) && cands.length) {
iceTransport.setRemoteCandidates(cands);
}
if (!usingBundle || sdpMLineIndex === 0) {
iceTransport.start(iceGatherer, remoteIceParameters,
'controlling');
dtlsTransport.start(remoteDtlsParameters);
}
self._transceive(transceiver,
direction === 'sendrecv' || direction === 'recvonly',
direction === 'sendrecv' || direction === 'sendonly');
if (rtpReceiver &&
(direction === 'sendrecv' || direction === 'sendonly')) {
track = rtpReceiver.track;
receiverList.push([track, rtpReceiver]);
if (remoteMsid) {
if (!streams[remoteMsid.stream]) {
streams[remoteMsid.stream] = new MediaStream();
}
streams[remoteMsid.stream].addTrack(track);
} else {
if (!streams.default) {
streams.default = new MediaStream();
}
streams.default.addTrack(track);
}
} else {
// FIXME: actually the receiver should be created later.
delete transceiver.rtpReceiver;
}
}
});
this.usingBundle = usingBundle;
this.remoteDescription = {
type: description.type,
sdp: description.sdp
};
switch (description.type) {
case 'offer':
this._updateSignalingState('have-remote-offer');
break;
case 'answer':
this._updateSignalingState('stable');
break;
default:
throw new TypeError('unsupported type "' + description.type +
'"');
}
Object.keys(streams).forEach(function(sid) {
var stream = streams[sid];
if (stream.getTracks().length) {
self.remoteStreams.push(stream);
var event = new Event('addstream');
event.stream = stream;
self.dispatchEvent(event);
if (self.onaddstream !== null) {
window.setTimeout(function() {
self.onaddstream(event);
}, 0);
}
receiverList.forEach(function(item) {
var track = item[0];
var receiver = item[1];
if (stream.id !== item[2].id) {
return;
}
var trackEvent = new Event('track');
trackEvent.track = track;
trackEvent.receiver = receiver;
trackEvent.streams = [stream];
self.dispatchEvent(trackEvent);
if (self.ontrack !== null) {
window.setTimeout(function() {
self.ontrack(trackEvent);
}, 0);
}
});
}
});
if (arguments.length > 1 && typeof arguments[1] === 'function') {
window.setTimeout(arguments[1], 0);
}
return Promise.resolve();
};
RTCPeerConnection.prototype.close = function() {
this.transceivers.forEach(function(transceiver) {
/* not yet
if (transceiver.iceGatherer) {
transceiver.iceGatherer.close();
}
*/
if (transceiver.iceTransport) {
transceiver.iceTransport.stop();
}
if (transceiver.dtlsTransport) {
transceiver.dtlsTransport.stop();
}
if (transceiver.rtpSender) {
transceiver.rtpSender.stop();
}
if (transceiver.rtpReceiver) {
transceiver.rtpReceiver.stop();
}
});
// FIXME: clean up tracks, local streams, remote streams, etc
this._updateSignalingState('closed');
};
// Update the signaling state.
RTCPeerConnection.prototype._updateSignalingState = function(newState) {
this.signalingState = newState;
var event = new Event('signalingstatechange');
this.dispatchEvent(event);
if (this.onsignalingstatechange !== null) {
this.onsignalingstatechange(event);
}
};
// Determine whether to fire the negotiationneeded event.
RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
// Fire away (for now).
var event = new Event('negotiationneeded');
this.dispatchEvent(event);
if (this.onnegotiationneeded !== null) {
this.onnegotiationneeded(event);
}
};
// Update the connection state.
RTCPeerConnection.prototype._updateConnectionState = function() {
var self = this;
var newState;
var states = {
'new': 0,
closed: 0,
connecting: 0,
checking: 0,
connected: 0,
completed: 0,
failed: 0
};
this.transceivers.forEach(function(transceiver) {
states[transceiver.iceTransport.state]++;
states[transceiver.dtlsTransport.state]++;
});
// ICETransport.completed and connected are the same for this purpose.
states.connected += states.completed;
newState = 'new';
if (states.failed > 0) {
newState = 'failed';
} else if (states.connecting > 0 || states.checking > 0) {
newState = 'connecting';
} else if (states.disconnected > 0) {
newState = 'disconnected';
} else if (states.new > 0) {
newState = 'new';
} else if (states.connected > 0 || states.completed > 0) {
newState = 'connected';
}
if (newState !== self.iceConnectionState) {
self.iceConnectionState = newState;
var event = new Event('iceconnectionstatechange');
this.dispatchEvent(event);
if (this.oniceconnectionstatechange !== null) {
this.oniceconnectionstatechange(event);
}
}
};
RTCPeerConnection.prototype.createOffer = function() {
var self = this;
if (this._pendingOffer) {
throw new Error('createOffer called while there is a pending offer.');
}
var offerOptions;
if (arguments.length === 1 && typeof arguments[0] !== 'function') {
offerOptions = arguments[0];
} else if (arguments.length === 3) {
offerOptions = arguments[2];
}
var tracks = [];
var numAudioTracks = 0;
var numVideoTracks = 0;
// Default to sendrecv.
if (this.localStreams.length) {
numAudioTracks = this.localStreams.reduce(function(numTracks, stream) {
return numTracks + stream.getAudioTracks().length;
}, 0);
numVideoTracks = this.localStreams.reduce(function(numTracks, stream) {
return numTracks + stream.getVideoTracks().length;
}, 0);
}
// Determine number of audio and video tracks we need to send/recv.
if (offerOptions) {
// Reject Chrome legacy constraints.
if (offerOptions.mandatory || offerOptions.optional) {
throw new TypeError(
'Legacy mandatory/optional constraints not supported.');
}
if (offerOptions.offerToReceiveAudio !== undefined) {
numAudioTracks = offerOptions.offerToReceiveAudio;
}
if (offerOptions.offerToReceiveVideo !== undefined) {
numVideoTracks = offerOptions.offerToReceiveVideo;
}
}
// Push local streams.
this.localStreams.forEach(function(localStream) {
localStream.getTracks().forEach(function(track) {
tracks.push({
kind: track.kind,
track: track,
stream: localStream,
wantReceive: track.kind === 'audio' ?
numAudioTracks > 0 : numVideoTracks > 0
});
if (track.kind === 'audio') {
numAudioTracks--;
} else if (track.kind === 'video') {
numVideoTracks--;
}
});
});
// Create M-lines for recvonly streams.
while (numAudioTracks > 0 || numVideoTracks > 0) {
if (numAudioTracks > 0) {
tracks.push({
kind: 'audio',
wantReceive: true
});
numAudioTracks--;
}
if (numVideoTracks > 0) {
tracks.push({
kind: 'video',
wantReceive: true
});
numVideoTracks--;
}
}
// reorder tracks
tracks = sortTracks(tracks);
var sdp = SDPUtils.writeSessionBoilerplate();
var transceivers = [];
tracks.forEach(function(mline, sdpMLineIndex) {
// For each track, create an ice gatherer, ice transport,
// dtls transport, potentially rtpsender and rtpreceiver.
var track = mline.track;
var kind = mline.kind;
var mid = SDPUtils.generateIdentifier();
var transports = self.usingBundle && sdpMLineIndex > 0 ? {
iceGatherer: transceivers[0].iceGatherer,
iceTransport: transceivers[0].iceTransport,
dtlsTransport: transceivers[0].dtlsTransport
} : self._createIceAndDtlsTransports(mid, sdpMLineIndex);
var localCapabilities = RTCRtpSender.getCapabilities(kind);
// filter RTX until additional stuff needed for RTX is implemented
// in adapter.js
if (edgeVersion < 15019) {
localCapabilities.codecs = localCapabilities.codecs.filter(
function(codec) {
return codec.name !== 'rtx';
});
}
localCapabilities.codecs.forEach(function(codec) {
// work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552
// by adding level-asymmetry-allowed=1
if (codec.name === 'H264' &&
codec.parameters['level-asymmetry-allowed'] === undefined) {
codec.parameters['level-asymmetry-allowed'] = '1';
}
});
var rtpSender;
var rtpReceiver;
// generate an ssrc now, to be used later in rtpSender.send
var sendEncodingParameters = [{
ssrc: (2 * sdpMLineIndex + 1) * 1001
}];
if (track) {
// add RTX
if (edgeVersion >= 15019 && kind === 'video') {
sendEncodingParameters[0].rtx = {
ssrc: (2 * sdpMLineIndex + 1) * 1001 + 1
};
}
rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
}
if (mline.wantReceive) {
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
}
transceivers[sdpMLineIndex] = {
iceGatherer: transports.iceGatherer,
iceTransport: transports.iceTransport,
dtlsTransport: transports.dtlsTransport,
localCapabilities: localCapabilities,
remoteCapabilities: null,
rtpSender: rtpSender,
rtpReceiver: rtpReceiver,
kind: kind,
mid: mid,
sendEncodingParameters: sendEncodingParameters,
recvEncodingParameters: null
};
});
// always offer BUNDLE and dispose on return if not supported.
if (this._config.bundlePolicy !== 'max-compat') {
sdp += 'a=group:BUNDLE ' + transceivers.map(function(t) {
return t.mid;
}).join(' ') + '\r\n';
}
sdp += 'a=ice-options:trickle\r\n';
tracks.forEach(function(mline, sdpMLineIndex) {
var transceiver = transceivers[sdpMLineIndex];
sdp += SDPUtils.writeMediaSection(transceiver,
transceiver.localCapabilities, 'offer', mline.stream);
sdp += 'a=rtcp-rsize\r\n';
});
this._pendingOffer = transceivers;
var desc = new RTCSessionDescription({
type: 'offer',
sdp: sdp
});
if (arguments.length && typeof arguments[0] === 'function') {
window.setTimeout(arguments[0], 0, desc);
}
return Promise.resolve(desc);
};
RTCPeerConnection.prototype.createAnswer = function() {
var self = this;
var sdp = SDPUtils.writeSessionBoilerplate();
if (this.usingBundle) {
sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) {
return t.mid;
}).join(' ') + '\r\n';
}
this.transceivers.forEach(function(transceiver) {
if (transceiver.isDatachannel) {
sdp += 'm=application 0 DTLS/SCTP 5000\r\n' +
'c=IN IP4 0.0.0.0\r\n' +
'a=mid:' + transceiver.mid + '\r\n';
return;
}
// Calculate intersection of capabilities.
var commonCapabilities = self._getCommonCapabilities(
transceiver.localCapabilities,
transceiver.remoteCapabilities);
var hasRtx = commonCapabilities.codecs.filter(function(c) {
return c.name.toLowerCase() === 'rtx';
}).length;
if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {
delete transceiver.sendEncodingParameters[0].rtx;
}
sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
'answer', self.localStreams[0]);
if (transceiver.rtcpParameters &&
transceiver.rtcpParameters.reducedSize) {
sdp += 'a=rtcp-rsize\r\n';
}
});
var desc = new RTCSessionDescription({
type: 'answer',
sdp: sdp
});
if (arguments.length && typeof arguments[0] === 'function') {
window.setTimeout(arguments[0], 0, desc);
}
return Promise.resolve(desc);
};
RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
if (!candidate) {
for (var j = 0; j < this.transceivers.length; j++) {
this.transceivers[j].iceTransport.addRemoteCandidate({});
if (this.usingBundle) {
return Promise.resolve();
}
}
} else {
var mLineIndex = candidate.sdpMLineIndex;
if (candidate.sdpMid) {
for (var i = 0; i < this.transceivers.length; i++) {
if (this.transceivers[i].mid === candidate.sdpMid) {
mLineIndex = i;
break;
}
}
}
var transceiver = this.transceivers[mLineIndex];
if (transceiver) {
var cand = Object.keys(candidate.candidate).length > 0 ?
SDPUtils.parseCandidate(candidate.candidate) : {};
// Ignore Chrome's invalid candidates since Edge does not like them.
if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {
return Promise.resolve();
}
// Ignore RTCP candidates, we assume RTCP-MUX.
if (cand.component !== '1') {
return Promise.resolve();
}
transceiver.iceTransport.addRemoteCandidate(cand);
// update the remoteDescription.
var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
: 'a=end-of-candidates') + '\r\n';
this.remoteDescription.sdp = sections.join('');
}
}
if (arguments.length > 1 && typeof arguments[1] === 'function') {
window.setTimeout(arguments[1], 0);
}
return Promise.resolve();
};
RTCPeerConnection.prototype.getStats = function() {
var promises = [];
this.transceivers.forEach(function(transceiver) {
['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
'dtlsTransport'].forEach(function(method) {
if (transceiver[method]) {
promises.push(transceiver[method].getStats());
}
});
});
var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
arguments[1];
var fixStatsType = function(stat) {
return {
inboundrtp: 'inbound-rtp',
outboundrtp: 'outbound-rtp',
candidatepair: 'candidate-pair',
localcandidate: 'local-candidate',
remotecandidate: 'remote-candidate'
}[stat.type] || stat.type;
};
return new Promise(function(resolve) {
// shim getStats with maplike support
var results = new Map();
Promise.all(promises).then(function(res) {
res.forEach(function(result) {
Object.keys(result).forEach(function(id) {
result[id].type = fixStatsType(result[id]);
results.set(id, result[id]);
});
});
if (cb) {
window.setTimeout(cb, 0, results);
}
resolve(results);
});
});
};
return RTCPeerConnection;
};
},{"sdp":37}],99:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var browserDetails = require('../utils').browserDetails;
var firefoxShim = {
shimOnTrack: function() {
if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
window.RTCPeerConnection.prototype)) {
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
get: function() {
return this._ontrack;
},
set: function(f) {
if (this._ontrack) {
this.removeEventListener('track', this._ontrack);
this.removeEventListener('addstream', this._ontrackpoly);
}
this.addEventListener('track', this._ontrack = f);
this.addEventListener('addstream', this._ontrackpoly = function(e) {
e.stream.getTracks().forEach(function(track) {
var event = new Event('track');
event.track = track;
event.receiver = {track: track};
event.streams = [e.stream];
this.dispatchEvent(event);
}.bind(this));
}.bind(this));
}
});
}
},
shimSourceObject: function() {
// Firefox has supported mozSrcObject since FF22, unprefixed in 42.
if (typeof window === 'object') {
if (window.HTMLMediaElement &&
!('srcObject' in window.HTMLMediaElement.prototype)) {
// Shim the srcObject property, once, when HTMLMediaElement is found.
Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
get: function() {
return this.mozSrcObject;
},
set: function(stream) {
this.mozSrcObject = stream;
}
});
}
}
},
shimPeerConnection: function() {
if (typeof window !== 'object' || !(window.RTCPeerConnection ||
window.mozRTCPeerConnection)) {
return; // probably media.peerconnection.enabled=false in about:config
}
// The RTCPeerConnection object.
if (!window.RTCPeerConnection) {
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
if (browserDetails.version < 38) {
// .urls is not supported in FF < 38.
// create RTCIceServers with a single url.
if (pcConfig && pcConfig.iceServers) {
var newIceServers = [];
for (var i = 0; i < pcConfig.iceServers.length; i++) {
var server = pcConfig.iceServers[i];
if (server.hasOwnProperty('urls')) {
for (var j = 0; j < server.urls.length; j++) {
var newServer = {
url: server.urls[j]
};
if (server.urls[j].indexOf('turn') === 0) {
newServer.username = server.username;
newServer.credential = server.credential;
}
newIceServers.push(newServer);
}
} else {
newIceServers.push(pcConfig.iceServers[i]);
}
}
pcConfig.iceServers = newIceServers;
}
}
return new mozRTCPeerConnection(pcConfig, pcConstraints);
};
window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype;
// wrap static methods. Currently just generateCertificate.
if (mozRTCPeerConnection.generateCertificate) {
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
get: function() {
return mozRTCPeerConnection.generateCertificate;
}
});
}
window.RTCSessionDescription = mozRTCSessionDescription;
window.RTCIceCandidate = mozRTCIceCandidate;
}
// shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
.forEach(function(method) {
var nativeMethod = RTCPeerConnection.prototype[method];
RTCPeerConnection.prototype[method] = function() {
arguments[0] = new ((method === 'addIceCandidate') ?
RTCIceCandidate : RTCSessionDescription)(arguments[0]);
return nativeMethod.apply(this, arguments);
};
});
// support for addIceCandidate(null or undefined)
var nativeAddIceCandidate =
RTCPeerConnection.prototype.addIceCandidate;
RTCPeerConnection.prototype.addIceCandidate = function() {
if (!arguments[0]) {
if (arguments[1]) {
arguments[1].apply(null);
}
return Promise.resolve();
}
return nativeAddIceCandidate.apply(this, arguments);
};
// shim getStats with maplike support
var makeMapStats = function(stats) {
var map = new Map();
Object.keys(stats).forEach(function(key) {
map.set(key, stats[key]);
map[key] = stats[key];
});
return map;
};
var modernStatsTypes = {
inboundrtp: 'inbound-rtp',
outboundrtp: 'outbound-rtp',
candidatepair: 'candidate-pair',
localcandidate: 'local-candidate',
remotecandidate: 'remote-candidate'
};
var nativeGetStats = RTCPeerConnection.prototype.getStats;
RTCPeerConnection.prototype.getStats = function(selector, onSucc, onErr) {
return nativeGetStats.apply(this, [selector || null])
.then(function(stats) {
if (browserDetails.version < 48) {
stats = makeMapStats(stats);
}
if (browserDetails.version < 53 && !onSucc) {
// Shim only promise getStats with spec-hyphens in type names
// Leave callback version alone; misc old uses of forEach before Map
try {
stats.forEach(function(stat) {
stat.type = modernStatsTypes[stat.type] || stat.type;
});
} catch (e) {
if (e.name !== 'TypeError') {
throw e;
}
// Avoid TypeError: "type" is read-only, in old versions. 34-43ish
stats.forEach(function(stat, i) {
stats.set(i, Object.assign({}, stat, {
type: modernStatsTypes[stat.type] || stat.type
}));
});
}
}
return stats;
})
.then(onSucc, onErr);
};
}
};
// Expose public methods.
module.exports = {
shimOnTrack: firefoxShim.shimOnTrack,
shimSourceObject: firefoxShim.shimSourceObject,
shimPeerConnection: firefoxShim.shimPeerConnection,
shimGetUserMedia: require('./getusermedia')
};
},{"../utils":102,"./getusermedia":100}],100:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logging = require('../utils').log;
var browserDetails = require('../utils').browserDetails;
// Expose public methods.
module.exports = function() {
var shimError_ = function(e) {
return {
name: {
NotSupportedError: 'TypeError',
SecurityError: 'NotAllowedError',
PermissionDeniedError: 'NotAllowedError'
}[e.name] || e.name,
message: {
'The operation is insecure.': 'The request is not allowed by the ' +
'user agent or the platform in the current context.'
}[e.message] || e.message,
constraint: e.constraint,
toString: function() {
return this.name + (this.message && ': ') + this.message;
}
};
};
// getUserMedia constraints shim.
var getUserMedia_ = function(constraints, onSuccess, onError) {
var constraintsToFF37_ = function(c) {
if (typeof c !== 'object' || c.require) {
return c;
}
var require = [];
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
return;
}
var r = c[key] = (typeof c[key] === 'object') ?
c[key] : {ideal: c[key]};
if (r.min !== undefined ||
r.max !== undefined || r.exact !== undefined) {
require.push(key);
}
if (r.exact !== undefined) {
if (typeof r.exact === 'number') {
r. min = r.max = r.exact;
} else {
c[key] = r.exact;
}
delete r.exact;
}
if (r.ideal !== undefined) {
c.advanced = c.advanced || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[key] = {min: r.ideal, max: r.ideal};
} else {
oc[key] = r.ideal;
}
c.advanced.push(oc);
delete r.ideal;
if (!Object.keys(r).length) {
delete c[key];
}
}
});
if (require.length) {
c.require = require;
}
return c;
};
constraints = JSON.parse(JSON.stringify(constraints));
if (browserDetails.version < 38) {
logging('spec: ' + JSON.stringify(constraints));
if (constraints.audio) {
constraints.audio = constraintsToFF37_(constraints.audio);
}
if (constraints.video) {
constraints.video = constraintsToFF37_(constraints.video);
}
logging('ff37: ' + JSON.stringify(constraints));
}
return navigator.mozGetUserMedia(constraints, onSuccess, function(e) {
onError(shimError_(e));
});
};
// Returns the result of getUserMedia as a Promise.
var getUserMediaPromise_ = function(constraints) {
return new Promise(function(resolve, reject) {
getUserMedia_(constraints, resolve, reject);
});
};
// Shim for mediaDevices on older versions.
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
addEventListener: function() { },
removeEventListener: function() { }
};
}
navigator.mediaDevices.enumerateDevices =
navigator.mediaDevices.enumerateDevices || function() {
return new Promise(function(resolve) {
var infos = [
{kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
{kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
];
resolve(infos);
});
};
if (browserDetails.version < 41) {
// Work around http://bugzil.la/1169665
var orgEnumerateDevices =
navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
navigator.mediaDevices.enumerateDevices = function() {
return orgEnumerateDevices().then(undefined, function(e) {
if (e.name === 'NotFoundError') {
return [];
}
throw e;
});
};
}
if (browserDetails.version < 49) {
var origGetUserMedia = navigator.mediaDevices.getUserMedia.
bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = function(c) {
return origGetUserMedia(c).then(function(stream) {
// Work around https://bugzil.la/802326
if (c.audio && !stream.getAudioTracks().length ||
c.video && !stream.getVideoTracks().length) {
stream.getTracks().forEach(function(track) {
track.stop();
});
throw new DOMException('The object can not be found here.',
'NotFoundError');
}
return stream;
}, function(e) {
return Promise.reject(shimError_(e));
});
};
}
navigator.getUserMedia = function(constraints, onSuccess, onError) {
if (browserDetails.version < 44) {
return getUserMedia_(constraints, onSuccess, onError);
}
// Replace Firefox 44+'s deprecation warning with unprefixed version.
console.warn('navigator.getUserMedia has been replaced by ' +
'navigator.mediaDevices.getUserMedia');
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
};
};
},{"../utils":102}],101:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
'use strict';
var safariShim = {
// TODO: DrAlex, should be here, double check against LayoutTests
// TODO: once the back-end for the mac port is done, add.
// TODO: check for webkitGTK+
// shimPeerConnection: function() { },
shimOnAddStream: function() {
if (typeof window === 'object' && window.RTCPeerConnection &&
!('onaddstream' in window.RTCPeerConnection.prototype)) {
Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {
get: function() {
return this._onaddstream;
},
set: function(f) {
if (this._onaddstream) {
this.removeEventListener('addstream', this._onaddstream);
this.removeEventListener('track', this._onaddstreampoly);
}
this.addEventListener('addstream', this._onaddstream = f);
this.addEventListener('track', this._onaddstreampoly = function(e) {
var stream = e.streams[0];
if (!this._streams) {
this._streams = [];
}
if (this._streams.indexOf(stream) >= 0) {
return;
}
this._streams.push(stream);
var event = new Event('addstream');
event.stream = e.streams[0];
this.dispatchEvent(event);
}.bind(this));
}
});
}
},
shimGetUserMedia: function() {
if (!navigator.getUserMedia) {
if (navigator.webkitGetUserMedia) {
navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
} else if (navigator.mediaDevices &&
navigator.mediaDevices.getUserMedia) {
navigator.getUserMedia = function(constraints, cb, errcb) {
navigator.mediaDevices.getUserMedia(constraints)
.then(cb, errcb);
}.bind(navigator);
}
}
}
};
// Expose public methods.
module.exports = {
shimOnAddStream: safariShim.shimOnAddStream,
shimGetUserMedia: safariShim.shimGetUserMedia
// TODO
// shimPeerConnection: safariShim.shimPeerConnection
};
},{}],102:[function(require,module,exports){
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* eslint-env node */
'use strict';
var logDisabled_ = true;
// Utility methods.
var utils = {
disableLog: function(bool) {
if (typeof bool !== 'boolean') {
return new Error('Argument type: ' + typeof bool +
'. Please use a boolean.');
}
logDisabled_ = bool;
return (bool) ? 'adapter.js logging disabled' :
'adapter.js logging enabled';
},
log: function() {
if (typeof window === 'object') {
if (logDisabled_) {
return;
}
if (typeof console !== 'undefined' && typeof console.log === 'function') {
console.log.apply(console, arguments);
}
}
},
/**
* Extract browser version out of the provided user agent string.
*
* @param {!string} uastring userAgent string.
* @param {!string} expr Regular expression used as match criteria.
* @param {!number} pos position in the version string to be returned.
* @return {!number} browser version.
*/
extractVersion: function(uastring, expr, pos) {
var match = uastring.match(expr);
return match && match.length >= pos && parseInt(match[pos], 10);
},
/**
* Browser detector.
*
* @return {object} result containing browser and version
* properties.
*/
detectBrowser: function() {
// Returned result object.
var result = {};
result.browser = null;
result.version = null;
// Fail early if it's not a browser
if (typeof window === 'undefined' || !window.navigator) {
result.browser = 'Not a browser.';
return result;
}
// Firefox.
if (navigator.mozGetUserMedia) {
result.browser = 'firefox';
result.version = this.extractVersion(navigator.userAgent,
/Firefox\/(\d+)\./, 1);
} else if (navigator.webkitGetUserMedia) {
// Chrome, Chromium, Webview, Opera, all use the chrome shim for now
if (window.webkitRTCPeerConnection) {
result.browser = 'chrome';
result.version = this.extractVersion(navigator.userAgent,
/Chrom(e|ium)\/(\d+)\./, 2);
} else { // Safari (in an unpublished version) or unknown webkit-based.
if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
result.browser = 'safari';
result.version = this.extractVersion(navigator.userAgent,
/AppleWebKit\/(\d+)\./, 1);
} else { // unknown webkit-based browser.
result.browser = 'Unsupported webkit-based browser ' +
'with GUM support but no WebRTC support.';
return result;
}
}
} else if (navigator.mediaDevices &&
navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge.
result.browser = 'edge';
result.version = this.extractVersion(navigator.userAgent,
/Edge\/(\d+).(\d+)$/, 2);
} else if (navigator.mediaDevices &&
navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
// Safari, with webkitGetUserMedia removed.
result.browser = 'safari';
result.version = this.extractVersion(navigator.userAgent,
/AppleWebKit\/(\d+)\./, 1);
} else { // Default fallthrough: not supported.
result.browser = 'Not a supported browser.';
return result;
}
return result;
},
// shimCreateObjectURL must be called before shimSourceObject to avoid loop.
shimCreateObjectURL: function() {
if (!(typeof window === 'object' && window.HTMLMediaElement &&
'srcObject' in window.HTMLMediaElement.prototype)) {
// Only shim CreateObjectURL using srcObject if srcObject exists.
return undefined;
}
var nativeCreateObjectURL = URL.createObjectURL.bind(URL);
var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL);
var streams = new Map(), newId = 0;
URL.createObjectURL = function(stream) {
if ('getTracks' in stream) {
var url = 'polyblob:' + (++newId);
streams.set(url, stream);
console.log('URL.createObjectURL(stream) is deprecated! ' +
'Use elem.srcObject = stream instead!');
return url;
}
return nativeCreateObjectURL(stream);
};
URL.revokeObjectURL = function(url) {
nativeRevokeObjectURL(url);
streams.delete(url);
};
var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,
'src');
Object.defineProperty(window.HTMLMediaElement.prototype, 'src', {
get: function() {
return dsc.get.apply(this);
},
set: function(url) {
this.srcObject = streams.get(url) || null;
return dsc.set.apply(this, [url]);
}
});
var nativeSetAttribute = HTMLMediaElement.prototype.setAttribute;
HTMLMediaElement.prototype.setAttribute = function() {
if (arguments.length === 2 &&
('' + arguments[0]).toLowerCase() === 'src') {
this.srcObject = streams.get(arguments[1]) || null;
}
return nativeSetAttribute.apply(this, arguments);
};
}
};
// Export.
module.exports = {
log: utils.log,
disableLog: utils.disableLog,
browserDetails: utils.detectBrowser(),
extractVersion: utils.extractVersion,
shimCreateObjectURL: utils.shimCreateObjectURL,
detectBrowser: utils.detectBrowser.bind(utils)
};
},{}],103:[function(require,module,exports){
/*!
* EventEmitter v4.2.9 - git.io/ee
* Oliver Caldwell
* MIT license
* @preserve
*/
(function () {
'use strict';
/**
* Class for managing events.
* Can be extended to provide event functionality in other classes.
*
* @class EventEmitter Manages event registering and emitting.
*/
function EventEmitter() {}
// Shortcuts to improve speed and size
var proto = EventEmitter.prototype;
var exports = this;
var originalGlobalValue = exports.EventEmitter;
/**
* Finds the index of the listener for the event in its storage array.
*
* @param {Function[]} listeners Array of listeners to search through.
* @param {Function} listener Method to look for.
* @return {Number} Index of the specified listener, -1 if not found
* @api private
*/
function indexOfListener(listeners, listener) {
var i = listeners.length;
while (i--) {
if (listeners[i].listener === listener) {
return i;
}
}
return -1;
}
/**
* Alias a method while keeping the context correct, to allow for overwriting of target method.
*
* @param {String} name The name of the target method.
* @return {Function} The aliased method
* @api private
*/
function alias(name) {
return function aliasClosure() {
return this[name].apply(this, arguments);
};
}
/**
* Returns the listener array for the specified event.
* Will initialise the event object and listener arrays if required.
* Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
* Each property in the object response is an array of listener functions.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Function[]|Object} All listener functions for the event.
*/
proto.getListeners = function getListeners(evt) {
var events = this._getEvents();
var response;
var key;
// Return a concatenated array of all matching events if
// the selector is a regular expression.
if (evt instanceof RegExp) {
response = {};
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
response[key] = events[key];
}
}
}
else {
response = events[evt] || (events[evt] = []);
}
return response;
};
/**
* Takes a list of listener objects and flattens it into a list of listener functions.
*
* @param {Object[]} listeners Raw listener objects.
* @return {Function[]} Just the listener functions.
*/
proto.flattenListeners = function flattenListeners(listeners) {
var flatListeners = [];
var i;
for (i = 0; i < listeners.length; i += 1) {
flatListeners.push(listeners[i].listener);
}
return flatListeners;
};
/**
* Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Object} All listener functions for an event in an object.
*/
proto.getListenersAsObject = function getListenersAsObject(evt) {
var listeners = this.getListeners(evt);
var response;
if (listeners instanceof Array) {
response = {};
response[evt] = listeners;
}
return response || listeners;
};
/**
* Adds a listener function to the specified event.
* The listener will not be added if it is a duplicate.
* If the listener returns true then it will be removed after it is called.
* If you pass a regular expression as the event name then the listener will be added to all events that match it.
*
* @param {String|RegExp} evt Name of the event to attach the listener to.
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addListener = function addListener(evt, listener) {
var listeners = this.getListenersAsObject(evt);
var listenerIsWrapped = typeof listener === 'object';
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
listeners[key].push(listenerIsWrapped ? listener : {
listener: listener,
once: false
});
}
}
return this;
};
/**
* Alias of addListener
*/
proto.on = alias('addListener');
/**
* Semi-alias of addListener. It will add a listener that will be
* automatically removed after its first execution.
*
* @param {String|RegExp} evt Name of the event to attach the listener to.
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addOnceListener = function addOnceListener(evt, listener) {
return this.addListener(evt, {
listener: listener,
once: true
});
};
/**
* Alias of addOnceListener.
*/
proto.once = alias('addOnceListener');
/**
* Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
* You need to tell it what event names should be matched by a regex.
*
* @param {String} evt Name of the event to create.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.defineEvent = function defineEvent(evt) {
this.getListeners(evt);
return this;
};
/**
* Uses defineEvent to define multiple events.
*
* @param {String[]} evts An array of event names to define.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.defineEvents = function defineEvents(evts) {
for (var i = 0; i < evts.length; i += 1) {
this.defineEvent(evts[i]);
}
return this;
};
/**
* Removes a listener function from the specified event.
* When passed a regular expression as the event name, it will remove the listener from all events that match it.
*
* @param {String|RegExp} evt Name of the event to remove the listener from.
* @param {Function} listener Method to remove from the event.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeListener = function removeListener(evt, listener) {
var listeners = this.getListenersAsObject(evt);
var index;
var key;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
index = indexOfListener(listeners[key], listener);
if (index !== -1) {
listeners[key].splice(index, 1);
}
}
}
return this;
};
/**
* Alias of removeListener
*/
proto.off = alias('removeListener');
/**
* Adds listeners in bulk using the manipulateListeners method.
* If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
* You can also pass it a regular expression to add the array of listeners to all events that match it.
* Yeah, this function does quite a bit. That's probably a bad thing.
*
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to add.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.addListeners = function addListeners(evt, listeners) {
// Pass through to manipulateListeners
return this.manipulateListeners(false, evt, listeners);
};
/**
* Removes listeners in bulk using the manipulateListeners method.
* If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
* You can also pass it an event name and an array of listeners to be removed.
* You can also pass it a regular expression to remove the listeners from all events that match it.
*
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to remove.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeListeners = function removeListeners(evt, listeners) {
// Pass through to manipulateListeners
return this.manipulateListeners(true, evt, listeners);
};
/**
* Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
* The first argument will determine if the listeners are removed (true) or added (false).
* If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
* You can also pass it an event name and an array of listeners to be added/removed.
* You can also pass it a regular expression to manipulate the listeners of all events that match it.
*
* @param {Boolean} remove True if you want to remove listeners, false if you want to add.
* @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
* @param {Function[]} [listeners] An optional array of listener functions to add/remove.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
var i;
var value;
var single = remove ? this.removeListener : this.addListener;
var multiple = remove ? this.removeListeners : this.addListeners;
// If evt is an object then pass each of its properties to this method
if (typeof evt === 'object' && !(evt instanceof RegExp)) {
for (i in evt) {
if (evt.hasOwnProperty(i) && (value = evt[i])) {
// Pass the single listener straight through to the singular method
if (typeof value === 'function') {
single.call(this, i, value);
}
else {
// Otherwise pass back to the multiple function
multiple.call(this, i, value);
}
}
}
}
else {
// So evt must be a string
// And listeners must be an array of listeners
// Loop over it and pass each one to the multiple method
i = listeners.length;
while (i--) {
single.call(this, evt, listeners[i]);
}
}
return this;
};
/**
* Removes all listeners from a specified event.
* If you do not specify an event then all listeners will be removed.
* That means every event will be emptied.
* You can also pass a regex to remove all events that match it.
*
* @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.removeEvent = function removeEvent(evt) {
var type = typeof evt;
var events = this._getEvents();
var key;
// Remove different things depending on the state of evt
if (type === 'string') {
// Remove all listeners for the specified event
delete events[evt];
}
else if (evt instanceof RegExp) {
// Remove all events matching the regex.
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
delete events[key];
}
}
}
else {
// Remove all listeners in all events
delete this._events;
}
return this;
};
/**
* Alias of removeEvent.
*
* Added to mirror the node API.
*/
proto.removeAllListeners = alias('removeEvent');
/**
* Emits an event of your choice.
* When emitted, every listener attached to that event will be executed.
* If you pass the optional argument array then those arguments will be passed to every listener upon execution.
* Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
* So they will not arrive within the array on the other side, they will be separate.
* You can also pass a regular expression to emit to all events that match it.
*
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
* @param {Array} [args] Optional array of arguments to be passed to each listener.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.emitEvent = function emitEvent(evt, args) {
var listeners = this.getListenersAsObject(evt);
var listener;
var i;
var key;
var response;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
i = listeners[key].length;
while (i--) {
// If the listener returns true then it shall be removed from the event
// The function is executed either with a basic call or an apply if there is an args array
listener = listeners[key][i];
if (listener.once === true) {
this.removeListener(evt, listener.listener);
}
response = listener.listener.apply(this, args || []);
if (response === this._getOnceReturnValue()) {
this.removeListener(evt, listener.listener);
}
}
}
}
return this;
};
/**
* Alias of emitEvent
*/
proto.trigger = alias('emitEvent');
/**
* Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
* As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
*
* @param {String|RegExp} evt Name of the event to emit and execute listeners for.
* @param {...*} Optional additional arguments to be passed to each listener.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.emit = function emit(evt) {
var args = Array.prototype.slice.call(arguments, 1);
return this.emitEvent(evt, args);
};
/**
* Sets the current value to check against when executing listeners. If a
* listeners return value matches the one set here then it will be removed
* after execution. This value defaults to true.
*
* @param {*} value The new value to check for when executing listeners.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.setOnceReturnValue = function setOnceReturnValue(value) {
this._onceReturnValue = value;
return this;
};
/**
* Fetches the current value to check against when executing listeners. If
* the listeners return value matches this one then it should be removed
* automatically. It will return true by default.
*
* @return {*|Boolean} The current value to check for or the default, true.
* @api private
*/
proto._getOnceReturnValue = function _getOnceReturnValue() {
if (this.hasOwnProperty('_onceReturnValue')) {
return this._onceReturnValue;
}
else {
return true;
}
};
/**
* Fetches the events object and creates one if required.
*
* @return {Object} The events storage object.
* @api private
*/
proto._getEvents = function _getEvents() {
return this._events || (this._events = {});
};
/**
* Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
*
* @return {Function} Non conflicting EventEmitter class.
*/
EventEmitter.noConflict = function noConflict() {
exports.EventEmitter = originalGlobalValue;
return EventEmitter;
};
// Expose the class either via AMD, CommonJS or the global object
if (typeof define === 'function' && define.amd) {
define(function () {
return EventEmitter;
});
}
else if (typeof module === 'object' && module.exports){
module.exports = EventEmitter;
}
else {
exports.EventEmitter = EventEmitter;
}
}.call(this));
},{}],104:[function(require,module,exports){
/**
* Module dependencies.
*/
var global = (function() { return this; })();
/**
* WebSocket constructor.
*/
var WebSocket = global.WebSocket || global.MozWebSocket;
/**
* Module exports.
*/
module.exports = WebSocket ? ws : null;
/**
* WebSocket constructor.
*
* The third `opts` options object gets ignored in web browsers, since it's
* non-standard, and throws a TypeError if passed to the constructor.
* See: https://github.com/einaros/ws/issues/227
*
* @param {String} uri
* @param {Array} protocols (optional)
* @param {Object) opts (optional)
* @api public
*/
function ws(uri, protocols, opts) {
var instance;
if (protocols) {
instance = new WebSocket(uri, protocols);
} else {
instance = new WebSocket(uri);
}
return instance;
}
if (WebSocket) ws.prototype = WebSocket.prototype;
},{}],105:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var OpenVidu_1 = require("./OpenVidu");
//This export with --standalone option allows using OpenVidu from bowser with namespace
//export { OpenVidu } from './OpenVidu';
//This "hack" allows to use OpenVidu from the global space window
if (window) {
window["OpenVidu"] = OpenVidu_1.OpenVidu;
}
//Command to generate bundle.js without namespace
//watchify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/OpenVidu.js -v
},{"./OpenVidu":106}],106:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
2016-10-11 09:53:32 +02:00
/*
* (C) Copyright 2016 OpenVidu (http://kurento.org/)
2016-10-11 09:53:32 +02:00
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var OpenViduInternal_1 = require("../OpenViduInternal/OpenViduInternal");
var Session_1 = require("./Session");
var Publisher_1 = require("./Publisher");
var adapter = require("webrtc-adapter");
if (window) {
window["adapter"] = adapter;
}
var OpenVidu = (function () {
function OpenVidu(wsUri) {
this.wsUri = wsUri;
this.openVidu = new OpenViduInternal_1.OpenViduInternal(wsUri);
}
OpenVidu.prototype.initSession = function (param1, param2) {
if (this.checkSystemRequirements()) {
if (typeof param2 == "string") {
return new Session_1.Session(this.openVidu.initSession(param2), this);
}
else {
return new Session_1.Session(this.openVidu.initSession(param1), this);
}
}
else {
alert("Browser not supported");
}
};
OpenVidu.prototype.initPublisher = function (parentId, cameraOptions, callback) {
if (this.checkSystemRequirements()) {
if (cameraOptions != null) {
var cameraOptionsAux = {
audio: cameraOptions.audio != null ? cameraOptions.audio : true,
video: cameraOptions.video != null ? cameraOptions.video : true,
data: true,
mediaConstraints: this.openVidu.generateMediaConstraints(cameraOptions.quality)
};
cameraOptions = cameraOptionsAux;
}
else {
cameraOptions = {
audio: true,
video: true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
};
}
return new Publisher_1.Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId);
}
else {
alert("Browser not supported");
}
};
OpenVidu.prototype.checkSystemRequirements = function () {
var browser = adapter.browserDetails.browser;
var version = adapter.browserDetails.version;
//Bug fix: 'navigator.userAgent' in Firefox for Ubuntu 14.04 does not return "Firefox/[version]" in the string, so version returned is null
if ((browser == 'firefox') && (version == null)) {
return 1;
}
if (((browser == 'chrome') && (version >= 28)) || ((browser == 'edge') && (version >= 12)) || ((browser == 'firefox') && (version >= 22))) {
return 1;
}
else {
return 0;
}
};
OpenVidu.prototype.getDevices = function (callback) {
navigator.mediaDevices.enumerateDevices().then(function (deviceInfos) {
callback(null, deviceInfos);
}).catch(function (error) {
console.log("Error getting devices: " + error);
callback(error, null);
});
};
return OpenVidu;
}());
exports.OpenVidu = OpenVidu;
},{"../OpenViduInternal/OpenViduInternal":111,"./Publisher":107,"./Session":108,"webrtc-adapter":93}],107:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter = require("wolfy87-eventemitter");
var Publisher = (function () {
function Publisher(stream, parentId) {
var _this = this;
this.ee = new EventEmitter();
this.accessAllowed = false;
this.stream = stream;
this.ee.on('camera-access-changed', function (event) {
_this.accessAllowed = event.accessAllowed;
if (_this.accessAllowed) {
_this.ee.emitEvent('accessAllowed');
}
else {
_this.ee.emitEvent('accessDenied');
}
});
if (document.getElementById(parentId) != null) {
this.element = document.getElementById(parentId);
}
}
Publisher.prototype.publishAudio = function (value) {
this.stream.getWebRtcPeer().audioEnabled = value;
};
Publisher.prototype.publishVideo = function (value) {
this.stream.getWebRtcPeer().videoEnabled = value;
};
Publisher.prototype.destroy = function () {
this.session.unpublish(this);
this.stream.dispose();
this.stream.removeVideo(this.element);
return this;
};
Publisher.prototype.on = function (eventName, callback) {
var _this = this;
this.ee.addListener(eventName, function (event) {
callback(event);
});
if (eventName == 'videoElementCreated') {
if (this.stream.isReady) {
this.ee.emitEvent('videoElementCreated', [{
element: this.stream.getVideoElement()
}]);
}
else {
this.stream.addEventListener('video-element-created-by-stream', function (element) {
console.warn("Publisher emitting videoElementCreated");
_this.id = element.id;
_this.ee.emitEvent('videoElementCreated', [{
element: element
}]);
});
}
}
};
return Publisher;
}());
exports.Publisher = Publisher;
},{"wolfy87-eventemitter":103}],108:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Subscriber_1 = require("./Subscriber");
var EventEmitter = require("wolfy87-eventemitter");
var Session = (function () {
function Session(session, openVidu) {
var _this = this;
this.session = session;
this.openVidu = openVidu;
this.ee = new EventEmitter();
this.sessionId = session.getSessionId();
// Listens to the deactivation of the default behaviour upon the deletion of a Stream object
this.session.addEventListener('stream-removed-default', function (event) {
event.stream.removeVideo();
});
// Sets or updates the value of 'connection' property. Triggered by SessionInternal when succesful connection
this.session.addEventListener('update-connection-object', function (event) {
_this.connection = event.connection;
});
}
Session.prototype.connect = function (param1, param2, param3) {
// Early configuration to deactivate automatic subscription to streams
if (typeof param2 == "string") {
this.session.configure({
sessionId: this.session.getSessionId(),
participantId: param1,
metadata: param2,
subscribeToStreams: false
});
this.session.connect(param1, param3);
}
else {
this.session.configure({
sessionId: this.session.getSessionId(),
participantId: param1,
metadata: '',
subscribeToStreams: false
});
this.session.connect(param1, param2);
}
};
Session.prototype.disconnect = function () {
this.openVidu.openVidu.close(false);
var s;
for (var _i = 0, _a = this.openVidu.openVidu.getRemoteStreams(); _i < _a.length; _i++) {
s = _a[_i];
s.removeVideo();
}
for (var streamId in this.connection.getStreams()) {
this.connection.getStreams()[streamId].removeVideo();
}
};
Session.prototype.publish = function (publisher) {
publisher.session = this;
publisher.stream.publish();
};
Session.prototype.unpublish = function (publisher) {
this.session.unpublish(publisher.stream);
};
Session.prototype.on = function (eventName, callback) {
var realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.addEventListener(realEventName, function (event) {
callback(event);
});
}
else {
this.session.addEventListener(eventName, function (event) {
callback(event);
});
}
};
Session.prototype.once = function (eventName, callback) {
var realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.addOnceEventListener(realEventName, function (event) {
callback(event);
});
}
else {
this.session.addOnceEventListener(eventName, function (event) {
callback(event);
});
}
};
Session.prototype.off = function (eventName, eventHandler) {
var realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.removeListener(realEventName, eventHandler);
}
else {
this.session.removeListener(eventName, eventHandler);
}
};
Session.prototype.subscribe = function (param1, param2, param3) {
// Subscription
this.session.subscribe(param1);
var subscriber = new Subscriber_1.Subscriber(param1, param2);
param1.playOnlyVideo(param2, null);
return subscriber;
};
Session.prototype.unsubscribe = function (subscriber) {
this.session.unsuscribe(subscriber.stream);
subscriber.stream.removeVideo();
};
/* Shortcut event API */
Session.prototype.onStreamCreated = function (callback) {
this.session.addEventListener("stream-added", function (streamEvent) {
callback(streamEvent.stream);
});
};
Session.prototype.onStreamDestroyed = function (callback) {
this.session.addEventListener("stream-removed", function (streamEvent) {
callback(streamEvent.stream);
});
};
Session.prototype.onParticipantJoined = function (callback) {
this.session.addEventListener("participant-joined", function (participantEvent) {
callback(participantEvent.connection);
});
};
Session.prototype.onParticipantLeft = function (callback) {
this.session.addEventListener("participant-left", function (participantEvent) {
callback(participantEvent.connection);
});
};
Session.prototype.onParticipantPublished = function (callback) {
this.session.addEventListener("participant-published", function (participantEvent) {
callback(participantEvent.connection);
});
};
Session.prototype.onParticipantEvicted = function (callback) {
this.session.addEventListener("participant-evicted", function (participantEvent) {
callback(participantEvent.connection);
});
};
Session.prototype.onRoomClosed = function (callback) {
this.session.addEventListener("room-closed", function (roomEvent) {
callback(roomEvent.room);
});
};
Session.prototype.onLostConnection = function (callback) {
this.session.addEventListener("lost-connection", function (roomEvent) {
callback(roomEvent.room);
});
};
Session.prototype.onMediaError = function (callback) {
this.session.addEventListener("error-media", function (errorEvent) {
callback(errorEvent.error);
});
};
return Session;
}());
exports.Session = Session;
},{"./Subscriber":109,"wolfy87-eventemitter":103}],109:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter = require("wolfy87-eventemitter");
var Subscriber = (function () {
function Subscriber(stream, parentId) {
this.ee = new EventEmitter();
this.stream = stream;
if (document.getElementById(parentId) != null) {
this.element = document.getElementById(parentId);
}
}
Subscriber.prototype.on = function (eventName, callback) {
var _this = this;
this.ee.addListener(eventName, function (event) {
callback(event);
});
if (eventName == 'videoElementCreated') {
if (this.stream.isReady) {
this.ee.emitEvent('videoElementCreated', [{
element: this.stream.getVideoElement()
}]);
}
else {
this.stream.addEventListener('video-element-created-by-stream', function (element) {
console.warn("Subscriber emitting videoElementCreated");
_this.id = element.id;
_this.ee.emitEvent('videoElementCreated', [{
element: element
}]);
});
}
}
};
return Subscriber;
}());
exports.Subscriber = Subscriber;
},{"wolfy87-eventemitter":103}],110:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Stream_1 = require("./Stream");
var Connection = (function () {
function Connection(openVidu, local, room, options) {
this.openVidu = openVidu;
this.local = local;
this.room = room;
this.options = options;
this.streams = {};
this.streamsOpts = [];
if (options) {
this.connectionId = options.id;
this.data = options.metadata;
if (options.streams) {
for (var _i = 0, _a = options.streams; _i < _a.length; _i++) {
var streamOptions = _a[_i];
var streamOpts = {
id: streamOptions.id,
connection: this,
recvVideo: (streamOptions.recvVideo == undefined ? true : streamOptions.recvVideo),
recvAudio: (streamOptions.recvAudio == undefined ? true : streamOptions.recvAudio),
audio: streamOptions.audio,
video: streamOptions.video,
data: streamOptions.data,
mediaConstraints: streamOptions.mediaConstraints
};
var stream = new Stream_1.Stream(openVidu, false, room, streamOpts);
this.addStream(stream);
this.streamsOpts.push(streamOpts);
}
}
}
console.log("New " + (local ? "local " : "remote ") + "participant " + this.connectionId
+ ", streams opts: ", this.streamsOpts);
}
Connection.prototype.setId = function (newId) {
this.connectionId = newId;
};
Connection.prototype.addStream = function (stream) {
this.streams[stream.getIdInParticipant()] = stream;
this.room.getStreams()[stream.getIdInParticipant()] = stream;
};
Connection.prototype.getStreams = function () {
return this.streams;
};
Connection.prototype.dispose = function () {
for (var key in this.streams) {
this.streams[key].dispose();
}
};
Connection.prototype.getId = function () {
return this.connectionId;
};
Connection.prototype.sendIceCandidate = function (candidate) {
console.debug((this.local ? "Local" : "Remote"), "candidate for", this.getId(), JSON.stringify(candidate));
this.openVidu.sendRequest("onIceCandidate", {
endpointName: this.getId(),
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, function (error, response) {
if (error) {
console.error("Error sending ICE candidate: "
+ JSON.stringify(error));
}
});
};
return Connection;
}());
exports.Connection = Connection;
},{"./Stream":113}],111:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/*
* (C) Copyright 2016 OpenVidu (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var SessionInternal_1 = require("./SessionInternal");
var Stream_1 = require("./Stream");
var RpcBuilder = require("kurento-jsonrpc");
var OpenViduInternal = (function () {
function OpenViduInternal(wsUri) {
this.wsUri = wsUri;
this.remoteStreams = [];
if (this.wsUri.charAt(wsUri.length - 1) != '/') {
this.wsUri += '/';
}
this.wsUri += 'room';
}
/* NEW METHODS */
OpenViduInternal.prototype.initSession = function (sessionId) {
console.log("Session initialized!");
this.session = new SessionInternal_1.SessionInternal(this, sessionId);
return this.session;
};
OpenViduInternal.prototype.initPublisherTagged = function (parentId, cameraOptions, callback) {
var _this = this;
console.log("Publisher tagged initialized!");
this.getCamera(cameraOptions);
if (callback == null) {
this.camera.requestCameraAccess(function (error, camera) {
if (error) {
console.log("Error accessing the camera");
}
else {
_this.camera.setVideoElement(_this.cameraReady(camera, parentId));
}
});
return this.camera;
}
else {
this.camera.requestCameraAccess(function (error, camera) {
if (error) {
callback(error);
}
else {
_this.camera.setVideoElement(_this.cameraReady(camera, parentId));
callback(undefined);
}
});
return this.camera;
}
};
OpenViduInternal.prototype.cameraReady = function (camera, parentId) {
this.camera = camera;
var videoElement = this.camera.playOnlyVideo(parentId, null);
this.camera.emitStreamReadyEvent();
return videoElement;
};
OpenViduInternal.prototype.initPublisher = function (cameraOptions, callback) {
console.log("Publisher initialized!");
this.getCamera(cameraOptions);
this.camera.requestCameraAccess(function (error, camera) {
if (error)
callback(error);
else
callback(undefined);
});
};
OpenViduInternal.prototype.getLocalStream = function () {
return this.camera;
};
OpenViduInternal.prototype.getRemoteStreams = function () {
return this.remoteStreams;
};
/* NEW METHODS */
OpenViduInternal.prototype.getRoom = function () {
return this.session;
};
OpenViduInternal.prototype.connect = function (callback) {
this.callback = callback;
this.initJsonRpcClient(this.wsUri);
};
OpenViduInternal.prototype.initJsonRpcClient = function (wsUri) {
var config = {
heartbeat: 3000,
sendCloseMessage: false,
ws: {
uri: wsUri,
useSockJS: false,
onconnected: this.connectCallback.bind(this),
ondisconnect: this.disconnectCallback.bind(this),
onreconnecting: this.reconnectingCallback.bind(this),
onreconnected: this.reconnectedCallback.bind(this)
},
rpc: {
requestTimeout: 15000,
//notifications
participantJoined: this.onParticipantJoined.bind(this),
participantPublished: this.onParticipantPublished.bind(this),
participantUnpublished: this.onParticipantLeft.bind(this),
participantLeft: this.onParticipantLeft.bind(this),
participantEvicted: this.onParticipantEvicted.bind(this),
sendMessage: this.onNewMessage.bind(this),
iceCandidate: this.iceCandidateEvent.bind(this),
mediaError: this.onMediaError.bind(this),
custonNotification: this.customNotification.bind(this)
}
};
this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
};
OpenViduInternal.prototype.customNotification = function (params) {
if (this.isRoomAvailable()) {
this.session.emitEvent("custom-message-received", [{ params: params }]);
}
};
OpenViduInternal.prototype.connectCallback = function (error) {
if (error) {
this.callback(error);
}
else {
this.callback(null);
}
};
OpenViduInternal.prototype.isRoomAvailable = function () {
if (this.session !== undefined && this.session instanceof SessionInternal_1.SessionInternal) {
return true;
}
else {
console.warn('Room instance not found');
return false;
}
};
OpenViduInternal.prototype.disconnectCallback = function () {
console.log('Websocket connection lost');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
}
else {
alert('Connection error. Please reload page.');
}
};
OpenViduInternal.prototype.reconnectingCallback = function () {
console.log('Websocket connection lost (reconnecting)');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
}
else {
alert('Connection error. Please reload page.');
}
};
OpenViduInternal.prototype.reconnectedCallback = function () {
console.log('Websocket reconnected');
};
OpenViduInternal.prototype.onParticipantJoined = function (params) {
if (this.isRoomAvailable()) {
this.session.onParticipantJoined(params);
}
};
OpenViduInternal.prototype.onParticipantPublished = function (params) {
if (this.isRoomAvailable()) {
this.session.onParticipantPublished(params);
}
};
OpenViduInternal.prototype.onParticipantLeft = function (params) {
if (this.isRoomAvailable()) {
this.session.onParticipantLeft(params);
}
};
OpenViduInternal.prototype.onParticipantEvicted = function (params) {
if (this.isRoomAvailable()) {
this.session.onParticipantEvicted(params);
}
};
OpenViduInternal.prototype.onNewMessage = function (params) {
if (this.isRoomAvailable()) {
this.session.onNewMessage(params);
}
};
OpenViduInternal.prototype.iceCandidateEvent = function (params) {
if (this.isRoomAvailable()) {
this.session.recvIceCandidate(params);
}
};
OpenViduInternal.prototype.onRoomClosed = function (params) {
if (this.isRoomAvailable()) {
this.session.onRoomClosed(params);
}
};
OpenViduInternal.prototype.onMediaError = function (params) {
if (this.isRoomAvailable()) {
this.session.onMediaError(params);
}
};
OpenViduInternal.prototype.setRpcParams = function (params) {
this.rpcParams = params;
};
OpenViduInternal.prototype.sendRequest = function (method, params, callback) {
if (params && params instanceof Function) {
callback = params;
params = undefined;
}
params = params || {};
if (this.rpcParams && this.rpcParams !== null && this.rpcParams !== undefined) {
for (var index in this.rpcParams) {
if (this.rpcParams.hasOwnProperty(index)) {
params[index] = this.rpcParams[index];
console.log('RPC param added to request {' + index + ': ' + this.rpcParams[index] + '}');
}
}
2016-10-11 09:53:32 +02:00
}
console.log('Sending request: { method:"' + method + '", params: ' + JSON.stringify(params) + ' }');
this.jsonRpcClient.send(method, params, callback);
};
OpenViduInternal.prototype.close = function (forced) {
if (this.isRoomAvailable()) {
this.session.leave(forced, this.jsonRpcClient);
}
};
;
OpenViduInternal.prototype.disconnectParticipant = function (stream) {
if (this.isRoomAvailable()) {
this.session.disconnect(stream);
}
};
OpenViduInternal.prototype.getCamera = function (options) {
if (this.camera) {
return this.camera;
}
options = options || {
audio: true,
video: true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
};
options.connection = this.session.getLocalParticipant();
this.camera = new Stream_1.Stream(this, true, this.session, options);
return this.camera;
};
;
/*joinSession(options: SessionOptions, callback: Callback<Session>) {
this.session.configure(options);
this.session.connect2();
this.session.addEventListener('room-connected', roomEvent => callback(undefined,this.session));
this.session.addEventListener('error-room', error => callback(error));
return this.session;
};*/
//CHAT
OpenViduInternal.prototype.sendMessage = function (room, user, message) {
this.sendRequest('sendMessage', {
message: message,
userMessage: user,
roomMessage: room
}, function (error, response) {
if (error) {
console.error(error);
}
});
};
;
OpenViduInternal.prototype.sendCustomRequest = function (params, callback) {
this.sendRequest('customRequest', params, callback);
};
;
OpenViduInternal.prototype.toggleLocalVideoTrack = function (activate) {
this.getCamera().getWebRtcPeer().videoEnabled = activate;
};
OpenViduInternal.prototype.toggleLocalAudioTrack = function (activate) {
this.getCamera().getWebRtcPeer().audioEnabled = activate;
};
OpenViduInternal.prototype.publishLocalVideoAudio = function () {
this.toggleLocalVideoTrack(true);
this.toggleLocalAudioTrack(true);
};
OpenViduInternal.prototype.unpublishLocalVideoAudio = function () {
this.toggleLocalVideoTrack(false);
this.toggleLocalAudioTrack(false);
};
OpenViduInternal.prototype.generateMediaConstraints = function (quality) {
var mediaConstraints = {
audio: true,
video: {}
};
var w, h;
switch (quality) {
case 'LOW':
w = 320;
h = 240;
break;
case 'MEDIUM':
w = 640;
h = 480;
break;
case 'HIGH':
w = 1280;
h = 720;
break;
default:
w = 640;
h = 480;
}
mediaConstraints.video['width'] = { exact: w };
mediaConstraints.video['height'] = { exact: h };
//mediaConstraints.video['frameRate'] = { ideal: Number((<HTMLInputElement>document.getElementById('frameRate')).value) };
return mediaConstraints;
};
return OpenViduInternal;
}());
exports.OpenViduInternal = OpenViduInternal;
2016-10-11 09:53:32 +02:00
},{"./SessionInternal":112,"./Stream":113,"kurento-jsonrpc":31}],112:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Connection_1 = require("./Connection");
var EventEmitter = require("wolfy87-eventemitter");
var SessionInternal = (function () {
function SessionInternal(openVidu, sessionId) {
this.openVidu = openVidu;
this.sessionId = sessionId;
this.ee = new EventEmitter();
this.streams = {};
this.participants = {};
this.participantsSpeaking = [];
this.connected = false;
this.localParticipant = new Connection_1.Connection(this.openVidu, true, this);
2016-10-11 09:53:32 +02:00
}
/* NEW METHODS */
SessionInternal.prototype.connect = function (token, callback) {
var _this = this;
this.openVidu.connect(function (error) {
if (error) {
callback('ERROR CONNECTING TO OPENVIDU');
}
else {
var joinParams = {
token: token,
session: _this.sessionId,
metadata: _this.options.metadata,
dataChannels: false
};
if (_this.localParticipant) {
if (Object.keys(_this.localParticipant.getStreams()).some(function (streamId) {
return _this.streams[streamId].isDataChannelEnabled();
})) {
joinParams.dataChannels = true;
}
}
_this.openVidu.sendRequest('joinRoom', joinParams, function (error, response) {
if (error) {
callback('UNABLE TO JOIN ROOM');
}
else {
_this.connected = true;
var exParticipants = response.value;
var roomEvent = {
participants: new Array(),
streams: new Array()
};
var length_1 = exParticipants.length;
for (var i = 0; i < length_1; i++) {
var connection = new Connection_1.Connection(_this.openVidu, false, _this, exParticipants[i]);
connection.creationTime = new Date().getTime();
_this.participants[connection.getId()] = connection;
roomEvent.participants.push(connection);
var streams = connection.getStreams();
for (var key in streams) {
roomEvent.streams.push(streams[key]);
if (_this.subscribeToStreams) {
streams[key].subscribe();
}
}
}
// Update local Connection object properties with values returned by server
_this.localParticipant.data = response.metadata;
_this.localParticipant.creationTime = new Date().getTime();
// Updates the value of property 'connection' in Session object
_this.ee.emitEvent('update-connection-object', [{ connection: _this.localParticipant }]);
// Own connection created event
_this.ee.emitEvent('connectionCreated', [{ connection: _this.localParticipant }]);
// One connection created event for each existing connection in the session
for (var _i = 0, _a = roomEvent.participants; _i < _a.length; _i++) {
var part = _a[_i];
_this.ee.emitEvent('connectionCreated', [{ connection: part }]);
}
//if (this.subscribeToStreams) {
for (var _b = 0, _c = roomEvent.streams; _b < _c.length; _b++) {
var stream = _c[_b];
_this.ee.emitEvent('stream-added', [{ stream: stream }]);
// Adding the remote stream to the OpenVidu object
_this.openVidu.getRemoteStreams().push(stream);
}
//}
callback(undefined);
}
});
}
});
};
SessionInternal.prototype.publish = function () {
this.openVidu.getCamera().publish();
};
/* NEW METHODS */
SessionInternal.prototype.configure = function (options) {
this.options = options;
this.id = options.sessionId;
this.subscribeToStreams = options.subscribeToStreams == null ? true : options.subscribeToStreams;
this.updateSpeakerInterval = options.updateSpeakerInterval || 1500;
this.thresholdSpeaker = options.thresholdSpeaker || -50;
this.localParticipant.setId(options.participantId);
this.activateUpdateMainSpeaker();
this.participants[options.participantId] = this.localParticipant;
};
SessionInternal.prototype.getId = function () {
return this.id;
};
SessionInternal.prototype.getSessionId = function () {
return this.sessionId;
};
SessionInternal.prototype.activateUpdateMainSpeaker = function () {
var _this = this;
setInterval(function () {
if (_this.participantsSpeaking.length > 0) {
_this.ee.emitEvent('update-main-speaker', [{
participantId: _this.participantsSpeaking[_this.participantsSpeaking.length - 1]
}]);
}
}, this.updateSpeakerInterval);
};
SessionInternal.prototype.getLocalParticipant = function () {
return this.localParticipant;
};
SessionInternal.prototype.addEventListener = function (eventName, listener) {
this.ee.on(eventName, listener);
};
SessionInternal.prototype.addOnceEventListener = function (eventName, listener) {
this.ee.once(eventName, listener);
};
SessionInternal.prototype.removeListener = function (eventName, listener) {
this.ee.off(eventName, listener);
};
SessionInternal.prototype.emitEvent = function (eventName, eventsArray) {
this.ee.emitEvent(eventName, eventsArray);
};
SessionInternal.prototype.subscribe = function (stream) {
2016-10-11 09:53:32 +02:00
stream.subscribe();
};
SessionInternal.prototype.unsuscribe = function (stream) {
console.log("Unsubscribing from " + stream.getId());
this.openVidu.sendRequest('unsubscribeFromVideo', {
sender: stream.getId()
}, function (error, response) {
if (error) {
console.error(error);
}
else {
console.info("Unsubscribed correctly from " + stream.getId());
}
});
};
SessionInternal.prototype.onParticipantPublished = function (options) {
options.metadata = this.participants[options.id].data;
var connection = new Connection_1.Connection(this.openVidu, false, this, options);
var pid = connection.getId();
if (!(pid in this.participants)) {
2016-10-11 09:53:32 +02:00
console.info("Publisher not found in participants list by its id", pid);
}
else {
2016-10-11 09:53:32 +02:00
console.log("Publisher found in participants list by its id", pid);
}
//replacing old connection (this one has streams)
connection.creationTime = this.participants[pid].creationTime;
this.participants[pid] = connection;
this.ee.emitEvent('participant-published', [{ connection: connection }]);
var streams = connection.getStreams();
2016-10-11 09:53:32 +02:00
for (var key in streams) {
var stream = streams[key];
if (this.subscribeToStreams) {
2016-10-11 09:53:32 +02:00
stream.subscribe();
}
this.ee.emitEvent('stream-added', [{ stream: stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
2016-10-11 09:53:32 +02:00
}
};
SessionInternal.prototype.onParticipantJoined = function (msg) {
var connection = new Connection_1.Connection(this.openVidu, false, this, msg);
connection.creationTime = new Date().getTime();
var pid = connection.getId();
if (!(pid in this.participants)) {
2016-10-11 09:53:32 +02:00
console.log("New participant to participants list with id", pid);
this.participants[pid] = connection;
}
else {
2016-10-11 09:53:32 +02:00
//use existing so that we don't lose streams info
console.info("Participant already exists in participants list with " +
"the same id, old:", this.participants[pid], ", joined now:", connection);
connection = this.participants[pid];
2016-10-11 09:53:32 +02:00
}
this.ee.emitEvent('participant-joined', [{
connection: connection
2016-10-11 09:53:32 +02:00
}]);
this.ee.emitEvent('connectionCreated', [{
connection: connection
}]);
};
SessionInternal.prototype.onParticipantLeft = function (msg) {
var _this = this;
var connection = this.participants[msg.name];
if (connection !== undefined) {
delete this.participants[msg.name];
this.ee.emitEvent('participant-left', [{
connection: connection
}]);
var streams = connection.getStreams();
2016-10-11 09:53:32 +02:00
for (var key in streams) {
this.ee.emitEvent('stream-removed', [{
stream: streams[key],
preventDefault: function () { _this.ee.removeEvent('stream-removed-default'); }
}]);
this.ee.emitEvent('stream-removed-default', [{
stream: streams[key]
}]);
// Deleting the removed stream from the OpenVidu object
var index = this.openVidu.getRemoteStreams().indexOf(streams[key]);
this.openVidu.getRemoteStreams().splice(index, 1);
2016-10-11 09:53:32 +02:00
}
connection.dispose();
this.ee.emitEvent('connectionDestroyed', [{
connection: connection
}]);
}
else {
2016-10-11 09:53:32 +02:00
console.warn("Participant " + msg.name
+ " unknown. Participants: "
+ JSON.stringify(this.participants));
2016-10-11 09:53:32 +02:00
}
};
;
SessionInternal.prototype.onParticipantEvicted = function (msg) {
this.ee.emitEvent('participant-evicted', [{
localParticipant: this.localParticipant
}]);
2016-10-11 09:53:32 +02:00
};
;
SessionInternal.prototype.onNewMessage = function (msg) {
2016-10-11 09:53:32 +02:00
console.log("New message: " + JSON.stringify(msg));
var room = msg.room;
var user = msg.user;
var message = msg.message;
if (user !== undefined) {
this.ee.emitEvent('newMessage', [{
room: room,
user: user,
message: message
}]);
}
else {
console.warn("User undefined in new message:", msg);
2016-10-11 09:53:32 +02:00
}
};
SessionInternal.prototype.recvIceCandidate = function (msg) {
2016-10-11 09:53:32 +02:00
var candidate = {
candidate: msg.candidate,
sdpMid: msg.sdpMid,
sdpMLineIndex: msg.sdpMLineIndex
};
var connection = this.participants[msg.endpointName];
if (!connection) {
2016-10-11 09:53:32 +02:00
console.error("Participant not found for endpoint " +
msg.endpointName + ". Ice candidate will be ignored.", candidate);
return;
2016-10-11 09:53:32 +02:00
}
var streams = connection.getStreams();
var _loop_1 = function (key) {
2016-10-11 09:53:32 +02:00
var stream = streams[key];
stream.getWebRtcPeer().addIceCandidate(candidate, function (error) {
if (error) {
console.error("Error adding candidate for " + key
+ " stream of endpoint " + msg.endpointName
+ ": " + error);
}
});
};
for (var key in streams) {
_loop_1(key);
2016-10-11 09:53:32 +02:00
}
};
SessionInternal.prototype.onRoomClosed = function (msg) {
2016-10-11 09:53:32 +02:00
console.log("Room closed: " + JSON.stringify(msg));
var room = msg.room;
if (room !== undefined) {
this.ee.emitEvent('room-closed', [{
room: room
}]);
}
else {
console.warn("Room undefined in on room closed", msg);
2016-10-11 09:53:32 +02:00
}
};
SessionInternal.prototype.onLostConnection = function () {
if (!this.connected) {
2016-10-11 09:53:32 +02:00
console.warn('Not connected to room, ignoring lost connection notification');
return;
2016-10-11 09:53:32 +02:00
}
console.log('Lost connection in room ' + this.id);
var room = this.id;
2016-10-11 09:53:32 +02:00
if (room !== undefined) {
this.ee.emitEvent('lost-connection', [{ room: room }]);
}
else {
console.warn('Room undefined when lost connection');
2016-10-11 09:53:32 +02:00
}
};
SessionInternal.prototype.onMediaError = function (params) {
2016-10-11 09:53:32 +02:00
console.error("Media error: " + JSON.stringify(params));
var error = params.error;
if (error) {
this.ee.emitEvent('error-media', [{
error: error
}]);
}
else {
console.warn("Received undefined media error. Params:", params);
2016-10-11 09:53:32 +02:00
}
};
2016-10-11 09:53:32 +02:00
/*
* forced means the user was evicted, no need to send the 'leaveRoom' request
*/
SessionInternal.prototype.leave = function (forced, jsonRpcClient) {
2016-10-11 09:53:32 +02:00
forced = !!forced;
console.log("Leaving room (forced=" + forced + ")");
if (this.connected && !forced) {
this.openVidu.sendRequest('leaveRoom', function (error, response) {
2016-10-11 09:53:32 +02:00
if (error) {
console.error(error);
}
jsonRpcClient.close();
});
}
else {
2016-10-11 09:53:32 +02:00
jsonRpcClient.close();
}
this.connected = false;
if (this.participants) {
for (var pid in this.participants) {
this.participants[pid].dispose();
delete this.participants[pid];
2016-10-11 09:53:32 +02:00
}
}
};
SessionInternal.prototype.disconnect = function (stream) {
var connection = stream.getParticipant();
if (!connection) {
2016-10-11 09:53:32 +02:00
console.error("Stream to disconnect has no participant", stream);
return;
2016-10-11 09:53:32 +02:00
}
delete this.participants[connection.getId()];
connection.dispose();
if (connection === this.localParticipant) {
console.log("Unpublishing my media (I'm " + connection.getId() + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
2016-10-11 09:53:32 +02:00
if (error) {
console.error(error);
}
else {
2016-10-11 09:53:32 +02:00
console.info("Media unpublished correctly");
}
});
}
else {
this.unsuscribe(stream);
}
};
SessionInternal.prototype.unpublish = function (stream) {
var connection = stream.getParticipant();
if (!connection) {
console.error("Stream to disconnect has no participant", stream);
return;
}
if (connection === this.localParticipant) {
delete this.participants[connection.getId()];
connection.dispose();
console.log("Unpublishing my media (I'm " + connection.getId() + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
}
else {
console.info("Media unpublished correctly");
}
});
}
};
SessionInternal.prototype.getStreams = function () {
return this.streams;
};
SessionInternal.prototype.addParticipantSpeaking = function (participantId) {
this.participantsSpeaking.push(participantId);
};
SessionInternal.prototype.removeParticipantSpeaking = function (participantId) {
2016-10-11 09:53:32 +02:00
var pos = -1;
for (var i = 0; i < this.participantsSpeaking.length; i++) {
if (this.participantsSpeaking[i] == participantId) {
2016-10-11 09:53:32 +02:00
pos = i;
break;
}
}
if (pos != -1) {
this.participantsSpeaking.splice(pos, 1);
}
};
return SessionInternal;
}());
exports.SessionInternal = SessionInternal;
},{"./Connection":110,"wolfy87-eventemitter":103}],113:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var EventEmitter = require("wolfy87-eventemitter");
var kurentoUtils = require("kurento-utils");
var adapter = require("webrtc-adapter");
if (window) {
window["adapter"] = adapter;
}
function jq(id) {
return id.replace(/(@|:|\.|\[|\]|,)/g, "\\$1");
}
function show(id) {
document.getElementById(jq(id)).style.display = 'block';
}
function hide(id) {
document.getElementById(jq(id)).style.display = 'none';
}
var Stream = (function () {
function Stream(openVidu, local, room, options) {
var _this = this;
this.openVidu = openVidu;
this.local = local;
this.room = room;
this.ee = new EventEmitter();
this.videoElements = [];
this.elements = [];
this.showMyRemote = false;
this.localMirrored = false;
this.chanId = 0;
this.dataChannelOpened = false;
this.isReady = false;
if (options.id) {
this.id = options.id;
}
else {
this.id = "webcam";
}
this.connection = options.connection;
this.recvVideo = options.recvVideo;
this.recvAudio = options.recvAudio;
this.dataChannel = options.data || false;
this.sendVideo = options.video;
this.sendAudio = options.audio;
this.mediaConstraints = options.mediaConstraints;
this.addEventListener('src-added', function (srcEvent) {
_this.videoSrc = srcEvent.src;
if (_this.video)
_this.video.src = srcEvent.src;
console.warn("Videosrc [" + srcEvent.src + "] added to stream [" + _this.getId() + "]");
});
2016-10-11 09:53:32 +02:00
}
Stream.prototype.emitSrcEvent = function (wrstream) {
this.ee.emitEvent('src-added', [{
src: URL.createObjectURL(wrstream)
}]);
};
Stream.prototype.emitStreamReadyEvent = function () {
this.ee.emitEvent('stream-ready'), [{}];
};
Stream.prototype.getVideoSrc = function () {
return this.videoSrc;
};
Stream.prototype.removeVideo = function (parentElement) {
if (typeof parentElement === "string") {
document.getElementById(parentElement).removeChild(this.video);
}
else if (parentElement instanceof Element) {
parentElement.removeChild(this.video);
}
else if (!parentElement) {
if (document.getElementById(this.parentId)) {
document.getElementById(this.parentId).removeChild(this.video);
}
}
};
Stream.prototype.getVideoElement = function () {
return this.video;
};
Stream.prototype.setVideoElement = function (video) {
this.video = video;
};
Stream.prototype.getRecvVideo = function () {
return this.recvVideo;
};
Stream.prototype.getRecvAudio = function () {
return this.recvAudio;
};
Stream.prototype.subscribeToMyRemote = function () {
this.showMyRemote = true;
};
Stream.prototype.displayMyRemote = function () {
return this.showMyRemote;
};
Stream.prototype.mirrorLocalStream = function (wr) {
this.showMyRemote = true;
this.localMirrored = true;
if (wr) {
this.wrStream = wr;
this.emitSrcEvent(this.wrStream);
}
};
Stream.prototype.isLocalMirrored = function () {
return this.localMirrored;
};
Stream.prototype.getChannelName = function () {
return this.getId() + '_' + this.chanId++;
};
Stream.prototype.isDataChannelEnabled = function () {
return this.dataChannel;
};
Stream.prototype.isDataChannelOpened = function () {
return this.dataChannelOpened;
};
Stream.prototype.onDataChannelOpen = function (event) {
2016-10-11 09:53:32 +02:00
console.log('Data channel is opened');
this.dataChannelOpened = true;
};
Stream.prototype.onDataChannelClosed = function (event) {
2016-10-11 09:53:32 +02:00
console.log('Data channel is closed');
this.dataChannelOpened = false;
};
Stream.prototype.sendData = function (data) {
if (this.wp === undefined) {
2016-10-11 09:53:32 +02:00
throw new Error('WebRTC peer has not been created yet');
}
if (!this.dataChannelOpened) {
2016-10-11 09:53:32 +02:00
throw new Error('Data channel is not opened');
}
console.log("Sending through data channel: " + data);
this.wp.send(data);
};
Stream.prototype.getWrStream = function () {
return this.wrStream;
};
Stream.prototype.getWebRtcPeer = function () {
return this.wp;
};
Stream.prototype.addEventListener = function (eventName, listener) {
this.ee.addListener(eventName, listener);
};
Stream.prototype.addOnceEventListener = function (eventName, listener) {
this.ee.addOnceListener(eventName, listener);
};
Stream.prototype.removeListener = function (eventName) {
this.ee.removeAllListeners(eventName);
};
Stream.prototype.showSpinner = function (spinnerParentId) {
2016-10-11 09:53:32 +02:00
var progress = document.createElement('div');
progress.id = 'progress-' + this.getId();
2016-10-11 09:53:32 +02:00
progress.style.background = "center transparent url('img/spinner.gif') no-repeat";
var spinnerParent = document.getElementById(spinnerParentId);
if (spinnerParent) {
spinnerParent.appendChild(progress);
}
};
Stream.prototype.hideSpinner = function (spinnerId) {
spinnerId = (spinnerId === undefined) ? this.getId() : spinnerId;
hide('progress-' + spinnerId);
};
Stream.prototype.playOnlyVideo = function (parentElement, thumbnailId) {
this.video = document.createElement('video');
this.video.id = 'native-video-' + this.getId();
this.video.autoplay = true;
this.video.controls = false;
this.video.src = this.videoSrc;
this.videoElements.push({
2016-10-11 09:53:32 +02:00
thumb: thumbnailId,
video: this.video
2016-10-11 09:53:32 +02:00
});
if (this.local) {
this.video.muted = true;
2016-10-11 09:53:32 +02:00
}
else {
this.video.title = this.getId();
}
2016-10-11 09:53:32 +02:00
if (typeof parentElement === "string") {
this.parentId = parentElement;
var parentElementDom = document.getElementById(parentElement);
if (parentElementDom) {
this.video = parentElementDom.appendChild(this.video);
}
2016-10-11 09:53:32 +02:00
}
else {
this.parentId = parentElement.id;
this.video = parentElement.appendChild(this.video);
}
this.ee.emitEvent('video-element-created-by-stream', [{
element: this.video
}]);
this.isReady = true;
return this.video;
};
Stream.prototype.playThumbnail = function (thumbnailId) {
2016-10-11 09:53:32 +02:00
var container = document.createElement('div');
container.className = "participant";
container.id = this.getId();
var thumbnail = document.getElementById(thumbnailId);
if (thumbnail) {
thumbnail.appendChild(container);
}
this.elements.push(container);
2016-10-11 09:53:32 +02:00
var name = document.createElement('div');
container.appendChild(name);
var userName = this.getId().replace('_webcam', '');
2016-10-11 09:53:32 +02:00
if (userName.length >= 16) {
userName = userName.substring(0, 16) + "...";
2016-10-11 09:53:32 +02:00
}
name.appendChild(document.createTextNode(userName));
name.id = "name-" + this.getId();
2016-10-11 09:53:32 +02:00
name.className = "name";
name.title = this.getId();
this.showSpinner(thumbnailId);
return this.playOnlyVideo(container, thumbnailId);
};
Stream.prototype.getIdInParticipant = function () {
return this.id;
};
Stream.prototype.getParticipant = function () {
return this.connection;
};
Stream.prototype.getId = function () {
if (this.connection) {
return this.connection.getId() + "_" + this.id;
2016-10-11 09:53:32 +02:00
}
else {
return this.id + "_webcam";
}
};
Stream.prototype.getRTCPeerConnection = function () {
return this.getWebRtcPeer().peerConnection;
};
Stream.prototype.requestCameraAccess = function (callback) {
var _this = this;
this.connection.addStream(this);
var constraints = this.mediaConstraints;
var constraints2 = {
2016-10-11 09:53:32 +02:00
audio: true,
video: {
width: {
ideal: 1280
},
frameRate: {
ideal: 15
}
}
};
navigator.mediaDevices.getUserMedia(constraints)
.then(function (userStream) {
userStream.getAudioTracks()[0].enabled = _this.sendAudio;
userStream.getVideoTracks()[0].enabled = _this.sendVideo;
_this.wrStream = userStream;
_this.emitSrcEvent(_this.wrStream);
_this.ee.emitEvent('camera-access-changed', [{
accessAllowed: true
}]);
callback(undefined, _this);
})
.catch(function (error) {
console.error("Access denied", error);
_this.ee.emitEvent('camera-access-changed', [{
accessAllowed: false
}]);
callback(error, undefined);
2016-10-11 09:53:32 +02:00
});
};
Stream.prototype.publishVideoCallback = function (error, sdpOfferParam, wp) {
var _this = this;
2016-10-11 09:53:32 +02:00
if (error) {
return console.error("(publish) SDP offer error: "
+ JSON.stringify(error));
}
console.log("Sending SDP offer to publish as "
+ this.getId(), sdpOfferParam);
this.openVidu.sendRequest("publishVideo", {
2016-10-11 09:53:32 +02:00
sdpOffer: sdpOfferParam,
doLoopback: this.displayMyRemote() || false
2016-10-11 09:53:32 +02:00
}, function (error, response) {
if (error) {
console.error("Error on publishVideo: " + JSON.stringify(error));
}
else {
_this.room.emitEvent('stream-published', [{
stream: _this
}]);
_this.processSdpAnswer(response.sdpAnswer);
2016-10-11 09:53:32 +02:00
}
});
};
Stream.prototype.startVideoCallback = function (error, sdpOfferParam, wp) {
var _this = this;
2016-10-11 09:53:32 +02:00
if (error) {
return console.error("(subscribe) SDP offer error: "
+ JSON.stringify(error));
}
console.log("Sending SDP offer to subscribe to "
+ this.getId(), sdpOfferParam);
this.openVidu.sendRequest("receiveVideoFrom", {
sender: this.getId(),
2016-10-11 09:53:32 +02:00
sdpOffer: sdpOfferParam
}, function (error, response) {
if (error) {
console.error("Error on recvVideoFrom: " + JSON.stringify(error));
}
else {
_this.processSdpAnswer(response.sdpAnswer);
2016-10-11 09:53:32 +02:00
}
});
};
Stream.prototype.initWebRtcPeer = function (sdpOfferCallback) {
var _this = this;
if (this.local) {
var userMediaConstraints = {
audio: this.sendAudio,
video: this.sendVideo
};
2016-10-11 09:53:32 +02:00
var options = {
videoStream: this.wrStream,
mediaConstraints: userMediaConstraints,
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
};
if (this.dataChannel) {
2016-10-11 09:53:32 +02:00
options.dataChannelConfig = {
id: this.getChannelName(),
onopen: this.onDataChannelOpen,
onclose: this.onDataChannelClosed
2016-10-11 09:53:32 +02:00
};
options.dataChannels = true;
}
if (this.displayMyRemote()) {
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, function (error) {
2016-10-11 09:53:32 +02:00
if (error) {
return console.error(error);
}
_this.wp.generateOffer(sdpOfferCallback.bind(_this));
2016-10-11 09:53:32 +02:00
});
}
else {
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, function (error) {
2016-10-11 09:53:32 +02:00
if (error) {
return console.error(error);
}
_this.wp.generateOffer(sdpOfferCallback.bind(_this));
2016-10-11 09:53:32 +02:00
});
}
}
else {
2016-10-11 09:53:32 +02:00
var offerConstraints = {
mandatory: {
OfferToReceiveVideo: this.recvVideo,
OfferToReceiveAudio: this.recvAudio
2016-10-11 09:53:32 +02:00
}
};
console.log("Constraints of generate SDP offer (subscribing)", offerConstraints);
2016-10-11 09:53:32 +02:00
var options = {
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
2016-10-11 09:53:32 +02:00
connectionConstraints: offerConstraints
};
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function (error) {
2016-10-11 09:53:32 +02:00
if (error) {
return console.error(error);
}
_this.wp.generateOffer(sdpOfferCallback.bind(_this));
2016-10-11 09:53:32 +02:00
});
}
console.log("Waiting for SDP offer to be generated ("
+ (this.local ? "local" : "remote") + " peer: " + this.getId() + ")");
};
Stream.prototype.publish = function () {
var _this = this;
2016-10-11 09:53:32 +02:00
// FIXME: Throw error when stream is not local
if (this.isReady) {
this.initWebRtcPeer(this.publishVideoCallback);
}
else {
this.ee.once('stream-ready', function (streamEvent) {
_this.publish();
});
}
2016-10-11 09:53:32 +02:00
// FIXME: Now we have coupled connecting to a room and adding a
// stream to this room. But in the new API, there are two steps.
// This is the second step. For now, it do nothing.
};
Stream.prototype.subscribe = function () {
2016-10-11 09:53:32 +02:00
// FIXME: In the current implementation all participants are subscribed
// automatically to all other participants. We use this method only to
// negotiate SDP
this.initWebRtcPeer(this.startVideoCallback);
};
Stream.prototype.processSdpAnswer = function (sdpAnswer) {
var _this = this;
2016-10-11 09:53:32 +02:00
var answer = new RTCSessionDescription({
type: 'answer',
sdp: sdpAnswer,
});
console.log(this.getId() + ": set peer connection with recvd SDP answer", sdpAnswer);
var participantId = this.getId();
var pc = this.wp.peerConnection;
2016-10-11 09:53:32 +02:00
pc.setRemoteDescription(answer, function () {
// Avoids to subscribe to your own stream remotely
// except when showMyRemote is true
if (!_this.local || _this.displayMyRemote()) {
_this.wrStream = pc.getRemoteStreams()[0];
console.log("Peer remote stream", _this.wrStream);
if (_this.wrStream != undefined) {
_this.emitSrcEvent(_this.wrStream);
_this.speechEvent = kurentoUtils.WebRtcPeer.hark(_this.wrStream, { threshold: _this.room.thresholdSpeaker });
_this.speechEvent.on('speaking', function () {
_this.room.addParticipantSpeaking(participantId);
_this.room.emitEvent('stream-speaking', [{
participantId: participantId
}]);
2016-10-11 09:53:32 +02:00
});
_this.speechEvent.on('stopped_speaking', function () {
_this.room.removeParticipantSpeaking(participantId);
_this.room.emitEvent('stream-stopped-speaking', [{
participantId: participantId
}]);
2016-10-11 09:53:32 +02:00
});
}
for (var _i = 0, _a = _this.videoElements; _i < _a.length; _i++) {
var videoElement = _a[_i];
var thumbnailId = videoElement.thumb;
var video = videoElement.video;
video.src = URL.createObjectURL(_this.wrStream);
2016-10-11 09:53:32 +02:00
video.onplay = function () {
console.log(_this.getId() + ': ' + 'Video playing');
//show(thumbnailId);
//this.hideSpinner(this.getId());
2016-10-11 09:53:32 +02:00
};
}
_this.room.emitEvent('stream-subscribed', [{
stream: _this
}]);
2016-10-11 09:53:32 +02:00
}
}, function (error) {
console.error(_this.getId() + ": Error setting SDP to the peer connection: "
2016-10-11 09:53:32 +02:00
+ JSON.stringify(error));
});
};
Stream.prototype.unpublish = function () {
if (this.wp) {
this.wp.dispose();
}
else {
if (this.wrStream) {
this.wrStream.getAudioTracks().forEach(function (track) {
track.stop && track.stop();
});
this.wrStream.getVideoTracks().forEach(function (track) {
track.stop && track.stop();
});
2016-10-11 09:53:32 +02:00
}
}
if (this.speechEvent) {
this.speechEvent.stop();
2016-10-11 09:53:32 +02:00
}
console.log(this.getId() + ": Stream '" + this.id + "' unpublished");
};
Stream.prototype.dispose = function () {
2016-10-11 09:53:32 +02:00
function disposeElement(element) {
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
}
this.elements.forEach(function (e) { return disposeElement(e); });
this.videoElements.forEach(function (ve) { return disposeElement(ve); });
disposeElement("progress-" + this.getId());
if (this.wp) {
this.wp.dispose();
2016-10-11 09:53:32 +02:00
}
else {
if (this.wrStream) {
this.wrStream.getAudioTracks().forEach(function (track) {
track.stop && track.stop();
});
this.wrStream.getVideoTracks().forEach(function (track) {
track.stop && track.stop();
});
2016-10-11 09:53:32 +02:00
}
}
if (this.speechEvent) {
this.speechEvent.stop();
2016-10-11 09:53:32 +02:00
}
console.log(this.getId() + ": Stream '" + this.id + "' disposed");
};
return Stream;
}());
exports.Stream = Stream;
},{"kurento-utils":2,"webrtc-adapter":93,"wolfy87-eventemitter":103}],114:[function(require,module,exports){
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
function EventEmitter() {
this._events = this._events || {};
this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
if (!isNumber(n) || n < 0 || isNaN(n))
throw TypeError('n must be a positive number');
this._maxListeners = n;
return this;
};
EventEmitter.prototype.emit = function(type) {
var er, handler, len, args, i, listeners;
if (!this._events)
this._events = {};
// If there is no 'error' event listener then throw.
if (type === 'error') {
if (!this._events.error ||
(isObject(this._events.error) && !this._events.error.length)) {
er = arguments[1];
if (er instanceof Error) {
throw er; // Unhandled 'error' event
} else {
// At least give some kind of context to the user
var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
err.context = er;
throw err;
}
}
}
handler = this._events[type];
if (isUndefined(handler))
return false;
if (isFunction(handler)) {
switch (arguments.length) {
// fast cases
case 1:
handler.call(this);
break;
case 2:
handler.call(this, arguments[1]);
break;
case 3:
handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
args = Array.prototype.slice.call(arguments, 1);
handler.apply(this, args);
}
} else if (isObject(handler)) {
args = Array.prototype.slice.call(arguments, 1);
listeners = handler.slice();
len = listeners.length;
for (i = 0; i < len; i++)
listeners[i].apply(this, args);
}
return true;
};
EventEmitter.prototype.addListener = function(type, listener) {
var m;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events)
this._events = {};
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (this._events.newListener)
this.emit('newListener', type,
isFunction(listener.listener) ?
listener.listener : listener);
if (!this._events[type])
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
else if (isObject(this._events[type]))
// If we've already got an array, just append.
this._events[type].push(listener);
else
// Adding the second element, need to change to array.
this._events[type] = [this._events[type], listener];
// Check for listener leak
if (isObject(this._events[type]) && !this._events[type].warned) {
if (!isUndefined(this._maxListeners)) {
m = this._maxListeners;
} else {
m = EventEmitter.defaultMaxListeners;
}
2016-10-11 09:53:32 +02:00
if (m && m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
this._events[type].length);
if (typeof console.trace === 'function') {
// not supported in IE 10
console.trace();
}
2016-10-11 09:53:32 +02:00
}
}
2016-10-11 09:53:32 +02:00
return this;
};
2016-10-11 09:53:32 +02:00
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
2016-10-11 09:53:32 +02:00
EventEmitter.prototype.once = function(type, listener) {
if (!isFunction(listener))
throw TypeError('listener must be a function');
2016-10-11 09:53:32 +02:00
var fired = false;
2016-10-11 09:53:32 +02:00
function g() {
this.removeListener(type, g);
2016-10-11 09:53:32 +02:00
if (!fired) {
fired = true;
listener.apply(this, arguments);
}
}
2016-10-11 09:53:32 +02:00
g.listener = listener;
this.on(type, g);
2016-10-11 09:53:32 +02:00
return this;
};
2016-10-11 09:53:32 +02:00
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
var list, position, length, i;
2016-10-11 09:53:32 +02:00
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events || !this._events[type])
return this;
list = this._events[type];
length = list.length;
position = -1;
if (list === listener ||
(isFunction(list.listener) && list.listener === listener)) {
delete this._events[type];
if (this._events.removeListener)
this.emit('removeListener', type, listener);
} else if (isObject(list)) {
for (i = length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
position = i;
break;
}
2016-10-11 09:53:32 +02:00
}
if (position < 0)
return this;
if (list.length === 1) {
list.length = 0;
delete this._events[type];
} else {
list.splice(position, 1);
2016-10-11 09:53:32 +02:00
}
if (this._events.removeListener)
this.emit('removeListener', type, listener);
}
return this;
};
EventEmitter.prototype.removeAllListeners = function(type) {
var key, listeners;
if (!this._events)
return this;
// not listening for removeListener, no need to emit
if (!this._events.removeListener) {
if (arguments.length === 0)
this._events = {};
else if (this._events[type])
delete this._events[type];
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
for (key in this._events) {
if (key === 'removeListener') continue;
this.removeAllListeners(key);
2016-10-11 09:53:32 +02:00
}
this.removeAllListeners('removeListener');
this._events = {};
return this;
}
listeners = this._events[type];
if (isFunction(listeners)) {
this.removeListener(type, listeners);
} else if (listeners) {
// LIFO order
while (listeners.length)
this.removeListener(type, listeners[listeners.length - 1]);
}
delete this._events[type];
return this;
};
EventEmitter.prototype.listeners = function(type) {
var ret;
if (!this._events || !this._events[type])
ret = [];
else if (isFunction(this._events[type]))
ret = [this._events[type]];
else
ret = this._events[type].slice();
return ret;
};
EventEmitter.prototype.listenerCount = function(type) {
if (this._events) {
var evlistener = this._events[type];
if (isFunction(evlistener))
return 1;
else if (evlistener)
return evlistener.length;
}
return 0;
};
EventEmitter.listenerCount = function(emitter, type) {
return emitter.listenerCount(type);
};
function isFunction(arg) {
return typeof arg === 'function';
}
2016-10-11 09:53:32 +02:00
function isNumber(arg) {
return typeof arg === 'number';
}
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
function isUndefined(arg) {
return arg === void 0;
}
},{}],115:[function(require,module,exports){
// shim for using process in browser
var process = module.exports = {};
// cached from whatever global is present so that test runners that stub it
// don't break things. But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals. It's inside a
// function because try/catches deoptimize in certain engines.
var cachedSetTimeout;
var cachedClearTimeout;
function defaultSetTimout() {
throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
throw new Error('clearTimeout has not been defined');
}
(function () {
try {
if (typeof setTimeout === 'function') {
cachedSetTimeout = setTimeout;
2016-10-11 09:53:32 +02:00
} else {
cachedSetTimeout = defaultSetTimout;
2016-10-11 09:53:32 +02:00
}
} catch (e) {
cachedSetTimeout = defaultSetTimout;
2016-10-11 09:53:32 +02:00
}
try {
if (typeof clearTimeout === 'function') {
cachedClearTimeout = clearTimeout;
2016-10-11 09:53:32 +02:00
} else {
cachedClearTimeout = defaultClearTimeout;
2016-10-11 09:53:32 +02:00
}
} catch (e) {
cachedClearTimeout = defaultClearTimeout;
2016-10-11 09:53:32 +02:00
}
} ())
function runTimeout(fun) {
if (cachedSetTimeout === setTimeout) {
//normal enviroments in sane situations
return setTimeout(fun, 0);
2016-10-11 09:53:32 +02:00
}
// if setTimeout wasn't available but was latter defined
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
cachedSetTimeout = setTimeout;
return setTimeout(fun, 0);
2016-10-11 09:53:32 +02:00
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedSetTimeout(fun, 0);
} catch(e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedSetTimeout.call(null, fun, 0);
} catch(e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
return cachedSetTimeout.call(this, fun, 0);
2016-10-11 09:53:32 +02:00
}
}
}
function runClearTimeout(marker) {
if (cachedClearTimeout === clearTimeout) {
//normal enviroments in sane situations
return clearTimeout(marker);
2016-10-11 09:53:32 +02:00
}
// if clearTimeout wasn't available but was latter defined
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
cachedClearTimeout = clearTimeout;
return clearTimeout(marker);
2016-10-11 09:53:32 +02:00
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedClearTimeout(marker);
} catch (e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedClearTimeout.call(null, marker);
} catch (e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
return cachedClearTimeout.call(this, marker);
2016-10-11 09:53:32 +02:00
}
}
}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
2016-10-11 09:53:32 +02:00
}
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
2016-10-11 09:53:32 +02:00
function drainQueue() {
if (draining) {
return;
}
var timeout = runTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
2016-10-11 09:53:32 +02:00
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
runClearTimeout(timeout);
}
2016-10-11 09:53:32 +02:00
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
2016-10-11 09:53:32 +02:00
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
runTimeout(drainQueue);
}
};
2016-10-11 09:53:32 +02:00
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
2016-10-11 09:53:32 +02:00
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.prependListener = noop;
process.prependOnceListener = noop;
process.listeners = function (name) { return [] }
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
},{}]},{},[105])
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3Vzci9saWIvbm9kZV9tb2R1bGVzL2Jyb3dzZXJpZnkvbm9kZV9tb2R1bGVzL2Jyb3dzZXItcGFjay9fcHJlbHVkZS5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbGliL1dlYlJ0Y1BlZXIuanMiLCIuLi8uLi8uLi8uLi8uLi8uLi8uLi9rdXJlbnRvLXV0aWxzLWpzL2xpYi9pbmRleC5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL2ZyZWVpY2UvaW5kZXguanMiLCIuLi8uLi8uLi8uLi8uLi8uLi8uLi9rdXJlbnRvLXV0aWxzLWpzL25vZGVfbW9kdWxlcy9mcmVlaWNlL3N0dW4uanNvbiIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL2ZyZWVpY2UvdHVybi5qc29uIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvaGFyay9oYXJrLmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvaW5oZXJpdHMvaW5oZXJpdHNfYnJvd3Nlci5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL21lcmdlL21lcmdlLmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvbm9ybWFsaWNlL2luZGV4LmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvc2RwLXRyYW5zZm9ybS9saWIvZ3JhbW1hci5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL3NkcC10cmFuc2Zvcm0vbGliL2luZGV4LmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvc2RwLXRyYW5zZm9ybS9saWIvcGFyc2VyLmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvc2RwLXRyYW5zZm9ybS9saWIvd3JpdGVyLmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvc2RwLXRyYW5zbGF0b3IvbGliL2FycmF5LWVxdWFscy5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL3NkcC10cmFuc2xhdG9yL2xpYi9pbmRleC5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL3NkcC10cmFuc2xhdG9yL2xpYi9pbnRlcm9wLmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvc2RwLXRyYW5zbGF0b3IvbGliL3RyYW5zZm9ybS5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL3VhLXBhcnNlci1qcy9zcmMvdWEtcGFyc2VyLmpzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4va3VyZW50by11dGlscy1qcy9ub2RlX21vZHVsZXMvdXVpZC9ybmctYnJvd3Nlci5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL3V1aWQvdXVpZC5qcyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL2t1cmVudG8tdXRpbHMtanMvbm9kZV9tb2R1bGVzL3dpbGRlbWl0dGVyL3dpbGRlbWl0dGVyLmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL2RlYnVnL3NyYy9icm93c2VyLmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL2RlYnVnL3NyYy9kZWJ1Zy5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9qc29uMy9saWIvanNvbjMuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMva3VyZW50by1qc29ucnBjL2xpYi9NYXBwZXIuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMva3VyZW50by1qc29ucnBjL2xpYi9jbGllbnRzL2luZGV4LmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL2t1cmVudG8tanNvbnJwYy9saWIvY2xpZW50cy9qc29ucnBjY2xpZW50LmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL2t1cmVudG8tanNvbnJwYy9saWIvY2xpZW50cy90cmFuc3BvcnRzL2luZGV4LmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL2t1cmVudG8tanNvbnJwYy9saWIvY2xpZW50cy90cmFuc3BvcnRzL3dlYlNvY2tldFdpdGhSZWNvbm5lY3Rpb24uanMiLCIuLi8uLi9ub2RlX21vZHVsZXMva3VyZW50by1qc29ucnBjL2xpYi9pbmRleC5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9rdXJlbnRvLWpzb25ycGMvbGliL3BhY2tlcnMvSnNvblJQQy5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9rdXJlbnRvLWpzb25ycGMvbGliL3BhY2tlcnMvWG1sUlBDLmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL2t1cmVudG8tanNvbnJwYy9saWIvcGFja2Vycy9pbmRleC5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9tcy9pbmRleC5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9yZXF1aXJlcy1wb3J0L2luZGV4LmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL3NkcC9zZHAuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMvc29ja2pzLWNsaWVudC9saWIvZW50cnkuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMvc29ja2pzLWNsaWVudC9saWIvZXZlbnQvY2xvc2UuanMiLCIuLi8uLi9ub2RlX21vZHVsZXMvc29ja2pzLWNsaWVudC9saWIvZXZlbnQvZW1pdHRlci5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9zb2NranMtY2xpZW50L2xpYi9ldmVudC9ldmVudC5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9zb2NranMtY2xpZW50L2xpYi9ldmVudC9ldmVudHRhcmdldC5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9zb2NranMtY2xpZW50L2xpYi9ldmVudC90cmFucy1tZXNzYWdlLmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL3NvY2tqcy1jbGllbnQvbGliL2ZhY2FkZS5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9zb2NranMtY2xpZW50L2xpYi9pZnJhbWUtYm9vdHN0cmFwLmpzIiwiLi4vLi4vbm9kZV9tb2R1bGVzL3NvY2tqcy1jbGllbnQvbGliL2luZm8tYWpheC5qcyIsIi4uLy4uL25vZGVfbW9kdWxlcy9zb2NranMtY2xpZW50L2x