From 1e33f2eaa1db40ea9da1d4b8ae1dbb7d87d071e9 Mon Sep 17 00:00:00 2001 From: csturiale Date: Mon, 7 Mar 2016 12:50:57 +0100 Subject: [PATCH] revert to package.json and bundle --- package.json | 2 +- simplewebrtc.bundle.js | 23509 ++++++++++++++++++--------------------- 2 files changed, 10911 insertions(+), 12600 deletions(-) diff --git a/package.json b/package.json index 4337d772..c1fbe377 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simplewebrtc", - "version": "2.1.2", + "version": "2.1.0", "repository": { "type": "git", "url": "git@github.com:henrikjoreteg/SimpleWebRTC.git" diff --git a/simplewebrtc.bundle.js b/simplewebrtc.bundle.js index 5803bc06..f64cef3c 100644 --- a/simplewebrtc.bundle.js +++ b/simplewebrtc.bundle.js @@ -1,626 +1,471 @@ (function(e){if("function"==typeof bootstrap)bootstrap("simplewebrtc",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeSimpleWebRTC=e}else"undefined"!=typeof window?window.SimpleWebRTC=e():global.SimpleWebRTC=e()})(function(){var define,ses,bootstrap,module,exports; return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s 0) { - self.emit('readyToCall', self.connection.getSessionid()); - } - } -}; - -SimpleWebRTC.prototype.createRoom = function (name, cb) { - this.roomName = name; - if (arguments.length === 2) { - this.connection.emit('create', name, cb); - } else { - this.connection.emit('create', name); - } -}; - -SimpleWebRTC.prototype.sendFile = function () { - if (!webrtcSupport.dataChannel) { - return this.emit('error', new Error('DataChannelNotSupported')); - } - -}; - -module.exports = SimpleWebRTC; - -},{"./socketioconnection":3,"./webrtc":2,"attachmediastream":7,"mockconsole":6,"webrtcsupport":5,"wildemitter":4}],4:[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); +var WebRTC = require('./webrtc'); +var WildEmitter = require('wildemitter'); +var webrtcSupport = require('webrtcsupport'); +var attachMediaStream = require('attachmediastream'); +var mockconsole = require('mockconsole'); +var SocketIoConnection = require('./socketioconnection'); -},{}],5:[function(require,module,exports){ +function SimpleWebRTC(opts) { + var self = this; + var options = opts || {}; + var config = this.config = { + url: 'https://sandbox.simplewebrtc.com:443/', + socketio: {/* 'force new connection':true*/}, + connection: null, + debug: false, + localVideoEl: '', + remoteVideosEl: '', + enableDataChannels: true, + autoRequestMedia: false, + autoRemoveVideos: true, + adjustPeerVolume: false, + peerVolumeWhenSpeaking: 0.25, + media: { + video: true, + audio: true + }, + receiveMedia: { + offerToReceiveAudio: 1, + offerToReceiveVideo: 1 + }, + localVideo: { + autoplay: true, + mirror: true, + muted: true + } + }; + var item, connection; + + // We also allow a 'logger' option. It can be any object that implements + // log, warn, and error methods. + // We log nothing by default, following "the rule of silence": + // http://www.linfo.org/rule_of_silence.html + this.logger = function () { + // we assume that if you're in debug mode and you didn't + // pass in a logger, you actually want to log as much as + // possible. + if (opts.debug) { + return opts.logger || console; + } else { + // or we'll use your logger which should have its own logic + // for output. Or we'll return the no-op. + return opts.logger || mockconsole; + } + }(); + + // set our config from options + for (item in options) { + this.config[item] = options[item]; + } + + // attach detected support for convenience + this.capabilities = webrtcSupport; + + // call WildEmitter constructor + WildEmitter.call(this); + + // create default SocketIoConnection if it's not passed in + if (this.config.connection === null) { + connection = this.connection = new SocketIoConnection(this.config); + } else { + connection = this.connection = this.config.connection; + } + + connection.on('connect', function () { + self.emit('connectionReady', connection.getSessionid()); + self.sessionReady = true; + self.testReadiness(); + }); + + connection.on('message', function (message) { + var peers = self.webrtc.getPeers(message.from, message.roomType); + var peer; + + if (message.type === 'offer') { + if (peers.length) { + peers.forEach(function (p) { + if (p.sid == message.sid) peer = p; + }); + //if (!peer) peer = peers[0]; // fallback for old protocol versions + } + if (!peer) { + peer = self.webrtc.createPeer({ + id: message.from, + sid: message.sid, + type: message.roomType, + enableDataChannels: self.config.enableDataChannels && message.roomType !== 'screen', + sharemyscreen: message.roomType === 'screen' && !message.broadcaster, + broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null + }); + self.emit('createdPeer', peer); + } + peer.handleMessage(message); + } else if (peers.length) { + peers.forEach(function (peer) { + if (message.sid) { + if (peer.sid === message.sid) { + peer.handleMessage(message); + } + } else { + peer.handleMessage(message); + } + }); + } + }); + + connection.on('remove', function (room) { + if (room.id !== self.connection.getSessionid()) { + self.webrtc.removePeers(room.id, room.type); + } + }); + + // instantiate our main WebRTC helper + // using same logger from logic here + opts.logger = this.logger; + opts.debug = false; + this.webrtc = new WebRTC(opts); + + // attach a few methods from underlying lib to simple. + ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers'].forEach(function (method) { + self[method] = self.webrtc[method].bind(self.webrtc); + }); + + // proxy events from WebRTC + this.webrtc.on('*', function () { + self.emit.apply(self, arguments); + }); + + // log all events in debug mode + if (config.debug) { + this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:')); + } + + // check for readiness + this.webrtc.on('localStream', function () { + self.testReadiness(); + }); + + this.webrtc.on('message', function (payload) { + self.connection.emit('message', payload); + }); + + this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this)); + this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this)); + + // echo cancellation attempts + if (this.config.adjustPeerVolume) { + this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking)); + this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1)); + } + + connection.on('stunservers', function (args) { + // resets/overrides the config + self.webrtc.config.peerConnectionConfig.iceServers = args; + self.emit('stunservers', args); + }); + connection.on('turnservers', function (args) { + // appends to the config + self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args); + self.emit('turnservers', args); + }); + + this.webrtc.on('iceFailed', function (peer) { + // local ice failure + }); + this.webrtc.on('connectivityError', function (peer) { + // remote ice failure + }); + + + // sending mute/unmute to all peers + this.webrtc.on('audioOn', function () { + self.webrtc.sendToAll('unmute', {name: 'audio'}); + }); + this.webrtc.on('audioOff', function () { + self.webrtc.sendToAll('mute', {name: 'audio'}); + }); + this.webrtc.on('videoOn', function () { + self.webrtc.sendToAll('unmute', {name: 'video'}); + }); + this.webrtc.on('videoOff', function () { + self.webrtc.sendToAll('mute', {name: 'video'}); + }); + + // screensharing events + this.webrtc.on('localScreen', function (stream) { + var item, + el = document.createElement('video'), + container = self.getRemoteVideoContainer(); + + el.oncontextmenu = function () { return false; }; + el.id = 'localScreen'; + attachMediaStream(stream, el); + if (container) { + container.appendChild(el); + } + + self.emit('localScreenAdded', el); + self.connection.emit('shareScreen'); + + self.webrtc.peers.forEach(function (existingPeer) { + var peer; + if (existingPeer.type === 'video') { + peer = self.webrtc.createPeer({ + id: existingPeer.id, + type: 'screen', + sharemyscreen: true, + enableDataChannels: false, + receiveMedia: { + offerToReceiveAudio: 0, + offerToReceiveVideo: 0 + }, + broadcaster: self.connection.getSessionid(), + }); + self.emit('createdPeer', peer); + peer.start(); + } + }); + }); + this.webrtc.on('localScreenStopped', function (stream) { + self.stopScreenShare(); + /* + self.connection.emit('unshareScreen'); + self.webrtc.peers.forEach(function (peer) { + if (peer.sharemyscreen) { + peer.end(); + } + }); + */ + }); + + this.webrtc.on('channelMessage', function (peer, label, data) { + if (data.type == 'volume') { + self.emit('remoteVolumeChange', peer, data.volume); + } + }); + + if (this.config.autoRequestMedia) this.startLocalVideo(); +} + + +SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, { + constructor: { + value: SimpleWebRTC + } +}); + +SimpleWebRTC.prototype.leaveRoom = function () { + if (this.roomName) { + this.connection.emit('leave'); + while (this.webrtc.peers.length) { + this.webrtc.peers.shift().end(); + } + if (this.getLocalScreen()) { + this.stopScreenShare(); + } + this.emit('leftRoom', this.roomName); + this.roomName = undefined; + } +}; + +SimpleWebRTC.prototype.disconnect = function () { + this.connection.disconnect(); + delete this.connection; +}; + +SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) { + var self = this; + var container = this.getRemoteVideoContainer(); + var video = attachMediaStream(peer.stream); + + // store video element as part of peer for easy removal + peer.videoEl = video; + video.id = this.getDomId(peer); + + if (container) container.appendChild(video); + + this.emit('videoAdded', video, peer); + + // send our mute status to new peer if we're muted + // currently called with a small delay because it arrives before + // the video element is created otherwise (which happens after + // the async setRemoteDescription-createAnswer) + window.setTimeout(function () { + if (!self.webrtc.isAudioEnabled()) { + peer.send('mute', {name: 'audio'}); + } + if (!self.webrtc.isVideoEnabled()) { + peer.send('mute', {name: 'video'}); + } + }, 250); +}; + +SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) { + var container = this.getRemoteVideoContainer(); + var videoEl = peer.videoEl; + if (this.config.autoRemoveVideos && container && videoEl) { + container.removeChild(videoEl); + } + if (videoEl) this.emit('videoRemoved', videoEl, peer); +}; + +SimpleWebRTC.prototype.getDomId = function (peer) { + return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_'); +}; + +// set volume on video tag for all peers takse a value between 0 and 1 +SimpleWebRTC.prototype.setVolumeForAll = function (volume) { + this.webrtc.peers.forEach(function (peer) { + if (peer.videoEl) peer.videoEl.volume = volume; + }); +}; + +SimpleWebRTC.prototype.joinRoom = function (name, cb) { + var self = this; + this.roomName = name; + this.connection.emit('join', name, function (err, roomDescription) { + console.log('join CB', err, roomDescription); + if (err) { + self.emit('error', err); + } else { + var id, + client, + type, + peer; + for (id in roomDescription.clients) { + client = roomDescription.clients[id]; + for (type in client) { + if (client[type]) { + peer = self.webrtc.createPeer({ + id: id, + type: type, + enableDataChannels: self.config.enableDataChannels && type !== 'screen', + receiveMedia: { + offerToReceiveAudio: type !== 'screen' && self.config.receiveMedia.offerToReceiveAudio ? 1 : 0, + offerToReceiveVideo: self.config.receiveMedia.offerToReceiveVideo + } + }); + self.emit('createdPeer', peer); + peer.start(); + } + } + } + } + + if (cb) cb(err, roomDescription); + self.emit('joinedRoom', name); + }); +}; + +SimpleWebRTC.prototype.getEl = function (idOrEl) { + if (typeof idOrEl === 'string') { + return document.getElementById(idOrEl); + } else { + return idOrEl; + } +}; + +SimpleWebRTC.prototype.startLocalVideo = function () { + var self = this; + this.webrtc.startLocalMedia(this.config.media, function (err, stream) { + if (err) { + self.emit('localMediaError', err); + } else { + attachMediaStream(stream, self.getLocalVideoContainer(), self.config.localVideo); + } + }); +}; + +SimpleWebRTC.prototype.stopLocalVideo = function () { + this.webrtc.stopLocalMedia(); +}; + +// this accepts either element ID or element +// and either the video tag itself or a container +// that will be used to put the video tag into. +SimpleWebRTC.prototype.getLocalVideoContainer = function () { + var el = this.getEl(this.config.localVideoEl); + if (el && el.tagName === 'VIDEO') { + el.oncontextmenu = function () { return false; }; + return el; + } else if (el) { + var video = document.createElement('video'); + video.oncontextmenu = function () { return false; }; + el.appendChild(video); + return video; + } else { + return; + } +}; + +SimpleWebRTC.prototype.getRemoteVideoContainer = function () { + return this.getEl(this.config.remoteVideosEl); +}; + +SimpleWebRTC.prototype.shareScreen = function (cb) { + this.webrtc.startScreenShare(cb); +}; + +SimpleWebRTC.prototype.getLocalScreen = function () { + return this.webrtc.localScreen; +}; + +SimpleWebRTC.prototype.stopScreenShare = function () { + this.connection.emit('unshareScreen'); + var videoEl = document.getElementById('localScreen'); + var container = this.getRemoteVideoContainer(); + var stream = this.getLocalScreen(); + + if (this.config.autoRemoveVideos && container && videoEl) { + container.removeChild(videoEl); + } + + // a hack to emit the event the removes the video + // element that we want + if (videoEl) this.emit('videoRemoved', videoEl); + if (stream) { + stream.getTracks().forEach(function (track) { track.stop(); }); + } + this.webrtc.peers.forEach(function (peer) { + if (peer.broadcaster) { + peer.end(); + } + }); + //delete this.webrtc.localScreen; +}; + +SimpleWebRTC.prototype.testReadiness = function () { + var self = this; + if (this.sessionReady) { + if (!this.config.media.video && !this.config.media.audio) { + self.emit('readyToCall', self.connection.getSessionid()); + } else if (this.webrtc.localStreams.length > 0) { + self.emit('readyToCall', self.connection.getSessionid()); + } + } +}; + +SimpleWebRTC.prototype.createRoom = function (name, cb) { + this.roomName = name; + if (arguments.length === 2) { + this.connection.emit('create', name, cb); + } else { + this.connection.emit('create', name); + } +}; + +SimpleWebRTC.prototype.sendFile = function () { + if (!webrtcSupport.dataChannel) { + return this.emit('error', new Error('DataChannelNotSupported')); + } + +}; + +module.exports = SimpleWebRTC; + +},{"./socketioconnection":3,"./webrtc":2,"attachmediastream":6,"mockconsole":7,"webrtcsupport":4,"wildemitter":5}],4:[function(require,module,exports){ // created by @HenrikJoreteg var prefix; var version; @@ -667,7 +512,152 @@ module.exports = { getUserMedia: getUserMedia }; -},{}],6:[function(require,module,exports){ +},{}],5:[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() { + this.isWildEmitter = true; + this.callbacks = {}; +} + +// Listen on the given `event` with `fn`. Store a group name if present. +WildEmitter.prototype.on = function (event, groupName, fn) { + 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. +WildEmitter.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 +WildEmitter.prototype.releaseGroup = function (groupName) { + 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. +WildEmitter.prototype.off = function (event, fn) { + 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 +WildEmitter.prototype.emit = function (event) { + 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]) { + listeners[i].apply(this, args); + } else { + break; + } + } + } + + if (specialCallbacks) { + len = specialCallbacks.length; + listeners = specialCallbacks.slice(); + for (i = 0, len = listeners.length; i < len; ++i) { + if (listeners[i]) { + listeners[i].apply(this, [event].concat(args)); + } else { + break; + } + } + } + + return this; +}; + +// Helper for for finding special wildcard event handlers that match the event +WildEmitter.prototype.getWildcardCallbacks = function (eventName) { + 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; +}; + +},{}],7:[function(require,module,exports){ var methods = "assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","); var l = methods.length; var fn = function () {}; @@ -680,202 +670,202 @@ while (l--) { module.exports = mockconsole; },{}],2:[function(require,module,exports){ -var util = require('util'); -var webrtc = require('webrtcsupport'); -var WildEmitter = require('wildemitter'); -var mockconsole = require('mockconsole'); -var localMedia = require('localmedia'); -var Peer = require('./peer'); - - -function WebRTC(opts) { - var self = this; - var options = opts || {}; - var config = this.config = { - debug: false, - // makes the entire PC config overridable - peerConnectionConfig: { - iceServers: [{'urls': 'stun:stun.l.google.com:19302'}] - }, - peerConnectionConstraints: { - optional: [] - }, - receiveMedia: { - offerToReceiveAudio: 1, - offerToReceiveVideo: 1 - }, - enableDataChannels: true - }; - var item; - - // expose screensharing check - this.screenSharingSupport = webrtc.screenSharing; - - // We also allow a 'logger' option. It can be any object that implements - // log, warn, and error methods. - // We log nothing by default, following "the rule of silence": - // http://www.linfo.org/rule_of_silence.html - this.logger = function () { - // we assume that if you're in debug mode and you didn't - // pass in a logger, you actually want to log as much as - // possible. - if (opts.debug) { - return opts.logger || console; - } else { - // or we'll use your logger which should have its own logic - // for output. Or we'll return the no-op. - return opts.logger || mockconsole; - } - }(); - - // set options - for (item in options) { - this.config[item] = options[item]; - } - - // check for support - if (!webrtc.support) { - this.logger.error('Your browser doesn\'t seem to support WebRTC'); - } - - // where we'll store our peer connections - this.peers = []; - - // call localMedia constructor - localMedia.call(this, this.config); - - this.on('speaking', function () { - if (!self.hardMuted) { - // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload - self.peers.forEach(function (peer) { - if (peer.enableDataChannels) { - var dc = peer.getDataChannel('hark'); - if (dc.readyState != 'open') return; - dc.send(JSON.stringify({type: 'speaking'})); - } - }); - } - }); - this.on('stoppedSpeaking', function () { - if (!self.hardMuted) { - // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload - self.peers.forEach(function (peer) { - if (peer.enableDataChannels) { - var dc = peer.getDataChannel('hark'); - if (dc.readyState != 'open') return; - dc.send(JSON.stringify({type: 'stoppedSpeaking'})); - } - }); - } - }); - this.on('volumeChange', function (volume, treshold) { - if (!self.hardMuted) { - // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload - self.peers.forEach(function (peer) { - if (peer.enableDataChannels) { - var dc = peer.getDataChannel('hark'); - if (dc.readyState != 'open') return; - dc.send(JSON.stringify({type: 'volume', volume: volume })); - } - }); - } - }); - - // log events in debug mode - if (this.config.debug) { - this.on('*', function (event, val1, val2) { - var logger; - // if you didn't pass in a logger and you explicitly turning on debug - // we're just going to assume you're wanting log output with console - if (self.config.logger === mockconsole) { - logger = console; - } else { - logger = self.logger; - } - logger.log('event:', event, val1, val2); - }); - } -} - -util.inherits(WebRTC, localMedia); - -WebRTC.prototype.createPeer = function (opts) { - var peer; - opts.parent = this; - peer = new Peer(opts); - this.peers.push(peer); - return peer; -}; - -// removes peers -WebRTC.prototype.removePeers = function (id, type) { - this.getPeers(id, type).forEach(function (peer) { - peer.end(); - }); -}; - -// fetches all Peer objects by session id and/or type -WebRTC.prototype.getPeers = function (sessionId, type) { - return this.peers.filter(function (peer) { - return (!sessionId || peer.id === sessionId) && (!type || peer.type === type); - }); -}; - -// sends message to all -WebRTC.prototype.sendToAll = function (message, payload) { - this.peers.forEach(function (peer) { - peer.send(message, payload); - }); -}; - -// sends message to all using a datachannel -// only sends to anyone who has an open datachannel -WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) { - this.peers.forEach(function (peer) { - if (peer.enableDataChannels) { - peer.sendDirectly(channel, message, payload); - } - }); -}; - -module.exports = WebRTC; - -},{"./peer":9,"localmedia":10,"mockconsole":6,"util":8,"webrtcsupport":5,"wildemitter":4}],3:[function(require,module,exports){ -var io = require('socket.io-client'); - -function SocketIoConnection(config) { - this.connection = io.connect(config.url, config.socketio); -} - -SocketIoConnection.prototype.on = function (ev, fn) { - this.connection.on(ev, fn); -}; - -SocketIoConnection.prototype.emit = function () { - this.connection.emit.apply(this.connection, arguments); -}; - -SocketIoConnection.prototype.getSessionid = function () { - return this.connection.id; -}; - -SocketIoConnection.prototype.disconnect = function () { - return this.connection.disconnect(); -}; - -module.exports = SocketIoConnection; +var util = require('util'); +var webrtc = require('webrtcsupport'); +var WildEmitter = require('wildemitter'); +var mockconsole = require('mockconsole'); +var localMedia = require('localmedia'); +var Peer = require('./peer'); -},{"socket.io-client":11}],8:[function(require,module,exports){ -var events = require('events'); -exports.isArray = isArray; -exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; -exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; +function WebRTC(opts) { + var self = this; + var options = opts || {}; + var config = this.config = { + debug: false, + // makes the entire PC config overridable + peerConnectionConfig: { + iceServers: [{'urls': 'stun:stun.l.google.com:19302'}] + }, + peerConnectionConstraints: { + optional: [] + }, + receiveMedia: { + offerToReceiveAudio: 1, + offerToReceiveVideo: 1 + }, + enableDataChannels: true + }; + var item; + // expose screensharing check + this.screenSharingSupport = webrtc.screenSharing; -exports.print = function () {}; -exports.puts = function () {}; -exports.debug = function() {}; + // We also allow a 'logger' option. It can be any object that implements + // log, warn, and error methods. + // We log nothing by default, following "the rule of silence": + // http://www.linfo.org/rule_of_silence.html + this.logger = function () { + // we assume that if you're in debug mode and you didn't + // pass in a logger, you actually want to log as much as + // possible. + if (opts.debug) { + return opts.logger || console; + } else { + // or we'll use your logger which should have its own logic + // for output. Or we'll return the no-op. + return opts.logger || mockconsole; + } + }(); + + // set options + for (item in options) { + this.config[item] = options[item]; + } + + // check for support + if (!webrtc.support) { + this.logger.error('Your browser doesn\'t seem to support WebRTC'); + } + + // where we'll store our peer connections + this.peers = []; + + // call localMedia constructor + localMedia.call(this, this.config); + + this.on('speaking', function () { + if (!self.hardMuted) { + // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload + self.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + var dc = peer.getDataChannel('hark'); + if (dc.readyState != 'open') return; + dc.send(JSON.stringify({type: 'speaking'})); + } + }); + } + }); + this.on('stoppedSpeaking', function () { + if (!self.hardMuted) { + // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload + self.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + var dc = peer.getDataChannel('hark'); + if (dc.readyState != 'open') return; + dc.send(JSON.stringify({type: 'stoppedSpeaking'})); + } + }); + } + }); + this.on('volumeChange', function (volume, treshold) { + if (!self.hardMuted) { + // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload + self.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + var dc = peer.getDataChannel('hark'); + if (dc.readyState != 'open') return; + dc.send(JSON.stringify({type: 'volume', volume: volume })); + } + }); + } + }); + + // log events in debug mode + if (this.config.debug) { + this.on('*', function (event, val1, val2) { + var logger; + // if you didn't pass in a logger and you explicitly turning on debug + // we're just going to assume you're wanting log output with console + if (self.config.logger === mockconsole) { + logger = console; + } else { + logger = self.logger; + } + logger.log('event:', event, val1, val2); + }); + } +} + +util.inherits(WebRTC, localMedia); + +WebRTC.prototype.createPeer = function (opts) { + var peer; + opts.parent = this; + peer = new Peer(opts); + this.peers.push(peer); + return peer; +}; + +// removes peers +WebRTC.prototype.removePeers = function (id, type) { + this.getPeers(id, type).forEach(function (peer) { + peer.end(); + }); +}; + +// fetches all Peer objects by session id and/or type +WebRTC.prototype.getPeers = function (sessionId, type) { + return this.peers.filter(function (peer) { + return (!sessionId || peer.id === sessionId) && (!type || peer.type === type); + }); +}; + +// sends message to all +WebRTC.prototype.sendToAll = function (message, payload) { + this.peers.forEach(function (peer) { + peer.send(message, payload); + }); +}; + +// sends message to all using a datachannel +// only sends to anyone who has an open datachannel +WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) { + this.peers.forEach(function (peer) { + if (peer.enableDataChannels) { + peer.sendDirectly(channel, message, payload); + } + }); +}; + +module.exports = WebRTC; + +},{"./peer":9,"localmedia":10,"mockconsole":7,"util":8,"webrtcsupport":4,"wildemitter":5}],3:[function(require,module,exports){ +var io = require('socket.io-client'); + +function SocketIoConnection(config) { + this.connection = io.connect(config.url, config.socketio); +} + +SocketIoConnection.prototype.on = function (ev, fn) { + this.connection.on(ev, fn); +}; + +SocketIoConnection.prototype.emit = function () { + this.connection.emit.apply(this.connection, arguments); +}; + +SocketIoConnection.prototype.getSessionid = function () { + return this.connection.id; +}; + +SocketIoConnection.prototype.disconnect = function () { + return this.connection.disconnect(); +}; + +module.exports = SocketIoConnection; + +},{"socket.io-client":11}],8:[function(require,module,exports){ +var events = require('events'); + +exports.isArray = isArray; +exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; +exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; + + +exports.print = function () {}; +exports.puts = function () {}; +exports.debug = function() {}; exports.inspect = function(obj, showHidden, depth, colors) { var seen = []; @@ -1212,985 +1202,418 @@ exports.format = function(f) { return str; }; -},{"events":12}],13:[function(require,module,exports){ -// shim for using process in browser - -var process = module.exports = {}; - -process.nextTick = (function () { - var canSetImmediate = typeof window !== 'undefined' - && window.setImmediate; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; +},{"events":12}],6:[function(require,module,exports){ +var adapter = require('webrtc-adapter-test'); +module.exports = function (stream, el, options) { + var item; + var URL = window.URL; + var element = el; + var opts = { + autoplay: true, + mirror: false, + muted: false, + audio: false, + disableContextMenu: false + }; - if (canSetImmediate) { - return function (f) { return window.setImmediate(f) }; + if (options) { + for (item in options) { + opts[item] = options[item]; + } } - if (canPost) { - var queue = []; - window.addEventListener('message', function (ev) { - var source = ev.source; - if ((source === window || source === null) && ev.data === 'process-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); + if (!element) { + element = document.createElement(opts.audio ? 'audio' : 'video'); + } else if (element.tagName.toLowerCase() === 'audio') { + opts.audio = true; + } - return function nextTick(fn) { - queue.push(fn); - window.postMessage('process-tick', '*'); + if (opts.disableContextMenu) { + element.oncontextmenu = function (e) { + e.preventDefault(); }; } - return function nextTick(fn) { - setTimeout(fn, 0); - }; -})(); + if (opts.autoplay) element.autoplay = 'autoplay'; + if (opts.muted) element.muted = true; + if (!opts.audio && opts.mirror) { + ['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) { + var styleName = prefix ? prefix + 'Transform' : 'transform'; + element.style[styleName] = 'scaleX(-1)'; + }); + } -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; + adapter.attachMediaStream(element, stream); + return element; +}; -process.binding = function (name) { - throw new Error('process.binding is not supported'); -} +},{"webrtc-adapter-test":13}],13:[function(require,module,exports){ +/* + * Copyright (c) 2014 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. + */ -// TODO(shtylman) -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; +/* More information about these options at jshint.com/docs/options */ +/* jshint browser: true, camelcase: true, curly: true, devel: true, + eqeqeq: true, forin: false, globalstrict: true, node: true, + quotmark: single, undef: true, unused: strict */ +/* global mozRTCIceCandidate, mozRTCPeerConnection, Promise, +mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */ +/* exported trace,requestUserMedia */ -},{}],12:[function(require,module,exports){ -var process=require("__browserify_process");if (!process.EventEmitter) process.EventEmitter = function () {}; +'use strict'; -var EventEmitter = exports.EventEmitter = process.EventEmitter; -var isArray = typeof Array.isArray === 'function' - ? Array.isArray - : function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]' - } -; -function indexOf (xs, x) { - if (xs.indexOf) return xs.indexOf(x); - for (var i = 0; i < xs.length; i++) { - if (x === xs[i]) return i; +var getUserMedia = null; +var attachMediaStream = null; +var reattachMediaStream = null; +var webrtcDetectedBrowser = null; +var webrtcDetectedVersion = null; +var webrtcMinimumVersion = null; +var webrtcUtils = { + log: function() { + // suppress console.log output when being included as a module. + if (typeof module !== 'undefined' || + typeof require === 'function' && typeof define === 'function') { + return; } - return -1; -} - -// 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. -// -// Obviously not all Emitters should be limited to 10. This function allows -// that to be increased. Set to zero for unlimited. -var defaultMaxListeners = 10; -EventEmitter.prototype.setMaxListeners = function(n) { - if (!this._events) this._events = {}; - this._events.maxListeners = n; + console.log.apply(console, arguments); + }, + extractVersion: function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos]); + } }; +function trace(text) { + // This function is used for logging. + if (text[text.length - 1] === '\n') { + text = text.substring(0, text.length - 1); + } + if (window.performance) { + var now = (window.performance.now() / 1000).toFixed(3); + webrtcUtils.log(now + ': ' + text); + } else { + webrtcUtils.log(text); + } +} -EventEmitter.prototype.emit = function(type) { - // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events || !this._events.error || - (isArray(this._events.error) && !this._events.error.length)) - { - if (arguments[1] instanceof Error) { - throw arguments[1]; // Unhandled 'error' event - } else { - throw new Error("Uncaught, unspecified 'error' event."); +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() { + // If prefixed srcObject property exists, return it. + // Otherwise use the shimmed property, _srcObject + return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject; + }, + set: function(stream) { + if ('mozSrcObject' in this) { + this.mozSrcObject = stream; + } else { + // Use _srcObject as a private property for this shim + this._srcObject = stream; + // TODO: revokeObjectUrl(this.src) when !stream to release resources? + this.src = URL.createObjectURL(stream); + } } - return false; - } + }); } + // Proxy existing globals + getUserMedia = window.navigator && window.navigator.getUserMedia; +} - if (!this._events) return false; - var handler = this._events[type]; - if (!handler) return false; - - if (typeof handler == 'function') { - 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: - var args = Array.prototype.slice.call(arguments, 1); - handler.apply(this, args); - } - return true; - - } else if (isArray(handler)) { - var args = Array.prototype.slice.call(arguments, 1); - - var listeners = handler.slice(); - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - return true; - - } else { - return false; - } +// Attach a media stream to an element. +attachMediaStream = function(element, stream) { + element.srcObject = stream; }; -// EventEmitter is defined in src/node_events.cc -// EventEmitter.prototype.emit() is also defined there. -EventEmitter.prototype.addListener = function(type, listener) { - if ('function' !== typeof listener) { - throw new Error('addListener only takes instances of Function'); - } +reattachMediaStream = function(to, from) { + to.srcObject = from.srcObject; +}; - if (!this._events) this._events = {}; +if (typeof window === 'undefined' || !window.navigator) { + webrtcUtils.log('This does not appear to be a browser'); + webrtcDetectedBrowser = 'not a browser'; +} else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) { + webrtcUtils.log('This appears to be Firefox'); - // To avoid recursion in the case that type == "newListeners"! Before - // adding it to the listeners, first emit "newListeners". - this.emit('newListener', type, listener); + webrtcDetectedBrowser = 'firefox'; - if (!this._events[type]) { - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - } else if (isArray(this._events[type])) { + // the detected firefox version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); - // Check for listener leak - if (!this._events[type].warned) { - var m; - if (this._events.maxListeners !== undefined) { - m = this._events.maxListeners; - } else { - m = defaultMaxListeners; - } + // the minimum firefox version still supported by adapter. + webrtcMinimumVersion = 31; - 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); - console.trace(); + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (webrtcDetectedVersion < 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); // jscs:ignore requireCapitalizedConstructors + }; - // 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]; + // The RTCSessionDescription object. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = mozRTCSessionDescription; } - return this; -}; - -EventEmitter.prototype.on = EventEmitter.prototype.addListener; + // The RTCIceCandidate object. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = mozRTCIceCandidate; + } -EventEmitter.prototype.once = function(type, listener) { - var self = this; - self.on(type, function g() { - self.removeListener(type, g); - listener.apply(this, arguments); - }); + // getUserMedia constraints shim. + 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; + }; + if (webrtcDetectedVersion < 38) { + webrtcUtils.log('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37(constraints.video); + } + webrtcUtils.log('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, onError); + }; - return this; -}; + navigator.getUserMedia = getUserMedia; -EventEmitter.prototype.removeListener = function(type, listener) { - if ('function' !== typeof listener) { - throw new Error('removeListener only takes instances of Function'); + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia, + 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); + }); + }; - // does not use listeners(), so no side effect of creating _events[type] - if (!this._events || !this._events[type]) return this; - - var list = this._events[type]; - - if (isArray(list)) { - var i = indexOf(list, listener); - if (i < 0) return this; - list.splice(i, 1); - if (list.length == 0) - delete this._events[type]; - } else if (this._events[type] === listener) { - delete this._events[type]; + if (webrtcDetectedVersion < 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; + }); + }; } +} else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { + webrtcUtils.log('This appears to be Chrome'); - return this; -}; + webrtcDetectedBrowser = 'chrome'; -EventEmitter.prototype.removeAllListeners = function(type) { - if (arguments.length === 0) { - this._events = {}; - return this; - } + // the detected chrome version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); - // does not use listeners(), so no side effect of creating _events[type] - if (type && this._events && this._events[type]) this._events[type] = null; - return this; -}; + // the minimum chrome version still supported by adapter. + webrtcMinimumVersion = 38; -EventEmitter.prototype.listeners = function(type) { - if (!this._events) this._events = {}; - if (!this._events[type]) this._events[type] = []; - if (!isArray(this._events[type])) { - this._events[type] = [this._events[type]]; - } - return this._events[type]; -}; + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } -EventEmitter.listenerCount = function(emitter, type) { - var ret; - if (!emitter._events || !emitter._events[type]) - ret = 0; - else if (typeof emitter._events[type] === 'function') - ret = 1; - else - ret = emitter._events[type].length; - return ret; -}; + var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors + var origGetStats = pc.getStats.bind(pc); + pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line + var self = this; + var args = arguments; -},{"__browserify_process":13}],9:[function(require,module,exports){ -var util = require('util'); -var webrtc = require('webrtcsupport'); -var PeerConnection = require('rtcpeerconnection'); -var WildEmitter = require('wildemitter'); -var FileTransfer = require('filetransfer'); - -// the inband-v1 protocol is sending metadata inband in a serialized JSON object -// followed by the actual data. Receiver closes the datachannel upon completion -var INBAND_FILETRANSFER_V1 = 'https://simplewebrtc.com/protocol/filetransfer#inband-v1'; - -function Peer(options) { - var self = this; - - // call emitter constructor - WildEmitter.call(this); - - this.id = options.id; - this.parent = options.parent; - this.type = options.type || 'video'; - this.oneway = options.oneway || false; - this.sharemyscreen = options.sharemyscreen || false; - this.browserPrefix = options.prefix; - this.stream = options.stream; - this.enableDataChannels = options.enableDataChannels === undefined ? this.parent.config.enableDataChannels : options.enableDataChannels; - this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia; - this.channels = {}; - this.sid = options.sid || Date.now().toString(); - // Create an RTCPeerConnection via the polyfill - this.pc = new PeerConnection(this.parent.config.peerConnectionConfig, this.parent.config.peerConnectionConstraints); - this.pc.on('ice', this.onIceCandidate.bind(this)); - this.pc.on('endOfCandidates', function (event) { - self.send('endOfCandidates', event); - }); - this.pc.on('offer', function (offer) { - if (self.parent.config.nick) offer.nick = self.parent.config.nick; - self.send('offer', offer); - }); - this.pc.on('answer', function (answer) { - if (self.parent.config.nick) answer.nick = self.parent.config.nick; - self.send('answer', answer); - }); - this.pc.on('addStream', this.handleRemoteStreamAdded.bind(this)); - this.pc.on('addChannel', this.handleDataChannelAdded.bind(this)); - this.pc.on('removeStream', this.handleStreamRemoved.bind(this)); - // Just fire negotiation needed events for now - // When browser re-negotiation handling seems to work - // we can use this as the trigger for starting the offer/answer process - // automatically. We'll just leave it be for now while this stabalizes. - this.pc.on('negotiationNeeded', this.emit.bind(this, 'negotiationNeeded')); - this.pc.on('iceConnectionStateChange', this.emit.bind(this, 'iceConnectionStateChange')); - this.pc.on('iceConnectionStateChange', function () { - switch (self.pc.iceConnectionState) { - case 'failed': - // currently, in chrome only the initiator goes to failed - // so we need to signal this to the peer - if (self.pc.pc.peerconnection.localDescription.type === 'offer') { - self.parent.emit('iceFailed', self); - self.send('connectivityError'); - } - break; - } - }); - this.pc.on('signalingStateChange', this.emit.bind(this, 'signalingStateChange')); - this.logger = this.parent.logger; - - // handle screensharing/broadcast mode - if (options.type === 'screen') { - if (this.parent.localScreen && this.sharemyscreen) { - this.logger.log('adding local screen stream to peer connection'); - this.pc.addStream(this.parent.localScreen); - this.broadcaster = options.broadcaster; - } - } else { - this.parent.localStreams.forEach(function (stream) { - self.pc.addStream(stream); - }); - } - - this.on('channelOpen', function (channel) { - if (channel.protocol === INBAND_FILETRANSFER_V1) { - channel.onmessage = function (event) { - var metadata = JSON.parse(event.data); - var receiver = new FileTransfer.Receiver(); - receiver.receive(metadata, channel); - self.emit('fileTransfer', metadata, receiver); - receiver.on('receivedFile', function (file, metadata) { - receiver.channel.close(); - }); - }; - } - }); - - // proxy events to parent - this.on('*', function () { - self.parent.emit.apply(self.parent, arguments); - }); -} - -util.inherits(Peer, WildEmitter); - -Peer.prototype.handleMessage = function (message) { - var self = this; - - this.logger.log('getting', message.type, message); - - if (message.prefix) this.browserPrefix = message.prefix; - - if (message.type === 'offer') { - if (!this.nick) this.nick = message.payload.nick; - delete message.payload.nick; - this.pc.handleOffer(message.payload, function (err) { - if (err) { - return; - } - // auto-accept - self.pc.answer(function (err, sessionDescription) { - //self.send('answer', sessionDescription); - }); - }); - } else if (message.type === 'answer') { - if (!this.nick) this.nick = message.payload.nick; - delete message.payload.nick; - this.pc.handleAnswer(message.payload); - } else if (message.type === 'candidate') { - this.pc.processIce(message.payload); - } else if (message.type === 'connectivityError') { - this.parent.emit('connectivityError', self); - } else if (message.type === 'mute') { - this.parent.emit('mute', {id: message.from, name: message.payload.name}); - } else if (message.type === 'unmute') { - this.parent.emit('unmute', {id: message.from, name: message.payload.name}); - } else if (message.type === 'endOfCandidates') { - // Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the - // shim this will only be called in Edge. - var mLines = this.pc.pc.peerconnection.transceivers || []; - mLines.forEach(function (mLine) { - if (mLine.iceTransport) { - mLine.iceTransport.addRemoteCandidate({}); - } - }); - } -}; - -// send via signalling channel -Peer.prototype.send = function (messageType, payload) { - var message = { - to: this.id, - sid: this.sid, - broadcaster: this.broadcaster, - roomType: this.type, - type: messageType, - payload: payload, - prefix: webrtc.prefix - }; - this.logger.log('sending', messageType, message); - this.parent.emit('message', message); -}; - -// send via data channel -// returns true when message was sent and false if channel is not open -Peer.prototype.sendDirectly = function (channel, messageType, payload) { - var message = { - type: messageType, - payload: payload - }; - this.logger.log('sending via datachannel', channel, messageType, message); - var dc = this.getDataChannel(channel); - if (dc.readyState != 'open') return false; - dc.send(JSON.stringify(message)); - return true; -}; - -// Internal method registering handlers for a data channel and emitting events on the peer -Peer.prototype._observeDataChannel = function (channel) { - var self = this; - channel.onclose = this.emit.bind(this, 'channelClose', channel); - channel.onerror = this.emit.bind(this, 'channelError', channel); - channel.onmessage = function (event) { - self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event); - }; - channel.onopen = this.emit.bind(this, 'channelOpen', channel); -}; - -// Fetch or create a data channel by the given name -Peer.prototype.getDataChannel = function (name, opts) { - if (!webrtc.supportDataChannel) return this.emit('error', new Error('createDataChannel not supported')); - var channel = this.channels[name]; - opts || (opts = {}); - if (channel) return channel; - // if we don't have one by this label, create it - channel = this.channels[name] = this.pc.createDataChannel(name, opts); - this._observeDataChannel(channel); - return channel; -}; - -Peer.prototype.onIceCandidate = function (candidate) { - if (this.closed) return; - if (candidate) { - var pcConfig = this.parent.config.peerConnectionConfig; - if (webrtc.prefix === 'moz' && pcConfig && pcConfig.iceTransports && - candidate.candidate && candidate.candidate.candidate && - candidate.candidate.candidate.indexOf(pcConfig.iceTransports) < 0) { - this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports); - } else { - this.send('candidate', candidate); - } - } else { - this.logger.log("End of candidates."); - } -}; - -Peer.prototype.start = function () { - var self = this; - - // well, the webrtc api requires that we either - // a) create a datachannel a priori - // b) do a renegotiation later to add the SCTP m-line - // Let's do (a) first... - if (this.enableDataChannels) { - this.getDataChannel('simplewebrtc'); - } - - this.pc.offer(this.receiveMedia, function (err, sessionDescription) { - //self.send('offer', sessionDescription); - }); -}; - -Peer.prototype.icerestart = function () { - var constraints = this.receiveMedia; - constraints.mandatory.IceRestart = true; - this.pc.offer(constraints, function (err, success) { }); -}; - -Peer.prototype.end = function () { - if (this.closed) return; - this.pc.close(); - this.handleStreamRemoved(); -}; - -Peer.prototype.handleRemoteStreamAdded = function (event) { - var self = this; - if (this.stream) { - this.logger.warn('Already have a remote stream'); - } else { - this.stream = event.stream; - // FIXME: addEventListener('ended', ...) would be nicer - // but does not work in firefox - this.stream.onended = function () { - self.end(); - }; - this.parent.emit('peerStreamAdded', this); - } -}; - -Peer.prototype.handleStreamRemoved = function () { - this.parent.peers.splice(this.parent.peers.indexOf(this), 1); - this.closed = true; - this.parent.emit('peerStreamRemoved', this); -}; - -Peer.prototype.handleDataChannelAdded = function (channel) { - this.channels[channel.label] = channel; - this._observeDataChannel(channel); -}; - -Peer.prototype.sendFile = function (file) { - var sender = new FileTransfer.Sender(); - var dc = this.getDataChannel('filetransfer' + (new Date()).getTime(), { - protocol: INBAND_FILETRANSFER_V1 - }); - // override onopen - dc.onopen = function () { - dc.send(JSON.stringify({ - size: file.size, - name: file.name - })); - sender.send(file, dc); - }; - // override onclose - dc.onclose = function () { - console.log('sender received transfer'); - sender.emit('complete'); - }; - return sender; -}; - -module.exports = Peer; - -},{"filetransfer":15,"rtcpeerconnection":14,"util":8,"webrtcsupport":5,"wildemitter":4}],7:[function(require,module,exports){ -var adapter = require('webrtc-adapter-test'); -module.exports = function (stream, el, options) { - var item; - var URL = window.URL; - var element = el; - var opts = { - autoplay: true, - mirror: false, - muted: false, - audio: false, - disableContextMenu: false - }; - - if (options) { - for (item in options) { - opts[item] = options[item]; - } - } - - if (!element) { - element = document.createElement(opts.audio ? 'audio' : 'video'); - } else if (element.tagName.toLowerCase() === 'audio') { - opts.audio = true; - } - - if (opts.disableContextMenu) { - element.oncontextmenu = function (e) { - e.preventDefault(); - }; - } + // 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(selector, successCallback); + } - if (opts.autoplay) element.autoplay = 'autoplay'; - if (opts.muted) element.muted = true; - if (!opts.audio && opts.mirror) { - ['', 'moz', 'webkit', 'o', 'ms'].forEach(function (prefix) { - var styleName = prefix ? prefix + 'Transform' : 'transform'; - element.style[styleName] = 'scaleX(-1)'; + var fixChromeStats = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; }); - } - - adapter.attachMediaStream(element, stream); - return element; -}; - -},{"webrtc-adapter-test":16}],16:[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. - */ - -/* More information about these options at jshint.com/docs/options */ -/* jshint browser: true, camelcase: true, curly: true, devel: true, - eqeqeq: true, forin: false, globalstrict: true, node: true, - quotmark: single, undef: true, unused: strict */ -/* global mozRTCIceCandidate, mozRTCPeerConnection, Promise, -mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack, -MediaStream, RTCIceGatherer, RTCIceTransport, RTCDtlsTransport, -RTCRtpSender, RTCRtpReceiver*/ -/* exported trace,requestUserMedia */ -'use strict'; + return standardReport; + }; -var getUserMedia = null; -var attachMediaStream = null; -var reattachMediaStream = null; -var webrtcDetectedBrowser = null; -var webrtcDetectedVersion = null; -var webrtcMinimumVersion = null; -var webrtcUtils = { - log: function() { - // suppress console.log output when being included as a module. - if (typeof module !== 'undefined' || - typeof require === 'function' && typeof define === 'function') { - return; - } - console.log.apply(console, arguments); - }, - extractVersion: function(uastring, expr, pos) { - var match = uastring.match(expr); - return match && match.length >= pos && parseInt(match[pos], 10); - } -}; + if (arguments.length >= 2) { + var successCallbackWrapper = function(response) { + args[1](fixChromeStats(response)); + }; -function trace(text) { - // This function is used for logging. - if (text[text.length - 1] === '\n') { - text = text.substring(0, text.length - 1); - } - if (window.performance) { - var now = (window.performance.now() / 1000).toFixed(3); - webrtcUtils.log(now + ': ' + text); - } else { - webrtcUtils.log(text); - } -} + return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]); + } -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() { - // If prefixed srcObject property exists, return it. - // Otherwise use the shimmed property, _srcObject - return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject; - }, - set: function(stream) { - if ('mozSrcObject' in this) { - this.mozSrcObject = stream; + // promise-support + return new Promise(function(resolve, reject) { + if (args.length === 1 && selector === null) { + origGetStats.apply(self, [ + function(response) { + resolve.apply(null, [fixChromeStats(response)]); + }, reject]); } else { - // Use _srcObject as a private property for this shim - this._srcObject = stream; - // TODO: revokeObjectUrl(this.src) when !stream to release resources? - this.src = URL.createObjectURL(stream); + origGetStats.apply(self, [resolve, reject]); } - } - }); - } - // Proxy existing globals - getUserMedia = window.navigator && window.navigator.getUserMedia; -} - -// Attach a media stream to an element. -attachMediaStream = function(element, stream) { - element.srcObject = stream; -}; - -reattachMediaStream = function(to, from) { - to.srcObject = from.srcObject; -}; - -if (typeof window === 'undefined' || !window.navigator) { - webrtcUtils.log('This does not appear to be a browser'); - webrtcDetectedBrowser = 'not a browser'; -} else if (navigator.mozGetUserMedia) { - webrtcUtils.log('This appears to be Firefox'); - - webrtcDetectedBrowser = 'firefox'; - - // the detected firefox version. - webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, - /Firefox\/([0-9]+)\./, 1); + }); + }; - // the minimum firefox version still supported by adapter. - webrtcMinimumVersion = 31; + return pc; + }; - // Shim for RTCPeerConnection on older versions. - if (!window.RTCPeerConnection) { - window.RTCPeerConnection = function(pcConfig, pcConstraints) { - if (webrtcDetectedVersion < 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; - } + // add promise support + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.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]); + }); + } else { + return nativeMethod.apply(this, arguments); } - return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors }; - window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (mozRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - if (arguments.length) { - return mozRTCPeerConnection.generateCertificate.apply(null, - arguments); - } else { - return mozRTCPeerConnection.generateCertificate; - } - } - }); - } + }); - window.RTCSessionDescription = mozRTCSessionDescription; - window.RTCIceCandidate = mozRTCIceCandidate; - } + ['setLocalDescription', 'setRemoteDescription', + 'addIceCandidate'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], + function() { + resolve(); + if (args.length >= 2) { + args[1].apply(null, []); + } + }, + function(err) { + reject(err); + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }] + ); + }); + }; + }); // getUserMedia constraints shim. - getUserMedia = function(constraints, onSuccess, onError) { - var constraintsToFF37 = function(c) { - if (typeof c !== 'object' || c.require) { - return c; + 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 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; - }; - if (webrtcDetectedVersion < 38) { - webrtcUtils.log('spec: ' + JSON.stringify(constraints)); - if (constraints.audio) { - constraints.audio = constraintsToFF37(constraints.audio); - } - if (constraints.video) { - constraints.video = constraintsToFF37(constraints.video); - } - webrtcUtils.log('ff37: ' + JSON.stringify(constraints)); - } - return navigator.mozGetUserMedia(constraints, onSuccess, onError); - }; - - navigator.getUserMedia = getUserMedia; - - // Shim for mediaDevices on older versions. - if (!navigator.mediaDevices) { - navigator.mediaDevices = {getUserMedia: requestUserMedia, - 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 (webrtcDetectedVersion < 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; - }); - }; - } -} else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { - webrtcUtils.log('This appears to be Chrome'); - - webrtcDetectedBrowser = 'chrome'; - - // the detected chrome version. - webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, - /Chrom(e|ium)\/([0-9]+)\./, 2); - - // the minimum chrome version still supported by adapter. - webrtcMinimumVersion = 38; - - // The RTCPeerConnection object. - window.RTCPeerConnection = function(pcConfig, pcConstraints) { - // Translate iceTransportPolicy to iceTransports, - // see https://code.google.com/p/webrtc/issues/detail?id=4869 - if (pcConfig && pcConfig.iceTransportPolicy) { - pcConfig.iceTransports = pcConfig.iceTransportPolicy; - } - - var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors - var origGetStats = pc.getStats.bind(pc); - pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line - 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(selector, successCallback); - } - - var fixChromeStats = function(response) { - var standardReport = {}; - var reports = response.result(); - reports.forEach(function(report) { - var standardStats = { - id: report.id, - timestamp: report.timestamp, - type: report.type - }; - report.names().forEach(function(name) { - standardStats[name] = report.stat(name); - }); - standardReport[standardStats.id] = standardStats; - }); - - return standardReport; - }; - - if (arguments.length >= 2) { - var successCallbackWrapper = function(response) { - args[1](fixChromeStats(response)); - }; - - return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]); - } - - // promise-support - return new Promise(function(resolve, reject) { - if (args.length === 1 && selector === null) { - origGetStats.apply(self, [ - function(response) { - resolve.apply(null, [fixChromeStats(response)]); - }, reject]); - } else { - origGetStats.apply(self, [resolve, reject]); - } - }); - }; - - return pc; - }; - window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (webkitRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - if (arguments.length) { - return webkitRTCPeerConnection.generateCertificate.apply(null, - arguments); - } else { - return webkitRTCPeerConnection.generateCertificate; - } - } - }); - } - - // add promise support - ['createOffer', 'createAnswer'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.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]); - }); - } else { - return nativeMethod.apply(this, arguments); - } - }; - }); - - ['setLocalDescription', 'setRemoteDescription', - 'addIceCandidate'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var args = arguments; - var self = this; - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [args[0], - function() { - resolve(); - if (args.length >= 2) { - args[1].apply(null, []); - } - }, - function(err) { - reject(err); - if (args.length >= 3) { - args[2].apply(null, [err]); - } - }] - ); - }); - }; - }); - - // getUserMedia constraints shim. - 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); + 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; }; @@ -2315,1276 +1738,620 @@ if (typeof window === 'undefined' || !window.navigator) { webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, /Edge\/(\d+).(\d+)$/, 2); - // The minimum version still supported by adapter. - // This is the build number for Edge. - webrtcMinimumVersion = 10547; - - if (window.RTCIceGatherer) { - // Generate an alphanumeric identifier for cname or mids. - // TODO: use UUIDs instead? https://gist.github.com/jed/982883 - var generateIdentifier = function() { - return Math.random().toString(36).substr(2, 10); - }; - - // The RTCP CNAME used by all peerconnections from the same JS. - var localCName = generateIdentifier(); - - // SDP helpers - to be moved into separate module. - var SDPUtils = {}; - - // 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('\r\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; - }); - }; + // the minimum version still supported by adapter. + webrtcMinimumVersion = 12; +} else { + webrtcUtils.log('Browser does not appear to be WebRTC-capable'); +} - // 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(' '); - } +// Returns the result of getUserMedia as a Promise. +function requestUserMedia(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); +} - var candidate = { - foundation: parts[0], - component: parts[1], - protocol: parts[2].toLowerCase(), - priority: parseInt(parts[3], 10), - ip: parts[4], - port: parseInt(parts[5], 10), - // skip parts[6] == 'typ' - type: parts[7] - }; +var webrtcTesting = {}; +try { + Object.defineProperty(webrtcTesting, 'version', { + set: function(version) { + webrtcDetectedVersion = version; + } + }); +} catch (e) {} - 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: // Unknown extensions are silently ignored. - break; - } - } - return candidate; +if (typeof module !== 'undefined') { + var RTCPeerConnection; + if (typeof window !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } + module.exports = { + RTCPeerConnection: RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting, + webrtcUtils: webrtcUtils + //requestUserMedia: not exposed on purpose. + //trace: not exposed on purpose. + }; +} else if ((typeof require === 'function') && (typeof define === 'function')) { + // Expose objects and functions when RequireJS is doing the loading. + define([], function() { + return { + RTCPeerConnection: window.RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting, + webrtcUtils: webrtcUtils + //requestUserMedia: not exposed on purpose. + //trace: not exposed on purpose. }; + }); +} - // 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(' '); - }; +},{}],14:[function(require,module,exports){ +// shim for using process in browser - // 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 - }; +var process = module.exports = {}; - parts = parts[0].split('/'); +process.nextTick = (function () { + var canSetImmediate = typeof window !== 'undefined' + && window.setImmediate; + var canPost = typeof window !== 'undefined' + && window.postMessage && window.addEventListener + ; - parsed.name = parts[0]; - parsed.clockRate = parseInt(parts[1], 10); // was: clockrate - parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels - return parsed; - }; + if (canSetImmediate) { + return function (f) { return window.setImmediate(f) }; + } - // 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'; - }; + if (canPost) { + var queue = []; + window.addEventListener('message', function (ev) { + var source = ev.source; + if ((source === window || source === null) && ev.data === 'process-tick') { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); - // 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; - }; + return function nextTick(fn) { + queue.push(fn); + window.postMessage('process-tick', '*'); + }; + } - // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. - SDPUtils.writeFtmp = function(codec) { - var line = ''; - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - if (codec.parameters && 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; + return function nextTick(fn) { + setTimeout(fn, 0); }; +})(); - // 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 + - '\r\n'; - }); - } - return lines; - }; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; - // 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: line.substr(7, sp - 7), - }; - 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; - }; +process.binding = function (name) { + throw new Error('process.binding is not supported'); +} - // 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.splitLines(mediaSection); - lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. - var fpLine = lines.filter(function(line) { - return line.indexOf('a=fingerprint:') === 0; - })[0].substr(14); - // Note: a=setup line is ignored since we use the 'auto' role. - var dtlsParameters = { - role: 'auto', - fingerprints: [{ - algorithm: fpLine.split(' ')[0], - value: fpLine.split(' ')[1] - }] - }; - return dtlsParameters; - }; +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; - // 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); - lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. - 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; - }; +},{}],12:[function(require,module,exports){ +var process=require("__browserify_process");if (!process.EventEmitter) process.EventEmitter = function () {}; - // Serializes ICE parameters to SDP. - SDPUtils.writeIceParameters = function(params) { - return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + - 'a=ice-pwd:' + params.password + '\r\n'; - }; +var EventEmitter = exports.EventEmitter = process.EventEmitter; +var isArray = typeof Array.isArray === 'function' + ? Array.isArray + : function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]' + } +; +function indexOf (xs, x) { + if (xs.indexOf) return xs.indexOf(x); + for (var i = 0; i < xs.length; i++) { + if (x === xs[i]) return i; + } + return -1; +} - // 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: 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); - } +// 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. +// +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +var defaultMaxListeners = 10; +EventEmitter.prototype.setMaxListeners = function(n) { + if (!this._events) this._events = {}; + this._events.maxListeners = n; +}; + + +EventEmitter.prototype.emit = function(type) { + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events || !this._events.error || + (isArray(this._events.error) && !this._events.error.length)) + { + if (arguments[1] instanceof Error) { + throw arguments[1]; // Unhandled 'error' event + } else { + throw new Error("Uncaught, unspecified 'error' event."); } - // FIXME: parse headerExtensions, fecMechanisms and rtcp. - return description; - }; + return false; + } + } - // 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'; + if (!this._events) return false; + var handler = this._events[type]; + if (!handler) return false; + + if (typeof handler == 'function') { + 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: + var args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + return true; - sdp += 'c=IN IP4 0.0.0.0\r\n'; - sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + } else if (isArray(handler)) { + var args = Array.prototype.slice.call(arguments, 1); - // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. - caps.codecs.forEach(function(codec) { - sdp += SDPUtils.writeRtpMap(codec); - sdp += SDPUtils.writeFtmp(codec); - sdp += SDPUtils.writeRtcpFb(codec); - }); - // FIXME: add headerExtensions, fecMechanismş and rtcp. - sdp += 'a=rtcp-mux\r\n'; - return sdp; - }; + var listeners = handler.slice(); + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + return true; - 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'; - }; + } else { + return false; + } +}; - SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { - var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); +// EventEmitter is defined in src/node_events.cc +// EventEmitter.prototype.emit() is also defined there. +EventEmitter.prototype.addListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new Error('addListener only takes instances of Function'); + } - // Map ICE parameters (ufrag, pwd) to SDP. - sdp += SDPUtils.writeIceParameters( - transceiver.iceGatherer.getLocalParameters()); + if (!this._events) this._events = {}; - // Map DTLS parameters to SDP. - sdp += SDPUtils.writeDtlsParameters( - transceiver.dtlsTransport.getLocalParameters(), - type === 'offer' ? 'actpass' : 'active'); + // To avoid recursion in the case that type == "newListeners"! Before + // adding it to the listeners, first emit "newListeners". + this.emit('newListener', type, listener); - sdp += 'a=mid:' + transceiver.mid + '\r\n'; + if (!this._events[type]) { + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + } else if (isArray(this._events[type])) { - 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'; + // Check for listener leak + if (!this._events[type].warned) { + var m; + if (this._events.maxListeners !== undefined) { + m = this._events.maxListeners; } else { - sdp += 'a=inactive\r\n'; - } - - // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. - if (transceiver.rtpSender) { - var msid = 'msid:' + stream.id + ' ' + - transceiver.rtpSender.track.id + '\r\n'; - sdp += 'a=' + msid; - sdp += 'a=ssrc:' + transceiver.sendSsrc + ' ' + msid; + m = defaultMaxListeners; } - // FIXME: this should be written by writeRtpDescription. - sdp += 'a=ssrc:' + transceiver.sendSsrc + ' cname:' + - 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); - } - } - if (sessionpart) { - return SDPUtils.getDirection(sessionpart); + 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); + console.trace(); } - return 'sendrecv'; - }; - - // 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; - }; } - window.RTCPeerConnection = function(config) { - var self = this; - - this.onicecandidate = null; - this.onaddstream = null; - this.onremovestream = null; - this.onsignalingstatechange = null; - this.oniceconnectionstatechange = null; - this.onnegotiationneeded = null; - this.ondatachannel = 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.iceOptions = { - gatherPolicy: 'all', - iceServers: [] - }; - if (config && config.iceTransportPolicy) { - switch (config.iceTransportPolicy) { - case 'all': - case 'relay': - this.iceOptions.gatherPolicy = config.iceTransportPolicy; - break; - case 'none': - // FIXME: remove once implementation and spec have added this. - throw new TypeError('iceTransportPolicy "none" not supported'); - } - } - if (config && config.iceServers) { - // Edge does not like - // 1) stun: - // 2) turn: that does not have all of turn:host:port?transport=udp - // 3) an array of urls - config.iceServers.forEach(function(server) { - if (server.urls) { - var url; - if (typeof(server.urls) === 'string') { - url = server.urls; - } else { - url = server.urls[0]; - } - if (url.indexOf('transport=udp') !== -1) { - self.iceServers.push({ - username: server.username, - credential: server.credential, - urls: url - }); - } - } - }); - } + // 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]; + } - // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... - // everything that is needed to describe a SDP m-line. - this.transceivers = []; + return this; +}; - // 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 = []; - }; +EventEmitter.prototype.on = EventEmitter.prototype.addListener; - window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { - var self = this; - // FIXME: need to apply ice candidates in a way which is async but in-order - this._localIceCandidatesBuffer.forEach(function(event) { - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - }); - this._localIceCandidatesBuffer = []; - }; - - window.RTCPeerConnection.prototype.addStream = function(stream) { - // Clone is necessary for local demos mostly, attaching directly - // to two different senders does not work (build 10547). - this.localStreams.push(stream.clone()); - this._maybeFireNegotiationNeeded(); - }; +EventEmitter.prototype.once = function(type, listener) { + var self = this; + self.on(type, function g() { + self.removeListener(type, g); + listener.apply(this, arguments); + }); - window.RTCPeerConnection.prototype.removeStream = function(stream) { - var idx = this.localStreams.indexOf(stream); - if (idx > -1) { - this.localStreams.splice(idx, 1); - this._maybeFireNegotiationNeeded(); - } - }; + return this; +}; - // Determines the intersection of local and remote capabilities. - window.RTCPeerConnection.prototype._getCommonCapabilities = - function(localCapabilities, remoteCapabilities) { - var commonCapabilities = { - codecs: [], - headerExtensions: [], - fecMechanisms: [] - }; - 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 && - lCodec.numChannels === rCodec.numChannels) { - // push rCodec so we reply with offerer payload type - commonCapabilities.codecs.push(rCodec); - - // FIXME: also need to determine intersection between - // .rtcpFeedback and .parameters - break; - } - } - }); +EventEmitter.prototype.removeListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new Error('removeListener only takes instances of Function'); + } - 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; - } - } - }); + // does not use listeners(), so no side effect of creating _events[type] + if (!this._events || !this._events[type]) return this; - // FIXME: fecMechanisms - return commonCapabilities; - }; + var list = this._events[type]; - // Create ICE gatherer, ICE transport and DTLS transport. - window.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 = {}; - event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; - - var cand = evt.candidate; - // Edge emits an empty object for RTCIceCandidateComplete‥ - if (!cand || Object.keys(cand).length === 0) { - // polyfill since RTCIceGatherer.state is not implemented in Edge 10547 yet. - if (iceGatherer.state === undefined) { - iceGatherer.state = 'completed'; - } + if (isArray(list)) { + var i = indexOf(list, listener); + if (i < 0) return this; + list.splice(i, 1); + if (list.length == 0) + delete this._events[type]; + } else if (this._events[type] === listener) { + delete this._events[type]; + } - // Emit a candidate with type endOfCandidates to make the samples work. - // Edge requires addIceCandidate with this empty candidate to start checking. - // The real solution is to signal end-of-candidates to the other side when - // getting the null candidate but some apps (like the samples) don't do that. - event.candidate.candidate = - 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; - } else { - // RTCIceCandidate doesn't have a component, needs to be added - cand.component = iceTransport.component === 'RTCP' ? 2 : 1; - event.candidate.candidate = SDPUtils.writeCandidate(cand); - } + return this; +}; - var complete = self.transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'completed'; - }); - // FIXME: update .localDescription with candidate and (potentially) end-of-candidates. - // To make this harder, the gatherer might emit candidates before localdescription - // is set. To make things worse, gather.getLocalCandidates still errors in - // Edge 10547 when no candidates have been gathered yet. +EventEmitter.prototype.removeAllListeners = function(type) { + if (arguments.length === 0) { + this._events = {}; + return this; + } - if (self.onicecandidate !== null) { - // Emit candidate if localDescription is set. - // Also emits null candidate when all gatherers are complete. - if (self.localDescription && self.localDescription.type === '') { - self._localIceCandidatesBuffer.push(event); - if (complete) { - self._localIceCandidatesBuffer.push({}); - } - } else { - self.onicecandidate(event); - if (complete) { - self.onicecandidate({}); - } - } - } - }; - iceTransport.onicestatechange = function() { - self._updateConnectionState(); - }; + // does not use listeners(), so no side effect of creating _events[type] + if (type && this._events && this._events[type]) this._events[type] = null; + return this; +}; - 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(); - }; +EventEmitter.prototype.listeners = function(type) { + if (!this._events) this._events = {}; + if (!this._events[type]) this._events[type] = []; + if (!isArray(this._events[type])) { + this._events[type] = [this._events[type]]; + } + return this._events[type]; +}; - return { - iceGatherer: iceGatherer, - iceTransport: iceTransport, - dtlsTransport: dtlsTransport - }; - }; +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (typeof emitter._events[type] === 'function') + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; - // Start the RTP Sender and Receiver for a transceiver. - window.RTCPeerConnection.prototype._transceive = function(transceiver, - send, recv) { - var params = this._getCommonCapabilities(transceiver.localCapabilities, - transceiver.remoteCapabilities); - if (send && transceiver.rtpSender) { - params.encodings = [{ - ssrc: transceiver.sendSsrc - }]; - params.rtcp = { - cname: localCName, - ssrc: transceiver.recvSsrc - }; - transceiver.rtpSender.send(params); - } - if (recv && transceiver.rtpReceiver) { - params.encodings = [{ - ssrc: transceiver.recvSsrc - }]; - params.rtcp = { - cname: transceiver.cname, - ssrc: transceiver.sendSsrc - }; - transceiver.rtpReceiver.receive(params); - } - }; +},{"__browserify_process":14}],9:[function(require,module,exports){ +var util = require('util'); +var webrtc = require('webrtcsupport'); +var PeerConnection = require('rtcpeerconnection'); +var WildEmitter = require('wildemitter'); +var FileTransfer = require('filetransfer'); - window.RTCPeerConnection.prototype.setLocalDescription = - function(description) { - var self = this; - if (description.type === 'offer') { - if (!this._pendingOffer) { - } else { - this.transceivers = this._pendingOffer; - delete this._pendingOffer; - } - } else if (description.type === 'answer') { - var sections = SDPUtils.splitSections(self.remoteDescription.sdp); - var sessionpart = sections.shift(); - 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) { - var remoteIceParameters = SDPUtils.getIceParameters(mediaSection, - sessionpart); - iceTransport.start(iceGatherer, remoteIceParameters, 'controlled'); - - var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, - sessionpart); - 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); - } - }); - } +// the inband-v1 protocol is sending metadata inband in a serialized JSON object +// followed by the actual data. Receiver closes the datachannel upon completion +var INBAND_FILETRANSFER_V1 = 'https://simplewebrtc.com/protocol/filetransfer#inband-v1'; - this.localDescription = description; - 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 + '"'); - } +function Peer(options) { + var self = this; - // 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(); - self._emitBufferedCandidates(); - }, 0); - } - var p = Promise.resolve(); - p.then(function() { - if (!hasCallback) { - window.setTimeout(self._emitBufferedCandidates.bind(self), 0); - } - }); - return p; - }; + // call emitter constructor + WildEmitter.call(this); - window.RTCPeerConnection.prototype.setRemoteDescription = - function(description) { - var self = this; - var stream = new MediaStream(); - var sections = SDPUtils.splitSections(description.sdp); - var sessionpart = sections.shift(); - 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 transceiver; - var iceGatherer; - var iceTransport; - var dtlsTransport; - var rtpSender; - var rtpReceiver; - var sendSsrc; - var recvSsrc; - var localCapabilities; - - // 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); + this.id = options.id; + this.parent = options.parent; + this.type = options.type || 'video'; + this.oneway = options.oneway || false; + this.sharemyscreen = options.sharemyscreen || false; + this.browserPrefix = options.prefix; + this.stream = options.stream; + this.enableDataChannels = options.enableDataChannels === undefined ? this.parent.config.enableDataChannels : options.enableDataChannels; + this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia; + this.channels = {}; + this.sid = options.sid || Date.now().toString(); + // Create an RTCPeerConnection via the polyfill + this.pc = new PeerConnection(this.parent.config.peerConnectionConfig, this.parent.config.peerConnectionConstraints); + this.pc.on('ice', this.onIceCandidate.bind(this)); + this.pc.on('endOfCandidates', function (event) { + self.send('endOfCandidates', event); + }); + this.pc.on('offer', function (offer) { + if (self.parent.config.nick) offer.nick = self.parent.config.nick; + self.send('offer', offer); + }); + this.pc.on('answer', function (answer) { + if (self.parent.config.nick) answer.nick = self.parent.config.nick; + self.send('answer', answer); + }); + this.pc.on('addStream', this.handleRemoteStreamAdded.bind(this)); + this.pc.on('addChannel', this.handleDataChannelAdded.bind(this)); + this.pc.on('removeStream', this.handleStreamRemoved.bind(this)); + // Just fire negotiation needed events for now + // When browser re-negotiation handling seems to work + // we can use this as the trigger for starting the offer/answer process + // automatically. We'll just leave it be for now while this stabalizes. + this.pc.on('negotiationNeeded', this.emit.bind(this, 'negotiationNeeded')); + this.pc.on('iceConnectionStateChange', this.emit.bind(this, 'iceConnectionStateChange')); + this.pc.on('iceConnectionStateChange', function () { + switch (self.pc.iceConnectionState) { + case 'failed': + // currently, in chrome only the initiator goes to failed + // so we need to signal this to the peer + if (self.pc.pc.peerconnection.localDescription.type === 'offer') { + self.parent.emit('iceFailed', self); + self.send('connectivityError'); + } + break; } - var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0].substr(6); - - 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) { - recvSsrc = parseInt(remoteSsrc.ssrc, 10); - cname = remoteSsrc.value; + }); + this.pc.on('signalingStateChange', this.emit.bind(this, 'signalingStateChange')); + this.logger = this.parent.logger; + + // handle screensharing/broadcast mode + if (options.type === 'screen') { + if (this.parent.localScreen && this.sharemyscreen) { + this.logger.log('adding local screen stream to peer connection'); + this.pc.addStream(this.parent.localScreen); + this.broadcaster = options.broadcaster; } + } else { + this.parent.localStreams.forEach(function (stream) { + self.pc.addStream(stream); + }); + } - if (description.type === 'offer') { - var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + this.on('channelOpen', function (channel) { + if (channel.protocol === INBAND_FILETRANSFER_V1) { + channel.onmessage = function (event) { + var metadata = JSON.parse(event.data); + var receiver = new FileTransfer.Receiver(); + receiver.receive(metadata, channel); + self.emit('fileTransfer', metadata, receiver); + receiver.on('receivedFile', function (file, metadata) { + receiver.channel.close(); + }); + }; + } + }); - localCapabilities = RTCRtpReceiver.getCapabilities(kind); - sendSsrc = (2 * sdpMLineIndex + 2) * 1001; + // proxy events to parent + this.on('*', function () { + self.parent.emit.apply(self.parent, arguments); + }); +} - rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); +util.inherits(Peer, WildEmitter); - // FIXME: not correct when there are multiple streams but that is - // not currently supported in this shim. - stream.addTrack(rtpReceiver.track); +Peer.prototype.handleMessage = function (message) { + var self = this; - // FIXME: look at direction. - if (self.localStreams.length > 0 && - self.localStreams[0].getTracks().length >= sdpMLineIndex) { - // FIXME: actually more complicated, needs to match types etc - var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex]; - rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport); - } + this.logger.log('getting', message.type, message); - self.transceivers[sdpMLineIndex] = { - iceGatherer: transports.iceGatherer, - iceTransport: transports.iceTransport, - dtlsTransport: transports.dtlsTransport, - localCapabilities: localCapabilities, - remoteCapabilities: remoteCapabilities, - rtpSender: rtpSender, - rtpReceiver: rtpReceiver, - kind: kind, - mid: mid, - cname: cname, - sendSsrc: sendSsrc, - recvSsrc: recvSsrc - }; - // 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) { - transceiver = self.transceivers[sdpMLineIndex]; - iceGatherer = transceiver.iceGatherer; - iceTransport = transceiver.iceTransport; - dtlsTransport = transceiver.dtlsTransport; - rtpSender = transceiver.rtpSender; - rtpReceiver = transceiver.rtpReceiver; - sendSsrc = transceiver.sendSsrc; - //recvSsrc = transceiver.recvSsrc; - localCapabilities = transceiver.localCapabilities; - - self.transceivers[sdpMLineIndex].recvSsrc = recvSsrc; - self.transceivers[sdpMLineIndex].remoteCapabilities = - remoteCapabilities; - self.transceivers[sdpMLineIndex].cname = cname; - - 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')) { - stream.addTrack(rtpReceiver.track); - } else { - // FIXME: actually the receiver should be created later. - delete transceiver.rtpReceiver; - } - } - }); + if (message.prefix) this.browserPrefix = message.prefix; - this.remoteDescription = description; - 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 + '"'); - } - window.setTimeout(function() { - if (self.onaddstream !== null && stream.getTracks().length) { - self.remoteStreams.push(stream); - window.setTimeout(function() { - self.onaddstream({stream: stream}); - }, 0); - } - }, 0); - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); - } - return Promise.resolve(); - }; + if (message.type === 'offer') { + if (!this.nick) this.nick = message.payload.nick; + delete message.payload.nick; + this.pc.handleOffer(message.payload, function (err) { + if (err) { + return; + } + // auto-accept + self.pc.answer(function (err, sessionDescription) { + //self.send('answer', sessionDescription); + }); + }); + } else if (message.type === 'answer') { + if (!this.nick) this.nick = message.payload.nick; + delete message.payload.nick; + this.pc.handleAnswer(message.payload); + } else if (message.type === 'candidate') { + this.pc.processIce(message.payload); + } else if (message.type === 'connectivityError') { + this.parent.emit('connectivityError', self); + } else if (message.type === 'mute') { + this.parent.emit('mute', {id: message.from, name: message.payload.name}); + } else if (message.type === 'unmute') { + this.parent.emit('unmute', {id: message.from, name: message.payload.name}); + } else if (message.type === 'endOfCandidates') { + // Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the + // shim this will only be called in Edge. + var mLines = this.pc.pc.peerconnection.transceivers || []; + mLines.forEach(function (mLine) { + if (mLine.iceTransport) { + mLine.iceTransport.addRemoteCandidate({}); + } + }); + } +}; - window.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'); +// send via signalling channel +Peer.prototype.send = function (messageType, payload) { + var message = { + to: this.id, + sid: this.sid, + broadcaster: this.broadcaster, + roomType: this.type, + type: messageType, + payload: payload, + prefix: webrtc.prefix }; + this.logger.log('sending', messageType, message); + this.parent.emit('message', message); +}; - // Update the signaling state. - window.RTCPeerConnection.prototype._updateSignalingState = - function(newState) { - this.signalingState = newState; - if (this.onsignalingstatechange !== null) { - this.onsignalingstatechange(); - } +// send via data channel +// returns true when message was sent and false if channel is not open +Peer.prototype.sendDirectly = function (channel, messageType, payload) { + var message = { + type: messageType, + payload: payload }; + this.logger.log('sending via datachannel', channel, messageType, message); + var dc = this.getDataChannel(channel); + if (dc.readyState != 'open') return false; + dc.send(JSON.stringify(message)); + return true; +}; - // Determine whether to fire the negotiationneeded event. - window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = - function() { - // Fire away (for now). - if (this.onnegotiationneeded !== null) { - this.onnegotiationneeded(); - } +// Internal method registering handlers for a data channel and emitting events on the peer +Peer.prototype._observeDataChannel = function (channel) { + var self = this; + channel.onclose = this.emit.bind(this, 'channelClose', channel); + channel.onerror = this.emit.bind(this, 'channelError', channel); + channel.onmessage = function (event) { + self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event); }; + channel.onopen = this.emit.bind(this, 'channelOpen', channel); +}; + +// Fetch or create a data channel by the given name +Peer.prototype.getDataChannel = function (name, opts) { + if (!webrtc.supportDataChannel) return this.emit('error', new Error('createDataChannel not supported')); + var channel = this.channels[name]; + opts || (opts = {}); + if (channel) return channel; + // if we don't have one by this label, create it + channel = this.channels[name] = this.pc.createDataChannel(name, opts); + this._observeDataChannel(channel); + return channel; +}; - // Update the connection state. - window.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.connecting > 0 || states.completed > 0) { - newState = 'connected'; - } - - if (newState !== self.iceConnectionState) { - self.iceConnectionState = newState; - if (this.oniceconnectionstatechange !== null) { - this.oniceconnectionstatechange(); +Peer.prototype.onIceCandidate = function (candidate) { + if (this.closed) return; + if (candidate) { + var pcConfig = this.parent.config.peerConnectionConfig; + if (webrtc.prefix === 'moz' && pcConfig && pcConfig.iceTransports && + candidate.candidate && candidate.candidate.candidate && + candidate.candidate.candidate.indexOf(pcConfig.iceTransports) < 0) { + this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports); + } else { + this.send('candidate', candidate); } - } - }; + } else { + this.logger.log("End of candidates."); + } +}; - window.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]; - } +Peer.prototype.start = function () { + var self = this; - var tracks = []; - var numAudioTracks = 0; - var numVideoTracks = 0; - // Default to sendrecv. - if (this.localStreams.length) { - numAudioTracks = this.localStreams[0].getAudioTracks().length; - numVideoTracks = this.localStreams[0].getVideoTracks().length; - } - // 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; - } - } - if (this.localStreams.length) { - // Push local streams. - this.localStreams[0].getTracks().forEach(function(track) { - tracks.push({ - kind: track.kind, - track: track, - 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--; - } - } + // well, the webrtc api requires that we either + // a) create a datachannel a priori + // b) do a renegotiation later to add the SCTP m-line + // Let's do (a) first... + if (this.enableDataChannels) { + this.getDataChannel('simplewebrtc'); + } - 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 = generateIdentifier(); - - var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); - - var localCapabilities = RTCRtpSender.getCapabilities(kind); - var rtpSender; - var rtpReceiver; - - // generate an ssrc now, to be used later in rtpSender.send - var sendSsrc = (2 * sdpMLineIndex + 1) * 1001; - if (track) { - rtpSender = new RTCRtpSender(track, transports.dtlsTransport); - } + this.pc.offer(this.receiveMedia, function (err, sessionDescription) { + //self.send('offer', sessionDescription); + }); +}; - if (mline.wantReceive) { - rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); - } +Peer.prototype.icerestart = function () { + var constraints = this.receiveMedia; + constraints.mandatory.IceRestart = true; + this.pc.offer(constraints, function (err, success) { }); +}; - transceivers[sdpMLineIndex] = { - iceGatherer: transports.iceGatherer, - iceTransport: transports.iceTransport, - dtlsTransport: transports.dtlsTransport, - localCapabilities: localCapabilities, - remoteCapabilities: null, - rtpSender: rtpSender, - rtpReceiver: rtpReceiver, - kind: kind, - mid: mid, - sendSsrc: sendSsrc, - recvSsrc: null +Peer.prototype.end = function () { + if (this.closed) return; + this.pc.close(); + this.handleStreamRemoved(); +}; + +Peer.prototype.handleRemoteStreamAdded = function (event) { + var self = this; + if (this.stream) { + this.logger.warn('Already have a remote stream'); + } else { + this.stream = event.stream; + // FIXME: addEventListener('ended', ...) would be nicer + // but does not work in firefox + this.stream.onended = function () { + self.end(); }; - var transceiver = transceivers[sdpMLineIndex]; - sdp += SDPUtils.writeMediaSection(transceiver, - transceiver.localCapabilities, 'offer', self.localStreams[0]); - }); + this.parent.emit('peerStreamAdded', this); + } +}; - 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); - }; +Peer.prototype.handleStreamRemoved = function () { + this.parent.peers.splice(this.parent.peers.indexOf(this), 1); + this.closed = true; + this.parent.emit('peerStreamRemoved', this); +}; - window.RTCPeerConnection.prototype.createAnswer = function() { - var self = this; - var answerOptions; - if (arguments.length === 1 && typeof arguments[0] !== 'function') { - answerOptions = arguments[0]; - } else if (arguments.length === 3) { - answerOptions = arguments[2]; - } +Peer.prototype.handleDataChannelAdded = function (channel) { + this.channels[channel.label] = channel; + this._observeDataChannel(channel); +}; - var sdp = SDPUtils.writeSessionBoilerplate(); - this.transceivers.forEach(function(transceiver) { - // Calculate intersection of capabilities. - var commonCapabilities = self._getCommonCapabilities( - transceiver.localCapabilities, - transceiver.remoteCapabilities); +Peer.prototype.sendFile = function (file) { + var sender = new FileTransfer.Sender(); + var dc = this.getDataChannel('filetransfer' + (new Date()).getTime(), { + protocol: INBAND_FILETRANSFER_V1 + }); + // override onopen + dc.onopen = function () { + dc.send(JSON.stringify({ + size: file.size, + name: file.name + })); + sender.send(file, dc); + }; + // override onclose + dc.onclose = function () { + console.log('sender received transfer'); + sender.emit('complete'); + }; + return sender; +}; - sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, - 'answer', self.localStreams[0]); - }); +module.exports = Peer; - 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); - }; +},{"filetransfer":16,"rtcpeerconnection":15,"util":8,"webrtcsupport":4,"wildemitter":5}],11:[function(require,module,exports){ - window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { - 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) { - return; - } - // Ignore RTCP candidates, we assume RTCP-MUX. - if (cand.component !== '1') { - return; - } - // A dirty hack to make samples work. - if (cand.type === 'endOfCandidates') { - cand = {}; - } - transceiver.iceTransport.addRemoteCandidate(cand); - } - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); - } - return Promise.resolve(); - }; +module.exports = require('./lib/'); - window.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]; - return new Promise(function(resolve) { - var results = {}; - Promise.all(promises).then(function(res) { - res.forEach(function(result) { - Object.keys(result).forEach(function(id) { - results[id] = result[id]; - }); - }); - if (cb) { - window.setTimeout(cb, 0, results); - } - resolve(results); - }); - }); - }; - } -} else { - webrtcUtils.log('Browser does not appear to be WebRTC-capable'); -} - -// Polyfill ontrack on browsers that don't yet have it -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) { - if (webrtcDetectedBrowser === 'chrome') { - // onaddstream does not fire when a track is added to an existing stream. - // but stream.onaddtrack is implemented so we use thたt - 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)); - } - }); -} - -// Returns the result of getUserMedia as a Promise. -function requestUserMedia(constraints) { - return new Promise(function(resolve, reject) { - getUserMedia(constraints, resolve, reject); - }); -} - -var webrtcTesting = {}; -try { - Object.defineProperty(webrtcTesting, 'version', { - set: function(version) { - webrtcDetectedVersion = version; - } - }); -} catch (e) {} - -if (typeof module !== 'undefined') { - var RTCPeerConnection; - var RTCIceCandidate; - var RTCSessionDescription; - if (typeof window !== 'undefined') { - RTCPeerConnection = window.RTCPeerConnection; - RTCIceCandidate = window.RTCIceCandidate; - RTCSessionDescription = window.RTCSessionDescription; - } - module.exports = { - RTCPeerConnection: RTCPeerConnection, - RTCIceCandidate: RTCIceCandidate, - RTCSessionDescription: RTCSessionDescription, - getUserMedia: getUserMedia, - attachMediaStream: attachMediaStream, - reattachMediaStream: reattachMediaStream, - webrtcDetectedBrowser: webrtcDetectedBrowser, - webrtcDetectedVersion: webrtcDetectedVersion, - webrtcMinimumVersion: webrtcMinimumVersion, - webrtcTesting: webrtcTesting, - webrtcUtils: webrtcUtils - //requestUserMedia: not exposed on purpose. - //trace: not exposed on purpose. - }; -} else if ((typeof require === 'function') && (typeof define === 'function')) { - // Expose objects and functions when RequireJS is doing the loading. - define([], function() { - return { - RTCPeerConnection: window.RTCPeerConnection, - RTCIceCandidate: window.RTCIceCandidate, - RTCSessionDescription: window.RTCSessionDescription, - getUserMedia: getUserMedia, - attachMediaStream: attachMediaStream, - reattachMediaStream: reattachMediaStream, - webrtcDetectedBrowser: webrtcDetectedBrowser, - webrtcDetectedVersion: webrtcDetectedVersion, - webrtcMinimumVersion: webrtcMinimumVersion, - webrtcTesting: webrtcTesting, - webrtcUtils: webrtcUtils - //requestUserMedia: not exposed on purpose. - //trace: not exposed on purpose. - }; - }); -} - -},{}],17:[function(require,module,exports){ -// created by @HenrikJoreteg -var prefix; -var version; - -if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) { - prefix = 'moz'; - version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); -} else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) { - prefix = 'webkit'; - version = navigator.userAgent.match(/Chrom(e|ium)/) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); +},{"./lib/":17}],18:[function(require,module,exports){ +// created by @HenrikJoreteg +var prefix; +var version; + +if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) { + prefix = 'moz'; + version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); +} else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) { + prefix = 'webkit'; + version = navigator.userAgent.match(/Chrom(e|ium)/) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); } var PC = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; @@ -3928,375 +2695,717 @@ Object.defineProperty(LocalMedia.prototype, 'localScreen', { module.exports = LocalMedia; -},{"getscreenmedia":21,"getusermedia":19,"hark":18,"mediastream-gain":20,"mockconsole":6,"util":8,"webrtcsupport":17,"wildemitter":4}],22:[function(require,module,exports){ - -/** - * Module exports. +},{"getscreenmedia":20,"getusermedia":21,"hark":19,"mediastream-gain":22,"mockconsole":7,"util":8,"webrtcsupport":18,"wildemitter":5}],23:[function(require,module,exports){ +/* + * Copyright (c) 2014 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. */ -module.exports = on; +/* More information about these options at jshint.com/docs/options */ +/* jshint browser: true, camelcase: true, curly: true, devel: true, + eqeqeq: true, forin: false, globalstrict: true, node: true, + quotmark: single, undef: true, unused: strict */ +/* global mozRTCIceCandidate, mozRTCPeerConnection, Promise, +mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */ +/* exported trace,requestUserMedia */ -/** - * Helper for subscriptions. - * - * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter` - * @param {String} event name - * @param {Function} callback - * @api public - */ +'use strict'; -function on(obj, ev, fn) { - obj.on(ev, fn); - return { - destroy: function(){ - obj.removeListener(ev, fn); +var getUserMedia = null; +var attachMediaStream = null; +var reattachMediaStream = null; +var webrtcDetectedBrowser = null; +var webrtcDetectedVersion = null; +var webrtcMinimumVersion = null; +var webrtcUtils = { + log: function() { + // suppress console.log output when being included as a module. + if (typeof module !== 'undefined' || + typeof require === 'function' && typeof define === 'function') { + return; } - }; + console.log.apply(console, arguments); + }, + extractVersion: function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos]); + } +}; + +function trace(text) { + // This function is used for logging. + if (text[text.length - 1] === '\n') { + text = text.substring(0, text.length - 1); + } + if (window.performance) { + var now = (window.performance.now() / 1000).toFixed(3); + webrtcUtils.log(now + ': ' + text); + } else { + webrtcUtils.log(text); + } } -},{}],11:[function(require,module,exports){ +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() { + // If prefixed srcObject property exists, return it. + // Otherwise use the shimmed property, _srcObject + return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject; + }, + set: function(stream) { + if ('mozSrcObject' in this) { + this.mozSrcObject = stream; + } else { + // Use _srcObject as a private property for this shim + this._srcObject = stream; + // TODO: revokeObjectUrl(this.src) when !stream to release resources? + this.src = URL.createObjectURL(stream); + } + } + }); + } + // Proxy existing globals + getUserMedia = window.navigator && window.navigator.getUserMedia; +} -/** - * Module dependencies. - */ +// Attach a media stream to an element. +attachMediaStream = function(element, stream) { + element.srcObject = stream; +}; -var url = require('./url'); -var parser = require('socket.io-parser'); -var Manager = require('./manager'); -var debug = require('debug')('socket.io-client'); +reattachMediaStream = function(to, from) { + to.srcObject = from.srcObject; +}; -/** - * Module exports. - */ +if (typeof window === 'undefined' || !window.navigator) { + webrtcUtils.log('This does not appear to be a browser'); + webrtcDetectedBrowser = 'not a browser'; +} else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) { + webrtcUtils.log('This appears to be Firefox'); -module.exports = exports = lookup; + webrtcDetectedBrowser = 'firefox'; -/** - * Managers cache. - */ + // the detected firefox version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); -var cache = exports.managers = {}; + // the minimum firefox version still supported by adapter. + webrtcMinimumVersion = 31; -/** - * Looks up an existing `Manager` for multiplexing. - * If the user summons: - * - * `io('http://localhost/a');` - * `io('http://localhost/b');` - * - * We reuse the existing instance based on same scheme/port/host, - * and we initialize sockets for each namespace. - * - * @api public - */ + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (webrtcDetectedVersion < 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); // jscs:ignore requireCapitalizedConstructors + }; -function lookup(uri, opts) { - if (typeof uri == 'object') { - opts = uri; - uri = undefined; + // The RTCSessionDescription object. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = mozRTCSessionDescription; } - opts = opts || {}; - - var parsed = url(uri); - var source = parsed.source; - var id = parsed.id; - var path = parsed.path; - var sameNamespace = cache[id] && path in cache[id].nsps; - var newConnection = opts.forceNew || opts['force new connection'] || - false === opts.multiplex || sameNamespace; - - var io; - - if (newConnection) { - debug('ignoring socket cache for %s', source); - io = Manager(source, opts); - } else { - if (!cache[id]) { - debug('new io instance for %s', source); - cache[id] = Manager(source, opts); - } - io = cache[id]; + // The RTCIceCandidate object. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = mozRTCIceCandidate; } - return io.socket(parsed.path); -} - -/** - * Protocol version. - * - * @api public - */ - -exports.protocol = parser.protocol; - -/** - * `connect`. - * - * @param {String} uri - * @api public - */ - -exports.connect = lookup; - -/** - * Expose constructors for standalone build. - * - * @api public - */ - -exports.Manager = require('./manager'); -exports.Socket = require('./socket'); - -},{"./manager":24,"./socket":25,"./url":23,"debug":26,"socket.io-parser":27}],15:[function(require,module,exports){ -var WildEmitter = require('wildemitter'); -var util = require('util'); - -function Sender(opts) { - WildEmitter.call(this); - var options = opts || {}; - this.config = { - chunksize: 16384, - pacing: 0 + // getUserMedia constraints shim. + 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; }; - // set our config from options - var item; - for (item in options) { - this.config[item] = options[item]; + if (webrtcDetectedVersion < 38) { + webrtcUtils.log('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37(constraints.video); + } + webrtcUtils.log('ff37: ' + JSON.stringify(constraints)); } + return navigator.mozGetUserMedia(constraints, onSuccess, onError); + }; - this.file = null; - this.channel = null; -} -util.inherits(Sender, WildEmitter); + navigator.getUserMedia = getUserMedia; -Sender.prototype.send = function (file, channel) { - var self = this; - this.file = file; - this.channel = channel; - var sliceFile = function(offset) { - var reader = new window.FileReader(); - reader.onload = (function() { - return function(e) { - self.channel.send(e.target.result); - self.emit('progress', offset, file.size, e.target.result); - if (file.size > offset + e.target.result.byteLength) { - window.setTimeout(sliceFile, self.config.pacing, offset + self.config.chunksize); - } else { - self.emit('progress', file.size, file.size, null); - self.emit('sentFile'); - } - }; - })(file); - var slice = file.slice(offset, offset + self.config.chunksize); - reader.readAsArrayBuffer(slice); + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia, + addEventListener: function() { }, + removeEventListener: function() { } }; - window.setTimeout(sliceFile, 0, 0); -}; + } + 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); + }); + }; -function Receiver() { - WildEmitter.call(this); + if (webrtcDetectedVersion < 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; + }); + }; + } +} else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { + webrtcUtils.log('This appears to be Chrome'); - this.receiveBuffer = []; - this.received = 0; - this.metadata = {}; - this.channel = null; -} -util.inherits(Receiver, WildEmitter); + webrtcDetectedBrowser = 'chrome'; -Receiver.prototype.receive = function (metadata, channel) { - var self = this; + // the detected chrome version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); - if (metadata) { - this.metadata = metadata; + // the minimum chrome version still supported by adapter. + webrtcMinimumVersion = 38; + + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; } - this.channel = channel; - // chrome only supports arraybuffers and those make it easier to calc the hash - channel.binaryType = 'arraybuffer'; - this.channel.onmessage = function (event) { - var len = event.data.byteLength; - self.received += len; - self.receiveBuffer.push(event.data); - self.emit('progress', self.received, self.metadata.size, event.data); - if (self.received === self.metadata.size) { - self.emit('receivedFile', new window.Blob(self.receiveBuffer), self.metadata); - self.receiveBuffer = []; // discard receivebuffer - } else if (self.received > self.metadata.size) { - // FIXME - console.error('received more than expected, discarding...'); - self.receiveBuffer = []; // just discard... + var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors + var origGetStats = pc.getStats.bind(pc); + pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line + 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(selector, successCallback); + } -module.exports = {}; -module.exports.support = typeof window !== 'undefined' && window && window.File && window.FileReader && window.Blob; -module.exports.Sender = Sender; -module.exports.Receiver = Receiver; + var fixChromeStats = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); -},{"util":8,"wildemitter":4}],14:[function(require,module,exports){ -var util = require('util'); -var each = require('lodash.foreach'); -var pluck = require('lodash.pluck'); -var SJJ = require('sdp-jingle-json'); -var WildEmitter = require('wildemitter'); -var Peerconn = require('traceablepeerconnection'); -var adapter = require('webrtc-adapter'); + return standardReport; + }; -function PeerConnection(config, constraints) { - var self = this; - var item; - WildEmitter.call(this); + if (arguments.length >= 2) { + var successCallbackWrapper = function(response) { + args[1](fixChromeStats(response)); + }; - config = config || {}; - config.iceServers = config.iceServers || []; + return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]); + } - // make sure this only gets enabled in Google Chrome - // EXPERIMENTAL FLAG, might get removed without notice - this.enableChromeNativeSimulcast = false; - if (constraints && constraints.optional && - adapter.webrtcDetectedBrowser === 'chrome' && - navigator.appVersion.match(/Chromium\//) === null) { - constraints.optional.forEach(function (constraint) { - if (constraint.enableChromeNativeSimulcast) { - self.enableChromeNativeSimulcast = true; - } - }); - } + // promise-support + return new Promise(function(resolve, reject) { + if (args.length === 1 && selector === null) { + origGetStats.apply(self, [ + function(response) { + resolve.apply(null, [fixChromeStats(response)]); + }, reject]); + } else { + origGetStats.apply(self, [resolve, reject]); + } + }); + }; - // EXPERIMENTAL FLAG, might get removed without notice - this.enableMultiStreamHacks = false; - if (constraints && constraints.optional && - adapter.webrtcDetectedBrowser === 'chrome') { - constraints.optional.forEach(function (constraint) { - if (constraint.enableMultiStreamHacks) { - self.enableMultiStreamHacks = true; - } - }); - } - // EXPERIMENTAL FLAG, might get removed without notice - this.restrictBandwidth = 0; - if (constraints && constraints.optional) { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetRestrictBandwidth) { - self.restrictBandwidth = constraint.andyetRestrictBandwidth; - } - }); - } + return pc; + }; - // EXPERIMENTAL FLAG, might get removed without notice - // bundle up ice candidates, only works for jingle mode - // number > 0 is the delay to wait for additional candidates - // ~20ms seems good - this.batchIceCandidates = 0; - if (constraints && constraints.optional) { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetBatchIce) { - self.batchIceCandidates = constraint.andyetBatchIce; - } + // add promise support + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.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]); }); - } - this.batchedIceCandidates = []; + } else { + return nativeMethod.apply(this, arguments); + } + }; + }); - // EXPERIMENTAL FLAG, might get removed without notice - // this attemps to strip out candidates with an already known foundation - // and type -- i.e. those which are gathered via the same TURN server - // but different transports (TURN udp, tcp and tls respectively) - if (constraints && constraints.optional && adapter.webrtcDetectedBrowser === 'chrome') { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetFasterICE) { - self.eliminateDuplicateCandidates = constraint.andyetFasterICE; - } - }); + ['setLocalDescription', 'setRemoteDescription', + 'addIceCandidate'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], + function() { + resolve(); + if (args.length >= 2) { + args[1].apply(null, []); + } + }, + function(err) { + reject(err); + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }] + ); + }); + }; + }); + + // getUserMedia constraints shim. + var constraintsToChrome = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; } - // EXPERIMENTAL FLAG, might get removed without notice - // when using a server such as the jitsi videobridge we don't need to signal - // our candidates - if (constraints && constraints.optional) { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetDontSignalCandidates) { - self.dontSignalCandidates = constraint.andyetDontSignalCandidates; - } + 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; + }; - - // EXPERIMENTAL FLAG, might get removed without notice - this.assumeSetLocalSuccess = false; - if (constraints && constraints.optional) { - constraints.optional.forEach(function (constraint) { - if (constraint.andyetAssumeSetLocalSuccess) { - self.assumeSetLocalSuccess = constraint.andyetAssumeSetLocalSuccess; - } - }); + getUserMedia = function(constraints, onSuccess, onError) { + if (constraints.audio) { + constraints.audio = constraintsToChrome(constraints.audio); } - - // EXPERIMENTAL FLAG, might get removed without notice - // working around https://bugzilla.mozilla.org/show_bug.cgi?id=1087551 - // pass in a timeout for this - if (adapter.webrtcDetectedBrowser === 'firefox') { - if (constraints && constraints.optional) { - this.wtFirefox = 0; - constraints.optional.forEach(function (constraint) { - if (constraint.andyetFirefoxMakesMeSad) { - self.wtFirefox = constraint.andyetFirefoxMakesMeSad; - if (self.wtFirefox > 0) { - self.firefoxcandidatebuffer = []; - } - } - }); - } + if (constraints.video) { + constraints.video = constraintsToChrome(constraints.video); } + webrtcUtils.log('chrome: ' + JSON.stringify(constraints)); + return navigator.webkitGetUserMedia(constraints, onSuccess, onError); + }; + navigator.getUserMedia = getUserMedia; + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia, + 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: ''}; + })); + }); + }); + }}; + } - this.pc = new Peerconn(config, constraints); - - this.getLocalStreams = this.pc.getLocalStreams.bind(this.pc); - this.getRemoteStreams = this.pc.getRemoteStreams.bind(this.pc); - this.addStream = this.pc.addStream.bind(this.pc); - this.removeStream = this.pc.removeStream.bind(this.pc); - - // proxy events - this.pc.on('*', function () { - self.emit.apply(self, arguments); - }); - - // proxy some events directly - this.pc.onremovestream = this.emit.bind(this, 'removeStream'); - this.pc.onaddstream = this.emit.bind(this, 'addStream'); - this.pc.onnegotiationneeded = this.emit.bind(this, 'negotiationNeeded'); - this.pc.oniceconnectionstatechange = this.emit.bind(this, 'iceConnectionStateChange'); - this.pc.onsignalingstatechange = this.emit.bind(this, 'signalingStateChange'); - - // handle ice candidate and data channel events - this.pc.onicecandidate = this._onIce.bind(this); - this.pc.ondatachannel = this._onDataChannel.bind(this); - - this.localDescription = { - contents: [] + // 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 requestUserMedia(constraints); }; - this.remoteDescription = { - contents: [] + } 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(c) { + webrtcUtils.log('spec: ' + JSON.stringify(c)); // whitespace for alignment + c.audio = constraintsToChrome(c.audio); + c.video = constraintsToChrome(c.video); + webrtcUtils.log('chrome: ' + JSON.stringify(c)); + return origGetUserMedia(c); }; + } - this.config = { - debug: false, - ice: {}, - sid: '', - isInitiator: true, - sdpSessionID: Date.now(), - useJingle: false + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + webrtcUtils.log('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + webrtcUtils.log('Dummy mediaDevices.removeEventListener called.'); }; + } - // apply our config - for (item in config) { - this.config[item] = config[item]; + // Attach a media stream to an element. + attachMediaStream = function(element, stream) { + if (webrtcDetectedVersion >= 43) { + element.srcObject = stream; + } else if (typeof element.src !== 'undefined') { + element.src = URL.createObjectURL(stream); + } else { + webrtcUtils.log('Error attaching stream to element.'); } - - if (this.config.debug) { - this.on('*', function () { + }; + reattachMediaStream = function(to, from) { + if (webrtcDetectedVersion >= 43) { + to.srcObject = from.srcObject; + } else { + to.src = from.src; + } + }; + +} else if (navigator.mediaDevices && navigator.userAgent.match( + /Edge\/(\d+).(\d+)$/)) { + webrtcUtils.log('This appears to be Edge'); + webrtcDetectedBrowser = 'edge'; + + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + + // the minimum version still supported by adapter. + webrtcMinimumVersion = 12; +} else { + webrtcUtils.log('Browser does not appear to be WebRTC-capable'); +} + +// Returns the result of getUserMedia as a Promise. +function requestUserMedia(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); +} + +var webrtcTesting = {}; +try { + Object.defineProperty(webrtcTesting, 'version', { + set: function(version) { + webrtcDetectedVersion = version; + } + }); +} catch (e) {} + +if (typeof module !== 'undefined') { + var RTCPeerConnection; + if (typeof window !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } + module.exports = { + RTCPeerConnection: RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting, + webrtcUtils: webrtcUtils + //requestUserMedia: not exposed on purpose. + //trace: not exposed on purpose. + }; +} else if ((typeof require === 'function') && (typeof define === 'function')) { + // Expose objects and functions when RequireJS is doing the loading. + define([], function() { + return { + RTCPeerConnection: window.RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting, + webrtcUtils: webrtcUtils + //requestUserMedia: not exposed on purpose. + //trace: not exposed on purpose. + }; + }); +} + +},{}],15:[function(require,module,exports){ +var util = require('util'); +var each = require('lodash.foreach'); +var pluck = require('lodash.pluck'); +var SJJ = require('sdp-jingle-json'); +var WildEmitter = require('wildemitter'); +var peerconn = require('traceablepeerconnection'); +var adapter = require('webrtc-adapter-test'); + +function PeerConnection(config, constraints) { + var self = this; + var item; + WildEmitter.call(this); + + config = config || {}; + config.iceServers = config.iceServers || []; + + // make sure this only gets enabled in Google Chrome + // EXPERIMENTAL FLAG, might get removed without notice + this.enableChromeNativeSimulcast = false; + if (constraints && constraints.optional && + adapter.webrtcDetectedBrowser === 'chrome' && + navigator.appVersion.match(/Chromium\//) === null) { + constraints.optional.forEach(function (constraint) { + if (constraint.enableChromeNativeSimulcast) { + self.enableChromeNativeSimulcast = true; + } + }); + } + + // EXPERIMENTAL FLAG, might get removed without notice + this.enableMultiStreamHacks = false; + if (constraints && constraints.optional && + adapter.webrtcDetectedBrowser === 'chrome') { + constraints.optional.forEach(function (constraint) { + if (constraint.enableMultiStreamHacks) { + self.enableMultiStreamHacks = true; + } + }); + } + // EXPERIMENTAL FLAG, might get removed without notice + this.restrictBandwidth = 0; + if (constraints && constraints.optional) { + constraints.optional.forEach(function (constraint) { + if (constraint.andyetRestrictBandwidth) { + self.restrictBandwidth = constraint.andyetRestrictBandwidth; + } + }); + } + + // EXPERIMENTAL FLAG, might get removed without notice + // bundle up ice candidates, only works for jingle mode + // number > 0 is the delay to wait for additional candidates + // ~20ms seems good + this.batchIceCandidates = 0; + if (constraints && constraints.optional) { + constraints.optional.forEach(function (constraint) { + if (constraint.andyetBatchIce) { + self.batchIceCandidates = constraint.andyetBatchIce; + } + }); + } + this.batchedIceCandidates = []; + + // EXPERIMENTAL FLAG, might get removed without notice + // this attemps to strip out candidates with an already known foundation + // and type -- i.e. those which are gathered via the same TURN server + // but different transports (TURN udp, tcp and tls respectively) + if (constraints && constraints.optional && adapter.webrtcDetectedBrowser === 'chrome') { + constraints.optional.forEach(function (constraint) { + if (constraint.andyetFasterICE) { + self.eliminateDuplicateCandidates = constraint.andyetFasterICE; + } + }); + } + // EXPERIMENTAL FLAG, might get removed without notice + // when using a server such as the jitsi videobridge we don't need to signal + // our candidates + if (constraints && constraints.optional) { + constraints.optional.forEach(function (constraint) { + if (constraint.andyetDontSignalCandidates) { + self.dontSignalCandidates = constraint.andyetDontSignalCandidates; + } + }); + } + + + // EXPERIMENTAL FLAG, might get removed without notice + this.assumeSetLocalSuccess = false; + if (constraints && constraints.optional) { + constraints.optional.forEach(function (constraint) { + if (constraint.andyetAssumeSetLocalSuccess) { + self.assumeSetLocalSuccess = constraint.andyetAssumeSetLocalSuccess; + } + }); + } + + // EXPERIMENTAL FLAG, might get removed without notice + // working around https://bugzilla.mozilla.org/show_bug.cgi?id=1087551 + // pass in a timeout for this + if (adapter.webrtcDetectedBrowser === 'firefox') { + if (constraints && constraints.optional) { + this.wtFirefox = 0; + constraints.optional.forEach(function (constraint) { + if (constraint.andyetFirefoxMakesMeSad) { + self.wtFirefox = constraint.andyetFirefoxMakesMeSad; + if (self.wtFirefox > 0) { + self.firefoxcandidatebuffer = []; + } + } + }); + } + } + + + this.pc = new peerconn(config, constraints); + + this.getLocalStreams = this.pc.getLocalStreams.bind(this.pc); + this.getRemoteStreams = this.pc.getRemoteStreams.bind(this.pc); + this.addStream = this.pc.addStream.bind(this.pc); + this.removeStream = this.pc.removeStream.bind(this.pc); + + // proxy events + this.pc.on('*', function () { + self.emit.apply(self, arguments); + }); + + // proxy some events directly + this.pc.onremovestream = this.emit.bind(this, 'removeStream'); + this.pc.onaddstream = this.emit.bind(this, 'addStream'); + this.pc.onnegotiationneeded = this.emit.bind(this, 'negotiationNeeded'); + this.pc.oniceconnectionstatechange = this.emit.bind(this, 'iceConnectionStateChange'); + this.pc.onsignalingstatechange = this.emit.bind(this, 'signalingStateChange'); + + // handle ice candidate and data channel events + this.pc.onicecandidate = this._onIce.bind(this); + this.pc.ondatachannel = this._onDataChannel.bind(this); + + this.localDescription = { + contents: [] + }; + this.remoteDescription = { + contents: [] + }; + + this.config = { + debug: false, + ice: {}, + sid: '', + isInitiator: true, + sdpSessionID: Date.now(), + useJingle: false + }; + + // apply our config + for (item in config) { + this.config[item] = config[item]; + } + + if (this.config.debug) { + this.on('*', function () { var logger = config.logger || console; logger.log('PeerConnection event:', arguments); }); @@ -4441,8 +3550,10 @@ PeerConnection.prototype.offer = function (constraints, cb) { var self = this; var hasConstraints = arguments.length === 2; var mediaConstraints = hasConstraints && constraints ? constraints : { - offerToReceiveAudio: 1, - offerToReceiveVideo: 1 + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + } }; cb = hasConstraints ? cb : constraints; cb = cb || function () {}; @@ -4963,6733 +4074,6151 @@ PeerConnection.prototype.getStats = function (cb) { module.exports = PeerConnection; -},{"lodash.foreach":31,"lodash.pluck":32,"sdp-jingle-json":28,"traceablepeerconnection":29,"util":8,"webrtc-adapter":30,"wildemitter":4}],20:[function(require,module,exports){ -var support = require('webrtcsupport'); - +},{"lodash.foreach":26,"lodash.pluck":27,"sdp-jingle-json":24,"traceablepeerconnection":25,"util":8,"webrtc-adapter-test":23,"wildemitter":5}],16:[function(require,module,exports){ +var WildEmitter = require('wildemitter'); +var util = require('util'); -function GainController(stream) { - this.support = support.webAudio && support.mediaStream; +function Sender(opts) { + WildEmitter.call(this); + var options = opts || {}; + this.config = { + chunksize: 16384, + pacing: 0 + }; + // set our config from options + var item; + for (item in options) { + this.config[item] = options[item]; + } - // set our starting value - this.gain = 1; - - if (this.support) { - var context = this.context = new support.AudioContext(); - this.microphone = context.createMediaStreamSource(stream); - this.gainFilter = context.createGain(); - this.destination = context.createMediaStreamDestination(); - this.outputStream = this.destination.stream; - this.microphone.connect(this.gainFilter); - this.gainFilter.connect(this.destination); - stream.addTrack(this.outputStream.getAudioTracks()[0]); - stream.removeTrack(stream.getAudioTracks()[0]); - } - this.stream = stream; + this.file = null; + this.channel = null; } +util.inherits(Sender, WildEmitter); -// setting -GainController.prototype.setGain = function (val) { - // check for support - if (!this.support) return; - this.gainFilter.gain.value = val; - this.gain = val; -}; - -GainController.prototype.getGain = function () { - return this.gain; +Sender.prototype.send = function (file, channel) { + var self = this; + this.file = file; + this.channel = channel; + var sliceFile = function(offset) { + var reader = new window.FileReader(); + reader.onload = (function() { + return function(e) { + self.channel.send(e.target.result); + self.emit('progress', offset, file.size, e.target.result); + if (file.size > offset + e.target.result.byteLength) { + window.setTimeout(sliceFile, self.config.pacing, offset + self.config.chunksize); + } else { + self.emit('progress', file.size, file.size, null); + self.emit('sentFile'); + } + }; + })(file); + var slice = file.slice(offset, offset + self.config.chunksize); + reader.readAsArrayBuffer(slice); + }; + window.setTimeout(sliceFile, 0, 0); }; -GainController.prototype.off = function () { - return this.setGain(0); -}; +function Receiver() { + WildEmitter.call(this); -GainController.prototype.on = function () { - this.setGain(1); -}; + this.receiveBuffer = []; + this.received = 0; + this.metadata = {}; + this.channel = null; +} +util.inherits(Receiver, WildEmitter); +Receiver.prototype.receive = function (metadata, channel) { + var self = this; -module.exports = GainController; + if (metadata) { + this.metadata = metadata; + } + this.channel = channel; + // chrome only supports arraybuffers and those make it easier to calc the hash + channel.binaryType = 'arraybuffer'; + this.channel.onmessage = function (event) { + var len = event.data.byteLength; + self.received += len; + self.receiveBuffer.push(event.data); -},{"webrtcsupport":33}],26:[function(require,module,exports){ + self.emit('progress', self.received, self.metadata.size, event.data); + if (self.received === self.metadata.size) { + self.emit('receivedFile', new window.Blob(self.receiveBuffer), self.metadata); + self.receiveBuffer = []; // discard receivebuffer + } else if (self.received > self.metadata.size) { + // FIXME + console.error('received more than expected, discarding...'); + self.receiveBuffer = []; // just discard... -/** - * 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(); +module.exports = {}; +module.exports.support = typeof window !== 'undefined' && window && window.File && window.FileReader && window.Blob; +module.exports.Sender = Sender; +module.exports.Receiver = Receiver; -/** - * Colors. - */ +},{"util":8,"wildemitter":5}],19:[function(require,module,exports){ +var WildEmitter = require('wildemitter'); -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; +function getMaxVolume (analyser, fftBins) { + var maxVolume = -Infinity; + analyser.getFloatFrequencyData(fftBins); -/** - * 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 - */ + for(var i=4, ii=fftBins.length; i < ii; i++) { + if (fftBins[i] > maxVolume && fftBins[i] < 0) { + maxVolume = fftBins[i]; + } + }; -function useColors() { - // is webkit? http://stackoverflow.com/a/16459606/376773 - return ('WebkitAppearance' in document.documentElement.style) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (window.console && (console.firebug || (console.exception && console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); + return maxVolume; } -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - return JSON.stringify(v); -}; +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(); -/** - * Colorize log arguments if enabled. - * - * @api public - */ -function formatArgs() { - var args = arguments; - var useColors = this.useColors; + // make it not break in non-supported browsers + if (!audioContextType) return harker; - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); + //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; - if (!useColors) return args; + //Setup Audio Context + if (!audioContext) { + audioContext = new audioContextType(); + } + var sourceNode, fftBins, analyser; - var c = 'color: ' + this.color; - args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); + analyser = audioContext.createAnalyser(); + analyser.fftSize = 512; + analyser.smoothingTimeConstant = smoothing; + fftBins = new Float32Array(analyser.fftSize); - // 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-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; - } - }); + 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; + } - args.splice(lastC, 0, c); - return args; -} + sourceNode.connect(analyser); + if (play) analyser.connect(audioContext.destination); -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ + harker.speaking = false; -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); -} + harker.setThreshold = function(t) { + threshold = t; + }; -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ + harker.setInterval = function(i) { + interval = i; + }; -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; + harker.stop = function() { + running = false; + harker.emit('volume_change', -100, threshold); + if (harker.speaking) { + harker.speaking = false; + harker.emit('stopped_speaking'); } - } catch(e) {} -} + }; + harker.speakingHistory = []; + for (var i = 0; i < history; i++) { + harker.speakingHistory.push(0); + } -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ + // Poll the analyser node to determine if speaking + // and emit events if changed + var looper = function() { + setTimeout(function() { -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} - return r; -} + //check if stop has been called + if(!running) { + return; + } -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ + var currentVolume = getMaxVolume(analyser, fftBins); -exports.enable(load()); + harker.emit('volume_change', currentVolume, threshold); -/** - * 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 - */ + 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)); -function localstorage(){ - try { - return window.localStorage; - } catch (e) {} + looper(); + }, interval); + }; + looper(); + + + return harker; } -},{"./debug":34}],23:[function(require,module,exports){ -var global=self; -/** - * Module dependencies. - */ +},{"wildemitter":28}],21:[function(require,module,exports){ +// getUserMedia helper by @HenrikJoreteg +var adapter = require('webrtc-adapter-test'); -var parseuri = require('parseuri'); -var debug = require('debug')('socket.io-client:url'); +module.exports = function (constraints, cb) { + var options, error; + var haveOpts = arguments.length === 2; + var defaultOpts = {video: true, audio: true}; -/** - * Module exports. - */ + var denied = 'PermissionDeniedError'; + var altDenied = 'PERMISSION_DENIED'; + var notSatisfied = 'ConstraintNotSatisfiedError'; -module.exports = url; + // make constraints optional + if (!haveOpts) { + cb = constraints; + constraints = defaultOpts; + } -/** - * URL parser. - * - * @param {String} url - * @param {Object} An object meant to mimic window.location. - * Defaults to window.location. - * @api public - */ + // treat lack of browser support like an error + if (!navigator.getUserMedia) { + // throw proper error per spec + error = new Error('MediaStreamError'); + error.name = 'NotSupportedError'; -function url(uri, loc){ - var obj = uri; + // keep all callbacks async + return window.setTimeout(function () { + cb(error); + }, 0); + } - // default to window.location - var loc = loc || global.location; - if (null == uri) uri = loc.protocol + '//' + loc.host; + // normalize error handling when no media types are requested + if (!constraints.audio && !constraints.video) { + error = new Error('MediaStreamError'); + error.name = 'NoMediaRequestedError'; - // relative path support - if ('string' == typeof uri) { - if ('/' == uri.charAt(0)) { - if ('/' == uri.charAt(1)) { - uri = loc.protocol + uri; - } else { - uri = loc.host + uri; - } + // keep all callbacks async + return window.setTimeout(function () { + cb(error); + }, 0); } - if (!/^(https?|wss?):\/\//.test(uri)) { - debug('protocol-less url %s', uri); - if ('undefined' != typeof loc) { - uri = loc.protocol + '//' + uri; - } else { - uri = 'https://' + uri; - } + // testing support + if (localStorage && localStorage.useFirefoxFakeDevice === "true") { + constraints.fake = true; } - // parse - debug('parse %s', uri); - obj = parseuri(uri); - } + navigator.getUserMedia(constraints, function (stream) { + cb(null, stream); + }, function (err) { + var error; + // coerce into an error object since FF gives us a string + // there are only two valid names according to the spec + // we coerce all non-denied to "constraint not satisfied". + if (typeof err === 'string') { + error = new Error('MediaStreamError'); + if (err === denied || err === altDenied) { + error.name = denied; + } else { + error.name = notSatisfied; + } + } else { + // if we get an error object make sure '.name' property is set + // according to spec: http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermediaerror-and-navigatorusermediaerrorcallback + error = err; + if (!error.name) { + // this is likely chrome which + // sets a property called "ERROR_DENIED" on the error object + // if so we make sure to set a name + if (error[denied]) { + err.name = denied; + } else { + err.name = notSatisfied; + } + } + } - // make sure we treat `localhost:80` and `localhost` equally - if (!obj.port) { - if (/^(http|ws)$/.test(obj.protocol)) { - obj.port = '80'; - } - else if (/^(http|ws)s$/.test(obj.protocol)) { - obj.port = '443'; - } - } + cb(error); + }); +}; - obj.path = obj.path || '/'; +},{"webrtc-adapter-test":29}],28:[function(require,module,exports){ +/* +WildEmitter.js is a slim little event emitter by @henrikjoreteg largely based +on @visionmedia's Emitter from UI Kit. - var ipv6 = obj.host.indexOf(':') !== -1; - var host = ipv6 ? '[' + obj.host + ']' : obj.host; +Why? I wanted it standalone. - // define unique id - obj.id = obj.protocol + '://' + host + ':' + obj.port; - // define href - obj.href = obj.protocol + '://' + host + (loc && loc.port == obj.port ? '' : (':' + obj.port)); +I also wanted support for wildcard emitters like this: - return obj; -} +emitter.on('*', function (eventName, other, event, payloads) { -},{"debug":26,"parseuri":35}],24:[function(require,module,exports){ +}); -/** - * Module dependencies. - */ +emitter.on('somenamespace*', function (eventName, payloads) { -var eio = require('engine.io-client'); -var Socket = require('./socket'); -var Emitter = require('component-emitter'); -var parser = require('socket.io-parser'); -var on = require('./on'); -var bind = require('component-bind'); -var debug = require('debug')('socket.io-client:manager'); -var indexOf = require('indexof'); -var Backoff = require('backo2'); +}); -/** - * IE6+ hasOwnProperty - */ +Please note that callbacks triggered by wildcard registered events also get +the event name as the first argument. +*/ -var has = Object.prototype.hasOwnProperty; +module.exports = WildEmitter; -/** - * Module exports - */ +function WildEmitter() { } -module.exports = Manager; +WildEmitter.mixin = function (constructor) { + var prototype = constructor.prototype || constructor; -/** - * `Manager` constructor. - * - * @param {String} engine instance or engine uri/opts - * @param {Object} options - * @api public - */ + prototype.isWildEmitter= true; -function Manager(uri, opts){ - if (!(this instanceof Manager)) return new Manager(uri, opts); - if (uri && ('object' == typeof uri)) { - opts = uri; - uri = undefined; - } - opts = opts || {}; + // 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; + }; - opts.path = opts.path || '/socket.io'; - this.nsps = {}; - this.subs = []; - this.opts = opts; - this.reconnection(opts.reconnection !== false); - this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); - this.reconnectionDelay(opts.reconnectionDelay || 1000); - this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); - this.randomizationFactor(opts.randomizationFactor || 0.5); - this.backoff = new Backoff({ - min: this.reconnectionDelay(), - max: this.reconnectionDelayMax(), - jitter: this.randomizationFactor() - }); - this.timeout(null == opts.timeout ? 20000 : opts.timeout); - this.readyState = 'closed'; - this.uri = uri; - this.connecting = []; - this.lastPing = null; - this.encoding = false; - this.packetBuffer = []; - this.encoder = new parser.Encoder(); - this.decoder = new parser.Decoder(); - this.autoConnect = opts.autoConnect !== false; - if (this.autoConnect) this.open(); -} + // 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; + }; -/** - * Propagate given event to sockets and emit on `this` - * - * @api private - */ + // 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; + }; -Manager.prototype.emitAll = function() { - this.emit.apply(this, arguments); - for (var nsp in this.nsps) { - if (has.call(this.nsps, nsp)) { - this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); - } - } -}; + // 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; -/** - * Update `socket.id` of all sockets - * - * @api private - */ - -Manager.prototype.updateSocketIds = function(){ - for (var nsp in this.nsps) { - if (has.call(this.nsps, nsp)) { - this.nsps[nsp].id = this.engine.id; - } - } -}; - -/** - * Mix in `Emitter`. - */ + if (!callbacks) return this; -Emitter(Manager.prototype); + // remove all handlers + if (arguments.length === 1) { + delete this.callbacks[event]; + return this; + } -/** - * Sets the `reconnection` config. - * - * @param {Boolean} true/false if it should automatically reconnect - * @return {Manager} self or value - * @api public - */ + // remove specific handler + i = callbacks.indexOf(fn); + callbacks.splice(i, 1); + if (callbacks.length === 0) { + delete this.callbacks[event]; + } + return this; + }; -Manager.prototype.reconnection = function(v){ - if (!arguments.length) return this._reconnection; - this._reconnection = !!v; - 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); + } + } -/** - * Sets the reconnection attempts config. - * - * @param {Number} max reconnection attempts before giving up - * @return {Manager} self or value - * @api public - */ + 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)); + } + } -Manager.prototype.reconnectionAttempts = function(v){ - if (!arguments.length) return this._reconnectionAttempts; - this._reconnectionAttempts = v; - return this; -}; + return this; + }; -/** - * Sets the delay between reconnections. - * - * @param {Number} delay - * @return {Manager} self or value - * @api public - */ + // 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; + }; -Manager.prototype.reconnectionDelay = function(v){ - if (!arguments.length) return this._reconnectionDelay; - this._reconnectionDelay = v; - this.backoff && this.backoff.setMin(v); - return this; }; -Manager.prototype.randomizationFactor = function(v){ - if (!arguments.length) return this._randomizationFactor; - this._randomizationFactor = v; - this.backoff && this.backoff.setJitter(v); - return this; -}; +WildEmitter.mixin(WildEmitter); -/** - * Sets the maximum delay between reconnections. +},{}],29:[function(require,module,exports){ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * - * @param {Number} delay - * @return {Manager} self or value - * @api public + * 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. */ -Manager.prototype.reconnectionDelayMax = function(v){ - if (!arguments.length) return this._reconnectionDelayMax; - this._reconnectionDelayMax = v; - this.backoff && this.backoff.setMax(v); - return this; -}; +/* More information about these options at jshint.com/docs/options */ +/* jshint browser: true, camelcase: true, curly: true, devel: true, + eqeqeq: true, forin: false, globalstrict: true, node: true, + quotmark: single, undef: true, unused: strict */ +/* global mozRTCIceCandidate, mozRTCPeerConnection, Promise, +mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */ +/* exported trace,requestUserMedia */ -/** - * Sets the connection timeout. `false` to disable - * - * @return {Manager} self or value - * @api public - */ +'use strict'; -Manager.prototype.timeout = function(v){ - if (!arguments.length) return this._timeout; - this._timeout = v; - return this; +var getUserMedia = null; +var attachMediaStream = null; +var reattachMediaStream = null; +var webrtcDetectedBrowser = null; +var webrtcDetectedVersion = null; +var webrtcMinimumVersion = null; +var webrtcUtils = { + log: function() { + // suppress console.log output when being included as a module. + if (typeof module !== 'undefined' || + typeof require === 'function' && typeof define === 'function') { + return; + } + console.log.apply(console, arguments); + }, + extractVersion: function(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos]); + } }; -/** - * Starts trying to reconnect if reconnection is enabled and we have not - * started reconnecting yet - * - * @api private - */ +function trace(text) { + // This function is used for logging. + if (text[text.length - 1] === '\n') { + text = text.substring(0, text.length - 1); + } + if (window.performance) { + var now = (window.performance.now() / 1000).toFixed(3); + webrtcUtils.log(now + ': ' + text); + } else { + webrtcUtils.log(text); + } +} -Manager.prototype.maybeReconnectOnOpen = function() { - // Only try to reconnect if it's the first time we're connecting - if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) { - // keeps reconnection from firing twice for the same reconnection loop - this.reconnect(); +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() { + // If prefixed srcObject property exists, return it. + // Otherwise use the shimmed property, _srcObject + return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject; + }, + set: function(stream) { + if ('mozSrcObject' in this) { + this.mozSrcObject = stream; + } else { + // Use _srcObject as a private property for this shim + this._srcObject = stream; + // TODO: revokeObjectUrl(this.src) when !stream to release resources? + this.src = URL.createObjectURL(stream); + } + } + }); } + // Proxy existing globals + getUserMedia = window.navigator && window.navigator.getUserMedia; +} + +// Attach a media stream to an element. +attachMediaStream = function(element, stream) { + element.srcObject = stream; }; +reattachMediaStream = function(to, from) { + to.srcObject = from.srcObject; +}; -/** - * Sets the current transport `socket`. - * - * @param {Function} optional, callback - * @return {Manager} self - * @api public - */ +if (typeof window === 'undefined' || !window.navigator) { + webrtcUtils.log('This does not appear to be a browser'); + webrtcDetectedBrowser = 'not a browser'; +} else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) { + webrtcUtils.log('This appears to be Firefox'); -Manager.prototype.open = -Manager.prototype.connect = function(fn){ - debug('readyState %s', this.readyState); - if (~this.readyState.indexOf('open')) return this; + webrtcDetectedBrowser = 'firefox'; - debug('opening %s', this.uri); - this.engine = eio(this.uri, this.opts); - var socket = this.engine; - var self = this; - this.readyState = 'opening'; - this.skipReconnect = false; + // the detected firefox version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Firefox\/([0-9]+)\./, 1); - // emit `open` - var openSub = on(socket, 'open', function() { - self.onopen(); - fn && fn(); - }); + // the minimum firefox version still supported by adapter. + webrtcMinimumVersion = 31; - // emit `connect_error` - var errorSub = on(socket, 'error', function(data){ - debug('connect_error'); - self.cleanup(); - self.readyState = 'closed'; - self.emitAll('connect_error', data); - if (fn) { - var err = new Error('Connection error'); - err.data = data; - fn(err); - } else { - // Only do this if there is no fn to handle the error - self.maybeReconnectOnOpen(); + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (webrtcDetectedVersion < 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; + } } - }); - - // emit `connect_timeout` - if (false !== this._timeout) { - var timeout = this._timeout; - debug('connect attempt will timeout after %d', timeout); - - // set timer - var timer = setTimeout(function(){ - debug('connect attempt timed out after %d', timeout); - openSub.destroy(); - socket.close(); - socket.emit('error', 'timeout'); - self.emitAll('connect_timeout', timeout); - }, timeout); + return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors + }; - this.subs.push({ - destroy: function(){ - clearTimeout(timer); - } - }); + // The RTCSessionDescription object. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = mozRTCSessionDescription; } - this.subs.push(openSub); - this.subs.push(errorSub); - - return this; -}; + // The RTCIceCandidate object. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = mozRTCIceCandidate; + } -/** - * Called upon transport open. - * - * @api private - */ + // getUserMedia constraints shim. + 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; + }; + if (webrtcDetectedVersion < 38) { + webrtcUtils.log('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37(constraints.video); + } + webrtcUtils.log('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, onError); + }; -Manager.prototype.onopen = function(){ - debug('open'); + navigator.getUserMedia = getUserMedia; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia, + 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 (webrtcDetectedVersion < 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; + }); + }; + } +} else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { + webrtcUtils.log('This appears to be Chrome'); + + webrtcDetectedBrowser = 'chrome'; + + // the detected chrome version. + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Chrom(e|ium)\/([0-9]+)\./, 2); + + // the minimum chrome version still supported by adapter. + webrtcMinimumVersion = 38; + + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors + var origGetStats = pc.getStats.bind(pc); + pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line + 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(selector, successCallback); + } + + var fixChromeStats = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + if (arguments.length >= 2) { + var successCallbackWrapper = function(response) { + args[1](fixChromeStats(response)); + }; + + return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + if (args.length === 1 && selector === null) { + origGetStats.apply(self, [ + function(response) { + resolve.apply(null, [fixChromeStats(response)]); + }, reject]); + } else { + origGetStats.apply(self, [resolve, reject]); + } + }); + }; + + return pc; + }; + + // add promise support + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.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]); + }); + } else { + return nativeMethod.apply(this, arguments); + } + }; + }); + + ['setLocalDescription', 'setRemoteDescription', + 'addIceCandidate'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], + function() { + resolve(); + if (args.length >= 2) { + args[1].apply(null, []); + } + }, + function(err) { + reject(err); + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }] + ); + }); + }; + }); + + // getUserMedia constraints shim. + 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; + }; + + getUserMedia = function(constraints, onSuccess, onError) { + if (constraints.audio) { + constraints.audio = constraintsToChrome(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToChrome(constraints.video); + } + webrtcUtils.log('chrome: ' + JSON.stringify(constraints)); + return navigator.webkitGetUserMedia(constraints, onSuccess, onError); + }; + navigator.getUserMedia = getUserMedia; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia, + 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: ''}; + })); + }); + }); + }}; + } + + // 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 requestUserMedia(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(c) { + webrtcUtils.log('spec: ' + JSON.stringify(c)); // whitespace for alignment + c.audio = constraintsToChrome(c.audio); + c.video = constraintsToChrome(c.video); + webrtcUtils.log('chrome: ' + JSON.stringify(c)); + return origGetUserMedia(c); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + webrtcUtils.log('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + webrtcUtils.log('Dummy mediaDevices.removeEventListener called.'); + }; + } + + // Attach a media stream to an element. + attachMediaStream = function(element, stream) { + if (webrtcDetectedVersion >= 43) { + element.srcObject = stream; + } else if (typeof element.src !== 'undefined') { + element.src = URL.createObjectURL(stream); + } else { + webrtcUtils.log('Error attaching stream to element.'); + } + }; + reattachMediaStream = function(to, from) { + if (webrtcDetectedVersion >= 43) { + to.srcObject = from.srcObject; + } else { + to.src = from.src; + } + }; + +} else if (navigator.mediaDevices && navigator.userAgent.match( + /Edge\/(\d+).(\d+)$/)) { + webrtcUtils.log('This appears to be Edge'); + webrtcDetectedBrowser = 'edge'; + + webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + + // the minimum version still supported by adapter. + webrtcMinimumVersion = 12; +} else { + webrtcUtils.log('Browser does not appear to be WebRTC-capable'); +} + +// Returns the result of getUserMedia as a Promise. +function requestUserMedia(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); +} + +var webrtcTesting = {}; +try { + Object.defineProperty(webrtcTesting, 'version', { + set: function(version) { + webrtcDetectedVersion = version; + } + }); +} catch (e) {} + +if (typeof module !== 'undefined') { + var RTCPeerConnection; + if (typeof window !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } + module.exports = { + RTCPeerConnection: RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting, + webrtcUtils: webrtcUtils + //requestUserMedia: not exposed on purpose. + //trace: not exposed on purpose. + }; +} else if ((typeof require === 'function') && (typeof define === 'function')) { + // Expose objects and functions when RequireJS is doing the loading. + define([], function() { + return { + RTCPeerConnection: window.RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting, + webrtcUtils: webrtcUtils + //requestUserMedia: not exposed on purpose. + //trace: not exposed on purpose. + }; + }); +} + +},{}],24:[function(require,module,exports){ +var toSDP = require('./lib/tosdp'); +var toJSON = require('./lib/tojson'); + + +// Converstion from JSON to SDP + +exports.toIncomingSDPOffer = function (session) { + return toSDP.toSessionSDP(session, { + role: 'responder', + direction: 'incoming' + }); +}; +exports.toOutgoingSDPOffer = function (session) { + return toSDP.toSessionSDP(session, { + role: 'initiator', + direction: 'outgoing' + }); +}; +exports.toIncomingSDPAnswer = function (session) { + return toSDP.toSessionSDP(session, { + role: 'initiator', + direction: 'incoming' + }); +}; +exports.toOutgoingSDPAnswer = function (session) { + return toSDP.toSessionSDP(session, { + role: 'responder', + direction: 'outgoing' + }); +}; +exports.toIncomingMediaSDPOffer = function (media) { + return toSDP.toMediaSDP(media, { + role: 'responder', + direction: 'incoming' + }); +}; +exports.toOutgoingMediaSDPOffer = function (media) { + return toSDP.toMediaSDP(media, { + role: 'initiator', + direction: 'outgoing' + }); +}; +exports.toIncomingMediaSDPAnswer = function (media) { + return toSDP.toMediaSDP(media, { + role: 'initiator', + direction: 'incoming' + }); +}; +exports.toOutgoingMediaSDPAnswer = function (media) { + return toSDP.toMediaSDP(media, { + role: 'responder', + direction: 'outgoing' + }); +}; +exports.toCandidateSDP = toSDP.toCandidateSDP; +exports.toMediaSDP = toSDP.toMediaSDP; +exports.toSessionSDP = toSDP.toSessionSDP; - // clear old subs - this.cleanup(); - // mark as open - this.readyState = 'open'; - this.emit('open'); +// Conversion from SDP to JSON - // add new subs - var socket = this.engine; - this.subs.push(on(socket, 'data', bind(this, 'ondata'))); - this.subs.push(on(socket, 'ping', bind(this, 'onping'))); - this.subs.push(on(socket, 'pong', bind(this, 'onpong'))); - this.subs.push(on(socket, 'error', bind(this, 'onerror'))); - this.subs.push(on(socket, 'close', bind(this, 'onclose'))); - this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded'))); +exports.toIncomingJSONOffer = function (sdp, creators) { + return toJSON.toSessionJSON(sdp, { + role: 'responder', + direction: 'incoming', + creators: creators + }); }; - -/** - * Called upon a ping. - * - * @api private - */ - -Manager.prototype.onping = function(){ - this.lastPing = new Date; - this.emitAll('ping'); +exports.toOutgoingJSONOffer = function (sdp, creators) { + return toJSON.toSessionJSON(sdp, { + role: 'initiator', + direction: 'outgoing', + creators: creators + }); +}; +exports.toIncomingJSONAnswer = function (sdp, creators) { + return toJSON.toSessionJSON(sdp, { + role: 'initiator', + direction: 'incoming', + creators: creators + }); +}; +exports.toOutgoingJSONAnswer = function (sdp, creators) { + return toJSON.toSessionJSON(sdp, { + role: 'responder', + direction: 'outgoing', + creators: creators + }); +}; +exports.toIncomingMediaJSONOffer = function (sdp, creator) { + return toJSON.toMediaJSON(sdp, { + role: 'responder', + direction: 'incoming', + creator: creator + }); +}; +exports.toOutgoingMediaJSONOffer = function (sdp, creator) { + return toJSON.toMediaJSON(sdp, { + role: 'initiator', + direction: 'outgoing', + creator: creator + }); +}; +exports.toIncomingMediaJSONAnswer = function (sdp, creator) { + return toJSON.toMediaJSON(sdp, { + role: 'initiator', + direction: 'incoming', + creator: creator + }); +}; +exports.toOutgoingMediaJSONAnswer = function (sdp, creator) { + return toJSON.toMediaJSON(sdp, { + role: 'responder', + direction: 'outgoing', + creator: creator + }); }; +exports.toCandidateJSON = toJSON.toCandidateJSON; +exports.toMediaJSON = toJSON.toMediaJSON; +exports.toSessionJSON = toJSON.toSessionJSON; -/** - * Called upon a packet. - * - * @api private - */ +},{"./lib/tojson":30,"./lib/tosdp":31}],20:[function(require,module,exports){ +// getScreenMedia helper by @HenrikJoreteg +var getUserMedia = require('getusermedia'); -Manager.prototype.onpong = function(){ - this.emitAll('pong', new Date - this.lastPing); -}; +// cache for constraints and callback +var cache = {}; -/** - * Called with data. - * - * @api private - */ +module.exports = function (constraints, cb) { + var hasConstraints = arguments.length === 2; + var callback = hasConstraints ? cb : constraints; + var error; -Manager.prototype.ondata = function(data){ - this.decoder.add(data); -}; + if (typeof window === 'undefined' || window.location.protocol === 'http:') { + error = new Error('NavigatorUserMediaError'); + error.name = 'HTTPS_REQUIRED'; + return callback(error); + } -/** - * Called when parser fully decodes a packet. - * - * @api private - */ + if (window.navigator.userAgent.match('Chrome')) { + var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10); + var maxver = 33; + var isCef = !window.chrome.webstore; + // "known" crash in chrome 34 and 35 on linux + if (window.navigator.userAgent.match('Linux')) maxver = 35; -Manager.prototype.ondecoded = function(packet) { - this.emit('packet', packet); + // check that the extension is installed by looking for a + // sessionStorage variable that contains the extension id + // this has to be set after installation unless the contest + // script does that + if (sessionStorage.getScreenMediaJSExtensionId) { + chrome.runtime.sendMessage(sessionStorage.getScreenMediaJSExtensionId, + {type:'getScreen', id: 1}, null, + function (data) { + if (!data || data.sourceId === '') { // user canceled + var error = new Error('NavigatorUserMediaError'); + error.name = 'PERMISSION_DENIED'; + callback(error); + } else { + constraints = (hasConstraints && constraints) || {audio: false, video: { + mandatory: { + chromeMediaSource: 'desktop', + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3 + }, + optional: [ + {googLeakyBucket: true}, + {googTemporalLayeredScreencast: true} + ] + }}; + constraints.video.mandatory.chromeMediaSourceId = data.sourceId; + getUserMedia(constraints, callback); + } + } + ); + } else if (window.cefGetScreenMedia) { + //window.cefGetScreenMedia is experimental - may be removed without notice + window.cefGetScreenMedia(function(sourceId) { + if (!sourceId) { + var error = new Error('cefGetScreenMediaError'); + error.name = 'CEF_GETSCREENMEDIA_CANCELED'; + callback(error); + } else { + constraints = (hasConstraints && constraints) || {audio: false, video: { + mandatory: { + chromeMediaSource: 'desktop', + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3 + }, + optional: [ + {googLeakyBucket: true}, + {googTemporalLayeredScreencast: true} + ] + }}; + constraints.video.mandatory.chromeMediaSourceId = sourceId; + getUserMedia(constraints, callback); + } + }); + } else if (isCef || (chromever >= 26 && chromever <= maxver)) { + // chrome 26 - chrome 33 way to do it -- requires bad chrome://flags + // note: this is basically in maintenance mode and will go away soon + constraints = (hasConstraints && constraints) || { + video: { + mandatory: { + googLeakyBucket: true, + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3, + chromeMediaSource: 'screen' + } + } + }; + getUserMedia(constraints, callback); + } else { + // chrome 34+ way requiring an extension + var pending = window.setTimeout(function () { + error = new Error('NavigatorUserMediaError'); + error.name = 'EXTENSION_UNAVAILABLE'; + return callback(error); + }, 1000); + cache[pending] = [callback, hasConstraints ? constraints : null]; + window.postMessage({ type: 'getScreen', id: pending }, '*'); + } + } else if (window.navigator.userAgent.match('Firefox')) { + var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); + if (ffver >= 33) { + constraints = (hasConstraints && constraints) || { + video: { + mozMediaSource: 'window', + mediaSource: 'window' + } + } + getUserMedia(constraints, function (err, stream) { + callback(err, stream); + // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 + if (!err) { + var lastTime = stream.currentTime; + var polly = window.setInterval(function () { + if (!stream) window.clearInterval(polly); + if (stream.currentTime == lastTime) { + window.clearInterval(polly); + if (stream.onended) { + stream.onended(); + } + } + lastTime = stream.currentTime; + }, 500); + } + }); + } else { + error = new Error('NavigatorUserMediaError'); + error.name = 'EXTENSION_UNAVAILABLE'; // does not make much sense but... + } + } }; -/** - * Called upon socket error. - * - * @api private - */ +window.addEventListener('message', function (event) { + if (event.origin != window.location.origin) { + return; + } + if (event.data.type == 'gotScreen' && cache[event.data.id]) { + var data = cache[event.data.id]; + var constraints = data[1]; + var callback = data[0]; + delete cache[event.data.id]; + + if (event.data.sourceId === '') { // user canceled + var error = new Error('NavigatorUserMediaError'); + error.name = 'PERMISSION_DENIED'; + callback(error); + } else { + constraints = constraints || {audio: false, video: { + mandatory: { + chromeMediaSource: 'desktop', + maxWidth: window.screen.width, + maxHeight: window.screen.height, + maxFrameRate: 3 + }, + optional: [ + {googLeakyBucket: true}, + {googTemporalLayeredScreencast: true} + ] + }}; + constraints.video.mandatory.chromeMediaSourceId = event.data.sourceId; + getUserMedia(constraints, callback); + } + } else if (event.data.type == 'getScreenPending') { + window.clearTimeout(event.data.id); + } +}); -Manager.prototype.onerror = function(err){ - debug('error', err); - this.emitAll('error', err); -}; +},{"getusermedia":21}],22:[function(require,module,exports){ +var support = require('webrtcsupport'); -/** - * Creates a new socket for the given `nsp`. - * - * @return {Socket} - * @api public - */ -Manager.prototype.socket = function(nsp){ - var socket = this.nsps[nsp]; - if (!socket) { - socket = new Socket(this, nsp); - this.nsps[nsp] = socket; - var self = this; - socket.on('connecting', onConnecting); - socket.on('connect', function(){ - socket.id = self.engine.id; - }); +function GainController(stream) { + this.support = support.webAudio && support.mediaStream; - if (this.autoConnect) { - // manually call here since connecting evnet is fired before listening - onConnecting(); - } - } + // set our starting value + this.gain = 1; - function onConnecting() { - if (!~indexOf(self.connecting, socket)) { - self.connecting.push(socket); + if (this.support) { + var context = this.context = new support.AudioContext(); + this.microphone = context.createMediaStreamSource(stream); + this.gainFilter = context.createGain(); + this.destination = context.createMediaStreamDestination(); + this.outputStream = this.destination.stream; + this.microphone.connect(this.gainFilter); + this.gainFilter.connect(this.destination); + stream.addTrack(this.outputStream.getAudioTracks()[0]); + stream.removeTrack(stream.getAudioTracks()[0]); } - } + this.stream = stream; +} - return socket; +// setting +GainController.prototype.setGain = function (val) { + // check for support + if (!this.support) return; + this.gainFilter.gain.value = val; + this.gain = val; }; -/** - * Called upon a socket close. - * - * @param {Socket} socket - */ +GainController.prototype.getGain = function () { + return this.gain; +}; -Manager.prototype.destroy = function(socket){ - var index = indexOf(this.connecting, socket); - if (~index) this.connecting.splice(index, 1); - if (this.connecting.length) return; +GainController.prototype.off = function () { + return this.setGain(0); +}; - this.close(); +GainController.prototype.on = function () { + this.setGain(1); }; -/** - * Writes a packet. - * - * @param {Object} packet - * @api private - */ -Manager.prototype.packet = function(packet){ - debug('writing packet %j', packet); - var self = this; +module.exports = GainController; - if (!self.encoding) { - // encode, then write to engine with result - self.encoding = true; - this.encoder.encode(packet, function(encodedPackets) { - for (var i = 0; i < encodedPackets.length; i++) { - self.engine.write(encodedPackets[i], packet.options); - } - self.encoding = false; - self.processPacketQueue(); - }); - } else { // add packet to the queue - self.packetBuffer.push(packet); - } -}; +},{"webrtcsupport":18}],17:[function(require,module,exports){ /** - * If packet buffer is non-empty, begins encoding the - * next packet in line. - * - * @api private + * Module dependencies. */ -Manager.prototype.processPacketQueue = function() { - if (this.packetBuffer.length > 0 && !this.encoding) { - var pack = this.packetBuffer.shift(); - this.packet(pack); - } -}; +var url = require('./url'); +var parser = require('socket.io-parser'); +var Manager = require('./manager'); +var debug = require('debug')('socket.io-client'); /** - * Clean up transport subscriptions and packet buffer. - * - * @api private + * Module exports. */ -Manager.prototype.cleanup = function(){ - debug('cleanup'); - - var sub; - while (sub = this.subs.shift()) sub.destroy(); - - this.packetBuffer = []; - this.encoding = false; - this.lastPing = null; - - this.decoder.destroy(); -}; +module.exports = exports = lookup; /** - * Close the current socket. - * - * @api private + * Managers cache. */ -Manager.prototype.close = -Manager.prototype.disconnect = function(){ - debug('disconnect'); - this.skipReconnect = true; - this.reconnecting = false; - if ('opening' == this.readyState) { - // `onclose` will not fire because - // an open event never happened - this.cleanup(); - } - this.backoff.reset(); - this.readyState = 'closed'; - if (this.engine) this.engine.close(); -}; +var cache = exports.managers = {}; /** - * Called upon engine close. + * Looks up an existing `Manager` for multiplexing. + * If the user summons: * - * @api private + * `io('http://localhost/a');` + * `io('http://localhost/b');` + * + * We reuse the existing instance based on same scheme/port/host, + * and we initialize sockets for each namespace. + * + * @api public */ -Manager.prototype.onclose = function(reason){ - debug('onclose'); - - this.cleanup(); - this.backoff.reset(); - this.readyState = 'closed'; - this.emit('close', reason); - - if (this._reconnection && !this.skipReconnect) { - this.reconnect(); +function lookup(uri, opts) { + if (typeof uri == 'object') { + opts = uri; + uri = undefined; } -}; - -/** - * Attempt a reconnection. - * - * @api private - */ -Manager.prototype.reconnect = function(){ - if (this.reconnecting || this.skipReconnect) return this; + opts = opts || {}; - var self = this; + var parsed = url(uri); + var source = parsed.source; + var id = parsed.id; + var io; - if (this.backoff.attempts >= this._reconnectionAttempts) { - debug('reconnect failed'); - this.backoff.reset(); - this.emitAll('reconnect_failed'); - this.reconnecting = false; + if (opts.forceNew || opts['force new connection'] || false === opts.multiplex) { + debug('ignoring socket cache for %s', source); + io = Manager(source, opts); } else { - var delay = this.backoff.duration(); - debug('will wait %dms before reconnect attempt', delay); - - this.reconnecting = true; - var timer = setTimeout(function(){ - if (self.skipReconnect) return; - - debug('attempting reconnect'); - self.emitAll('reconnect_attempt', self.backoff.attempts); - self.emitAll('reconnecting', self.backoff.attempts); - - // check again for the case socket closed in above events - if (self.skipReconnect) return; - - self.open(function(err){ - if (err) { - debug('reconnect attempt error'); - self.reconnecting = false; - self.reconnect(); - self.emitAll('reconnect_error', err.data); - } else { - debug('reconnect success'); - self.onreconnect(); - } - }); - }, delay); - - this.subs.push({ - destroy: function(){ - clearTimeout(timer); - } - }); + if (!cache[id]) { + debug('new io instance for %s', source); + cache[id] = Manager(source, opts); + } + io = cache[id]; } -}; - -/** - * Called upon successful reconnect. - * - * @api private - */ - -Manager.prototype.onreconnect = function(){ - var attempt = this.backoff.attempts; - this.reconnecting = false; - this.backoff.reset(); - this.updateSocketIds(); - this.emitAll('reconnect', attempt); -}; -},{"./on":22,"./socket":25,"backo2":40,"component-bind":38,"component-emitter":36,"debug":26,"engine.io-client":37,"indexof":39,"socket.io-parser":27}],25:[function(require,module,exports){ + return io.socket(parsed.path); +} /** - * Module dependencies. + * Protocol version. + * + * @api public */ -var parser = require('socket.io-parser'); -var Emitter = require('component-emitter'); -var toArray = require('to-array'); -var on = require('./on'); -var bind = require('component-bind'); -var debug = require('debug')('socket.io-client:socket'); -var hasBin = require('has-binary'); +exports.protocol = parser.protocol; /** - * Module exports. + * `connect`. + * + * @param {String} uri + * @api public */ -module.exports = exports = Socket; +exports.connect = lookup; /** - * Internal events (blacklisted). - * These events can't be emitted by the user. + * Expose constructors for standalone build. * - * @api private + * @api public */ -var events = { - connect: 1, - connect_error: 1, - connect_timeout: 1, - connecting: 1, - disconnect: 1, - error: 1, - reconnect: 1, - reconnect_attempt: 1, - reconnect_failed: 1, - reconnect_error: 1, - reconnecting: 1, - ping: 1, - pong: 1 -}; +exports.Manager = require('./manager'); +exports.Socket = require('./socket'); + +},{"./manager":33,"./socket":34,"./url":32,"debug":35,"socket.io-parser":36}],35:[function(require,module,exports){ /** - * Shortcut to `Emitter#emit`. + * Expose `debug()` as the module. */ -var emit = Emitter.prototype.emit; +module.exports = debug; /** - * `Socket` constructor. + * Create a debugger with the given `name`. * + * @param {String} name + * @return {Type} * @api public */ -function Socket(io, nsp){ - this.io = io; - this.nsp = nsp; - this.json = this; // compat - this.ids = 0; - this.acks = {}; - this.receiveBuffer = []; - this.sendBuffer = []; - this.connected = false; - this.disconnected = true; - if (this.io.autoConnect) this.open(); +function debug(name) { + if (!debug.enabled(name)) return function(){}; + + return function(fmt){ + fmt = coerce(fmt); + + var curr = new Date; + var ms = curr - (debug[name] || curr); + debug[name] = curr; + + fmt = name + + ' ' + + fmt + + ' +' + debug.humanize(ms); + + // This hackery is required for IE8 + // where `console.log` doesn't have 'apply' + window.console + && console.log + && Function.prototype.apply.call(console.log, console, arguments); + } } /** - * Mix in `Emitter`. + * The currently active debug mode names. */ -Emitter(Socket.prototype); +debug.names = []; +debug.skips = []; /** - * Subscribe to open, close and packet events + * Enables a debug mode by name. This can include modes + * separated by a colon and wildcards. * - * @api private + * @param {String} name + * @api public */ -Socket.prototype.subEvents = function() { - if (this.subs) return; +debug.enable = function(name) { + try { + localStorage.debug = name; + } catch(e){} - var io = this.io; - this.subs = [ - on(io, 'open', bind(this, 'onopen')), - on(io, 'packet', bind(this, 'onpacket')), - on(io, 'close', bind(this, 'onclose')) - ]; + var split = (name || '').split(/[\s,]+/) + , len = split.length; + + for (var i = 0; i < len; i++) { + name = split[i].replace('*', '.*?'); + if (name[0] === '-') { + debug.skips.push(new RegExp('^' + name.substr(1) + '$')); + } + else { + debug.names.push(new RegExp('^' + name + '$')); + } + } }; /** - * "Opens" the socket. + * Disable debug output. * * @api public */ -Socket.prototype.open = -Socket.prototype.connect = function(){ - if (this.connected) return this; - - this.subEvents(); - this.io.open(); // ensure open - if ('open' == this.io.readyState) this.onopen(); - this.emit('connecting'); - return this; +debug.disable = function(){ + debug.enable(''); }; /** - * Sends a `message` event. + * Humanize the given `ms`. * - * @return {Socket} self - * @api public + * @param {Number} m + * @return {String} + * @api private */ -Socket.prototype.send = function(){ - var args = toArray(arguments); - args.unshift('message'); - this.emit.apply(this, args); - return this; +debug.humanize = function(ms) { + var sec = 1000 + , min = 60 * 1000 + , hour = 60 * min; + + if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; + if (ms >= min) return (ms / min).toFixed(1) + 'm'; + if (ms >= sec) return (ms / sec | 0) + 's'; + return ms + 'ms'; }; /** - * Override `emit`. - * If the event is in `events`, it's emitted normally. + * Returns true if the given mode name is enabled, false otherwise. * - * @param {String} event name - * @return {Socket} self + * @param {String} name + * @return {Boolean} * @api public */ -Socket.prototype.emit = function(ev){ - if (events.hasOwnProperty(ev)) { - emit.apply(this, arguments); - return this; +debug.enabled = function(name) { + for (var i = 0, len = debug.skips.length; i < len; i++) { + if (debug.skips[i].test(name)) { + return false; + } } + for (var i = 0, len = debug.names.length; i < len; i++) { + if (debug.names[i].test(name)) { + return true; + } + } + return false; +}; - var args = toArray(arguments); - var parserType = parser.EVENT; // default - if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary - var packet = { type: parserType, data: args }; - - packet.options = {}; - packet.options.compress = !this.flags || false !== this.flags.compress; +/** + * Coerce `val`. + */ - // event ack callback - if ('function' == typeof args[args.length - 1]) { - debug('emitting packet with ack id %d', this.ids); - this.acks[this.ids] = args.pop(); - packet.id = this.ids++; - } +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} - if (this.connected) { - this.packet(packet); - } else { - this.sendBuffer.push(packet); - } +// persist - delete this.flags; +try { + if (window.localStorage) debug.enable(localStorage.debug); +} catch(e){} - return this; -}; +},{}],37:[function(require,module,exports){ /** - * Sends a packet. - * - * @param {Object} packet - * @api private + * Module exports. */ -Socket.prototype.packet = function(packet){ - packet.nsp = this.nsp; - this.io.packet(packet); -}; +module.exports = on; /** - * Called upon engine `open`. + * Helper for subscriptions. * - * @api private + * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter` + * @param {String} event name + * @param {Function} callback + * @api public */ -Socket.prototype.onopen = function(){ - debug('transport is open - connecting'); - - // write connect packet if necessary - if ('/' != this.nsp) { - this.packet({ type: parser.CONNECT }); - } -}; +function on(obj, ev, fn) { + obj.on(ev, fn); + return { + destroy: function(){ + obj.removeListener(ev, fn); + } + }; +} +},{}],27:[function(require,module,exports){ /** - * Called upon engine `close`. - * - * @param {String} reason - * @api private + * lodash 3.1.2 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license */ +var baseGet = require('lodash._baseget'), + toPath = require('lodash._topath'), + isArray = require('lodash.isarray'), + map = require('lodash.map'); -Socket.prototype.onclose = function(reason){ - debug('close (%s)', reason); - this.connected = false; - this.disconnected = true; - delete this.id; - this.emit('disconnect', reason); -}; +/** Used to match property names within property paths. */ +var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/; /** - * Called with socket packet. + * The base implementation of `_.property` without support for deep paths. * - * @param {Object} packet - * @api private + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} -Socket.prototype.onpacket = function(packet){ - if (packet.nsp != this.nsp) return; - - switch (packet.type) { - case parser.CONNECT: - this.onconnect(); - break; - - case parser.EVENT: - this.onevent(packet); - break; - - case parser.BINARY_EVENT: - this.onevent(packet); - break; - - case parser.ACK: - this.onack(packet); - break; - - case parser.BINARY_ACK: - this.onack(packet); - break; - - case parser.DISCONNECT: - this.ondisconnect(); - break; - - case parser.ERROR: - this.emit('error', packet.data); - break; - } -}; +/** + * A specialized version of `baseProperty` which supports deep paths. + * + * @private + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new function. + */ +function basePropertyDeep(path) { + var pathKey = (path + ''); + path = toPath(path); + return function(object) { + return baseGet(object, path, pathKey); + }; +} /** - * Called upon a server event. + * Checks if `value` is a property name and not a property path. * - * @param {Object} packet - * @api private + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. */ - -Socket.prototype.onevent = function(packet){ - var args = packet.data || []; - debug('emitting event %j', args); - - if (null != packet.id) { - debug('attaching ack callback to event'); - args.push(this.ack(packet.id)); +function isKey(value, object) { + var type = typeof value; + if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') { + return true; } - - if (this.connected) { - emit.apply(this, args); - } else { - this.receiveBuffer.push(args); + if (isArray(value)) { + return false; } -}; + var result = !reIsDeepProp.test(value); + return result || (object != null && value in toObject(object)); +} /** - * Produces an ack callback to emit with an event. + * Converts `value` to an object if it's not one. * - * @api private + * @private + * @param {*} value The value to process. + * @returns {Object} Returns the object. */ - -Socket.prototype.ack = function(id){ - var self = this; - var sent = false; - return function(){ - // prevent double callbacks - if (sent) return; - sent = true; - var args = toArray(arguments); - debug('sending ack %j', args); - - var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; - self.packet({ - type: type, - id: id, - data: args - }); - }; -}; +function toObject(value) { + return isObject(value) ? value : Object(value); +} /** - * Called upon a server acknowlegement. + * Gets the property value of `path` from all elements in `collection`. * - * @param {Object} packet - * @api private + * @static + * @memberOf _ + * @category Collection + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Array|string} path The path of the property to pluck. + * @returns {Array} Returns the property values. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 40 } + * ]; + * + * _.pluck(users, 'user'); + * // => ['barney', 'fred'] + * + * var userIndex = _.indexBy(users, 'user'); + * _.pluck(userIndex, 'age'); + * // => [36, 40] (iteration order is not guaranteed) */ - -Socket.prototype.onack = function(packet){ - var ack = this.acks[packet.id]; - if ('function' == typeof ack) { - debug('calling ack %s with %j', packet.id, packet.data); - ack.apply(this, packet.data); - delete this.acks[packet.id]; - } else { - debug('bad ack %s', packet.id); - } -}; +function pluck(collection, path) { + return map(collection, property(path)); +} /** - * Called upon server connect. + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * - * @api private + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false */ - -Socket.prototype.onconnect = function(){ - this.connected = true; - this.disconnected = false; - this.emit('connect'); - this.emitBuffered(); -}; +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} /** - * Emit buffered events (received and emitted). + * Creates a function which returns the property value at `path` on a + * given object. * - * @api private + * @static + * @memberOf _ + * @category Utility + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new function. + * @example + * + * var objects = [ + * { 'a': { 'b': { 'c': 2 } } }, + * { 'a': { 'b': { 'c': 1 } } } + * ]; + * + * _.map(objects, _.property('a.b.c')); + * // => [2, 1] + * + * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c'); + * // => [1, 2] */ +function property(path) { + return isKey(path) ? baseProperty(path) : basePropertyDeep(path); +} -Socket.prototype.emitBuffered = function(){ - var i; - for (i = 0; i < this.receiveBuffer.length; i++) { - emit.apply(this, this.receiveBuffer[i]); - } - this.receiveBuffer = []; - - for (i = 0; i < this.sendBuffer.length; i++) { - this.packet(this.sendBuffer[i]); - } - this.sendBuffer = []; -}; +module.exports = pluck; +},{"lodash._baseget":38,"lodash._topath":39,"lodash.isarray":40,"lodash.map":41}],26:[function(require,module,exports){ /** - * Called upon server disconnect. - * - * @api private + * lodash 3.0.3 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license */ - -Socket.prototype.ondisconnect = function(){ - debug('server disconnect (%s)', this.nsp); - this.destroy(); - this.onclose('io server disconnect'); -}; +var arrayEach = require('lodash._arrayeach'), + baseEach = require('lodash._baseeach'), + bindCallback = require('lodash._bindcallback'), + isArray = require('lodash.isarray'); /** - * Called upon forced client/server side disconnections, - * this method ensures the manager stops tracking us and - * that reconnections don't get triggered for this. + * Creates a function for `_.forEach` or `_.forEachRight`. * - * @api private. + * @private + * @param {Function} arrayFunc The function to iterate over an array. + * @param {Function} eachFunc The function to iterate over a collection. + * @returns {Function} Returns the new each function. */ - -Socket.prototype.destroy = function(){ - if (this.subs) { - // clean subscriptions to avoid reconnections - for (var i = 0; i < this.subs.length; i++) { - this.subs[i].destroy(); - } - this.subs = null; - } - - this.io.destroy(this); -}; +function createForEach(arrayFunc, eachFunc) { + return function(collection, iteratee, thisArg) { + return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection)) + ? arrayFunc(collection, iteratee) + : eachFunc(collection, bindCallback(iteratee, thisArg, 3)); + }; +} /** - * Disconnects the socket manually. + * Iterates over elements of `collection` invoking `iteratee` for each element. + * The `iteratee` is bound to `thisArg` and invoked with three arguments: + * (value, index|key, collection). Iteratee functions may exit iteration early + * by explicitly returning `false`. * - * @return {Socket} self - * @api public + * **Note:** As with other "Collections" methods, objects with a "length" property + * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn` + * may be used for object iteration. + * + * @static + * @memberOf _ + * @alias each + * @category Collection + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {Array|Object|string} Returns `collection`. + * @example + * + * _([1, 2]).forEach(function(n) { + * console.log(n); + * }).value(); + * // => logs each value from left to right and returns the array + * + * _.forEach({ 'a': 1, 'b': 2 }, function(n, key) { + * console.log(n, key); + * }); + * // => logs each value-key pair and returns the object (iteration order is not guaranteed) */ +var forEach = createForEach(arrayEach, baseEach); -Socket.prototype.close = -Socket.prototype.disconnect = function(){ - if (this.connected) { - debug('performing disconnect (%s)', this.nsp); - this.packet({ type: parser.DISCONNECT }); - } +module.exports = forEach; + +},{"lodash._arrayeach":42,"lodash._baseeach":45,"lodash._bindcallback":43,"lodash.isarray":44}],30:[function(require,module,exports){ +var SENDERS = require('./senders'); +var parsers = require('./parsers'); +var idCounter = Math.random(); - // remove socket from pool - this.destroy(); - if (this.connected) { - // fire events - this.onclose('io client disconnect'); - } - return this; +exports._setIdCounter = function (counter) { + idCounter = counter; }; -/** - * Sets the compress flag. - * - * @param {Boolean} if `true`, compresses the sending data - * @return {Socket} self - * @api public - */ +exports.toSessionJSON = function (sdp, opts) { + var i; + var creators = opts.creators || []; + var role = opts.role || 'initiator'; + var direction = opts.direction || 'outgoing'; -Socket.prototype.compress = function(compress){ - this.flags = this.flags || {}; - this.flags.compress = compress; - return this; -}; -},{"./on":22,"component-bind":38,"component-emitter":36,"debug":26,"has-binary":42,"socket.io-parser":27,"to-array":41}],33:[function(require,module,exports){ -// created by @HenrikJoreteg -var prefix; -var version; + // Divide the SDP into session and media sections. + var media = sdp.split('\r\nm='); + for (i = 1; i < media.length; i++) { + media[i] = 'm=' + media[i]; + if (i !== media.length - 1) { + media[i] += '\r\n'; + } + } + var session = media.shift() + '\r\n'; + var sessionLines = parsers.lines(session); + var parsed = {}; -if (window.mozRTCPeerConnection || navigator.mozGetUserMedia) { - prefix = 'moz'; - version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); -} else if (window.webkitRTCPeerConnection || navigator.webkitGetUserMedia) { - prefix = 'webkit'; - version = navigator.userAgent.match(/Chrom(e|ium)/) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); -} + var contents = []; + for (i = 0; i < media.length; i++) { + contents.push(exports.toMediaJSON(media[i], session, { + role: role, + direction: direction, + creator: creators[i] || 'initiator' + })); + } + parsed.contents = contents; -var PC = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; -var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate; -var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription; -var MediaStream = window.webkitMediaStream || window.MediaStream; -var screenSharing = window.location.protocol === 'https:' && - ((prefix === 'webkit' && version >= 26) || - (prefix === 'moz' && version >= 33)) -var AudioContext = window.AudioContext || window.webkitAudioContext; -var videoEl = document.createElement('video'); -var supportVp8 = videoEl && videoEl.canPlayType && videoEl.canPlayType('video/webm; codecs="vp8", vorbis') === "probably"; -var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia; + var groupLines = parsers.findLines('a=group:', sessionLines); + if (groupLines.length) { + parsed.groups = parsers.groups(groupLines); + } -// export support flags and constructors.prototype && PC -module.exports = { - prefix: prefix, - browserVersion: version, - support: !!PC && supportVp8 && !!getUserMedia, - // new support style - supportRTCPeerConnection: !!PC, - supportVp8: supportVp8, - supportGetUserMedia: !!getUserMedia, - supportDataChannel: !!(PC && PC.prototype && PC.prototype.createDataChannel), - supportWebAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource), - supportMediaStream: !!(MediaStream && MediaStream.prototype.removeTrack), - supportScreenSharing: !!screenSharing, - // old deprecated style. Dont use this anymore - dataChannel: !!(PC && PC.prototype && PC.prototype.createDataChannel), - webAudio: !!(AudioContext && AudioContext.prototype.createMediaStreamSource), - mediaStream: !!(MediaStream && MediaStream.prototype.removeTrack), - screenSharing: !!screenSharing, - // constructors - AudioContext: AudioContext, - PeerConnection: PC, - SessionDescription: SessionDescription, - IceCandidate: IceCandidate, - MediaStream: MediaStream, - getUserMedia: getUserMedia + return parsed; }; -},{}],18:[function(require,module,exports){ -var WildEmitter = require('wildemitter'); +exports.toMediaJSON = function (media, session, opts) { + var creator = opts.creator || 'initiator'; + var role = opts.role || 'initiator'; + var direction = opts.direction || 'outgoing'; -function getMaxVolume (analyser, fftBins) { - var maxVolume = -Infinity; - analyser.getFloatFrequencyData(fftBins); + var lines = parsers.lines(media); + var sessionLines = parsers.lines(session); + var mline = parsers.mline(lines[0]); - for(var i=4, ii=fftBins.length; i < ii; i++) { - if (fftBins[i] > maxVolume && fftBins[i] < 0) { - maxVolume = fftBins[i]; + var content = { + creator: creator, + name: mline.media, + description: { + descType: 'rtp', + media: mline.media, + payloads: [], + encryption: [], + feedback: [], + headerExtensions: [] + }, + transport: { + transType: 'iceUdp', + candidates: [], + fingerprints: [] + } + }; + if (mline.media == 'application') { + // FIXME: the description is most likely to be independent + // of the SDP and should be processed by other parts of the library + content.description = { + descType: 'datachannel' + }; + content.transport.sctp = []; } - }; - - return maxVolume; -} - + var desc = content.description; + var trans = content.transport; -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(); + // If we have a mid, use that for the content name instead. + var mid = parsers.findLine('a=mid:', lines); + if (mid) { + content.name = mid.substr(6); + } + if (parsers.findLine('a=sendrecv', lines, sessionLines)) { + content.senders = 'both'; + } else if (parsers.findLine('a=sendonly', lines, sessionLines)) { + content.senders = SENDERS[role][direction].sendonly; + } else if (parsers.findLine('a=recvonly', lines, sessionLines)) { + content.senders = SENDERS[role][direction].recvonly; + } else if (parsers.findLine('a=inactive', lines, sessionLines)) { + content.senders = 'none'; + } - // make it not break in non-supported browsers - if (!audioContextType) return harker; + if (desc.descType == 'rtp') { + var bandwidth = parsers.findLine('b=', lines); + if (bandwidth) { + desc.bandwidth = parsers.bandwidth(bandwidth); + } - //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; + var ssrc = parsers.findLine('a=ssrc:', lines); + if (ssrc) { + desc.ssrc = ssrc.substr(7).split(' ')[0]; + } - //Setup Audio Context - if (!audioContext) { - audioContext = new audioContextType(); - } - var sourceNode, fftBins, analyser; + var rtpmapLines = parsers.findLines('a=rtpmap:', lines); + rtpmapLines.forEach(function (line) { + var payload = parsers.rtpmap(line); + payload.parameters = []; + payload.feedback = []; - analyser = audioContext.createAnalyser(); - analyser.fftSize = 512; - analyser.smoothingTimeConstant = smoothing; - fftBins = new Float32Array(analyser.fftSize); + var fmtpLines = parsers.findLines('a=fmtp:' + payload.id, lines); + // There should only be one fmtp line per payload + fmtpLines.forEach(function (line) { + payload.parameters = parsers.fmtp(line); + }); - 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; - } + var fbLines = parsers.findLines('a=rtcp-fb:' + payload.id, lines); + fbLines.forEach(function (line) { + payload.feedback.push(parsers.rtcpfb(line)); + }); - sourceNode.connect(analyser); - if (play) analyser.connect(audioContext.destination); + desc.payloads.push(payload); + }); - harker.speaking = false; + var cryptoLines = parsers.findLines('a=crypto:', lines, sessionLines); + cryptoLines.forEach(function (line) { + desc.encryption.push(parsers.crypto(line)); + }); - harker.setThreshold = function(t) { - threshold = t; - }; + if (parsers.findLine('a=rtcp-mux', lines)) { + desc.mux = true; + } - 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); - } + var fbLines = parsers.findLines('a=rtcp-fb:*', lines); + fbLines.forEach(function (line) { + desc.feedback.push(parsers.rtcpfb(line)); + }); - // 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); + var extLines = parsers.findLines('a=extmap:', lines); + extLines.forEach(function (line) { + var ext = parsers.extmap(line); - harker.emit('volume_change', currentVolume, threshold); + ext.senders = SENDERS[role][direction][ext.senders]; - 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'); + desc.headerExtensions.push(ext); + }); + + var ssrcGroupLines = parsers.findLines('a=ssrc-group:', lines); + desc.sourceGroups = parsers.sourceGroups(ssrcGroupLines || []); + + var ssrcLines = parsers.findLines('a=ssrc:', lines); + var sources = desc.sources = parsers.sources(ssrcLines || []); + + var msidLine = parsers.findLine('a=msid:', lines); + if (msidLine) { + var msid = parsers.msid(msidLine); + ['msid', 'mslabel', 'label'].forEach(function (key) { + for (var i = 0; i < sources.length; i++) { + var found = false; + for (var j = 0; j < sources[i].parameters.length; j++) { + if (sources[i].parameters[j].key === key) { + found = true; + } + } + if (!found) { + sources[i].parameters.push({ key: key, value: msid[key] }); + } + } + }); } - } else if (currentVolume < threshold && harker.speaking) { - for (var i = 0; i < harker.speakingHistory.length; i++) { - history += harker.speakingHistory[i]; + + if (parsers.findLine('a=x-google-flag:conference', lines, sessionLines)) { + desc.googConferenceFlag = true; } - if (history == 0) { - harker.speaking = false; - harker.emit('stopped_speaking'); + } + + // transport specific attributes + var fingerprintLines = parsers.findLines('a=fingerprint:', lines, sessionLines); + var setup = parsers.findLine('a=setup:', lines, sessionLines); + fingerprintLines.forEach(function (line) { + var fp = parsers.fingerprint(line); + if (setup) { + fp.setup = setup.substr(8); } - } - harker.speakingHistory.shift(); - harker.speakingHistory.push(0 + (currentVolume > threshold)); + trans.fingerprints.push(fp); + }); - looper(); - }, interval); - }; - looper(); + var ufragLine = parsers.findLine('a=ice-ufrag:', lines, sessionLines); + var pwdLine = parsers.findLine('a=ice-pwd:', lines, sessionLines); + if (ufragLine && pwdLine) { + trans.ufrag = ufragLine.substr(12); + trans.pwd = pwdLine.substr(10); + trans.candidates = []; + var candidateLines = parsers.findLines('a=candidate:', lines, sessionLines); + candidateLines.forEach(function (line) { + trans.candidates.push(exports.toCandidateJSON(line)); + }); + } - return harker; -} + if (desc.descType == 'datachannel') { + var sctpmapLines = parsers.findLines('a=sctpmap:', lines); + sctpmapLines.forEach(function (line) { + var sctp = parsers.sctpmap(line); + trans.sctp.push(sctp); + }); + } -},{"wildemitter":4}],28:[function(require,module,exports){ -var toSDP = require('./lib/tosdp'); -var toJSON = require('./lib/tojson'); + return content; +}; +exports.toCandidateJSON = function (line) { + var candidate = parsers.candidate(line.split('\r\n')[0]); + candidate.id = (idCounter++).toString(36).substr(0, 12); + return candidate; +}; -// Converstion from JSON to SDP +},{"./parsers":47,"./senders":46}],31:[function(require,module,exports){ +var SENDERS = require('./senders'); -exports.toIncomingSDPOffer = function (session) { - return toSDP.toSessionSDP(session, { - role: 'responder', - direction: 'incoming' - }); -}; -exports.toOutgoingSDPOffer = function (session) { - return toSDP.toSessionSDP(session, { - role: 'initiator', - direction: 'outgoing' - }); -}; -exports.toIncomingSDPAnswer = function (session) { - return toSDP.toSessionSDP(session, { - role: 'initiator', - direction: 'incoming' - }); -}; -exports.toOutgoingSDPAnswer = function (session) { - return toSDP.toSessionSDP(session, { - role: 'responder', - direction: 'outgoing' - }); -}; -exports.toIncomingMediaSDPOffer = function (media) { - return toSDP.toMediaSDP(media, { - role: 'responder', - direction: 'incoming' - }); -}; -exports.toOutgoingMediaSDPOffer = function (media) { - return toSDP.toMediaSDP(media, { - role: 'initiator', - direction: 'outgoing' - }); -}; -exports.toIncomingMediaSDPAnswer = function (media) { - return toSDP.toMediaSDP(media, { - role: 'initiator', - direction: 'incoming' - }); -}; -exports.toOutgoingMediaSDPAnswer = function (media) { - return toSDP.toMediaSDP(media, { - role: 'responder', - direction: 'outgoing' - }); -}; -exports.toCandidateSDP = toSDP.toCandidateSDP; -exports.toMediaSDP = toSDP.toMediaSDP; -exports.toSessionSDP = toSDP.toSessionSDP; +exports.toSessionSDP = function (session, opts) { + var role = opts.role || 'initiator'; + var direction = opts.direction || 'outgoing'; + var sid = opts.sid || session.sid || Date.now(); + var time = opts.time || Date.now(); -// Conversion from SDP to JSON + var sdp = [ + 'v=0', + 'o=- ' + sid + ' ' + time + ' IN IP4 0.0.0.0', + 's=-', + 't=0 0', + 'a=msid-semantic: WMS *' + ]; -exports.toIncomingJSONOffer = function (sdp, creators) { - return toJSON.toSessionJSON(sdp, { - role: 'responder', - direction: 'incoming', - creators: creators - }); -}; -exports.toOutgoingJSONOffer = function (sdp, creators) { - return toJSON.toSessionJSON(sdp, { - role: 'initiator', - direction: 'outgoing', - creators: creators - }); -}; -exports.toIncomingJSONAnswer = function (sdp, creators) { - return toJSON.toSessionJSON(sdp, { - role: 'initiator', - direction: 'incoming', - creators: creators - }); -}; -exports.toOutgoingJSONAnswer = function (sdp, creators) { - return toJSON.toSessionJSON(sdp, { - role: 'responder', - direction: 'outgoing', - creators: creators - }); -}; -exports.toIncomingMediaJSONOffer = function (sdp, creator) { - return toJSON.toMediaJSON(sdp, { - role: 'responder', - direction: 'incoming', - creator: creator - }); -}; -exports.toOutgoingMediaJSONOffer = function (sdp, creator) { - return toJSON.toMediaJSON(sdp, { - role: 'initiator', - direction: 'outgoing', - creator: creator - }); -}; -exports.toIncomingMediaJSONAnswer = function (sdp, creator) { - return toJSON.toMediaJSON(sdp, { - role: 'initiator', - direction: 'incoming', - creator: creator + var groups = session.groups || []; + groups.forEach(function (group) { + sdp.push('a=group:' + group.semantics + ' ' + group.contents.join(' ')); }); -}; -exports.toOutgoingMediaJSONAnswer = function (sdp, creator) { - return toJSON.toMediaJSON(sdp, { - role: 'responder', - direction: 'outgoing', - creator: creator + + var contents = session.contents || []; + contents.forEach(function (content) { + sdp.push(exports.toMediaSDP(content, opts)); }); + + return sdp.join('\r\n') + '\r\n'; }; -exports.toCandidateJSON = toJSON.toCandidateJSON; -exports.toMediaJSON = toJSON.toMediaJSON; -exports.toSessionJSON = toJSON.toSessionJSON; -},{"./lib/tojson":44,"./lib/tosdp":43}],19:[function(require,module,exports){ -// getUserMedia helper by @HenrikJoreteg used for navigator.getUserMedia shim -var adapter = require('webrtc-adapter'); +exports.toMediaSDP = function (content, opts) { + var sdp = []; -module.exports = function (constraints, cb) { - var error; - var haveOpts = arguments.length === 2; - var defaultOpts = {video: true, audio: true}; + var role = opts.role || 'initiator'; + var direction = opts.direction || 'outgoing'; - var denied = 'PermissionDeniedError'; - var altDenied = 'PERMISSION_DENIED'; - var notSatisfied = 'ConstraintNotSatisfiedError'; + var desc = content.description; + var transport = content.transport; + var payloads = desc.payloads || []; + var fingerprints = (transport && transport.fingerprints) || []; - // make constraints optional - if (!haveOpts) { - cb = constraints; - constraints = defaultOpts; + var mline = []; + if (desc.descType == 'datachannel') { + mline.push('application'); + mline.push('1'); + mline.push('DTLS/SCTP'); + if (transport.sctp) { + transport.sctp.forEach(function (map) { + mline.push(map.number); + }); + } + } else { + mline.push(desc.media); + mline.push('1'); + if ((desc.encryption && desc.encryption.length > 0) || (fingerprints.length > 0)) { + mline.push('RTP/SAVPF'); + } else { + mline.push('RTP/AVPF'); + } + payloads.forEach(function (payload) { + mline.push(payload.id); + }); } - // treat lack of browser support like an error - if (typeof navigator === 'undefined' || !navigator.getUserMedia) { - // throw proper error per spec - error = new Error('MediaStreamError'); - error.name = 'NotSupportedError'; - - // keep all callbacks async - return setTimeout(function () { - cb(error); - }, 0); - } - // normalize error handling when no media types are requested - if (!constraints.audio && !constraints.video) { - error = new Error('MediaStreamError'); - error.name = 'NoMediaRequestedError'; + sdp.push('m=' + mline.join(' ')); - // keep all callbacks async - return setTimeout(function () { - cb(error); - }, 0); + sdp.push('c=IN IP4 0.0.0.0'); + if (desc.bandwidth && desc.bandwidth.type && desc.bandwidth.bandwidth) { + sdp.push('b=' + desc.bandwidth.type + ':' + desc.bandwidth.bandwidth); } - - // testing support -- note: using the about:config pref is better - // for Firefox 39+, this might get removed in the future - if (localStorage && localStorage.useFirefoxFakeDevice === 'true') { - constraints.fake = true; + if (desc.descType == 'rtp') { + sdp.push('a=rtcp:1 IN IP4 0.0.0.0'); } - navigator.getUserMedia(constraints, function (stream) { - cb(null, stream); - }, function (err) { - var error; - // coerce into an error object since FF gives us a string - // there are only two valid names according to the spec - // we coerce all non-denied to "constraint not satisfied". - if (typeof err === 'string') { - error = new Error('MediaStreamError'); - if (err === denied || err === altDenied) { - error.name = denied; - } else { - error.name = notSatisfied; - } - } else { - // if we get an error object make sure '.name' property is set - // according to spec: http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermediaerror-and-navigatorusermediaerrorcallback - error = err; - if (!error.name) { - // this is likely chrome which - // sets a property called "ERROR_DENIED" on the error object - // if so we make sure to set a name - if (error[denied]) { - err.name = denied; - } else { - err.name = notSatisfied; - } - } + if (transport) { + if (transport.ufrag) { + sdp.push('a=ice-ufrag:' + transport.ufrag); + } + if (transport.pwd) { + sdp.push('a=ice-pwd:' + transport.pwd); } - cb(error); - }); -}; + var pushedSetup = false; + fingerprints.forEach(function (fingerprint) { + sdp.push('a=fingerprint:' + fingerprint.hash + ' ' + fingerprint.value); + if (fingerprint.setup && !pushedSetup) { + sdp.push('a=setup:' + fingerprint.setup); + } + }); -},{"webrtc-adapter":30}],21:[function(require,module,exports){ -// getScreenMedia helper by @HenrikJoreteg -var getUserMedia = require('getusermedia'); + if (transport.sctp) { + transport.sctp.forEach(function (map) { + sdp.push('a=sctpmap:' + map.number + ' ' + map.protocol + ' ' + map.streams); + }); + } + } -// cache for constraints and callback -var cache = {}; + if (desc.descType == 'rtp') { + sdp.push('a=' + (SENDERS[role][direction][content.senders] || 'sendrecv')); + } + sdp.push('a=mid:' + content.name); -module.exports = function (constraints, cb) { - var hasConstraints = arguments.length === 2; - var callback = hasConstraints ? cb : constraints; - var error; + if (desc.sources && desc.sources.length) { + (desc.sources[0].parameters || []).forEach(function (param) { + if (param.key === 'msid') { + sdp.push('a=msid:' + param.value); + } + }); + } - if (typeof window === 'undefined' || window.location.protocol === 'http:') { - error = new Error('NavigatorUserMediaError'); - error.name = 'HTTPS_REQUIRED'; - return callback(error); + if (desc.mux) { + sdp.push('a=rtcp-mux'); } - if (window.navigator.userAgent.match('Chrome')) { - var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10); - var maxver = 33; - var isCef = !window.chrome.webstore; - // "known" crash in chrome 34 and 35 on linux - if (window.navigator.userAgent.match('Linux')) maxver = 35; + var encryption = desc.encryption || []; + encryption.forEach(function (crypto) { + sdp.push('a=crypto:' + crypto.tag + ' ' + crypto.cipherSuite + ' ' + crypto.keyParams + (crypto.sessionParams ? ' ' + crypto.sessionParams : '')); + }); + if (desc.googConferenceFlag) { + sdp.push('a=x-google-flag:conference'); + } - // check that the extension is installed by looking for a - // sessionStorage variable that contains the extension id - // this has to be set after installation unless the contest - // script does that - if (sessionStorage.getScreenMediaJSExtensionId) { - chrome.runtime.sendMessage(sessionStorage.getScreenMediaJSExtensionId, - {type:'getScreen', id: 1}, null, - function (data) { - if (!data || data.sourceId === '') { // user canceled - var error = new Error('NavigatorUserMediaError'); - error.name = 'PERMISSION_DENIED'; - callback(error); - } else { - constraints = (hasConstraints && constraints) || {audio: false, video: { - mandatory: { - chromeMediaSource: 'desktop', - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - }, - optional: [ - {googLeakyBucket: true}, - {googTemporalLayeredScreencast: true} - ] - }}; - constraints.video.mandatory.chromeMediaSourceId = data.sourceId; - getUserMedia(constraints, callback); - } - } - ); - } else if (window.cefGetScreenMedia) { - //window.cefGetScreenMedia is experimental - may be removed without notice - window.cefGetScreenMedia(function(sourceId) { - if (!sourceId) { - var error = new Error('cefGetScreenMediaError'); - error.name = 'CEF_GETSCREENMEDIA_CANCELED'; - callback(error); + payloads.forEach(function (payload) { + var rtpmap = 'a=rtpmap:' + payload.id + ' ' + payload.name + '/' + payload.clockrate; + if (payload.channels && payload.channels != '1') { + rtpmap += '/' + payload.channels; + } + sdp.push(rtpmap); + + if (payload.parameters && payload.parameters.length) { + var fmtp = ['a=fmtp:' + payload.id]; + var parameters = []; + payload.parameters.forEach(function (param) { + parameters.push((param.key ? param.key + '=' : '') + param.value); + }); + fmtp.push(parameters.join(';')); + sdp.push(fmtp.join(' ')); + } + + if (payload.feedback) { + payload.feedback.forEach(function (fb) { + if (fb.type === 'trr-int') { + sdp.push('a=rtcp-fb:' + payload.id + ' trr-int ' + (fb.value ? fb.value : '0')); } else { - constraints = (hasConstraints && constraints) || {audio: false, video: { - mandatory: { - chromeMediaSource: 'desktop', - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - }, - optional: [ - {googLeakyBucket: true}, - {googTemporalLayeredScreencast: true} - ] - }}; - constraints.video.mandatory.chromeMediaSourceId = sourceId; - getUserMedia(constraints, callback); + sdp.push('a=rtcp-fb:' + payload.id + ' ' + fb.type + (fb.subtype ? ' ' + fb.subtype : '')); } }); - } else if (isCef || (chromever >= 26 && chromever <= maxver)) { - // chrome 26 - chrome 33 way to do it -- requires bad chrome://flags - // note: this is basically in maintenance mode and will go away soon - constraints = (hasConstraints && constraints) || { - video: { - mandatory: { - googLeakyBucket: true, - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3, - chromeMediaSource: 'screen' - } - } - }; - getUserMedia(constraints, callback); - } else { - // chrome 34+ way requiring an extension - var pending = window.setTimeout(function () { - error = new Error('NavigatorUserMediaError'); - error.name = 'EXTENSION_UNAVAILABLE'; - return callback(error); - }, 1000); - cache[pending] = [callback, hasConstraints ? constraints : null]; - window.postMessage({ type: 'getScreen', id: pending }, '*'); } - } else if (window.navigator.userAgent.match('Firefox')) { - var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10); - if (ffver >= 33) { - constraints = (hasConstraints && constraints) || { - video: { - mozMediaSource: 'window', - mediaSource: 'window' - } + }); + + if (desc.feedback) { + desc.feedback.forEach(function (fb) { + if (fb.type === 'trr-int') { + sdp.push('a=rtcp-fb:* trr-int ' + (fb.value ? fb.value : '0')); + } else { + sdp.push('a=rtcp-fb:* ' + fb.type + (fb.subtype ? ' ' + fb.subtype : '')); } - getUserMedia(constraints, function (err, stream) { - callback(err, stream); - // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 - if (!err) { - var lastTime = stream.currentTime; - var polly = window.setInterval(function () { - if (!stream) window.clearInterval(polly); - if (stream.currentTime == lastTime) { - window.clearInterval(polly); - if (stream.onended) { - stream.onended(); - } - } - lastTime = stream.currentTime; - }, 500); - } - }); - } else { - error = new Error('NavigatorUserMediaError'); - error.name = 'EXTENSION_UNAVAILABLE'; // does not make much sense but... + }); + } + + var hdrExts = desc.headerExtensions || []; + hdrExts.forEach(function (hdr) { + sdp.push('a=extmap:' + hdr.id + (hdr.senders ? '/' + SENDERS[role][direction][hdr.senders] : '') + ' ' + hdr.uri); + }); + + var ssrcGroups = desc.sourceGroups || []; + ssrcGroups.forEach(function (ssrcGroup) { + sdp.push('a=ssrc-group:' + ssrcGroup.semantics + ' ' + ssrcGroup.sources.join(' ')); + }); + + var ssrcs = desc.sources || []; + ssrcs.forEach(function (ssrc) { + for (var i = 0; i < ssrc.parameters.length; i++) { + var param = ssrc.parameters[i]; + sdp.push('a=ssrc:' + (ssrc.ssrc || desc.ssrc) + ' ' + param.key + (param.value ? (':' + param.value) : '')); } - } + }); + + var candidates = transport.candidates || []; + candidates.forEach(function (candidate) { + sdp.push(exports.toCandidateSDP(candidate)); + }); + + return sdp.join('\r\n'); }; -window.addEventListener('message', function (event) { - if (event.origin != window.location.origin) { - return; - } - if (event.data.type == 'gotScreen' && cache[event.data.id]) { - var data = cache[event.data.id]; - var constraints = data[1]; - var callback = data[0]; - delete cache[event.data.id]; +exports.toCandidateSDP = function (candidate) { + var sdp = []; - if (event.data.sourceId === '') { // user canceled - var error = new Error('NavigatorUserMediaError'); - error.name = 'PERMISSION_DENIED'; - callback(error); - } else { - constraints = constraints || {audio: false, video: { - mandatory: { - chromeMediaSource: 'desktop', - maxWidth: window.screen.width, - maxHeight: window.screen.height, - maxFrameRate: 3 - }, - optional: [ - {googLeakyBucket: true}, - {googTemporalLayeredScreencast: true} - ] - }}; - constraints.video.mandatory.chromeMediaSourceId = event.data.sourceId; - getUserMedia(constraints, callback); + 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 === 'srflx' || type === 'prflx' || type === 'relay') { + if (candidate.relAddr && candidate.relPort) { + sdp.push('raddr'); + sdp.push(candidate.relAddr); + sdp.push('rport'); + sdp.push(candidate.relPort); } - } else if (event.data.type == 'getScreenPending') { - window.clearTimeout(event.data.id); } -}); + if (candidate.tcpType && candidate.protocol.toUpperCase() == 'TCP') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + + sdp.push('generation'); + sdp.push(candidate.generation || '0'); -},{"getusermedia":19}],36:[function(require,module,exports){ + // FIXME: apparently this is wrong per spec + // but then, we need this when actually putting this into + // SDP so it's going to stay. + // decision needs to be revisited when browsers dont + // accept this any longer + return 'a=candidate:' + sdp.join(' '); +}; +},{"./senders":46}],38:[function(require,module,exports){ /** - * Expose `Emitter`. + * lodash 3.7.2 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license */ -module.exports = Emitter; - /** - * Initialize a new `Emitter`. + * The base implementation of `get` without support for string paths + * and default values. * - * @api public + * @private + * @param {Object} object The object to query. + * @param {Array} path The path of the property to get. + * @param {string} [pathKey] The key representation of path. + * @returns {*} Returns the resolved value. */ +function baseGet(object, path, pathKey) { + if (object == null) { + return; + } + if (pathKey !== undefined && pathKey in toObject(object)) { + path = [pathKey]; + } + var index = 0, + length = path.length; -function Emitter(obj) { - if (obj) return mixin(obj); -}; + while (object != null && index < length) { + object = object[path[index++]]; + } + return (index && index == length) ? object : undefined; +} /** - * Mixin the emitter properties. + * Converts `value` to an object if it's not one. * - * @param {Object} obj - * @return {Object} - * @api private + * @private + * @param {*} value The value to process. + * @returns {Object} Returns the object. */ - -function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - return obj; +function toObject(value) { + return isObject(value) ? value : Object(value); } /** - * Listen on the given `event` with `fn`. + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} -Emitter.prototype.on = -Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks['$' + event] = this._callbacks['$' + event] || []) - .push(fn); - return this; -}; +module.exports = baseGet; +},{}],40:[function(require,module,exports){ /** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public + * lodash 3.0.4 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license */ -Emitter.prototype.once = function(event, fn){ - function on() { - this.off(event, on); - fn.apply(this, arguments); - } +/** `Object#toString` result references. */ +var arrayTag = '[object Array]', + funcTag = '[object Function]'; - on.fn = fn; - this.on(event, on); - return this; -}; +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; /** - * Remove the given callback for `event` or all - * registered callbacks. + * Checks if `value` is object-like. * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} -Emitter.prototype.off = -Emitter.prototype.removeListener = -Emitter.prototype.removeAllListeners = -Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; +/** Used for native method references. */ +var objectProto = Object.prototype; - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; - // specific event - var callbacks = this._callbacks['$' + event]; - if (!callbacks) return this; +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks['$' + event]; - return this; - } +/** + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } - return this; -}; +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeIsArray = getNative(Array, 'isArray'); /** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. */ - -Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - var args = [].slice.call(arguments, 1) - , callbacks = this._callbacks['$' + event]; - - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } - - return this; -}; +var MAX_SAFE_INTEGER = 9007199254740991; /** - * Return array of callbacks for `event`. + * Gets the native function at `key` of `object`. * - * @param {String} event - * @return {Array} - * @api public + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. */ - -Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks['$' + event] || []; -}; +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; +} /** - * Check if this emitter has `event` handlers. + * Checks if `value` is a valid array-like length. * - * @param {String} event - * @return {Boolean} - * @api public + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} -Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; +/** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(function() { return arguments; }()); + * // => false + */ +var isArray = nativeIsArray || function(value) { + return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; }; -},{}],45:[function(require,module,exports){ -var global=self; -module.exports = isBuf; - /** - * Returns true if obj is a buffer or an arraybuffer. + * Checks if `value` is classified as a `Function` object. * - * @api private + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false */ - -function isBuf(obj) { - return (global.Buffer && global.Buffer.isBuffer(obj)) || - (global.ArrayBuffer && obj instanceof ArrayBuffer); +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 equivalents which return 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; } -},{}],35:[function(require,module,exports){ /** - * Parses an URI + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * - * @author Steven Levithan (MIT license) - * @api private + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} -var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - -var parts = [ - 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' -]; - -module.exports = function parseuri(str) { - var src = str, - b = str.indexOf('['), - e = str.indexOf(']'); +/** + * Checks if `value` is a native function. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ +function isNative(value) { + if (value == null) { + return false; + } + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); + } + return isObjectLike(value) && reIsHostCtor.test(value); +} - if (b != -1 && e != -1) { - str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); - } +module.exports = isArray; - var m = re.exec(str || ''), - uri = {}, - i = 14; +},{}],42:[function(require,module,exports){ +/** + * lodash 3.0.0 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.7.0 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ - while (i--) { - uri[parts[i]] = m[i] || ''; - } +/** + * A specialized version of `_.forEach` for arrays without support for callback + * shorthands or `this` binding. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ +function arrayEach(array, iteratee) { + var index = -1, + length = array.length; - if (b != -1 && e != -1) { - uri.source = src; - uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':'); - uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':'); - uri.ipv6uri = true; + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; } + } + return array; +} - return uri; -}; +module.exports = arrayEach; -},{}],38:[function(require,module,exports){ +},{}],43:[function(require,module,exports){ /** - * Slice reference. + * lodash 3.0.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license */ -var slice = [].slice; - /** - * Bind `obj` to `fn`. + * A specialized version of `baseCallback` which only supports `this` binding + * and specifying the number of arguments to provide to `func`. * - * @param {Object} obj - * @param {Function|String} fn or string - * @return {Function} - * @api public + * @private + * @param {Function} func The function to bind. + * @param {*} thisArg The `this` binding of `func`. + * @param {number} [argCount] The number of arguments to provide to `func`. + * @returns {Function} Returns the callback. */ - -module.exports = function(obj, fn){ - if ('string' == typeof fn) fn = obj[fn]; - if ('function' != typeof fn) throw new Error('bind() requires a function'); - var args = slice.call(arguments, 2); - return function(){ - return fn.apply(obj, args.concat(slice.call(arguments))); +function bindCallback(func, thisArg, argCount) { + if (typeof func != 'function') { + return identity; } -}; - -},{}],39:[function(require,module,exports){ - -var indexOf = [].indexOf; - -module.exports = function(arr, obj){ - if (indexOf) return arr.indexOf(obj); - for (var i = 0; i < arr.length; ++i) { - if (arr[i] === obj) return i; + if (thisArg === undefined) { + return func; + } + switch (argCount) { + case 1: return function(value) { + return func.call(thisArg, value); + }; + case 3: return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + case 5: return function(value, other, key, object, source) { + return func.call(thisArg, value, other, key, object, source); + }; } - return -1; -}; -},{}],40:[function(require,module,exports){ - -/** - * Expose `Backoff`. - */ - -module.exports = Backoff; + return function() { + return func.apply(thisArg, arguments); + }; +} /** - * Initialize backoff timer with `opts`. + * This method returns the first argument provided to it. * - * - `min` initial timeout in milliseconds [100] - * - `max` max timeout [10000] - * - `jitter` [0] - * - `factor` [2] + * @static + * @memberOf _ + * @category Utility + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example * - * @param {Object} opts - * @api public + * var object = { 'user': 'fred' }; + * + * _.identity(object) === object; + * // => true */ - -function Backoff(opts) { - opts = opts || {}; - this.ms = opts.min || 100; - this.max = opts.max || 10000; - this.factor = opts.factor || 2; - this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; - this.attempts = 0; +function identity(value) { + return value; } +module.exports = bindCallback; + +},{}],44:[function(require,module,exports){ /** - * Return the backoff duration. - * - * @return {Number} - * @api public + * lodash 3.0.4 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license */ -Backoff.prototype.duration = function(){ - var ms = this.ms * Math.pow(this.factor, this.attempts++); - if (this.jitter) { - var rand = Math.random(); - var deviation = Math.floor(rand * this.jitter * ms); - ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation; - } - return Math.min(ms, this.max) | 0; -}; +/** `Object#toString` result references. */ +var arrayTag = '[object Array]', + funcTag = '[object Function]'; + +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; /** - * Reset the number of attempts. + * Checks if `value` is object-like. * - * @api public + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} -Backoff.prototype.reset = function(){ - this.attempts = 0; -}; +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; /** - * Set the minimum duration - * - * @api public + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. */ +var objToString = objectProto.toString; -Backoff.prototype.setMin = function(min){ - this.ms = min; -}; +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeIsArray = getNative(Array, 'isArray'); /** - * Set the maximum duration - * - * @api public + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. */ - -Backoff.prototype.setMax = function(max){ - this.max = max; -}; +var MAX_SAFE_INTEGER = 9007199254740991; /** - * Set the jitter + * Gets the native function at `key` of `object`. * - * @api public + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. */ - -Backoff.prototype.setJitter = function(jitter){ - this.jitter = jitter; -}; - - -},{}],41:[function(require,module,exports){ -module.exports = toArray - -function toArray(list, index) { - var array = [] - - index = index || 0 - - for (var i = index || 0; i < list.length; i++) { - array[i - index] = list[i] - } - - return array +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; } -},{}],30:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +/** + * Checks if `value` is a valid array-like length. * - * 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. + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ -'use strict'; - -// Shimming starts here. -(function() { - // Utils. - var logging = require('./utils').log; - var browserDetails = require('./utils').browserDetails; - // Export to the adapter global object visible in the browser. - module.exports.browserDetails = browserDetails; - module.exports.extractVersion = require('./utils').extractVersion; - module.exports.disableLog = require('./utils').disableLog; - - // Uncomment if you do not want any logging at all including the switch - // statement below. Can also be turned off in the browser via - // adapter.disableLog(true) but then logging from the switch statement below - // will still appear. - //require('./utils').disableLog(true); - - // Warn if version is not supported regardless of browser. - // Min version can be set per browser in utils.js - if (browserDetails.version < browserDetails.minVersion) { - logging('Browser: ' + browserDetails.browser + ' Version: ' + - browserDetails.version + ' <' + ' minimum supported version: ' + - browserDetails.minVersion + '\n some things might not work!'); - } - - // Browser shims. - var chromeShim = require('./chrome/chrome_shim') || null; - var edgeShim = require('./edge/edge_shim') || null; - var firefoxShim = require('./firefox/firefox_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.shimSourceObject(); - chromeShim.shimPeerConnection(); - chromeShim.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.shimPeerConnection(); - 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(); - firefoxShim.shimSourceObject(); - firefoxShim.shimPeerConnection(); - firefoxShim.shimOnTrack(); - break; - default: - logging('Unsupported browser!'); - } -})(); +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} -},{"./chrome/chrome_shim":48,"./edge/edge_shim":47,"./firefox/firefox_shim":49,"./utils":46}],46:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +/** + * Checks if `value` is classified as an `Array` object. * - * 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. + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(function() { return arguments; }()); + * // => false */ -'use strict'; - -var logDisabled_ = false; - -// 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; - } - 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, version and minVersion - * properties. - */ - detectBrowser: function() { - // Returned result object. - var result = {}; - result.browser = null; - result.version = null; - result.minVersion = null; - - // Non supported browser. - if (typeof window === 'undefined' || !window.navigator) { - result.browser = 'Not a supported browser.'; - return result; - } - - // Firefox. - if (navigator.mozGetUserMedia) { - result.browser = 'firefox'; - result.version = this.extractVersion(navigator.userAgent, - /Firefox\/([0-9]+)\./, 1); - result.minVersion = 31; - return result; - } - - // Chrome/Chromium/Webview. - if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { - result.browser = 'chrome'; - result.version = this.extractVersion(navigator.userAgent, - /Chrom(e|ium)\/([0-9]+)\./, 2); - result.minVersion = 38; - return result; - } - - // Edge. - if (navigator.mediaDevices && - navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { - result.browser = 'edge'; - result.version = this.extractVersion(navigator.userAgent, - /Edge\/(\d+).(\d+)$/, 2); - result.minVersion = 10547; - return result; - } - - // Non supported browser default. - result.browser = 'Not a supported browser.'; - return result; - } -}; - -// Export. -module.exports = { - log: utils.log, - disableLog: utils.disableLog, - browserDetails: utils.detectBrowser(), - extractVersion: utils.extractVersion +var isArray = nativeIsArray || function(value) { + return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; }; -},{}],50:[function(require,module,exports){ - /** - * Expose `Emitter`. + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false */ - -module.exports = Emitter; +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 equivalents which return 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; +} /** - * Initialize a new `Emitter`. + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true * - * @api public + * _.isObject(1); + * // => false */ - -function Emitter(obj) { - if (obj) return mixin(obj); -}; +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} /** - * Mixin the emitter properties. + * Checks if `value` is a native function. * - * @param {Object} obj - * @return {Object} - * @api private + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false */ - -function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; +function isNative(value) { + if (value == null) { + return false; } - return obj; + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); + } + return isObjectLike(value) && reIsHostCtor.test(value); } +module.exports = isArray; + +},{}],32:[function(require,module,exports){ +var global=self; /** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public + * Module dependencies. */ -Emitter.prototype.on = -Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks[event] = this._callbacks[event] || []) - .push(fn); - return this; -}; +var parseuri = require('parseuri'); +var debug = require('debug')('socket.io-client:url'); /** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public + * Module exports. */ -Emitter.prototype.once = function(event, fn){ - var self = this; - this._callbacks = this._callbacks || {}; - - function on() { - self.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; -}; +module.exports = url; /** - * Remove the given callback for `event` or all - * registered callbacks. + * URL parser. * - * @param {String} event - * @param {Function} fn - * @return {Emitter} + * @param {String} url + * @param {Object} An object meant to mimic window.location. + * Defaults to window.location. * @api public */ -Emitter.prototype.off = -Emitter.prototype.removeListener = -Emitter.prototype.removeAllListeners = -Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } - - // specific event - var callbacks = this._callbacks[event]; - if (!callbacks) return this; +function url(uri, loc){ + var obj = uri; - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks[event]; - return this; - } + // default to window.location + var loc = loc || global.location; + if (null == uri) uri = loc.protocol + '//' + loc.host; - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; + // relative path support + if ('string' == typeof uri) { + if ('/' == uri.charAt(0)) { + if ('/' == uri.charAt(1)) { + uri = loc.protocol + uri; + } else { + uri = loc.hostname + uri; + } } - } - return this; -}; -/** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} - */ + if (!/^(https?|wss?):\/\//.test(uri)) { + debug('protocol-less url %s', uri); + if ('undefined' != typeof loc) { + uri = loc.protocol + '//' + uri; + } else { + uri = 'https://' + uri; + } + } -Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - var args = [].slice.call(arguments, 1) - , callbacks = this._callbacks[event]; + // parse + debug('parse %s', uri); + obj = parseuri(uri); + } - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); + // make sure we treat `localhost:80` and `localhost` equally + if (!obj.port) { + if (/^(http|ws)$/.test(obj.protocol)) { + obj.port = '80'; + } + else if (/^(http|ws)s$/.test(obj.protocol)) { + obj.port = '443'; } } - return this; -}; - -/** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ - -Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks[event] || []; -}; + obj.path = obj.path || '/'; -/** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ + // define unique id + obj.id = obj.protocol + '://' + obj.host + ':' + obj.port; + // define href + obj.href = obj.protocol + '://' + obj.host + (loc && loc.port == obj.port ? '' : (':' + obj.port)); -Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; -}; + return obj; +} -},{}],27:[function(require,module,exports){ +},{"debug":35,"parseuri":48}],33:[function(require,module,exports){ /** * Module dependencies. */ -var debug = require('debug')('socket.io-parser'); -var json = require('json3'); -var isArray = require('isarray'); +var url = require('./url'); +var eio = require('engine.io-client'); +var Socket = require('./socket'); var Emitter = require('component-emitter'); -var binary = require('./binary'); -var isBuf = require('./is-buffer'); +var parser = require('socket.io-parser'); +var on = require('./on'); +var bind = require('component-bind'); +var object = require('object-component'); +var debug = require('debug')('socket.io-client:manager'); +var indexOf = require('indexof'); +var Backoff = require('backo2'); /** - * Protocol version. - * - * @api public + * Module exports */ -exports.protocol = 4; +module.exports = Manager; /** - * Packet types. + * `Manager` constructor. * + * @param {String} engine instance or engine uri/opts + * @param {Object} options * @api public */ -exports.types = [ - 'CONNECT', - 'DISCONNECT', - 'EVENT', - 'ACK', - 'ERROR', - 'BINARY_EVENT', - 'BINARY_ACK' -]; +function Manager(uri, opts){ + if (!(this instanceof Manager)) return new Manager(uri, opts); + if (uri && ('object' == typeof uri)) { + opts = uri; + uri = undefined; + } + opts = opts || {}; + + opts.path = opts.path || '/socket.io'; + this.nsps = {}; + this.subs = []; + this.opts = opts; + this.reconnection(opts.reconnection !== false); + this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); + this.reconnectionDelay(opts.reconnectionDelay || 1000); + this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); + this.randomizationFactor(opts.randomizationFactor || 0.5); + this.backoff = new Backoff({ + min: this.reconnectionDelay(), + max: this.reconnectionDelayMax(), + jitter: this.randomizationFactor() + }); + this.timeout(null == opts.timeout ? 20000 : opts.timeout); + this.readyState = 'closed'; + this.uri = uri; + this.connected = []; + this.encoding = false; + this.packetBuffer = []; + this.encoder = new parser.Encoder(); + this.decoder = new parser.Decoder(); + this.autoConnect = opts.autoConnect !== false; + if (this.autoConnect) this.open(); +} /** - * Packet type `connect`. + * Propagate given event to sockets and emit on `this` * - * @api public + * @api private */ -exports.CONNECT = 0; +Manager.prototype.emitAll = function() { + this.emit.apply(this, arguments); + for (var nsp in this.nsps) { + this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); + } +}; /** - * Packet type `disconnect`. + * Update `socket.id` of all sockets * - * @api public + * @api private */ -exports.DISCONNECT = 1; +Manager.prototype.updateSocketIds = function(){ + for (var nsp in this.nsps) { + this.nsps[nsp].id = this.engine.id; + } +}; /** - * Packet type `event`. - * - * @api public + * Mix in `Emitter`. */ -exports.EVENT = 2; +Emitter(Manager.prototype); /** - * Packet type `ack`. + * Sets the `reconnection` config. * + * @param {Boolean} true/false if it should automatically reconnect + * @return {Manager} self or value * @api public */ -exports.ACK = 3; +Manager.prototype.reconnection = function(v){ + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + return this; +}; /** - * Packet type `error`. + * Sets the reconnection attempts config. * + * @param {Number} max reconnection attempts before giving up + * @return {Manager} self or value * @api public */ -exports.ERROR = 4; +Manager.prototype.reconnectionAttempts = function(v){ + if (!arguments.length) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; +}; /** - * Packet type 'binary event' + * Sets the delay between reconnections. * + * @param {Number} delay + * @return {Manager} self or value * @api public */ -exports.BINARY_EVENT = 5; +Manager.prototype.reconnectionDelay = function(v){ + if (!arguments.length) return this._reconnectionDelay; + this._reconnectionDelay = v; + this.backoff && this.backoff.setMin(v); + return this; +}; + +Manager.prototype.randomizationFactor = function(v){ + if (!arguments.length) return this._randomizationFactor; + this._randomizationFactor = v; + this.backoff && this.backoff.setJitter(v); + return this; +}; /** - * Packet type `binary ack`. For acks with binary arguments. + * Sets the maximum delay between reconnections. * + * @param {Number} delay + * @return {Manager} self or value * @api public */ -exports.BINARY_ACK = 6; +Manager.prototype.reconnectionDelayMax = function(v){ + if (!arguments.length) return this._reconnectionDelayMax; + this._reconnectionDelayMax = v; + this.backoff && this.backoff.setMax(v); + return this; +}; /** - * Encoder constructor. + * Sets the connection timeout. `false` to disable * + * @return {Manager} self or value * @api public */ -exports.Encoder = Encoder; +Manager.prototype.timeout = function(v){ + if (!arguments.length) return this._timeout; + this._timeout = v; + return this; +}; /** - * Decoder constructor. + * Starts trying to reconnect if reconnection is enabled and we have not + * started reconnecting yet * - * @api public + * @api private */ -exports.Decoder = Decoder; +Manager.prototype.maybeReconnectOnOpen = function() { + // Only try to reconnect if it's the first time we're connecting + if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) { + // keeps reconnection from firing twice for the same reconnection loop + this.reconnect(); + } +}; + /** - * A socket.io Encoder instance + * Sets the current transport `socket`. * + * @param {Function} optional, callback + * @return {Manager} self * @api public */ -function Encoder() {} +Manager.prototype.open = +Manager.prototype.connect = function(fn){ + debug('readyState %s', this.readyState); + if (~this.readyState.indexOf('open')) return this; -/** - * Encode a packet as a single string if non-binary, or as a - * buffer sequence, depending on packet type. - * - * @param {Object} obj - packet object - * @param {Function} callback - function to handle encodings (likely engine.write) - * @return Calls callback with Array of encodings - * @api public - */ + debug('opening %s', this.uri); + this.engine = eio(this.uri, this.opts); + var socket = this.engine; + var self = this; + this.readyState = 'opening'; + this.skipReconnect = false; -Encoder.prototype.encode = function(obj, callback){ - debug('encoding packet %j', obj); + // emit `open` + var openSub = on(socket, 'open', function() { + self.onopen(); + fn && fn(); + }); - if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { - encodeAsBinary(obj, callback); - } - else { - var encoding = encodeAsString(obj); - callback([encoding]); + // emit `connect_error` + var errorSub = on(socket, 'error', function(data){ + debug('connect_error'); + self.cleanup(); + self.readyState = 'closed'; + self.emitAll('connect_error', data); + if (fn) { + var err = new Error('Connection error'); + err.data = data; + fn(err); + } else { + // Only do this if there is no fn to handle the error + self.maybeReconnectOnOpen(); + } + }); + + // emit `connect_timeout` + if (false !== this._timeout) { + var timeout = this._timeout; + debug('connect attempt will timeout after %d', timeout); + + // set timer + var timer = setTimeout(function(){ + debug('connect attempt timed out after %d', timeout); + openSub.destroy(); + socket.close(); + socket.emit('error', 'timeout'); + self.emitAll('connect_timeout', timeout); + }, timeout); + + this.subs.push({ + destroy: function(){ + clearTimeout(timer); + } + }); } + + this.subs.push(openSub); + this.subs.push(errorSub); + + return this; }; /** - * Encode packet as string. + * Called upon transport open. * - * @param {Object} packet - * @return {String} encoded * @api private */ -function encodeAsString(obj) { - var str = ''; - var nsp = false; - - // first is type - str += obj.type; +Manager.prototype.onopen = function(){ + debug('open'); - // attachments if we have them - if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { - str += obj.attachments; - str += '-'; - } + // clear old subs + this.cleanup(); - // if we have a namespace other than `/` - // we append it followed by a comma `,` - if (obj.nsp && '/' != obj.nsp) { - nsp = true; - str += obj.nsp; - } + // mark as open + this.readyState = 'open'; + this.emit('open'); - // immediately followed by the id - if (null != obj.id) { - if (nsp) { - str += ','; - nsp = false; - } - str += obj.id; - } + // add new subs + var socket = this.engine; + this.subs.push(on(socket, 'data', bind(this, 'ondata'))); + this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded'))); + this.subs.push(on(socket, 'error', bind(this, 'onerror'))); + this.subs.push(on(socket, 'close', bind(this, 'onclose'))); +}; - // json data - if (null != obj.data) { - if (nsp) str += ','; - str += json.stringify(obj.data); - } +/** + * Called with data. + * + * @api private + */ - debug('encoded %j as %s', obj, str); - return str; -} +Manager.prototype.ondata = function(data){ + this.decoder.add(data); +}; /** - * Encode packet as 'buffer sequence' by removing blobs, and - * deconstructing packet into object with placeholders and - * a list of buffers. + * Called when parser fully decodes a packet. + * + * @api private + */ + +Manager.prototype.ondecoded = function(packet) { + this.emit('packet', packet); +}; + +/** + * Called upon socket error. * - * @param {Object} packet - * @return {Buffer} encoded * @api private */ -function encodeAsBinary(obj, callback) { - - function writeEncoding(bloblessData) { - var deconstruction = binary.deconstructPacket(bloblessData); - var pack = encodeAsString(deconstruction.packet); - var buffers = deconstruction.buffers; - - buffers.unshift(pack); // add packet info to beginning of data list - callback(buffers); // write all the buffers - } - - binary.removeBlobs(obj, writeEncoding); -} +Manager.prototype.onerror = function(err){ + debug('error', err); + this.emitAll('error', err); +}; /** - * A socket.io Decoder instance + * Creates a new socket for the given `nsp`. * - * @return {Object} decoder + * @return {Socket} * @api public */ -function Decoder() { - this.reconstructor = null; -} +Manager.prototype.socket = function(nsp){ + var socket = this.nsps[nsp]; + if (!socket) { + socket = new Socket(this, nsp); + this.nsps[nsp] = socket; + var self = this; + socket.on('connect', function(){ + socket.id = self.engine.id; + if (!~indexOf(self.connected, socket)) { + self.connected.push(socket); + } + }); + } + return socket; +}; /** - * Mix in `Emitter` with Decoder. + * Called upon a socket close. + * + * @param {Socket} socket */ -Emitter(Decoder.prototype); +Manager.prototype.destroy = function(socket){ + var index = indexOf(this.connected, socket); + if (~index) this.connected.splice(index, 1); + if (this.connected.length) return; + + this.close(); +}; /** - * Decodes an ecoded packet string into packet JSON. + * Writes a packet. * - * @param {String} obj - encoded packet - * @return {Object} packet - * @api public + * @param {Object} packet + * @api private */ -Decoder.prototype.add = function(obj) { - var packet; - if ('string' == typeof obj) { - packet = decodeString(obj); - if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json - this.reconstructor = new BinaryReconstructor(packet); +Manager.prototype.packet = function(packet){ + debug('writing packet %j', packet); + var self = this; - // no attachments, labeled binary but no binary data to follow - if (this.reconstructor.reconPack.attachments === 0) { - this.emit('decoded', packet); - } - } else { // non-binary full packet - this.emit('decoded', packet); - } - } - else if (isBuf(obj) || obj.base64) { // raw binary data - if (!this.reconstructor) { - throw new Error('got binary data when not reconstructing a packet'); - } else { - packet = this.reconstructor.takeBinaryData(obj); - if (packet) { // received final buffer - this.reconstructor = null; - this.emit('decoded', packet); + if (!self.encoding) { + // encode, then write to engine with result + self.encoding = true; + this.encoder.encode(packet, function(encodedPackets) { + for (var i = 0; i < encodedPackets.length; i++) { + self.engine.write(encodedPackets[i]); } - } - } - else { - throw new Error('Unknown type: ' + obj); + self.encoding = false; + self.processPacketQueue(); + }); + } else { // add packet to the queue + self.packetBuffer.push(packet); } }; /** - * Decode a packet String (JSON data) + * If packet buffer is non-empty, begins encoding the + * next packet in line. * - * @param {String} str - * @return {Object} packet * @api private */ -function decodeString(str) { - var p = {}; - var i = 0; - - // look up type - p.type = Number(str.charAt(0)); - if (null == exports.types[p.type]) return error(); - - // look up attachments if type binary - if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) { - var buf = ''; - while (str.charAt(++i) != '-') { - buf += str.charAt(i); - if (i == str.length) break; - } - if (buf != Number(buf) || str.charAt(i) != '-') { - throw new Error('Illegal attachments'); - } - p.attachments = Number(buf); +Manager.prototype.processPacketQueue = function() { + if (this.packetBuffer.length > 0 && !this.encoding) { + var pack = this.packetBuffer.shift(); + this.packet(pack); } +}; - // look up namespace (if any) - if ('/' == str.charAt(i + 1)) { - p.nsp = ''; - while (++i) { - var c = str.charAt(i); - if (',' == c) break; - p.nsp += c; - if (i == str.length) break; - } - } else { - p.nsp = '/'; - } +/** + * Clean up transport subscriptions and packet buffer. + * + * @api private + */ - // look up id - var next = str.charAt(i + 1); - if ('' !== next && Number(next) == next) { - p.id = ''; - while (++i) { - var c = str.charAt(i); - if (null == c || Number(c) != c) { - --i; - break; - } - p.id += str.charAt(i); - if (i == str.length) break; - } - p.id = Number(p.id); - } +Manager.prototype.cleanup = function(){ + var sub; + while (sub = this.subs.shift()) sub.destroy(); - // look up json data - if (str.charAt(++i)) { - try { - p.data = json.parse(str.substr(i)); - } catch(e){ - return error(); - } - } + this.packetBuffer = []; + this.encoding = false; - debug('decoded %s as %j', str, p); - return p; -} + this.decoder.destroy(); +}; /** - * Deallocates a parser's resources + * Close the current socket. * - * @api public + * @api private */ -Decoder.prototype.destroy = function() { - if (this.reconstructor) { - this.reconstructor.finishedReconstruction(); - } +Manager.prototype.close = +Manager.prototype.disconnect = function(){ + this.skipReconnect = true; + this.backoff.reset(); + this.readyState = 'closed'; + this.engine && this.engine.close(); }; /** - * A manager of a binary event's 'buffer sequence'. Should - * be constructed whenever a packet of type BINARY_EVENT is - * decoded. + * Called upon engine close. * - * @param {Object} packet - * @return {BinaryReconstructor} initialized reconstructor * @api private */ -function BinaryReconstructor(packet) { - this.reconPack = packet; - this.buffers = []; -} +Manager.prototype.onclose = function(reason){ + debug('close'); + this.cleanup(); + this.backoff.reset(); + this.readyState = 'closed'; + this.emit('close', reason); + if (this._reconnection && !this.skipReconnect) { + this.reconnect(); + } +}; /** - * Method to be called when binary data received from connection - * after a BINARY_EVENT packet. + * Attempt a reconnection. * - * @param {Buffer | ArrayBuffer} binData - the raw binary data received - * @return {null | Object} returns null if more binary data is expected or - * a reconstructed packet object if all buffers have been received. * @api private */ -BinaryReconstructor.prototype.takeBinaryData = function(binData) { - this.buffers.push(binData); - if (this.buffers.length == this.reconPack.attachments) { // done with buffer list - var packet = binary.reconstructPacket(this.reconPack, this.buffers); - this.finishedReconstruction(); - return packet; +Manager.prototype.reconnect = function(){ + if (this.reconnecting || this.skipReconnect) return this; + + var self = this; + + if (this.backoff.attempts >= this._reconnectionAttempts) { + debug('reconnect failed'); + this.backoff.reset(); + this.emitAll('reconnect_failed'); + this.reconnecting = false; + } else { + var delay = this.backoff.duration(); + debug('will wait %dms before reconnect attempt', delay); + + this.reconnecting = true; + var timer = setTimeout(function(){ + if (self.skipReconnect) return; + + debug('attempting reconnect'); + self.emitAll('reconnect_attempt', self.backoff.attempts); + self.emitAll('reconnecting', self.backoff.attempts); + + // check again for the case socket closed in above events + if (self.skipReconnect) return; + + self.open(function(err){ + if (err) { + debug('reconnect attempt error'); + self.reconnecting = false; + self.reconnect(); + self.emitAll('reconnect_error', err.data); + } else { + debug('reconnect success'); + self.onreconnect(); + } + }); + }, delay); + + this.subs.push({ + destroy: function(){ + clearTimeout(timer); + } + }); } - return null; }; /** - * Cleans up binary packet reconstruction variables. + * Called upon successful reconnect. * * @api private */ -BinaryReconstructor.prototype.finishedReconstruction = function() { - this.reconPack = null; - this.buffers = []; +Manager.prototype.onreconnect = function(){ + var attempt = this.backoff.attempts; + this.reconnecting = false; + this.backoff.reset(); + this.updateSocketIds(); + this.emitAll('reconnect', attempt); }; -function error(data){ - return { - type: exports.ERROR, - data: 'parser error' - }; -} - -},{"./binary":51,"./is-buffer":45,"component-emitter":50,"debug":26,"isarray":52,"json3":53}],29:[function(require,module,exports){ -// based on https://github.com/ESTOS/strophe.jingle/ -// adds wildemitter support -var util = require('util'); -var adapter = require('webrtc-adapter'); // jshint ignore:line -var WildEmitter = require('wildemitter'); - -function dumpSDP(description) { - return { - type: description.type, - sdp: description.sdp - }; -} - -function dumpStream(stream) { - var info = { - label: stream.id, - }; - if (stream.getAudioTracks().length) { - info.audio = stream.getAudioTracks().map(function (track) { - return track.id; - }); - } - if (stream.getVideoTracks().length) { - info.video = stream.getVideoTracks().map(function (track) { - return track.id; - }); - } - return info; -} - -function TraceablePeerConnection(config, constraints) { - var self = this; - WildEmitter.call(this); +},{"./on":37,"./socket":34,"./url":32,"backo2":54,"component-bind":51,"component-emitter":49,"debug":35,"engine.io-client":50,"indexof":53,"object-component":52,"socket.io-parser":36}],34:[function(require,module,exports){ - this.peerconnection = new window.RTCPeerConnection(config, constraints); +/** + * Module dependencies. + */ - this.trace = function (what, info) { - self.emit('PeerConnectionTrace', { - time: new Date(), - type: what, - value: info || "" - }); - }; +var parser = require('socket.io-parser'); +var Emitter = require('component-emitter'); +var toArray = require('to-array'); +var on = require('./on'); +var bind = require('component-bind'); +var debug = require('debug')('socket.io-client:socket'); +var hasBin = require('has-binary'); - this.onicecandidate = null; - this.peerconnection.onicecandidate = function (event) { - self.trace('onicecandidate', event.candidate); - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - }; - this.onaddstream = null; - this.peerconnection.onaddstream = function (event) { - self.trace('onaddstream', dumpStream(event.stream)); - if (self.onaddstream !== null) { - self.onaddstream(event); - } - }; - this.onremovestream = null; - this.peerconnection.onremovestream = function (event) { - self.trace('onremovestream', dumpStream(event.stream)); - if (self.onremovestream !== null) { - self.onremovestream(event); - } - }; - this.onsignalingstatechange = null; - this.peerconnection.onsignalingstatechange = function (event) { - self.trace('onsignalingstatechange', self.signalingState); - if (self.onsignalingstatechange !== null) { - self.onsignalingstatechange(event); - } - }; - this.oniceconnectionstatechange = null; - this.peerconnection.oniceconnectionstatechange = function (event) { - self.trace('oniceconnectionstatechange', self.iceConnectionState); - if (self.oniceconnectionstatechange !== null) { - self.oniceconnectionstatechange(event); - } - }; - this.onnegotiationneeded = null; - this.peerconnection.onnegotiationneeded = function (event) { - self.trace('onnegotiationneeded'); - if (self.onnegotiationneeded !== null) { - self.onnegotiationneeded(event); - } - }; - self.ondatachannel = null; - this.peerconnection.ondatachannel = function (event) { - self.trace('ondatachannel', event); - if (self.ondatachannel !== null) { - self.ondatachannel(event); - } - }; - this.getLocalStreams = this.peerconnection.getLocalStreams.bind(this.peerconnection); - this.getRemoteStreams = this.peerconnection.getRemoteStreams.bind(this.peerconnection); -} +/** + * Module exports. + */ -util.inherits(TraceablePeerConnection, WildEmitter); +module.exports = exports = Socket; -['signalingState', 'iceConnectionState', 'localDescription', 'remoteDescription'].forEach(function (prop) { - Object.defineProperty(TraceablePeerConnection.prototype, prop, { - get: function () { - return this.peerconnection[prop]; - } - }); -}); +/** + * Internal events (blacklisted). + * These events can't be emitted by the user. + * + * @api private + */ -TraceablePeerConnection.prototype.addStream = function (stream) { - this.trace('addStream', dumpStream(stream)); - this.peerconnection.addStream(stream); +var events = { + connect: 1, + connect_error: 1, + connect_timeout: 1, + disconnect: 1, + error: 1, + reconnect: 1, + reconnect_attempt: 1, + reconnect_failed: 1, + reconnect_error: 1, + reconnecting: 1 }; -TraceablePeerConnection.prototype.removeStream = function (stream) { - this.trace('removeStream', dumpStream(stream)); - this.peerconnection.removeStream(stream); -}; +/** + * Shortcut to `Emitter#emit`. + */ -TraceablePeerConnection.prototype.createDataChannel = function (label, opts) { - this.trace('createDataChannel', label, opts); - return this.peerconnection.createDataChannel(label, opts); -}; +var emit = Emitter.prototype.emit; -TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) { - var self = this; - this.trace('setLocalDescription', dumpSDP(description)); - this.peerconnection.setLocalDescription(description, - function () { - self.trace('setLocalDescriptionOnSuccess'); - if (successCallback) successCallback(); - }, - function (err) { - self.trace('setLocalDescriptionOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); -}; +/** + * `Socket` constructor. + * + * @api public + */ -TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) { - var self = this; - this.trace('setRemoteDescription', dumpSDP(description)); - this.peerconnection.setRemoteDescription(description, - function () { - self.trace('setRemoteDescriptionOnSuccess'); - if (successCallback) successCallback(); - }, - function (err) { - self.trace('setRemoteDescriptionOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); -}; +function Socket(io, nsp){ + this.io = io; + this.nsp = nsp; + this.json = this; // compat + this.ids = 0; + this.acks = {}; + if (this.io.autoConnect) this.open(); + this.receiveBuffer = []; + this.sendBuffer = []; + this.connected = false; + this.disconnected = true; +} -TraceablePeerConnection.prototype.close = function () { - this.trace('stop'); - if (this.peerconnection.signalingState != 'closed') { - this.peerconnection.close(); - } -}; +/** + * Mix in `Emitter`. + */ -TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) { - var self = this; - this.trace('createOffer', constraints); - this.peerconnection.createOffer( - function (offer) { - self.trace('createOfferOnSuccess', dumpSDP(offer)); - if (successCallback) successCallback(offer); - }, - function (err) { - self.trace('createOfferOnFailure', err); - if (failureCallback) failureCallback(err); - }, - constraints - ); -}; +Emitter(Socket.prototype); -TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) { - var self = this; - this.trace('createAnswer', constraints); - this.peerconnection.createAnswer( - function (answer) { - self.trace('createAnswerOnSuccess', dumpSDP(answer)); - if (successCallback) successCallback(answer); - }, - function (err) { - self.trace('createAnswerOnFailure', err); - if (failureCallback) failureCallback(err); - }, - constraints - ); -}; +/** + * Subscribe to open, close and packet events + * + * @api private + */ -TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) { - var self = this; - this.trace('addIceCandidate', candidate); - this.peerconnection.addIceCandidate(candidate, - function () { - //self.trace('addIceCandidateOnSuccess'); - if (successCallback) successCallback(); - }, - function (err) { - self.trace('addIceCandidateOnFailure', err); - if (failureCallback) failureCallback(err); - } - ); +Socket.prototype.subEvents = function() { + if (this.subs) return; + + var io = this.io; + this.subs = [ + on(io, 'open', bind(this, 'onopen')), + on(io, 'packet', bind(this, 'onpacket')), + on(io, 'close', bind(this, 'onclose')) + ]; }; -TraceablePeerConnection.prototype.getStats = function () { - this.peerconnection.getStats.apply(this.peerconnection, arguments); +/** + * "Opens" the socket. + * + * @api public + */ + +Socket.prototype.open = +Socket.prototype.connect = function(){ + if (this.connected) return this; + + this.subEvents(); + this.io.open(); // ensure open + if ('open' == this.io.readyState) this.onopen(); + return this; }; -module.exports = TraceablePeerConnection; +/** + * Sends a `message` event. + * + * @return {Socket} self + * @api public + */ -},{"util":8,"webrtc-adapter":30,"wildemitter":4}],37:[function(require,module,exports){ +Socket.prototype.send = function(){ + var args = toArray(arguments); + args.unshift('message'); + this.emit.apply(this, args); + return this; +}; -module.exports = require('./lib/'); +/** + * Override `emit`. + * If the event is in `events`, it's emitted normally. + * + * @param {String} event name + * @return {Socket} self + * @api public + */ -},{"./lib/":54}],43:[function(require,module,exports){ -var SENDERS = require('./senders'); +Socket.prototype.emit = function(ev){ + if (events.hasOwnProperty(ev)) { + emit.apply(this, arguments); + return this; + } + var args = toArray(arguments); + var parserType = parser.EVENT; // default + if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary + var packet = { type: parserType, data: args }; -exports.toSessionSDP = function (session, opts) { - var role = opts.role || 'initiator'; - var direction = opts.direction || 'outgoing'; - var sid = opts.sid || session.sid || Date.now(); - var time = opts.time || Date.now(); + // event ack callback + if ('function' == typeof args[args.length - 1]) { + debug('emitting packet with ack id %d', this.ids); + this.acks[this.ids] = args.pop(); + packet.id = this.ids++; + } - var sdp = [ - 'v=0', - 'o=- ' + sid + ' ' + time + ' IN IP4 0.0.0.0', - 's=-', - 't=0 0' - ]; + if (this.connected) { + this.packet(packet); + } else { + this.sendBuffer.push(packet); + } - var contents = session.contents || []; - var hasSources = false; - contents.forEach(function (content) { - if (content.description.sources && - content.description.sources.length) { - hasSources = true; - } - }); + return this; +}; - if (hasSources) { - sdp.push('a=msid-semantic: WMS *'); - } +/** + * Sends a packet. + * + * @param {Object} packet + * @api private + */ - var groups = session.groups || []; - groups.forEach(function (group) { - sdp.push('a=group:' + group.semantics + ' ' + group.contents.join(' ')); - }); +Socket.prototype.packet = function(packet){ + packet.nsp = this.nsp; + this.io.packet(packet); +}; +/** + * Called upon engine `open`. + * + * @api private + */ - contents.forEach(function (content) { - sdp.push(exports.toMediaSDP(content, opts)); - }); +Socket.prototype.onopen = function(){ + debug('transport is open - connecting'); - return sdp.join('\r\n') + '\r\n'; + // write connect packet if necessary + if ('/' != this.nsp) { + this.packet({ type: parser.CONNECT }); + } }; -exports.toMediaSDP = function (content, opts) { - var sdp = []; +/** + * Called upon engine `close`. + * + * @param {String} reason + * @api private + */ - var role = opts.role || 'initiator'; - var direction = opts.direction || 'outgoing'; +Socket.prototype.onclose = function(reason){ + debug('close (%s)', reason); + this.connected = false; + this.disconnected = true; + delete this.id; + this.emit('disconnect', reason); +}; - var desc = content.description; - var transport = content.transport; - var payloads = desc.payloads || []; - var fingerprints = (transport && transport.fingerprints) || []; +/** + * Called with socket packet. + * + * @param {Object} packet + * @api private + */ - var mline = []; - if (desc.descType == 'datachannel') { - mline.push('application'); - mline.push('1'); - mline.push('DTLS/SCTP'); - if (transport.sctp) { - transport.sctp.forEach(function (map) { - mline.push(map.number); - }); - } - } else { - mline.push(desc.media); - mline.push('1'); - if (fingerprints.length > 0) { - mline.push('UDP/TLS/RTP/SAVPF'); - } else if (desc.encryption && desc.encryption.length > 0) { - mline.push('RTP/SAVPF'); - } else { - mline.push('RTP/AVPF'); - } - payloads.forEach(function (payload) { - mline.push(payload.id); - }); - } +Socket.prototype.onpacket = function(packet){ + if (packet.nsp != this.nsp) return; + switch (packet.type) { + case parser.CONNECT: + this.onconnect(); + break; - sdp.push('m=' + mline.join(' ')); + case parser.EVENT: + this.onevent(packet); + break; - sdp.push('c=IN IP4 0.0.0.0'); - if (desc.bandwidth && desc.bandwidth.type && desc.bandwidth.bandwidth) { - sdp.push('b=' + desc.bandwidth.type + ':' + desc.bandwidth.bandwidth); - } - if (desc.descType == 'rtp') { - sdp.push('a=rtcp:1 IN IP4 0.0.0.0'); - } + case parser.BINARY_EVENT: + this.onevent(packet); + break; - if (transport) { - if (transport.ufrag) { - sdp.push('a=ice-ufrag:' + transport.ufrag); - } - if (transport.pwd) { - sdp.push('a=ice-pwd:' + transport.pwd); - } + case parser.ACK: + this.onack(packet); + break; - var pushedSetup = false; - fingerprints.forEach(function (fingerprint) { - sdp.push('a=fingerprint:' + fingerprint.hash + ' ' + fingerprint.value); - if (fingerprint.setup && !pushedSetup) { - sdp.push('a=setup:' + fingerprint.setup); - } - }); + case parser.BINARY_ACK: + this.onack(packet); + break; - if (transport.sctp) { - transport.sctp.forEach(function (map) { - sdp.push('a=sctpmap:' + map.number + ' ' + map.protocol + ' ' + map.streams); - }); - } - } + case parser.DISCONNECT: + this.ondisconnect(); + break; - if (desc.descType == 'rtp') { - sdp.push('a=' + (SENDERS[role][direction][content.senders] || 'sendrecv')); - } - sdp.push('a=mid:' + content.name); + case parser.ERROR: + this.emit('error', packet.data); + break; + } +}; - if (desc.sources && desc.sources.length) { - (desc.sources[0].parameters || []).forEach(function (param) { - if (param.key === 'msid') { - sdp.push('a=msid:' + param.value); - } - }); - } +/** + * Called upon a server event. + * + * @param {Object} packet + * @api private + */ - if (desc.mux) { - sdp.push('a=rtcp-mux'); - } +Socket.prototype.onevent = function(packet){ + var args = packet.data || []; + debug('emitting event %j', args); + + if (null != packet.id) { + debug('attaching ack callback to event'); + args.push(this.ack(packet.id)); + } - var encryption = desc.encryption || []; - encryption.forEach(function (crypto) { - sdp.push('a=crypto:' + crypto.tag + ' ' + crypto.cipherSuite + ' ' + crypto.keyParams + (crypto.sessionParams ? ' ' + crypto.sessionParams : '')); - }); - if (desc.googConferenceFlag) { - sdp.push('a=x-google-flag:conference'); - } + if (this.connected) { + emit.apply(this, args); + } else { + this.receiveBuffer.push(args); + } +}; - payloads.forEach(function (payload) { - var rtpmap = 'a=rtpmap:' + payload.id + ' ' + payload.name + '/' + payload.clockrate; - if (payload.channels && payload.channels != '1') { - rtpmap += '/' + payload.channels; - } - sdp.push(rtpmap); +/** + * Produces an ack callback to emit with an event. + * + * @api private + */ - if (payload.parameters && payload.parameters.length) { - var fmtp = ['a=fmtp:' + payload.id]; - var parameters = []; - payload.parameters.forEach(function (param) { - parameters.push((param.key ? param.key + '=' : '') + param.value); - }); - fmtp.push(parameters.join(';')); - sdp.push(fmtp.join(' ')); - } +Socket.prototype.ack = function(id){ + var self = this; + var sent = false; + return function(){ + // prevent double callbacks + if (sent) return; + sent = true; + var args = toArray(arguments); + debug('sending ack %j', args); - if (payload.feedback) { - payload.feedback.forEach(function (fb) { - if (fb.type === 'trr-int') { - sdp.push('a=rtcp-fb:' + payload.id + ' trr-int ' + (fb.value ? fb.value : '0')); - } else { - sdp.push('a=rtcp-fb:' + payload.id + ' ' + fb.type + (fb.subtype ? ' ' + fb.subtype : '')); - } - }); - } + var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; + self.packet({ + type: type, + id: id, + data: args }); + }; +}; - if (desc.feedback) { - desc.feedback.forEach(function (fb) { - if (fb.type === 'trr-int') { - sdp.push('a=rtcp-fb:* trr-int ' + (fb.value ? fb.value : '0')); - } else { - sdp.push('a=rtcp-fb:* ' + fb.type + (fb.subtype ? ' ' + fb.subtype : '')); - } - }); - } +/** + * Called upon a server acknowlegement. + * + * @param {Object} packet + * @api private + */ - var hdrExts = desc.headerExtensions || []; - hdrExts.forEach(function (hdr) { - sdp.push('a=extmap:' + hdr.id + (hdr.senders ? '/' + SENDERS[role][direction][hdr.senders] : '') + ' ' + hdr.uri); - }); +Socket.prototype.onack = function(packet){ + debug('calling ack %s with %j', packet.id, packet.data); + var fn = this.acks[packet.id]; + fn.apply(this, packet.data); + delete this.acks[packet.id]; +}; - var ssrcGroups = desc.sourceGroups || []; - ssrcGroups.forEach(function (ssrcGroup) { - sdp.push('a=ssrc-group:' + ssrcGroup.semantics + ' ' + ssrcGroup.sources.join(' ')); - }); +/** + * Called upon server connect. + * + * @api private + */ - var ssrcs = desc.sources || []; - ssrcs.forEach(function (ssrc) { - for (var i = 0; i < ssrc.parameters.length; i++) { - var param = ssrc.parameters[i]; - sdp.push('a=ssrc:' + (ssrc.ssrc || desc.ssrc) + ' ' + param.key + (param.value ? (':' + param.value) : '')); - } - }); +Socket.prototype.onconnect = function(){ + this.connected = true; + this.disconnected = false; + this.emit('connect'); + this.emitBuffered(); +}; - var candidates = transport.candidates || []; - candidates.forEach(function (candidate) { - sdp.push(exports.toCandidateSDP(candidate)); - }); +/** + * Emit buffered events (received and emitted). + * + * @api private + */ - return sdp.join('\r\n'); +Socket.prototype.emitBuffered = function(){ + var i; + for (i = 0; i < this.receiveBuffer.length; i++) { + emit.apply(this, this.receiveBuffer[i]); + } + this.receiveBuffer = []; + + for (i = 0; i < this.sendBuffer.length; i++) { + this.packet(this.sendBuffer[i]); + } + this.sendBuffer = []; }; -exports.toCandidateSDP = function (candidate) { - var sdp = []; +/** + * Called upon server disconnect. + * + * @api private + */ - 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); +Socket.prototype.ondisconnect = function(){ + debug('server disconnect (%s)', this.nsp); + this.destroy(); + this.onclose('io server disconnect'); +}; - var type = candidate.type; - sdp.push('typ'); - sdp.push(type); - if (type === 'srflx' || type === 'prflx' || type === 'relay') { - if (candidate.relAddr && candidate.relPort) { - sdp.push('raddr'); - sdp.push(candidate.relAddr); - sdp.push('rport'); - sdp.push(candidate.relPort); - } - } - if (candidate.tcpType && candidate.protocol.toUpperCase() == 'TCP') { - sdp.push('tcptype'); - sdp.push(candidate.tcpType); - } +/** + * Called upon forced client/server side disconnections, + * this method ensures the manager stops tracking us and + * that reconnections don't get triggered for this. + * + * @api private. + */ - sdp.push('generation'); - sdp.push(candidate.generation || '0'); +Socket.prototype.destroy = function(){ + if (this.subs) { + // clean subscriptions to avoid reconnections + for (var i = 0; i < this.subs.length; i++) { + this.subs[i].destroy(); + } + this.subs = null; + } - // FIXME: apparently this is wrong per spec - // but then, we need this when actually putting this into - // SDP so it's going to stay. - // decision needs to be revisited when browsers dont - // accept this any longer - return 'a=candidate:' + sdp.join(' '); + this.io.destroy(this); }; -},{"./senders":55}],44:[function(require,module,exports){ -var SENDERS = require('./senders'); -var parsers = require('./parsers'); -var idCounter = Math.random(); +/** + * Disconnects the socket manually. + * + * @return {Socket} self + * @api public + */ + +Socket.prototype.close = +Socket.prototype.disconnect = function(){ + if (this.connected) { + debug('performing disconnect (%s)', this.nsp); + this.packet({ type: parser.DISCONNECT }); + } + // remove socket from pool + this.destroy(); -exports._setIdCounter = function (counter) { - idCounter = counter; + if (this.connected) { + // fire events + this.onclose('io client disconnect'); + } + return this; }; -exports.toSessionJSON = function (sdp, opts) { - var i; - var creators = opts.creators || []; - var role = opts.role || 'initiator'; - var direction = opts.direction || 'outgoing'; +},{"./on":37,"component-bind":51,"component-emitter":49,"debug":35,"has-binary":56,"socket.io-parser":36,"to-array":55}],46:[function(require,module,exports){ +module.exports = { + initiator: { + incoming: { + initiator: 'recvonly', + responder: 'sendonly', + both: 'sendrecv', + none: 'inactive', + recvonly: 'initiator', + sendonly: 'responder', + sendrecv: 'both', + inactive: 'none' + }, + outgoing: { + initiator: 'sendonly', + responder: 'recvonly', + both: 'sendrecv', + none: 'inactive', + recvonly: 'responder', + sendonly: 'initiator', + sendrecv: 'both', + inactive: 'none' + } + }, + responder: { + incoming: { + initiator: 'sendonly', + responder: 'recvonly', + both: 'sendrecv', + none: 'inactive', + recvonly: 'responder', + sendonly: 'initiator', + sendrecv: 'both', + inactive: 'none' + }, + outgoing: { + initiator: 'recvonly', + responder: 'sendonly', + both: 'sendrecv', + none: 'inactive', + recvonly: 'initiator', + sendonly: 'responder', + sendrecv: 'both', + inactive: 'none' + } + } +}; +},{}],47:[function(require,module,exports){ +exports.lines = function (sdp) { + return sdp.split('\r\n').filter(function (line) { + return line.length > 0; + }); +}; - // Divide the SDP into session and media sections. - var media = sdp.split('\r\nm='); - for (i = 1; i < media.length; i++) { - media[i] = 'm=' + media[i]; - if (i !== media.length - 1) { - media[i] += '\r\n'; +exports.findLine = function (prefix, mediaLines, sessionLines) { + var prefixLength = prefix.length; + for (var i = 0; i < mediaLines.length; i++) { + if (mediaLines[i].substr(0, prefixLength) === prefix) { + return mediaLines[i]; } } - var session = media.shift() + '\r\n'; - var sessionLines = parsers.lines(session); - var parsed = {}; - - var contents = []; - for (i = 0; i < media.length; i++) { - contents.push(exports.toMediaJSON(media[i], session, { - role: role, - direction: direction, - creator: creators[i] || 'initiator' - })); + // Continue searching in parent session section + if (!sessionLines) { + return false; } - parsed.contents = contents; - var groupLines = parsers.findLines('a=group:', sessionLines); - if (groupLines.length) { - parsed.groups = parsers.groups(groupLines); + for (var j = 0; j < sessionLines.length; j++) { + if (sessionLines[j].substr(0, prefixLength) === prefix) { + return sessionLines[j]; + } } - return parsed; + return false; }; -exports.toMediaJSON = function (media, session, opts) { - var creator = opts.creator || 'initiator'; - var role = opts.role || 'initiator'; - var direction = opts.direction || 'outgoing'; - - var lines = parsers.lines(media); - var sessionLines = parsers.lines(session); - var mline = parsers.mline(lines[0]); - - var content = { - creator: creator, - name: mline.media, - description: { - descType: 'rtp', - media: mline.media, - payloads: [], - encryption: [], - feedback: [], - headerExtensions: [] - }, - transport: { - transType: 'iceUdp', - candidates: [], - fingerprints: [] +exports.findLines = function (prefix, mediaLines, sessionLines) { + var results = []; + var prefixLength = prefix.length; + for (var i = 0; i < mediaLines.length; i++) { + if (mediaLines[i].substr(0, prefixLength) === prefix) { + results.push(mediaLines[i]); } - }; - if (mline.media == 'application') { - // FIXME: the description is most likely to be independent - // of the SDP and should be processed by other parts of the library - content.description = { - descType: 'datachannel' - }; - content.transport.sctp = []; - } - var desc = content.description; - var trans = content.transport; - - // If we have a mid, use that for the content name instead. - var mid = parsers.findLine('a=mid:', lines); - if (mid) { - content.name = mid.substr(6); } - - if (parsers.findLine('a=sendrecv', lines, sessionLines)) { - content.senders = 'both'; - } else if (parsers.findLine('a=sendonly', lines, sessionLines)) { - content.senders = SENDERS[role][direction].sendonly; - } else if (parsers.findLine('a=recvonly', lines, sessionLines)) { - content.senders = SENDERS[role][direction].recvonly; - } else if (parsers.findLine('a=inactive', lines, sessionLines)) { - content.senders = 'none'; + if (results.length || !sessionLines) { + return results; } - - if (desc.descType == 'rtp') { - var bandwidth = parsers.findLine('b=', lines); - if (bandwidth) { - desc.bandwidth = parsers.bandwidth(bandwidth); - } - - var ssrc = parsers.findLine('a=ssrc:', lines); - if (ssrc) { - desc.ssrc = ssrc.substr(7).split(' ')[0]; + for (var j = 0; j < sessionLines.length; j++) { + if (sessionLines[j].substr(0, prefixLength) === prefix) { + results.push(sessionLines[j]); } + } + return results; +}; - var rtpmapLines = parsers.findLines('a=rtpmap:', lines); - rtpmapLines.forEach(function (line) { - var payload = parsers.rtpmap(line); - payload.parameters = []; - payload.feedback = []; - - var fmtpLines = parsers.findLines('a=fmtp:' + payload.id, lines); - // There should only be one fmtp line per payload - fmtpLines.forEach(function (line) { - payload.parameters = parsers.fmtp(line); - }); - - var fbLines = parsers.findLines('a=rtcp-fb:' + payload.id, lines); - fbLines.forEach(function (line) { - payload.feedback.push(parsers.rtcpfb(line)); - }); - - desc.payloads.push(payload); - }); - - var cryptoLines = parsers.findLines('a=crypto:', lines, sessionLines); - cryptoLines.forEach(function (line) { - desc.encryption.push(parsers.crypto(line)); - }); - - if (parsers.findLine('a=rtcp-mux', lines)) { - desc.mux = true; +exports.mline = function (line) { + var parts = line.substr(2).split(' '); + var parsed = { + media: parts[0], + port: parts[1], + proto: parts[2], + formats: [] + }; + for (var i = 3; i < parts.length; i++) { + if (parts[i]) { + parsed.formats.push(parts[i]); } + } + return parsed; +}; - var fbLines = parsers.findLines('a=rtcp-fb:*', lines); - fbLines.forEach(function (line) { - desc.feedback.push(parsers.rtcpfb(line)); - }); - - var extLines = parsers.findLines('a=extmap:', lines); - extLines.forEach(function (line) { - var ext = parsers.extmap(line); - - ext.senders = SENDERS[role][direction][ext.senders]; - - desc.headerExtensions.push(ext); - }); - - var ssrcGroupLines = parsers.findLines('a=ssrc-group:', lines); - desc.sourceGroups = parsers.sourceGroups(ssrcGroupLines || []); - - var ssrcLines = parsers.findLines('a=ssrc:', lines); - var sources = desc.sources = parsers.sources(ssrcLines || []); - - var msidLine = parsers.findLine('a=msid:', lines); - if (msidLine) { - var msid = parsers.msid(msidLine); - ['msid', 'mslabel', 'label'].forEach(function (key) { - for (var i = 0; i < sources.length; i++) { - var found = false; - for (var j = 0; j < sources[i].parameters.length; j++) { - if (sources[i].parameters[j].key === key) { - found = true; - } - } - if (!found) { - sources[i].parameters.push({ key: key, value: msid[key] }); - } - } - }); - } +exports.rtpmap = function (line) { + var parts = line.substr(9).split(' '); + var parsed = { + id: parts.shift() + }; - if (parsers.findLine('a=x-google-flag:conference', lines, sessionLines)) { - desc.googConferenceFlag = true; - } - } + parts = parts[0].split('/'); - // transport specific attributes - var fingerprintLines = parsers.findLines('a=fingerprint:', lines, sessionLines); - var setup = parsers.findLine('a=setup:', lines, sessionLines); - fingerprintLines.forEach(function (line) { - var fp = parsers.fingerprint(line); - if (setup) { - fp.setup = setup.substr(8); - } - trans.fingerprints.push(fp); - }); + parsed.name = parts[0]; + parsed.clockrate = parts[1]; + parsed.channels = parts.length == 3 ? parts[2] : '1'; + return parsed; +}; - var ufragLine = parsers.findLine('a=ice-ufrag:', lines, sessionLines); - var pwdLine = parsers.findLine('a=ice-pwd:', lines, sessionLines); - if (ufragLine && pwdLine) { - trans.ufrag = ufragLine.substr(12); - trans.pwd = pwdLine.substr(10); - trans.candidates = []; +exports.sctpmap = function (line) { + // based on -05 draft + var parts = line.substr(10).split(' '); + var parsed = { + number: parts.shift(), + protocol: parts.shift(), + streams: parts.shift() + }; + return parsed; +}; - var candidateLines = parsers.findLines('a=candidate:', lines, sessionLines); - candidateLines.forEach(function (line) { - trans.candidates.push(exports.toCandidateJSON(line)); - }); - } - if (desc.descType == 'datachannel') { - var sctpmapLines = parsers.findLines('a=sctpmap:', lines); - sctpmapLines.forEach(function (line) { - var sctp = parsers.sctpmap(line); - trans.sctp.push(sctp); - }); +exports.fmtp = function (line) { + var kv, key, value; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + var parsed = []; + for (var i = 0; i < parts.length; i++) { + kv = parts[i].split('='); + key = kv[0].trim(); + value = kv[1]; + if (key && value) { + parsed.push({key: key, value: value}); + } else if (key) { + parsed.push({key: '', value: key}); + } } - - return content; + return parsed; }; -exports.toCandidateJSON = function (line) { - var candidate = parsers.candidate(line.split('\r\n')[0]); - candidate.id = (idCounter++).toString(36).substr(0, 12); - return candidate; +exports.crypto = function (line) { + var parts = line.substr(9).split(' '); + var parsed = { + tag: parts[0], + cipherSuite: parts[1], + keyParams: parts[2], + sessionParams: parts.slice(3).join(' ') + }; + return parsed; }; -},{"./parsers":56,"./senders":55}],52:[function(require,module,exports){ -module.exports = Array.isArray || function (arr) { - return Object.prototype.toString.call(arr) == '[object Array]'; +exports.fingerprint = function (line) { + var parts = line.substr(14).split(' '); + return { + hash: parts[0], + value: parts[1] + }; }; -},{}],31:[function(require,module,exports){ -/** - * lodash 3.0.3 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ -var arrayEach = require('lodash._arrayeach'), - baseEach = require('lodash._baseeach'), - bindCallback = require('lodash._bindcallback'), - isArray = require('lodash.isarray'); - -/** - * Creates a function for `_.forEach` or `_.forEachRight`. - * - * @private - * @param {Function} arrayFunc The function to iterate over an array. - * @param {Function} eachFunc The function to iterate over a collection. - * @returns {Function} Returns the new each function. - */ -function createForEach(arrayFunc, eachFunc) { - return function(collection, iteratee, thisArg) { - return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection)) - ? arrayFunc(collection, iteratee) - : eachFunc(collection, bindCallback(iteratee, thisArg, 3)); - }; -} +exports.extmap = function (line) { + var parts = line.substr(9).split(' '); + var parsed = {}; -/** - * Iterates over elements of `collection` invoking `iteratee` for each element. - * The `iteratee` is bound to `thisArg` and invoked with three arguments: - * (value, index|key, collection). Iteratee functions may exit iteration early - * by explicitly returning `false`. - * - * **Note:** As with other "Collections" methods, objects with a "length" property - * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn` - * may be used for object iteration. - * - * @static - * @memberOf _ - * @alias each - * @category Collection - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [thisArg] The `this` binding of `iteratee`. - * @returns {Array|Object|string} Returns `collection`. - * @example - * - * _([1, 2]).forEach(function(n) { - * console.log(n); - * }).value(); - * // => logs each value from left to right and returns the array - * - * _.forEach({ 'a': 1, 'b': 2 }, function(n, key) { - * console.log(n, key); - * }); - * // => logs each value-key pair and returns the object (iteration order is not guaranteed) - */ -var forEach = createForEach(arrayEach, baseEach); + var idpart = parts.shift(); + var sp = idpart.indexOf('/'); + if (sp >= 0) { + parsed.id = idpart.substr(0, sp); + parsed.senders = idpart.substr(sp + 1); + } else { + parsed.id = idpart; + parsed.senders = 'sendrecv'; + } -module.exports = forEach; + parsed.uri = parts.shift() || ''; -},{"lodash._arrayeach":57,"lodash._baseeach":59,"lodash._bindcallback":58,"lodash.isarray":60}],53:[function(require,module,exports){ -var global=self;/*! 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; + return parsed; +}; - // A set of types used to distinguish objects from primitives. - var objectTypes = { - "function": true, - "object": true - }; +exports.rtcpfb = function (line) { + var parts = line.substr(10).split(' '); + var parsed = {}; + parsed.id = parts.shift(); + parsed.type = parts.shift(); + if (parsed.type === 'trr-int') { + parsed.value = parts.shift(); + } else { + parsed.subtype = parts.shift() || ''; + } + parsed.parameters = parts; + return parsed; +}; - // 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); - }; - } +exports.candidate = function (line) { + var parts; + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { // no a=candidate + parts = line.substring(10).split(' '); + } - // 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); - }; - } + var candidate = { + foundation: parts[0], + component: parts[1], + protocol: parts[2].toLowerCase(), + priority: parts[3], + ip: parts[4], + port: parts[5], + // skip parts[6] == 'typ' + type: parts[7], + generation: '0' + }; - // 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); - } - }; + for (var i = 8; i < parts.length; i += 2) { + if (parts[i] === 'raddr') { + candidate.relAddr = parts[i + 1]; + } else if (parts[i] === 'rport') { + candidate.relPort = parts[i + 1]; + } else if (parts[i] === 'generation') { + candidate.generation = parts[i + 1]; + } else if (parts[i] === 'tcptype') { + candidate.tcpType = parts[i + 1]; } - 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" - }; + candidate.network = '1'; - // 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); - }; + return candidate; +}; - // 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 + '"'; - }; +exports.sourceGroups = function (lines) { + var parsed = []; + for (var i = 0; i < lines.length; i++) { + var parts = lines[i].substr(13).split(' '); + parsed.push({ + semantics: parts.shift(), + sources: parts + }); + } + return parsed; +}; - // 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; - } - }; +exports.sources = function (lines) { + // http://tools.ietf.org/html/rfc5576 + var parsed = []; + var sources = {}; + for (var i = 0; i < lines.length; i++) { + var parts = lines[i].substr(7).split(' '); + var ssrc = parts.shift(); - // 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, "", []); - }; - } + if (!sources[ssrc]) { + var source = { + ssrc: ssrc, + parameters: [] + }; + parsed.push(source); - // 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" - }; + // Keep an index + sources[ssrc] = source; + } + + parts = parts.join(' ').split(':'); + var attribute = parts.shift(); + var value = parts.join(':') || null; - // Internal: Stores the parser state. - var Index, Source; + sources[ssrc].parameters.push({ + key: attribute, + value: value + }); + } - // Internal: Resets the parser state and throws a `SyntaxError`. - var abort = function () { - Index = Source = null; - throw SyntaxError(); - }; + return parsed; +}; - // 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 "$"; - }; +exports.groups = function (lines) { + // http://tools.ietf.org/html/rfc5888 + var parsed = []; + var parts; + for (var i = 0; i < lines.length; i++) { + parts = lines[i].substr(8).split(' '); + parsed.push({ + semantics: parts.shift(), + contents: parts + }); + } + return parsed; +}; - // 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; - }; +exports.bandwidth = function (line) { + var parts = line.substr(2).split(':'); + var parsed = {}; + parsed.type = parts.shift(); + parsed.bandwidth = parts.shift(); + return parsed; +}; - // 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; - } - }; +exports.msid = function (line) { + var data = line.substr(7); + var parts = data.split(' '); + return { + msid: data, + mslabel: parts[0], + label: parts[1] + }; +}; - // 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); - }; +},{}],57:[function(require,module,exports){ +var global=self; +module.exports = isBuf; - // 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; - }; - } - } +/** + * Returns true if obj is a buffer or an arraybuffer. + * + * @api private + */ - exports["runInContext"] = runInContext; - return exports; - } +function isBuf(obj) { + return (global.Buffer && global.Buffer.isBuffer(obj)) || + (global.ArrayBuffer && obj instanceof ArrayBuffer); +} - 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; - } - })); +},{}],49:[function(require,module,exports){ - root.JSON = { - "parse": JSON3.parse, - "stringify": JSON3.stringify - }; - } +/** + * Expose `Emitter`. + */ - // Export for asynchronous module loaders. - if (isLoader) { - define(function () { - return JSON3; - }); - } -}).call(this); +module.exports = Emitter; -},{}],32:[function(require,module,exports){ /** - * lodash 3.1.2 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license + * Initialize a new `Emitter`. + * + * @api public */ -var baseGet = require('lodash._baseget'), - toPath = require('lodash._topath'), - isArray = require('lodash.isarray'), - map = require('lodash.map'); -/** Used to match property names within property paths. */ -var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/, - reIsPlainProp = /^\w*$/; +function Emitter(obj) { + if (obj) return mixin(obj); +}; /** - * The base implementation of `_.property` without support for deep paths. + * Mixin the emitter properties. * - * @private - * @param {string} key The key of the property to get. - * @returns {Function} Returns the new function. + * @param {Object} obj + * @return {Object} + * @api private */ -function baseProperty(key) { - return function(object) { - return object == null ? undefined : object[key]; - }; + +function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; } /** - * A specialized version of `baseProperty` which supports deep paths. + * Listen on the given `event` with `fn`. * - * @private - * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new function. + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public */ -function basePropertyDeep(path) { - var pathKey = (path + ''); - path = toPath(path); - return function(object) { - return baseGet(object, path, pathKey); - }; -} + +Emitter.prototype.on = +Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; +}; /** - * Checks if `value` is a property name and not a property path. + * Adds an `event` listener that will be invoked a single + * time then automatically removed. * - * @private - * @param {*} value The value to check. - * @param {Object} [object] The object to query keys on. - * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public */ -function isKey(value, object) { - var type = typeof value; - if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') { - return true; + +Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; +}; + +/** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.off = +Emitter.prototype.removeListener = +Emitter.prototype.removeAllListeners = +Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; } - if (isArray(value)) { - return false; + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } } - var result = !reIsDeepProp.test(value); - return result || (object != null && value in toObject(object)); -} + return this; +}; /** - * Converts `value` to an object if it's not one. + * Emit `event` with the given args. * - * @private - * @param {*} value The value to process. - * @returns {Object} Returns the object. + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} */ -function toObject(value) { - return isObject(value) ? value : Object(value); -} -/** - * Gets the property value of `path` from all elements in `collection`. - * - * @static - * @memberOf _ - * @category Collection - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Array|string} path The path of the property to pluck. - * @returns {Array} Returns the property values. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 } - * ]; - * - * _.pluck(users, 'user'); - * // => ['barney', 'fred'] - * - * var userIndex = _.indexBy(users, 'user'); - * _.pluck(userIndex, 'age'); - * // => [36, 40] (iteration order is not guaranteed) - */ -function pluck(collection, path) { - return map(collection, property(path)); -} +Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; +}; /** - * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. - * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true + * Return array of callbacks for `event`. * - * _.isObject(1); - * // => false + * @param {String} event + * @return {Array} + * @api public */ -function isObject(value) { - // Avoid a V8 JIT bug in Chrome 19-20. - // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. - var type = typeof value; - return !!value && (type == 'object' || type == 'function'); -} + +Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; +}; /** - * Creates a function which returns the property value at `path` on a - * given object. - * - * @static - * @memberOf _ - * @category Utility - * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new function. - * @example - * - * var objects = [ - * { 'a': { 'b': { 'c': 2 } } }, - * { 'a': { 'b': { 'c': 1 } } } - * ]; - * - * _.map(objects, _.property('a.b.c')); - * // => [2, 1] + * Check if this emitter has `event` handlers. * - * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c'); - * // => [1, 2] + * @param {String} event + * @return {Boolean} + * @api public */ -function property(path) { - return isKey(path) ? baseProperty(path) : basePropertyDeep(path); -} -module.exports = pluck; +Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; +}; -},{"lodash._baseget":62,"lodash._topath":61,"lodash.isarray":60,"lodash.map":63}],48:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +},{}],48:[function(require,module,exports){ +/** + * Parses an URI * - * 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. + * @author Steven Levithan (MIT license) + * @api private */ -'use strict'; -var logging = require('../utils.js').log; -var browserDetails = require('../utils.js').browserDetails; - -var chromeShim = { - 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)); - } - }); - } - }, - - 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) { - // Use _srcObject as a private property for this shim - this._srcObject = stream; - if (this.src) { - URL.revokeObjectURL(this.src); - } - 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. - window.RTCPeerConnection = function(pcConfig, pcConstraints) { - // Translate iceTransportPolicy to iceTransports, - // see https://code.google.com/p/webrtc/issues/detail?id=4869 - logging('PeerConnection'); - if (pcConfig && pcConfig.iceTransportPolicy) { - pcConfig.iceTransports = pcConfig.iceTransportPolicy; - } - var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors - var origGetStats = pc.getStats.bind(pc); - pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line - var self = this; - var args = arguments; +var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - // 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(selector, successCallback); - } +var parts = [ + 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host' + , 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' +]; - var fixChromeStats_ = function(response) { - var standardReport = {}; - var reports = response.result(); - reports.forEach(function(report) { - var standardStats = { - id: report.id, - timestamp: report.timestamp, - type: report.type - }; - report.names().forEach(function(name) { - standardStats[name] = report.stat(name); - }); - standardReport[standardStats.id] = standardStats; - }); +module.exports = function parseuri(str) { + var m = re.exec(str || '') + , uri = {} + , i = 14; - return standardReport; - }; + while (i--) { + uri[parts[i]] = m[i] || ''; + } - if (arguments.length >= 2) { - var successCallbackWrapper_ = function(response) { - args[1](fixChromeStats_(response)); - }; + return uri; +}; - return origGetStats.apply(this, [successCallbackWrapper_, arguments[0]]); - } +},{}],25:[function(require,module,exports){ +// based on https://github.com/ESTOS/strophe.jingle/ +// adds wildemitter support +var util = require('util'); +var adapter = require('webrtc-adapter-test'); +var WildEmitter = require('wildemitter'); - // promise-support - return new Promise(function(resolve, reject) { - if (args.length === 1 && selector === null) { - origGetStats.apply(self, [ - function(response) { - resolve.apply(null, [fixChromeStats_(response)]); - }, reject]); - } else { - origGetStats.apply(self, [resolve, reject]); - } - }); - }; +function dumpSDP(description) { + return { + type: description.type, + sdp: description.sdp + }; +} - return pc; +function dumpStream(stream) { + var info = { + label: stream.id, }; - window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (webkitRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - if (arguments.length) { - return webkitRTCPeerConnection.generateCertificate.apply(null, - arguments); - } else { - return webkitRTCPeerConnection.generateCertificate; - } - } - }); + if (stream.getAudioTracks().length) { + info.audio = stream.getAudioTracks().map(function (track) { + return track.id; + }); + } + if (stream.getVideoTracks().length) { + info.video = stream.getVideoTracks().map(function (track) { + return track.id; + }); } + return info; +} - // add promise support - ['createOffer', 'createAnswer'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.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]); - }); - } else { - return nativeMethod.apply(this, arguments); - } - }; - }); +function TraceablePeerConnection(config, constraints) { + var self = this; + WildEmitter.call(this); - ['setLocalDescription', 'setRemoteDescription', - 'addIceCandidate'].forEach(function(method) { - var nativeMethod = webkitRTCPeerConnection.prototype[method]; - webkitRTCPeerConnection.prototype[method] = function() { - var args = arguments; - var self = this; - return new Promise(function(resolve, reject) { - nativeMethod.apply(self, [args[0], - function() { - resolve(); - if (args.length >= 2) { - args[1].apply(null, []); - } - }, - function(err) { - reject(err); - if (args.length >= 3) { - args[2].apply(null, [err]); - } - }] - ); + this.peerconnection = new window.RTCPeerConnection(config, constraints); + + this.trace = function (what, info) { + self.emit('PeerConnectionTrace', { + time: new Date(), + type: what, + value: info || "" }); - }; - }); - }, + }; - shimGetUserMedia: 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; + this.onicecandidate = null; + this.peerconnection.onicecandidate = function (event) { + self.trace('onicecandidate', event.candidate); + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + }; + this.onaddstream = null; + this.peerconnection.onaddstream = function (event) { + self.trace('onaddstream', dumpStream(event.stream)); + if (self.onaddstream !== null) { + self.onaddstream(event); + } + }; + this.onremovestream = null; + this.peerconnection.onremovestream = function (event) { + self.trace('onremovestream', dumpStream(event.stream)); + if (self.onremovestream !== null) { + self.onremovestream(event); } - 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; + }; + this.onsignalingstatechange = null; + this.peerconnection.onsignalingstatechange = function (event) { + self.trace('onsignalingstatechange', self.signalingState); + if (self.onsignalingstatechange !== null) { + self.onsignalingstatechange(event); } - 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); - } + }; + this.oniceconnectionstatechange = null; + this.peerconnection.oniceconnectionstatechange = function (event) { + self.trace('oniceconnectionstatechange', self.iceConnectionState); + if (self.oniceconnectionstatechange !== null) { + self.oniceconnectionstatechange(event); } - 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]; - } - }); + }; + this.onnegotiationneeded = null; + this.peerconnection.onnegotiationneeded = function (event) { + self.trace('onnegotiationneeded'); + if (self.onnegotiationneeded !== null) { + self.onnegotiationneeded(event); } - }); - if (c.advanced) { - cc.optional = (cc.optional || []).concat(c.advanced); - } - return cc; }; - - var getUserMedia_ = function(constraints, onSuccess, onError) { - if (constraints.audio) { - constraints.audio = constraintsToChrome_(constraints.audio); - } - if (constraints.video) { - constraints.video = constraintsToChrome_(constraints.video); - } - logging('chrome: ' + JSON.stringify(constraints)); - return navigator.webkitGetUserMedia(constraints, onSuccess, onError); + self.ondatachannel = null; + this.peerconnection.ondatachannel = function (event) { + self.trace('ondatachannel', event); + if (self.ondatachannel !== null) { + self.ondatachannel(event); + } }; - 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); - }); - } + this.getLocalStreams = this.peerconnection.getLocalStreams.bind(this.peerconnection); + this.getRemoteStreams = this.peerconnection.getRemoteStreams.bind(this.peerconnection); +} - 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: ''}; - })); - }); - }); - }}; - } +util.inherits(TraceablePeerConnection, WildEmitter); - // 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(c) { - if (c) { - logging('spec: ' + JSON.stringify(c)); // whitespace for alignment - c.audio = constraintsToChrome_(c.audio); - c.video = constraintsToChrome_(c.video); - logging('chrome: ' + JSON.stringify(c)); +['signalingState', 'iceConnectionState', 'localDescription', 'remoteDescription'].forEach(function (prop) { + Object.defineProperty(TraceablePeerConnection.prototype, prop, { + get: function () { + return this.peerconnection[prop]; } - return origGetUserMedia(c); - }.bind(this); - } - - // 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.'); - }; - } - }, + }); +}); - // Attach a media stream to an element. - attachMediaStream: function(element, stream) { - logging('DEPRECATED, attachMediaStream will soon be removed.'); - if (browserDetails.version >= 43) { - element.srcObject = stream; - } else if (typeof element.src !== 'undefined') { - element.src = URL.createObjectURL(stream); - } else { - logging('Error attaching stream to element.'); - } - }, +TraceablePeerConnection.prototype.addStream = function (stream) { + this.trace('addStream', dumpStream(stream)); + this.peerconnection.addStream(stream); +}; - reattachMediaStream: function(to, from) { - logging('DEPRECATED, reattachMediaStream will soon be removed.'); - if (browserDetails.version >= 43) { - to.srcObject = from.srcObject; - } else { - to.src = from.src; - } - } -} +TraceablePeerConnection.prototype.removeStream = function (stream) { + this.trace('removeStream', dumpStream(stream)); + this.peerconnection.removeStream(stream); +}; -// Expose public methods. -module.exports = { - shimOnTrack: chromeShim.shimOnTrack, - shimSourceObject: chromeShim.shimSourceObject, - shimPeerConnection: chromeShim.shimPeerConnection, - shimGetUserMedia: chromeShim.shimGetUserMedia, - attachMediaStream: chromeShim.attachMediaStream, - reattachMediaStream: chromeShim.reattachMediaStream +TraceablePeerConnection.prototype.createDataChannel = function (label, opts) { + this.trace('createDataChannel', label, opts); + return this.peerconnection.createDataChannel(label, opts); }; -},{"../utils.js":46}],55:[function(require,module,exports){ -module.exports = { - initiator: { - incoming: { - initiator: 'recvonly', - responder: 'sendonly', - both: 'sendrecv', - none: 'inactive', - recvonly: 'initiator', - sendonly: 'responder', - sendrecv: 'both', - inactive: 'none' +TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) { + var self = this; + this.trace('setLocalDescription', dumpSDP(description)); + this.peerconnection.setLocalDescription(description, + function () { + self.trace('setLocalDescriptionOnSuccess'); + if (successCallback) successCallback(); }, - outgoing: { - initiator: 'sendonly', - responder: 'recvonly', - both: 'sendrecv', - none: 'inactive', - recvonly: 'responder', - sendonly: 'initiator', - sendrecv: 'both', - inactive: 'none' + function (err) { + self.trace('setLocalDescriptionOnFailure', err); + if (failureCallback) failureCallback(err); } - }, - responder: { - incoming: { - initiator: 'sendonly', - responder: 'recvonly', - both: 'sendrecv', - none: 'inactive', - recvonly: 'responder', - sendonly: 'initiator', - sendrecv: 'both', - inactive: 'none' + ); +}; + +TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) { + var self = this; + this.trace('setRemoteDescription', dumpSDP(description)); + this.peerconnection.setRemoteDescription(description, + function () { + self.trace('setRemoteDescriptionOnSuccess'); + if (successCallback) successCallback(); }, - outgoing: { - initiator: 'recvonly', - responder: 'sendonly', - both: 'sendrecv', - none: 'inactive', - recvonly: 'initiator', - sendonly: 'responder', - sendrecv: 'both', - inactive: 'none' + function (err) { + self.trace('setRemoteDescriptionOnFailure', err); + if (failureCallback) failureCallback(err); } + ); +}; + +TraceablePeerConnection.prototype.close = function () { + this.trace('stop'); + if (this.peerconnection.signalingState != 'closed') { + this.peerconnection.close(); } }; -},{}],56:[function(require,module,exports){ -exports.lines = function (sdp) { - return sdp.split('\r\n').filter(function (line) { - return line.length > 0; - }); +TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) { + var self = this; + this.trace('createOffer', constraints); + this.peerconnection.createOffer( + function (offer) { + self.trace('createOfferOnSuccess', dumpSDP(offer)); + if (successCallback) successCallback(offer); + }, + function (err) { + self.trace('createOfferOnFailure', err); + if (failureCallback) failureCallback(err); + }, + constraints + ); }; -exports.findLine = function (prefix, mediaLines, sessionLines) { - var prefixLength = prefix.length; - for (var i = 0; i < mediaLines.length; i++) { - if (mediaLines[i].substr(0, prefixLength) === prefix) { - return mediaLines[i]; - } - } - // Continue searching in parent session section - if (!sessionLines) { - return false; - } +TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) { + var self = this; + this.trace('createAnswer', constraints); + this.peerconnection.createAnswer( + function (answer) { + self.trace('createAnswerOnSuccess', dumpSDP(answer)); + if (successCallback) successCallback(answer); + }, + function (err) { + self.trace('createAnswerOnFailure', err); + if (failureCallback) failureCallback(err); + }, + constraints + ); +}; - for (var j = 0; j < sessionLines.length; j++) { - if (sessionLines[j].substr(0, prefixLength) === prefix) { - return sessionLines[j]; +TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) { + var self = this; + this.trace('addIceCandidate', candidate); + this.peerconnection.addIceCandidate(candidate, + function () { + //self.trace('addIceCandidateOnSuccess'); + if (successCallback) successCallback(); + }, + function (err) { + self.trace('addIceCandidateOnFailure', err); + if (failureCallback) failureCallback(err); } - } + ); +}; + +TraceablePeerConnection.prototype.getStats = function () { + this.peerconnection.getStats.apply(this.peerconnection, arguments); +}; + +module.exports = TraceablePeerConnection; + +},{"util":8,"webrtc-adapter-test":23,"wildemitter":5}],51:[function(require,module,exports){ +/** + * Slice reference. + */ + +var slice = [].slice; + +/** + * Bind `obj` to `fn`. + * + * @param {Object} obj + * @param {Function|String} fn or string + * @return {Function} + * @api public + */ + +module.exports = function(obj, fn){ + if ('string' == typeof fn) fn = obj[fn]; + if ('function' != typeof fn) throw new Error('bind() requires a function'); + var args = slice.call(arguments, 2); + return function(){ + return fn.apply(obj, args.concat(slice.call(arguments))); + } +}; + +},{}],52:[function(require,module,exports){ + +/** + * HOP ref. + */ - return false; -}; +var has = Object.prototype.hasOwnProperty; -exports.findLines = function (prefix, mediaLines, sessionLines) { - var results = []; - var prefixLength = prefix.length; - for (var i = 0; i < mediaLines.length; i++) { - if (mediaLines[i].substr(0, prefixLength) === prefix) { - results.push(mediaLines[i]); - } - } - if (results.length || !sessionLines) { - return results; - } - for (var j = 0; j < sessionLines.length; j++) { - if (sessionLines[j].substr(0, prefixLength) === prefix) { - results.push(sessionLines[j]); - } +/** + * Return own keys in `obj`. + * + * @param {Object} obj + * @return {Array} + * @api public + */ + +exports.keys = Object.keys || function(obj){ + var keys = []; + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); } - return results; + } + return keys; }; -exports.mline = function (line) { - var parts = line.substr(2).split(' '); - var parsed = { - media: parts[0], - port: parts[1], - proto: parts[2], - formats: [] - }; - for (var i = 3; i < parts.length; i++) { - if (parts[i]) { - parsed.formats.push(parts[i]); - } +/** + * Return own values in `obj`. + * + * @param {Object} obj + * @return {Array} + * @api public + */ + +exports.values = function(obj){ + var vals = []; + for (var key in obj) { + if (has.call(obj, key)) { + vals.push(obj[key]); } - return parsed; + } + return vals; }; -exports.rtpmap = function (line) { - var parts = line.substr(9).split(' '); - var parsed = { - id: parts.shift() - }; - - parts = parts[0].split('/'); +/** + * Merge `b` into `a`. + * + * @param {Object} a + * @param {Object} b + * @return {Object} a + * @api public + */ - parsed.name = parts[0]; - parsed.clockrate = parts[1]; - parsed.channels = parts.length == 3 ? parts[2] : '1'; - return parsed; +exports.merge = function(a, b){ + for (var key in b) { + if (has.call(b, key)) { + a[key] = b[key]; + } + } + return a; }; -exports.sctpmap = function (line) { - // based on -05 draft - var parts = line.substr(10).split(' '); - var parsed = { - number: parts.shift(), - protocol: parts.shift(), - streams: parts.shift() - }; - return parsed; +/** + * Return length of `obj`. + * + * @param {Object} obj + * @return {Number} + * @api public + */ + +exports.length = function(obj){ + return exports.keys(obj).length; }; +/** + * Check if `obj` is empty. + * + * @param {Object} obj + * @return {Boolean} + * @api public + */ -exports.fmtp = function (line) { - var kv, key, value; - var parts = line.substr(line.indexOf(' ') + 1).split(';'); - var parsed = []; - for (var i = 0; i < parts.length; i++) { - kv = parts[i].split('='); - key = kv[0].trim(); - value = kv[1]; - if (key && value) { - parsed.push({key: key, value: value}); - } else if (key) { - parsed.push({key: '', value: key}); - } - } - return parsed; +exports.isEmpty = function(obj){ + return 0 == exports.length(obj); }; +},{}],53:[function(require,module,exports){ -exports.crypto = function (line) { - var parts = line.substr(9).split(' '); - var parsed = { - tag: parts[0], - cipherSuite: parts[1], - keyParams: parts[2], - sessionParams: parts.slice(3).join(' ') - }; - return parsed; -}; +var indexOf = [].indexOf; -exports.fingerprint = function (line) { - var parts = line.substr(14).split(' '); - return { - hash: parts[0], - value: parts[1] - }; +module.exports = function(arr, obj){ + if (indexOf) return arr.indexOf(obj); + for (var i = 0; i < arr.length; ++i) { + if (arr[i] === obj) return i; + } + return -1; }; +},{}],54:[function(require,module,exports){ -exports.extmap = function (line) { - var parts = line.substr(9).split(' '); - var parsed = {}; +/** + * Expose `Backoff`. + */ - var idpart = parts.shift(); - var sp = idpart.indexOf('/'); - if (sp >= 0) { - parsed.id = idpart.substr(0, sp); - parsed.senders = idpart.substr(sp + 1); - } else { - parsed.id = idpart; - parsed.senders = 'sendrecv'; - } +module.exports = Backoff; - parsed.uri = parts.shift() || ''; +/** + * Initialize backoff timer with `opts`. + * + * - `min` initial timeout in milliseconds [100] + * - `max` max timeout [10000] + * - `jitter` [0] + * - `factor` [2] + * + * @param {Object} opts + * @api public + */ - return parsed; -}; +function Backoff(opts) { + opts = opts || {}; + this.ms = opts.min || 100; + this.max = opts.max || 10000; + this.factor = opts.factor || 2; + this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; + this.attempts = 0; +} -exports.rtcpfb = function (line) { - var parts = line.substr(10).split(' '); - var parsed = {}; - parsed.id = parts.shift(); - parsed.type = parts.shift(); - if (parsed.type === 'trr-int') { - parsed.value = parts.shift(); - } else { - parsed.subtype = parts.shift() || ''; - } - parsed.parameters = parts; - return parsed; -}; +/** + * Return the backoff duration. + * + * @return {Number} + * @api public + */ -exports.candidate = function (line) { - var parts; - if (line.indexOf('a=candidate:') === 0) { - parts = line.substring(12).split(' '); - } else { // no a=candidate - parts = line.substring(10).split(' '); - } +Backoff.prototype.duration = function(){ + var ms = this.ms * Math.pow(this.factor, this.attempts++); + if (this.jitter) { + var rand = Math.random(); + var deviation = Math.floor(rand * this.jitter * ms); + ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation; + } + return Math.min(ms, this.max) | 0; +}; - var candidate = { - foundation: parts[0], - component: parts[1], - protocol: parts[2].toLowerCase(), - priority: parts[3], - ip: parts[4], - port: parts[5], - // skip parts[6] == 'typ' - type: parts[7], - generation: '0' - }; +/** + * Reset the number of attempts. + * + * @api public + */ - for (var i = 8; i < parts.length; i += 2) { - if (parts[i] === 'raddr') { - candidate.relAddr = parts[i + 1]; - } else if (parts[i] === 'rport') { - candidate.relPort = parts[i + 1]; - } else if (parts[i] === 'generation') { - candidate.generation = parts[i + 1]; - } else if (parts[i] === 'tcptype') { - candidate.tcpType = parts[i + 1]; - } - } +Backoff.prototype.reset = function(){ + this.attempts = 0; +}; - candidate.network = '1'; +/** + * Set the minimum duration + * + * @api public + */ - return candidate; +Backoff.prototype.setMin = function(min){ + this.ms = min; }; -exports.sourceGroups = function (lines) { - var parsed = []; - for (var i = 0; i < lines.length; i++) { - var parts = lines[i].substr(13).split(' '); - parsed.push({ - semantics: parts.shift(), - sources: parts - }); - } - return parsed; +/** + * Set the maximum duration + * + * @api public + */ + +Backoff.prototype.setMax = function(max){ + this.max = max; }; -exports.sources = function (lines) { - // http://tools.ietf.org/html/rfc5576 - var parsed = []; - var sources = {}; - for (var i = 0; i < lines.length; i++) { - var parts = lines[i].substr(7).split(' '); - var ssrc = parts.shift(); +/** + * Set the jitter + * + * @api public + */ - if (!sources[ssrc]) { - var source = { - ssrc: ssrc, - parameters: [] - }; - parsed.push(source); +Backoff.prototype.setJitter = function(jitter){ + this.jitter = jitter; +}; - // Keep an index - sources[ssrc] = source; - } - parts = parts.join(' ').split(':'); - var attribute = parts.shift(); - var value = parts.join(':') || null; +},{}],55:[function(require,module,exports){ +module.exports = toArray - sources[ssrc].parameters.push({ - key: attribute, - value: value - }); - } +function toArray(list, index) { + var array = [] - return parsed; -}; + index = index || 0 -exports.groups = function (lines) { - // http://tools.ietf.org/html/rfc5888 - var parsed = []; - var parts; - for (var i = 0; i < lines.length; i++) { - parts = lines[i].substr(8).split(' '); - parsed.push({ - semantics: parts.shift(), - contents: parts - }); + for (var i = index || 0; i < list.length; i++) { + array[i - index] = list[i] } - return parsed; -}; -exports.bandwidth = function (line) { - var parts = line.substr(2).split(':'); - var parsed = {}; - parsed.type = parts.shift(); - parsed.bandwidth = parts.shift(); - return parsed; -}; + return array +} -exports.msid = function (line) { - var data = line.substr(7); - var parts = data.split(' '); - return { - msid: data, - mslabel: parts[0], - label: parts[1] - }; +},{}],58:[function(require,module,exports){ +module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; }; -},{}],49:[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'; +},{}],59:[function(require,module,exports){ +/*! JSON v3.2.6 | http://bestiejs.github.io/json3 | Copyright 2012-2013, Kit Cambridge | http://kit.mit-license.org */ +;(function (window) { + // Convenience aliases. + var getClass = {}.toString, isProperty, forEach, undef; + + // 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; + + // Detect native implementations. + var nativeJSON = typeof JSON == "object" && JSON; + + // Set up the JSON 3 namespace, preferring the CommonJS `exports` object if + // available. + var JSON3 = typeof exports == "object" && exports && !exports.nodeType && exports; + + if (JSON3 && nativeJSON) { + // Explicitly delegate to the native `stringify` and `parse` + // implementations in CommonJS environments. + JSON3.stringify = nativeJSON.stringify; + JSON3.parse = nativeJSON.parse; + } else { + // Export for web browsers, JavaScript engines, and asynchronous module + // loaders, using the global `JSON` object if available. + JSON3 = window.JSON = nativeJSON || {}; + } -var logging = require('../utils').log; -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) { - var self = this; - if (this._ontrack) { - this.removeEventListener('track', this._ontrack); - this.removeEventListener('addstream', this._ontrackpoly); + // 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 = JSON3.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; } - 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)); } - }); + isSupported = stringifySupported; + } + // Test `JSON.parse`. + if (name == "json-parse") { + var parse = JSON3.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]"; + var dateClass = "[object Date]"; + var numberClass = "[object Number]"; + var stringClass = "[object String]"; + var arrayClass = "[object Array]"; + var 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); + }; } - }, - 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; + // Internal: Determines if a property is a direct property of the given + // object. Delegates to the native `Object#hasOwnProperty` method. + if (!(isProperty = {}.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: A set of primitive types used by `isHostType`. + var PrimitiveTypes = { + 'boolean': 1, + 'number': 1, + 'string': 1, + 'undefined': 1 + }; + + // Internal: Determines if the given object `property` value is a + // non-primitive. + var isHostType = function (object, property) { + var type = typeof object[property]; + return type == 'object' ? !!object[property] : !PrimitiveTypes[type]; + }; + + // 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' && isHostType(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); + }; - shimPeerConnection: function() { - // 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]); + // 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, isLarge = length > 10 && charIndexBuggy, symbols; + if (isLarge) { + symbols = value.split(""); + } + 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; } - } - pcConfig.iceServers = newIceServers; + result += isLarge ? symbols[index] : charIndexBuggy ? value.charAt(index) : value[index]; } } - return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors + return result + '"'; }; - window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; - - // wrap static methods. Currently just generateCertificate. - if (mozRTCPeerConnection.generateCertificate) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function() { - if (arguments.length) { - return mozRTCPeerConnection.generateCertificate.apply(null, - arguments); + + // 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 { - return mozRTCPeerConnection.generateCertificate; + 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; + } + }; - window.RTCSessionDescription = mozRTCSessionDescription; - window.RTCIceCandidate = mozRTCIceCandidate; + // Public: `JSON.stringify`. See ES 5.1 section 15.12.3. + JSON3.stringify = function (source, filter, width) { + var whitespace, callback, properties, className; + if (typeof filter == "function" || typeof filter == "object" && 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, "", []); + }; } - }, - shimGetUserMedia: function() { - // 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; + // 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(); } - 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); + } + // 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); } - if (r.exact !== undefined) { - if (typeof r.exact === 'number') { - r. min = r.max = r.exact; - } else { - c[key] = r.exact; + // 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)); } - 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; + 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()); } - c.advanced.push(oc); - delete r.ideal; - if (!Object.keys(r).length) { - delete c[key]; + 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); + }); } - }); - if (require.length) { - c.require = require; } - return c; + return callback.call(source, property, value); }; - 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); + + // Public: `JSON.parse`. See ES 5.1 section 15.12.2. + JSON3.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(); } - logging('ff37: ' + JSON.stringify(constraints)); - } - return navigator.mozGetUserMedia(constraints, onSuccess, onError); - }; + // Reset the parser state. + Index = Source = null; + return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result; + }; + } + } - navigator.getUserMedia = getUserMedia_; + // Export for asynchronous module loaders. + if (isLoader) { + define(function () { + return JSON3; + }); + } +}(this)); - // Returns the result of getUserMedia as a Promise. - var getUserMediaPromise_ = function(constraints) { - return new Promise(function(resolve, reject) { - navigator.getUserMedia(constraints, resolve, reject); - }); - } +},{}],36:[function(require,module,exports){ - // 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); - }); - }; +/** + * Module dependencies. + */ - 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; - }); - }; - } - }, +var debug = require('debug')('socket.io-parser'); +var json = require('json3'); +var isArray = require('isarray'); +var Emitter = require('component-emitter'); +var binary = require('./binary'); +var isBuf = require('./is-buffer'); - // Attach a media stream to an element. - attachMediaStream: function(element, stream) { - logging('DEPRECATED, attachMediaStream will soon be removed.'); - element.srcObject = stream; - }, +/** + * Protocol version. + * + * @api public + */ - reattachMediaStream: function(to, from) { - logging('DEPRECATED, reattachMediaStream will soon be removed.'); - to.srcObject = from.srcObject; - } -} +exports.protocol = 4; -// Expose public methods. -module.exports = { - shimOnTrack: firefoxShim.shimOnTrack, - shimSourceObject: firefoxShim.shimSourceObject, - shimPeerConnection: firefoxShim.shimPeerConnection, - shimGetUserMedia: firefoxShim.shimGetUserMedia, - attachMediaStream: firefoxShim.attachMediaStream, - reattachMediaStream: firefoxShim.reattachMediaStream -} +/** + * Packet types. + * + * @api public + */ -},{"../utils":46}],47:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +exports.types = [ + 'CONNECT', + 'DISCONNECT', + 'EVENT', + 'BINARY_EVENT', + 'ACK', + 'BINARY_ACK', + 'ERROR' +]; + +/** + * Packet type `connect`. * - * 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. + * @api public */ -'use strict'; -var SDPUtils = require('./edge_sdp'); -var logging = require('../utils').log; -var browserDetails = require('../utils').browserDetails; - -var edgeShim = { - 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; - }; - } - } +exports.CONNECT = 0; - window.RTCPeerConnection = function(config) { - var self = this; +/** + * Packet type `disconnect`. + * + * @api public + */ - this.onicecandidate = null; - this.onaddstream = null; - this.onremovestream = null; - this.onsignalingstatechange = null; - this.oniceconnectionstatechange = null; - this.onnegotiationneeded = null; - this.ondatachannel = 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'; +exports.DISCONNECT = 1; - this.iceOptions = { - gatherPolicy: 'all', - iceServers: [] - }; - if (config && config.iceTransportPolicy) { - switch (config.iceTransportPolicy) { - case 'all': - case 'relay': - this.iceOptions.gatherPolicy = config.iceTransportPolicy; - break; - case 'none': - // FIXME: remove once implementation and spec have added this. - throw new TypeError('iceTransportPolicy "none" not supported'); - } - } - if (config && config.iceServers) { - // Edge does not like - // 1) stun: - // 2) turn: that does not have all of turn:host:port?transport=udp - this.iceOptions.iceServers = config.iceServers.filter(function(server) { - if (server && server.urls) { - server.urls = server.urls.filter(function(url) { - return url.indexOf('transport=udp') !== -1; - })[0]; - return true; - } - return false; - }); - } +/** + * Packet type `event`. + * + * @api public + */ - // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... - // everything that is needed to describe a SDP m-line. - this.transceivers = []; +exports.EVENT = 2; - // 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 = []; - }; +/** + * Packet type `ack`. + * + * @api public + */ - window.RTCPeerConnection.prototype._emitBufferedCandidates = function() { - var self = this; - // FIXME: need to apply ice candidates in a way which is async but in-order - this._localIceCandidatesBuffer.forEach(function(event) { - if (self.onicecandidate !== null) { - self.onicecandidate(event); - } - }); - this._localIceCandidatesBuffer = []; - }; +exports.ACK = 3; - window.RTCPeerConnection.prototype.addStream = function(stream) { - // Clone is necessary for local demos mostly, attaching directly - // to two different senders does not work (build 10547). - this.localStreams.push(stream.clone()); - this._maybeFireNegotiationNeeded(); - }; +/** + * Packet type `error`. + * + * @api public + */ - window.RTCPeerConnection.prototype.removeStream = function(stream) { - var idx = this.localStreams.indexOf(stream); - if (idx > -1) { - this.localStreams.splice(idx, 1); - this._maybeFireNegotiationNeeded(); - } - }; +exports.ERROR = 4; - // Determines the intersection of local and remote capabilities. - window.RTCPeerConnection.prototype._getCommonCapabilities = - function(localCapabilities, remoteCapabilities) { - var commonCapabilities = { - codecs: [], - headerExtensions: [], - fecMechanisms: [] - }; - 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 && - lCodec.numChannels === rCodec.numChannels) { - // push rCodec so we reply with offerer payload type - commonCapabilities.codecs.push(rCodec); - - // FIXME: also need to determine intersection between - // .rtcpFeedback and .parameters - break; - } - } - }); +/** + * Packet type 'binary event' + * + * @api public + */ - 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; - } - } - }); +exports.BINARY_EVENT = 5; - // FIXME: fecMechanisms - return commonCapabilities; - }; +/** + * Packet type `binary ack`. For acks with binary arguments. + * + * @api public + */ - // Create ICE gatherer, ICE transport and DTLS transport. - window.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 = {}; - event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; - - var cand = evt.candidate; - // Edge emits an empty object for RTCIceCandidateComplete‥ - if (!cand || Object.keys(cand).length === 0) { - // polyfill since RTCIceGatherer.state is not implemented in Edge 10547 yet. - if (iceGatherer.state === undefined) { - iceGatherer.state = 'completed'; - } +exports.BINARY_ACK = 6; - // Emit a candidate with type endOfCandidates to make the samples work. - // Edge requires addIceCandidate with this empty candidate to start checking. - // The real solution is to signal end-of-candidates to the other side when - // getting the null candidate but some apps (like the samples) don't do that. - event.candidate.candidate = - 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; - } else { - // RTCIceCandidate doesn't have a component, needs to be added - cand.component = iceTransport.component === 'RTCP' ? 2 : 1; - event.candidate.candidate = SDPUtils.writeCandidate(cand); - } +/** + * Encoder constructor. + * + * @api public + */ - var complete = self.transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'completed'; - }); - // FIXME: update .localDescription with candidate and (potentially) end-of-candidates. - // To make this harder, the gatherer might emit candidates before localdescription - // is set. To make things worse, gather.getLocalCandidates still errors in - // Edge 10547 when no candidates have been gathered yet. +exports.Encoder = Encoder; - if (self.onicecandidate !== null) { - // Emit candidate if localDescription is set. - // Also emits null candidate when all gatherers are complete. - if (self.localDescription && self.localDescription.type === '') { - self._localIceCandidatesBuffer.push(event); - if (complete) { - self._localIceCandidatesBuffer.push({}); - } - } else { - self.onicecandidate(event); - if (complete) { - self.onicecandidate({}); - } - } - } - }; - iceTransport.onicestatechange = function() { - self._updateConnectionState(); - }; +/** + * Decoder constructor. + * + * @api public + */ - 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(); - }; +exports.Decoder = Decoder; - return { - iceGatherer: iceGatherer, - iceTransport: iceTransport, - dtlsTransport: dtlsTransport - }; - }; +/** + * A socket.io Encoder instance + * + * @api public + */ - // Start the RTP Sender and Receiver for a transceiver. - window.RTCPeerConnection.prototype._transceive = function(transceiver, - send, recv) { - var params = this._getCommonCapabilities(transceiver.localCapabilities, - transceiver.remoteCapabilities); - if (send && transceiver.rtpSender) { - params.encodings = [{ - ssrc: transceiver.sendSsrc - }]; - params.rtcp = { - cname: SDPUtils.localCName, - ssrc: transceiver.recvSsrc - }; - transceiver.rtpSender.send(params); - } - if (recv && transceiver.rtpReceiver) { - params.encodings = [{ - ssrc: transceiver.recvSsrc - }]; - params.rtcp = { - cname: transceiver.cname, - ssrc: transceiver.sendSsrc - }; - transceiver.rtpReceiver.receive(params); - } - }; +function Encoder() {} - window.RTCPeerConnection.prototype.setLocalDescription = - function(description) { - var self = this; - if (description.type === 'offer') { - if (!this._pendingOffer) { - } else { - this.transceivers = this._pendingOffer; - delete this._pendingOffer; - } - } else if (description.type === 'answer') { - var sections = SDPUtils.splitSections(self.remoteDescription.sdp); - var sessionpart = sections.shift(); - 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) { - var remoteIceParameters = SDPUtils.getIceParameters(mediaSection, - sessionpart); - iceTransport.start(iceGatherer, remoteIceParameters, 'controlled'); - - var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, - sessionpart); - 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); - } - }); - } +/** + * Encode a packet as a single string if non-binary, or as a + * buffer sequence, depending on packet type. + * + * @param {Object} obj - packet object + * @param {Function} callback - function to handle encodings (likely engine.write) + * @return Calls callback with Array of encodings + * @api public + */ - this.localDescription = description; - 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 + '"'); - } +Encoder.prototype.encode = function(obj, callback){ + debug('encoding packet %j', obj); - // 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(); - self._emitBufferedCandidates(); - }, 0); - } - var p = Promise.resolve(); - p.then(function() { - if (!hasCallback) { - window.setTimeout(self._emitBufferedCandidates.bind(self), 0); - } - }); - return p; - }; + if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { + encodeAsBinary(obj, callback); + } + else { + var encoding = encodeAsString(obj); + callback([encoding]); + } +}; - window.RTCPeerConnection.prototype.setRemoteDescription = - function(description) { - var self = this; - var stream = new MediaStream(); - var sections = SDPUtils.splitSections(description.sdp); - var sessionpart = sections.shift(); - 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 transceiver; - var iceGatherer; - var iceTransport; - var dtlsTransport; - var rtpSender; - var rtpReceiver; - var sendSsrc; - var recvSsrc; - var localCapabilities; - - // 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); - } - var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0].substr(6); - - 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) { - recvSsrc = parseInt(remoteSsrc.ssrc, 10); - cname = remoteSsrc.value; - } +/** + * Encode packet as string. + * + * @param {Object} packet + * @return {String} encoded + * @api private + */ + +function encodeAsString(obj) { + var str = ''; + var nsp = false; + + // first is type + str += obj.type; + + // attachments if we have them + if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) { + str += obj.attachments; + str += '-'; + } + + // if we have a namespace other than `/` + // we append it followed by a comma `,` + if (obj.nsp && '/' != obj.nsp) { + nsp = true; + str += obj.nsp; + } + + // immediately followed by the id + if (null != obj.id) { + if (nsp) { + str += ','; + nsp = false; + } + str += obj.id; + } - if (description.type === 'offer') { - var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + // json data + if (null != obj.data) { + if (nsp) str += ','; + str += json.stringify(obj.data); + } - localCapabilities = RTCRtpReceiver.getCapabilities(kind); - sendSsrc = (2 * sdpMLineIndex + 2) * 1001; + debug('encoded %j as %s', obj, str); + return str; +} - rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); +/** + * Encode packet as 'buffer sequence' by removing blobs, and + * deconstructing packet into object with placeholders and + * a list of buffers. + * + * @param {Object} packet + * @return {Buffer} encoded + * @api private + */ - // FIXME: not correct when there are multiple streams but that is - // not currently supported in this shim. - stream.addTrack(rtpReceiver.track); +function encodeAsBinary(obj, callback) { - // FIXME: look at direction. - if (self.localStreams.length > 0 && - self.localStreams[0].getTracks().length >= sdpMLineIndex) { - // FIXME: actually more complicated, needs to match types etc - var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex]; - rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport); - } + function writeEncoding(bloblessData) { + var deconstruction = binary.deconstructPacket(bloblessData); + var pack = encodeAsString(deconstruction.packet); + var buffers = deconstruction.buffers; - self.transceivers[sdpMLineIndex] = { - iceGatherer: transports.iceGatherer, - iceTransport: transports.iceTransport, - dtlsTransport: transports.dtlsTransport, - localCapabilities: localCapabilities, - remoteCapabilities: remoteCapabilities, - rtpSender: rtpSender, - rtpReceiver: rtpReceiver, - kind: kind, - mid: mid, - cname: cname, - sendSsrc: sendSsrc, - recvSsrc: recvSsrc - }; - // 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) { - transceiver = self.transceivers[sdpMLineIndex]; - iceGatherer = transceiver.iceGatherer; - iceTransport = transceiver.iceTransport; - dtlsTransport = transceiver.dtlsTransport; - rtpSender = transceiver.rtpSender; - rtpReceiver = transceiver.rtpReceiver; - sendSsrc = transceiver.sendSsrc; - //recvSsrc = transceiver.recvSsrc; - localCapabilities = transceiver.localCapabilities; - - self.transceivers[sdpMLineIndex].recvSsrc = recvSsrc; - self.transceivers[sdpMLineIndex].remoteCapabilities = - remoteCapabilities; - self.transceivers[sdpMLineIndex].cname = cname; - - 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')) { - stream.addTrack(rtpReceiver.track); - } else { - // FIXME: actually the receiver should be created later. - delete transceiver.rtpReceiver; - } - } - }); + buffers.unshift(pack); // add packet info to beginning of data list + callback(buffers); // write all the buffers + } - this.remoteDescription = description; - 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 + '"'); - } - window.setTimeout(function() { - if (self.onaddstream !== null && stream.getTracks().length) { - self.remoteStreams.push(stream); - window.setTimeout(function() { - self.onaddstream({stream: stream}); - }, 0); - } - }, 0); - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); - } - return Promise.resolve(); - }; + binary.removeBlobs(obj, writeEncoding); +} - window.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'); - }; +/** + * A socket.io Decoder instance + * + * @return {Object} decoder + * @api public + */ - // Update the signaling state. - window.RTCPeerConnection.prototype._updateSignalingState = - function(newState) { - this.signalingState = newState; - if (this.onsignalingstatechange !== null) { - this.onsignalingstatechange(); - } - }; +function Decoder() { + this.reconstructor = null; +} - // Determine whether to fire the negotiationneeded event. - window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = - function() { - // Fire away (for now). - if (this.onnegotiationneeded !== null) { - this.onnegotiationneeded(); - } - }; +/** + * Mix in `Emitter` with Decoder. + */ - // Update the connection state. - window.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['connecting'] > 0 || states['completed'] > 0) { - newState = 'connected'; - } +Emitter(Decoder.prototype); - if (newState !== self.iceConnectionState) { - self.iceConnectionState = newState; - if (this.oniceconnectionstatechange !== null) { - this.oniceconnectionstatechange(); - } - } - }; +/** + * Decodes an ecoded packet string into packet JSON. + * + * @param {String} obj - encoded packet + * @return {Object} packet + * @api public + */ - window.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]; - } +Decoder.prototype.add = function(obj) { + var packet; + if ('string' == typeof obj) { + packet = decodeString(obj); + if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json + this.reconstructor = new BinaryReconstructor(packet); - var tracks = []; - var numAudioTracks = 0; - var numVideoTracks = 0; - // Default to sendrecv. - if (this.localStreams.length) { - numAudioTracks = this.localStreams[0].getAudioTracks().length; - numVideoTracks = this.localStreams[0].getVideoTracks().length; - } - // 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; - } - } - if (this.localStreams.length) { - // Push local streams. - this.localStreams[0].getTracks().forEach(function(track) { - tracks.push({ - kind: track.kind, - track: track, - wantReceive: track.kind === 'audio' ? - numAudioTracks > 0 : numVideoTracks > 0 - }); - if (track.kind === 'audio') { - numAudioTracks--; - } else if (track.kind === 'video') { - numVideoTracks--; - } - }); + // no attachments, labeled binary but no binary data to follow + if (this.reconstructor.reconPack.attachments === 0) { + this.emit('decoded', packet); } - // 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--; - } + } else { // non-binary full packet + this.emit('decoded', packet); + } + } + else if (isBuf(obj) || obj.base64) { // raw binary data + if (!this.reconstructor) { + throw new Error('got binary data when not reconstructing a packet'); + } else { + packet = this.reconstructor.takeBinaryData(obj); + if (packet) { // received final buffer + this.reconstructor = null; + this.emit('decoded', packet); } + } + } + else { + throw new Error('Unknown type: ' + obj); + } +}; - 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._createIceAndDtlsTransports(mid, sdpMLineIndex); - - var localCapabilities = RTCRtpSender.getCapabilities(kind); - var rtpSender; - var rtpReceiver; - - // generate an ssrc now, to be used later in rtpSender.send - var sendSsrc = (2 * sdpMLineIndex + 1) * 1001; - if (track) { - 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, - sendSsrc: sendSsrc, - recvSsrc: null - }; - var transceiver = transceivers[sdpMLineIndex]; - sdp += SDPUtils.writeMediaSection(transceiver, - transceiver.localCapabilities, 'offer', self.localStreams[0]); - }); - - 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); - }; +/** + * Decode a packet String (JSON data) + * + * @param {String} str + * @return {Object} packet + * @api private + */ - window.RTCPeerConnection.prototype.createAnswer = function() { - var self = this; - var answerOptions; - if (arguments.length === 1 && typeof arguments[0] !== 'function') { - answerOptions = arguments[0]; - } else if (arguments.length === 3) { - answerOptions = arguments[2]; - } +function decodeString(str) { + var p = {}; + var i = 0; - var sdp = SDPUtils.writeSessionBoilerplate(); - this.transceivers.forEach(function(transceiver) { - // Calculate intersection of capabilities. - var commonCapabilities = self._getCommonCapabilities( - transceiver.localCapabilities, - transceiver.remoteCapabilities); + // look up type + p.type = Number(str.charAt(0)); + if (null == exports.types[p.type]) return error(); - sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities, - 'answer', self.localStreams[0]); - }); + // look up attachments if type binary + if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) { + var buf = ''; + while (str.charAt(++i) != '-') { + buf += str.charAt(i); + if (i == str.length) break; + } + if (buf != Number(buf) || str.charAt(i) != '-') { + throw new Error('Illegal attachments'); + } + p.attachments = Number(buf); + } - 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); - }; + // look up namespace (if any) + if ('/' == str.charAt(i + 1)) { + p.nsp = ''; + while (++i) { + var c = str.charAt(i); + if (',' == c) break; + p.nsp += c; + if (i == str.length) break; + } + } else { + p.nsp = '/'; + } - window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { - 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) { - return; - } - // Ignore RTCP candidates, we assume RTCP-MUX. - if (cand.component !== '1') { - return; - } - // A dirty hack to make samples work. - if (cand.type === 'endOfCandidates') { - cand = {}; - } - transceiver.iceTransport.addRemoteCandidate(cand); - } - if (arguments.length > 1 && typeof arguments[1] === 'function') { - window.setTimeout(arguments[1], 0); + // look up id + var next = str.charAt(i + 1); + if ('' !== next && Number(next) == next) { + p.id = ''; + while (++i) { + var c = str.charAt(i); + if (null == c || Number(c) != c) { + --i; + break; } - return Promise.resolve(); - }; - - window.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]; - return new Promise(function(resolve) { - var results = {}; - Promise.all(promises).then(function(res) { - res.forEach(function(result) { - Object.keys(result).forEach(function(id) { - results[id] = result[id]; - }); - }); - if (cb) { - window.setTimeout(cb, 0, results); - } - resolve(results); - }); - }); - }; - }, - - // Attach a media stream to an element. - attachMediaStream: function(element, stream) { - logging('DEPRECATED, attachMediaStream will soon be removed.'); - element.srcObject = stream; - }, + p.id += str.charAt(i); + if (i == str.length) break; + } + p.id = Number(p.id); + } - reattachMediaStream: function(to, from) { - logging('DEPRECATED, reattachMediaStream will soon be removed.'); - to.srcObject = from.srcObject; + // look up json data + if (str.charAt(++i)) { + try { + p.data = json.parse(str.substr(i)); + } catch(e){ + return error(); + } } -} -// Expose public methods. -module.exports = { - shimPeerConnection: edgeShim.shimPeerConnection, - attachMediaStream: edgeShim.attachMediaStream, - reattachMediaStream: edgeShim.reattachMediaStream + debug('decoded %s as %j', str, p); + return p; } - -},{"../utils":46,"./edge_sdp":64}],57:[function(require,module,exports){ -/** - * lodash 3.0.0 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.7.0 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license - */ - /** - * A specialized version of `_.forEach` for arrays without support for callback - * shorthands or `this` binding. + * Deallocates a parser's resources * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. - */ -function arrayEach(array, iteratee) { - var index = -1, - length = array.length; - - while (++index < length) { - if (iteratee(array[index], index, array) === false) { - break; - } - } - return array; -} + * @api public + */ -module.exports = arrayEach; +Decoder.prototype.destroy = function() { + if (this.reconstructor) { + this.reconstructor.finishedReconstruction(); + } +}; -},{}],58:[function(require,module,exports){ /** - * lodash 3.0.1 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license + * A manager of a binary event's 'buffer sequence'. Should + * be constructed whenever a packet of type BINARY_EVENT is + * decoded. + * + * @param {Object} packet + * @return {BinaryReconstructor} initialized reconstructor + * @api private */ +function BinaryReconstructor(packet) { + this.reconPack = packet; + this.buffers = []; +} + /** - * A specialized version of `baseCallback` which only supports `this` binding - * and specifying the number of arguments to provide to `func`. + * Method to be called when binary data received from connection + * after a BINARY_EVENT packet. * - * @private - * @param {Function} func The function to bind. - * @param {*} thisArg The `this` binding of `func`. - * @param {number} [argCount] The number of arguments to provide to `func`. - * @returns {Function} Returns the callback. + * @param {Buffer | ArrayBuffer} binData - the raw binary data received + * @return {null | Object} returns null if more binary data is expected or + * a reconstructed packet object if all buffers have been received. + * @api private */ -function bindCallback(func, thisArg, argCount) { - if (typeof func != 'function') { - return identity; - } - if (thisArg === undefined) { - return func; - } - switch (argCount) { - case 1: return function(value) { - return func.call(thisArg, value); - }; - case 3: return function(value, index, collection) { - return func.call(thisArg, value, index, collection); - }; - case 4: return function(accumulator, value, index, collection) { - return func.call(thisArg, accumulator, value, index, collection); - }; - case 5: return function(value, other, key, object, source) { - return func.call(thisArg, value, other, key, object, source); - }; + +BinaryReconstructor.prototype.takeBinaryData = function(binData) { + this.buffers.push(binData); + if (this.buffers.length == this.reconPack.attachments) { // done with buffer list + var packet = binary.reconstructPacket(this.reconPack, this.buffers); + this.finishedReconstruction(); + return packet; } - return function() { - return func.apply(thisArg, arguments); - }; -} + return null; +}; /** - * This method returns the first argument provided to it. - * - * @static - * @memberOf _ - * @category Utility - * @param {*} value Any value. - * @returns {*} Returns `value`. - * @example - * - * var object = { 'user': 'fred' }; + * Cleans up binary packet reconstruction variables. * - * _.identity(object) === object; - * // => true + * @api private */ -function identity(value) { - return value; -} -module.exports = bindCallback; +BinaryReconstructor.prototype.finishedReconstruction = function() { + this.reconPack = null; + this.buffers = []; +}; + +function error(data){ + return { + type: exports.ERROR, + data: 'parser error' + }; +} -},{}],62:[function(require,module,exports){ +},{"./binary":60,"./is-buffer":57,"component-emitter":49,"debug":35,"isarray":58,"json3":59}],45:[function(require,module,exports){ /** - * lodash 3.7.2 (Custom Build) + * lodash 3.0.4 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ +var keys = require('lodash.keys'); /** - * The base implementation of `get` without support for string paths - * and default values. - * - * @private - * @param {Object} object The object to query. - * @param {Array} path The path of the property to get. - * @param {string} [pathKey] The key representation of path. - * @returns {*} Returns the resolved value. + * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) + * of an array-like value. */ -function baseGet(object, path, pathKey) { - if (object == null) { - return; - } - if (pathKey !== undefined && pathKey in toObject(object)) { - path = [pathKey]; - } - var index = 0, - length = path.length; - - while (object != null && index < length) { - object = object[path[index++]]; - } - return (index && index == length) ? object : undefined; -} +var MAX_SAFE_INTEGER = 9007199254740991; /** - * Converts `value` to an object if it's not one. + * The base implementation of `_.forEach` without support for callback + * shorthands and `this` binding. * * @private - * @param {*} value The value to process. - * @returns {Object} Returns the object. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object|string} Returns `collection`. */ -function toObject(value) { - return isObject(value) ? value : Object(value); -} +var baseEach = createBaseEach(baseForOwn); /** - * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. - * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true + * The base implementation of `baseForIn` and `baseForOwn` which iterates + * over `object` properties returned by `keysFunc` invoking `iteratee` for + * each property. Iteratee functions may exit iteration early by explicitly + * returning `false`. * - * _.isObject(1); - * // => false + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. */ -function isObject(value) { - // Avoid a V8 JIT bug in Chrome 19-20. - // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. - var type = typeof value; - return !!value && (type == 'object' || type == 'function'); -} - -module.exports = baseGet; +var baseFor = createBaseFor(); -},{}],60:[function(require,module,exports){ /** - * lodash 3.0.4 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license + * The base implementation of `_.forOwn` without support for callback + * shorthands and `this` binding. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. */ - -/** `Object#toString` result references. */ -var arrayTag = '[object Array]', - funcTag = '[object Function]'; - -/** Used to detect host constructors (Safari > 5). */ -var reIsHostCtor = /^\[object .+?Constructor\]$/; +function baseForOwn(object, iteratee) { + return baseFor(object, iteratee, keys); +} /** - * Checks if `value` is object-like. + * The base implementation of `_.property` without support for deep paths. * * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. */ -function isObjectLike(value) { - return !!value && typeof value == 'object'; +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; } -/** Used for native method references. */ -var objectProto = Object.prototype; - -/** Used to resolve the decompiled source of functions. */ -var fnToString = Function.prototype.toString; - -/** Used to check objects for own properties. */ -var hasOwnProperty = objectProto.hasOwnProperty; - /** - * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) - * of values. + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. */ -var objToString = objectProto.toString; - -/** Used to detect if a method is native. */ -var reIsNative = RegExp('^' + - fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') - .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' -); +function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + var length = collection ? getLength(collection) : 0; + if (!isLength(length)) { + return eachFunc(collection, iteratee); + } + var index = fromRight ? length : -1, + iterable = toObject(collection); -/* Native method references for those with the same name as other `lodash` methods. */ -var nativeIsArray = getNative(Array, 'isArray'); + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; +} /** - * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) - * of an array-like value. + * Creates a base function for `_.forIn` or `_.forInRight`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. */ -var MAX_SAFE_INTEGER = 9007199254740991; +function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var iterable = toObject(object), + props = keysFunc(object), + length = props.length, + index = fromRight ? length : -1; + + while ((fromRight ? index-- : ++index < length)) { + var key = props[index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; +} /** - * Gets the native function at `key` of `object`. + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. * * @private * @param {Object} object The object to query. - * @param {string} key The key of the method to get. - * @returns {*} Returns the function if it's native, else `undefined`. + * @returns {*} Returns the "length" value. */ -function getNative(object, key) { - var value = object == null ? undefined : object[key]; - return isNative(value) ? value : undefined; -} +var getLength = baseProperty('length'); /** * Checks if `value` is a valid array-like length. * - * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). * * @private * @param {*} value The value to check. @@ -11700,46 +10229,14 @@ function isLength(value) { } /** - * Checks if `value` is classified as an `Array` object. - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. - * @example - * - * _.isArray([1, 2, 3]); - * // => true - * - * _.isArray(function() { return arguments; }()); - * // => false - */ -var isArray = nativeIsArray || function(value) { - return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; -}; - -/** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. - * @example - * - * _.isFunction(_); - * // => true + * Converts `value` to an object if it's not one. * - * _.isFunction(/abc/); - * // => false + * @private + * @param {*} value The value to process. + * @returns {Object} Returns the object. */ -function isFunction(value) { - // The use of `Object#toString` avoids issues with the `typeof` operator - // in older versions of Chrome and Safari which return 'function' for regexes - // and Safari 8 equivalents which return 'object' for typed array constructors. - return isObject(value) && objToString.call(value) == funcTag; +function toObject(value) { + return isObject(value) ? value : Object(value); } /** @@ -11769,35 +10266,13 @@ function isObject(value) { return !!value && (type == 'object' || type == 'function'); } -/** - * Checks if `value` is a native function. - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, else `false`. - * @example - * - * _.isNative(Array.prototype.push); - * // => true - * - * _.isNative(_); - * // => false - */ -function isNative(value) { - if (value == null) { - return false; - } - if (isFunction(value)) { - return reIsNative.test(fnToString.call(value)); - } - return isObjectLike(value) && reIsHostCtor.test(value); -} +module.exports = baseEach; -module.exports = isArray; +},{"lodash.keys":61}],50:[function(require,module,exports){ -},{}],51:[function(require,module,exports){ +module.exports = require('./lib/'); + +},{"./lib/":62}],60:[function(require,module,exports){ var global=self;/*global Blob,File*/ /** @@ -11940,794 +10415,831 @@ exports.removeBlobs = function(data, callback) { } }; -},{"./is-buffer":45,"isarray":52}],34:[function(require,module,exports){ +},{"./is-buffer":57,"isarray":58}],56:[function(require,module,exports){ +var global=self; +/* + * Module requirements. + */ + +var isArray = require('isarray'); + +/** + * Module exports. + */ + +module.exports = hasBinary; + +/** + * Checks for binary data. + * + * Right now only Buffer and ArrayBuffer are supported.. + * + * @param {Object} anything + * @api public + */ + +function hasBinary(data) { + + function _hasBinary(obj) { + if (!obj) return false; + + if ( (global.Buffer && global.Buffer.isBuffer(obj)) || + (global.ArrayBuffer && obj instanceof ArrayBuffer) || + (global.Blob && obj instanceof Blob) || + (global.File && obj instanceof File) + ) { + return true; + } + + if (isArray(obj)) { + for (var i = 0; i < obj.length; i++) { + if (_hasBinary(obj[i])) { + return true; + } + } + } else if (obj && 'object' == typeof obj) { + if (obj.toJSON) { + obj = obj.toJSON(); + } + + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) { + return true; + } + } + } + + return false; + } + + return _hasBinary(data); +} + +},{"isarray":63}],64:[function(require,module,exports){ +/** + * lodash 3.0.0 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.7.0 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** + * A specialized version of `_.map` for arrays without support for callback + * shorthands or `this` binding. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ +function arrayMap(array, iteratee) { + var index = -1, + length = array.length, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; +} + +module.exports = arrayMap; + +},{}],39:[function(require,module,exports){ +/** + * lodash 3.8.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var isArray = require('lodash.isarray'); + +/** Used to match property names within property paths. */ +var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g; + +/** Used to match backslashes in property paths. */ +var reEscapeChar = /\\(\\)?/g; + +/** + * Converts `value` to a string if it's not one. An empty string is returned + * for `null` or `undefined` values. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ +function baseToString(value) { + return value == null ? '' : (value + ''); +} + +/** + * Converts `value` to property path array if it's not one. + * + * @private + * @param {*} value The value to process. + * @returns {Array} Returns the property path array. + */ +function toPath(value) { + if (isArray(value)) { + return value; + } + var result = []; + baseToString(value).replace(rePropName, function(match, number, quote, string) { + result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; +} + +module.exports = toPath; + +},{"lodash.isarray":40}],41:[function(require,module,exports){ +/** + * lodash 3.1.4 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var arrayMap = require('lodash._arraymap'), + baseCallback = require('lodash._basecallback'), + baseEach = require('lodash._baseeach'), + isArray = require('lodash.isarray'); /** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. + * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) + * of an array-like value. */ - -exports = module.exports = debug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = require('ms'); +var MAX_SAFE_INTEGER = 9007199254740991; /** - * The currently active debug mode names, and names to skip. + * The base implementation of `_.map` without support for callback shorthands + * and `this` binding. + * + * @private + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. */ +function baseMap(collection, iteratee) { + var index = -1, + result = isArrayLike(collection) ? Array(collection.length) : []; -exports.names = []; -exports.skips = []; + baseEach(collection, function(value, key, collection) { + result[++index] = iteratee(value, key, collection); + }); + return result; +} /** - * Map of special "%n" handling functions, for the debug "format" argument. + * The base implementation of `_.property` without support for deep paths. * - * Valid key names are a single, lowercased letter, i.e. "n". + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. */ - -exports.formatters = {}; +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} /** - * Previously assigned color. + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. */ - -var prevColor = 0; +var getLength = baseProperty('length'); /** - * Previous log timestamp. + * Checks if `value` is array-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. */ - -var prevTime; +function isArrayLike(value) { + return value != null && isLength(getLength(value)); +} /** - * Select a color. + * Checks if `value` is a valid array-like length. * - * @return {Number} - * @api private + * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ - -function selectColor() { - return exports.colors[prevColor++ % exports.colors.length]; +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** - * Create a debugger with the given `namespace`. + * Creates an array of values by running each element in `collection` through + * `iteratee`. The `iteratee` is bound to `thisArg` and invoked with three + * arguments: (value, index|key, collection). * - * @param {String} namespace - * @return {Function} - * @api public + * If a property name is provided for `iteratee` the created `_.property` + * style callback returns the property value of the given element. + * + * If a value is also provided for `thisArg` the created `_.matchesProperty` + * style callback returns `true` for elements that have a matching property + * value, else `false`. + * + * If an object is provided for `iteratee` the created `_.matches` style + * callback returns `true` for elements that have the properties of the given + * object, else `false`. + * + * Many lodash methods are guarded to work as iteratees for methods like + * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`. + * + * The guarded methods are: + * `ary`, `callback`, `chunk`, `clone`, `create`, `curry`, `curryRight`, + * `drop`, `dropRight`, `every`, `fill`, `flatten`, `invert`, `max`, `min`, + * `parseInt`, `slice`, `sortBy`, `take`, `takeRight`, `template`, `trim`, + * `trimLeft`, `trimRight`, `trunc`, `random`, `range`, `sample`, `some`, + * `sum`, `uniq`, and `words` + * + * @static + * @memberOf _ + * @alias collect + * @category Collection + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [iteratee=_.identity] The function invoked + * per iteration. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @returns {Array} Returns the new mapped array. + * @example + * + * function timesThree(n) { + * return n * 3; + * } + * + * _.map([1, 2], timesThree); + * // => [3, 6] + * + * _.map({ 'a': 1, 'b': 2 }, timesThree); + * // => [3, 6] (iteration order is not guaranteed) + * + * var users = [ + * { 'user': 'barney' }, + * { 'user': 'fred' } + * ]; + * + * // using the `_.property` callback shorthand + * _.map(users, 'user'); + * // => ['barney', 'fred'] */ +function map(collection, iteratee, thisArg) { + var func = isArray(collection) ? arrayMap : baseMap; + iteratee = baseCallback(iteratee, thisArg, 3); + return func(collection, iteratee); +} -function debug(namespace) { - - // define the `disabled` version - function disabled() { - } - disabled.enabled = false; - - // define the `enabled` version - function enabled() { - - var self = enabled; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // add the `color` if not set - if (null == self.useColors) self.useColors = exports.useColors(); - if (null == self.color && self.useColors) self.color = selectColor(); - - var args = Array.prototype.slice.call(arguments); +module.exports = map; - args[0] = exports.coerce(args[0]); +},{"lodash._arraymap":64,"lodash._basecallback":65,"lodash._baseeach":66,"lodash.isarray":40}],63:[function(require,module,exports){ +module.exports = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; +}; - if ('string' !== typeof args[0]) { - // anything else let's inspect with %o - args = ['%o'].concat(args); - } +},{}],67:[function(require,module,exports){ +/** + * lodash 3.0.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-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); +/** + * A specialized version of `baseCallback` which only supports `this` binding + * and specifying the number of arguments to provide to `func`. + * + * @private + * @param {Function} func The function to bind. + * @param {*} thisArg The `this` binding of `func`. + * @param {number} [argCount] The number of arguments to provide to `func`. + * @returns {Function} Returns the callback. + */ +function bindCallback(func, thisArg, argCount) { + if (typeof func != 'function') { + return identity; + } + if (thisArg === undefined) { + return func; + } + switch (argCount) { + case 1: return function(value) { + return func.call(thisArg, value); + }; + case 3: return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + case 5: return function(value, other, key, object, source) { + return func.call(thisArg, value, other, key, object, source); + }; + } + return function() { + return func.apply(thisArg, arguments); + }; +} - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); +/** + * This method returns the first argument provided to it. + * + * @static + * @memberOf _ + * @category Utility + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'user': 'fred' }; + * + * _.identity(object) === object; + * // => true + */ +function identity(value) { + return value; +} - if ('function' === typeof exports.formatArgs) { - args = exports.formatArgs.apply(self, args); - } - var logFn = enabled.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - enabled.enabled = true; +module.exports = bindCallback; - var fn = exports.enabled(namespace) ? enabled : disabled; +},{}],68:[function(require,module,exports){ +/** + * lodash 3.9.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ - fn.namespace = namespace; +/** `Object#toString` result references. */ +var funcTag = '[object Function]'; - return fn; -} +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. + * Checks if `value` is object-like. * - * @param {String} namespaces - * @api public + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} -function enable(namespaces) { - exports.save(namespaces); +/** Used for native method references. */ +var objectProto = Object.prototype; - var split = (namespaces || '').split(/[\s,]+/); - var len = split.length; +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; - 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 + '$')); - } - } -} +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; /** - * Disable debug output. - * - * @api public + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. */ +var objToString = objectProto.toString; -function disable() { - exports.enable(''); -} +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); /** - * Returns true if the given mode name is enabled, false otherwise. + * Gets the native function at `key` of `object`. * - * @param {String} name - * @return {Boolean} - * @api public + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. */ - -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; +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; } /** - * Coerce `val`. + * Checks if `value` is classified as a `Function` object. * - * @param {Mixed} val - * @return {Mixed} - * @api private + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 equivalents which return 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; } -},{"ms":65}],42:[function(require,module,exports){ -var global=self; -/* - * Module requirements. - */ - -var isArray = require('isarray'); - -/** - * Module exports. - */ - -module.exports = hasBinary; - /** - * Checks for binary data. + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * - * Right now only Buffer and ArrayBuffer are supported.. + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example * - * @param {Object} anything - * @api public + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false */ - -function hasBinary(data) { - - function _hasBinary(obj) { - if (!obj) return false; - - if ( (global.Buffer && global.Buffer.isBuffer && global.Buffer.isBuffer(obj)) || - (global.ArrayBuffer && obj instanceof ArrayBuffer) || - (global.Blob && obj instanceof Blob) || - (global.File && obj instanceof File) - ) { - return true; - } - - if (isArray(obj)) { - for (var i = 0; i < obj.length; i++) { - if (_hasBinary(obj[i])) { - return true; - } - } - } else if (obj && 'object' == typeof obj) { - // see: https://github.com/Automattic/has-binary/pull/4 - if (obj.toJSON && 'function' == typeof obj.toJSON) { - obj = obj.toJSON(); - } - - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) { - return true; - } - } - } - - return false; - } - - return _hasBinary(data); +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); } -},{"isarray":52}],64:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. +/** + * Checks if `value` is a native function. * - * 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'; - -// 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('\r\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: parts[1], - 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: // Unknown extensions are silently ignored. - 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 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 - parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels - 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 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.writeFtmp = function(codec) { - var line = ''; - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - if (codec.parameters && 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'; + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ +function isNative(value) { + if (value == null) { + return false; } - 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 + - '\r\n'; - }); + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); } - return lines; -}; + return isObjectLike(value) && reIsHostCtor.test(value); +} -// 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: line.substr(7, sp - 7), - }; - 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 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.splitLines(mediaSection); - lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. - var fpLine = lines.filter(function(line) { - return line.indexOf('a=fingerprint:') === 0; - })[0].substr(14); - // Note: a=setup line is ignored since we use the 'auto' role. - var dtlsParameters = { - role: 'auto', - fingerprints: [{ - algorithm: fpLine.split(' ')[0], - value: fpLine.split(' ')[1] - }] - }; - return dtlsParameters; -}; +module.exports = getNative; -// 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); - lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. - 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; -}; +},{}],69:[function(require,module,exports){ +/** + * lodash 3.0.4 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ -// Serializes ICE parameters to SDP. -SDPUtils.writeIceParameters = function(params) { - return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + - 'a=ice-pwd:' + params.password + '\r\n'; -}; +/** + * Checks if `value` is object-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} -// 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: 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); - } - } - // FIXME: parse headerExtensions, fecMechanisms and 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.writeFtmp(codec); - sdp += SDPUtils.writeRtcpFb(codec); - }); - // FIXME: add headerExtensions, fecMechanismş and rtcp. - sdp += 'a=rtcp-mux\r\n'; - return sdp; -}; +/** Used for native method references. */ +var objectProto = Object.prototype; -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'; -}; +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; -SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { - var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); +/** Native method references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable; - // Map ICE parameters (ufrag, pwd) to SDP. - sdp += SDPUtils.writeIceParameters( - transceiver.iceGatherer.getLocalParameters()); +/** + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; - // Map DTLS parameters to SDP. - sdp += SDPUtils.writeDtlsParameters( - transceiver.dtlsTransport.getLocalParameters(), - type === 'offer' ? 'actpass' : 'active'); +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} - sdp += 'a=mid:' + transceiver.mid + '\r\n'; +/** + * Gets the "length" property value of `object`. + * + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. + */ +var getLength = baseProperty('length'); - 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'; - } +/** + * Checks if `value` is array-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + */ +function isArrayLike(value) { + return value != null && isLength(getLength(value)); +} - // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet. - if (transceiver.rtpSender) { - var msid = 'msid:' + stream.id + ' ' + - transceiver.rtpSender.track.id + '\r\n'; - sdp += 'a=' + msid; - sdp += 'a=ssrc:' + transceiver.sendSsrc + ' ' + msid; - } - // FIXME: this should be written by writeRtpDescription. - sdp += 'a=ssrc:' + transceiver.sendSsrc + ' cname:' + - SDPUtils.localCName + '\r\n'; - return sdp; -}; +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} -// 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); - } - } - if (sessionpart) { - return SDPUtils.getDirection(sessionpart); - } - return 'sendrecv'; -}; +/** + * Checks if `value` is classified as an `arguments` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +function isArguments(value) { + return isObjectLike(value) && isArrayLike(value) && + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); +} -// Expose public methods. -module.exports = SDPUtils; +module.exports = isArguments; -},{}],65:[function(require,module,exports){ +},{}],66:[function(require,module,exports){ /** - * Helpers. + * lodash 3.0.4 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license */ +var keys = require('lodash.keys'); -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; +/** + * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; /** - * Parse or format the given `val`. + * The base implementation of `_.forEach` without support for callback + * shorthands and `this` binding. * - * Options: + * @private + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object|string} Returns `collection`. + */ +var baseEach = createBaseEach(baseForOwn); + +/** + * The base implementation of `baseForIn` and `baseForOwn` which iterates + * over `object` properties returned by `keysFunc` invoking `iteratee` for + * each property. Iteratee functions may exit iteration early by explicitly + * returning `false`. * - * - `long` verbose formatting [false] + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ +var baseFor = createBaseFor(); + +/** + * The base implementation of `_.forOwn` without support for callback + * shorthands and `this` binding. * - * @param {String|Number} val - * @param {Object} options - * @return {String|Number} - * @api public + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. */ +function baseForOwn(object, iteratee) { + return baseFor(object, iteratee, keys); +} -module.exports = function(val, options){ - options = options || {}; - if ('string' == typeof val) return parse(val); - return options.long - ? long(val) - : short(val); -}; +/** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new function. + */ +function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; +} /** - * Parse the given `str` and return milliseconds. + * Creates a `baseEach` or `baseEachRight` function. * - * @param {String} str - * @return {Number} - * @api private + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. */ +function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + var length = collection ? getLength(collection) : 0; + if (!isLength(length)) { + return eachFunc(collection, iteratee); + } + var index = fromRight ? length : -1, + iterable = toObject(collection); -function parse(str) { - str = '' + 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; - } + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; } /** - * Short format for `ms`. + * Creates a base function for `_.forIn` or `_.forInRight`. * - * @param {Number} ms - * @return {String} - * @api private + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. */ +function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var iterable = toObject(object), + props = keysFunc(object), + length = props.length, + index = fromRight ? length : -1; -function short(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'; + while ((fromRight ? index-- : ++index < length)) { + var key = props[index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; } /** - * Long format for `ms`. + * Gets the "length" property value of `object`. * - * @param {Number} ms - * @return {String} - * @api private + * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) + * that affects Safari on at least iOS 8.1-8.3 ARM64. + * + * @private + * @param {Object} object The object to query. + * @returns {*} Returns the "length" value. */ +var getLength = baseProperty('length'); -function long(ms) { - return plural(ms, d, 'day') - || plural(ms, h, 'hour') - || plural(ms, m, 'minute') - || plural(ms, s, 'second') - || ms + ' ms'; +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** - * Pluralization helper. + * Converts `value` to an object if it's not one. + * + * @private + * @param {*} value The value to process. + * @returns {Object} Returns the object. */ +function toObject(value) { + return isObject(value) ? value : Object(value); +} -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'; +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); } -},{}],61:[function(require,module,exports){ +module.exports = baseEach; + +},{"lodash.keys":70}],62:[function(require,module,exports){ + +module.exports = require('./socket'); + /** - * lodash 3.8.1 (Custom Build) + * Exports parser + * + * @api public + * + */ +module.exports.parser = require('engine.io-parser'); + +},{"./socket":71,"engine.io-parser":72}],65:[function(require,module,exports){ +/** + * lodash 3.3.1 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ -var isArray = require('lodash.isarray'); +var baseIsEqual = require('lodash._baseisequal'), + bindCallback = require('lodash._bindcallback'), + isArray = require('lodash.isarray'), + pairs = require('lodash.pairs'); /** Used to match property names within property paths. */ -var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g; +var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/, + rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g; /** Used to match backslashes in property paths. */ var reEscapeChar = /\\(\\)?/g; @@ -12745,78 +11257,164 @@ function baseToString(value) { } /** - * Converts `value` to property path array if it's not one. + * The base implementation of `_.callback` which supports specifying the + * number of arguments to provide to `func`. * * @private - * @param {*} value The value to process. - * @returns {Array} Returns the property path array. + * @param {*} [func=_.identity] The value to convert to a callback. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {number} [argCount] The number of arguments to provide to `func`. + * @returns {Function} Returns the callback. */ -function toPath(value) { - if (isArray(value)) { - return value; +function baseCallback(func, thisArg, argCount) { + var type = typeof func; + if (type == 'function') { + return thisArg === undefined + ? func + : bindCallback(func, thisArg, argCount); } - var result = []; - baseToString(value).replace(rePropName, function(match, number, quote, string) { - result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match)); - }); - return result; + if (func == null) { + return identity; + } + if (type == 'object') { + return baseMatches(func); + } + return thisArg === undefined + ? property(func) + : baseMatchesProperty(func, thisArg); } -module.exports = toPath; - -},{"lodash.isarray":60}],59:[function(require,module,exports){ /** - * lodash 3.0.4 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license + * The base implementation of `get` without support for string paths + * and default values. + * + * @private + * @param {Object} object The object to query. + * @param {Array} path The path of the property to get. + * @param {string} [pathKey] The key representation of path. + * @returns {*} Returns the resolved value. */ -var keys = require('lodash.keys'); +function baseGet(object, path, pathKey) { + if (object == null) { + return; + } + if (pathKey !== undefined && pathKey in toObject(object)) { + path = [pathKey]; + } + var index = 0, + length = path.length; + + while (object != null && index < length) { + object = object[path[index++]]; + } + return (index && index == length) ? object : undefined; +} /** - * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) - * of an array-like value. + * The base implementation of `_.isMatch` without support for callback + * shorthands and `this` binding. + * + * @private + * @param {Object} object The object to inspect. + * @param {Array} matchData The propery names, values, and compare flags to match. + * @param {Function} [customizer] The function to customize comparing objects. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. */ -var MAX_SAFE_INTEGER = 9007199254740991; +function baseIsMatch(object, matchData, customizer) { + var index = matchData.length, + length = index, + noCustomizer = !customizer; + + if (object == null) { + return !length; + } + object = toObject(object); + while (index--) { + var data = matchData[index]; + if ((noCustomizer && data[2]) + ? data[1] !== object[data[0]] + : !(data[0] in object) + ) { + return false; + } + } + while (++index < length) { + data = matchData[index]; + var key = data[0], + objValue = object[key], + srcValue = data[1]; + + if (noCustomizer && data[2]) { + if (objValue === undefined && !(key in object)) { + return false; + } + } else { + var result = customizer ? customizer(objValue, srcValue, key) : undefined; + if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, true) : result)) { + return false; + } + } + } + return true; +} /** - * The base implementation of `_.forEach` without support for callback - * shorthands and `this` binding. + * The base implementation of `_.matches` which does not clone `source`. * * @private - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array|Object|string} Returns `collection`. + * @param {Object} source The object of property values to match. + * @returns {Function} Returns the new function. */ -var baseEach = createBaseEach(baseForOwn); +function baseMatches(source) { + var matchData = getMatchData(source); + if (matchData.length == 1 && matchData[0][2]) { + var key = matchData[0][0], + value = matchData[0][1]; + + return function(object) { + if (object == null) { + return false; + } + return object[key] === value && (value !== undefined || (key in toObject(object))); + }; + } + return function(object) { + return baseIsMatch(object, matchData); + }; +} /** - * The base implementation of `baseForIn` and `baseForOwn` which iterates - * over `object` properties returned by `keysFunc` invoking `iteratee` for - * each property. Iteratee functions may exit iteration early by explicitly - * returning `false`. + * The base implementation of `_.matchesProperty` which does not clone `srcValue`. * * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {Function} keysFunc The function to get the keys of `object`. - * @returns {Object} Returns `object`. + * @param {string} path The path of the property to get. + * @param {*} srcValue The value to compare. + * @returns {Function} Returns the new function. */ -var baseFor = createBaseFor(); +function baseMatchesProperty(path, srcValue) { + var isArr = isArray(path), + isCommon = isKey(path) && isStrictComparable(srcValue), + pathKey = (path + ''); -/** - * The base implementation of `_.forOwn` without support for callback - * shorthands and `this` binding. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Object} Returns `object`. - */ -function baseForOwn(object, iteratee) { - return baseFor(object, iteratee, keys); + path = toPath(path); + return function(object) { + if (object == null) { + return false; + } + var key = pathKey; + object = toObject(object); + if ((isArr || !isCommon) && !(key in object)) { + object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1)); + if (object == null) { + return false; + } + key = last(path); + object = toObject(object); + } + return object[key] === srcValue + ? (srcValue !== undefined || (key in object)) + : baseIsEqual(srcValue, object[key], undefined, true); + }; } /** @@ -12833,78 +11431,98 @@ function baseProperty(key) { } /** - * Creates a `baseEach` or `baseEachRight` function. + * A specialized version of `baseProperty` which supports deep paths. * * @private - * @param {Function} eachFunc The function to iterate over a collection. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new function. */ -function createBaseEach(eachFunc, fromRight) { - return function(collection, iteratee) { - var length = collection ? getLength(collection) : 0; - if (!isLength(length)) { - return eachFunc(collection, iteratee); - } - var index = fromRight ? length : -1, - iterable = toObject(collection); - - while ((fromRight ? index-- : ++index < length)) { - if (iteratee(iterable[index], index, iterable) === false) { - break; - } - } - return collection; +function basePropertyDeep(path) { + var pathKey = (path + ''); + path = toPath(path); + return function(object) { + return baseGet(object, path, pathKey); }; } /** - * Creates a base function for `_.forIn` or `_.forInRight`. + * The base implementation of `_.slice` without an iteratee call guard. * * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. + * @param {Array} array The array to slice. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the slice of `array`. */ -function createBaseFor(fromRight) { - return function(object, iteratee, keysFunc) { - var iterable = toObject(object), - props = keysFunc(object), - length = props.length, - index = fromRight ? length : -1; +function baseSlice(array, start, end) { + var index = -1, + length = array.length; - while ((fromRight ? index-- : ++index < length)) { - var key = props[index]; - if (iteratee(iterable[key], key, iterable) === false) { - break; - } - } - return object; - }; + start = start == null ? 0 : (+start || 0); + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = (end === undefined || end > length) ? length : (+end || 0); + if (end < 0) { + end += length; + } + length = start > end ? 0 : ((end - start) >>> 0); + start >>>= 0; + + var result = Array(length); + while (++index < length) { + result[index] = array[index + start]; + } + return result; } /** - * Gets the "length" property value of `object`. - * - * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) - * that affects Safari on at least iOS 8.1-8.3 ARM64. + * Gets the propery names, values, and compare flags of `object`. * * @private * @param {Object} object The object to query. - * @returns {*} Returns the "length" value. + * @returns {Array} Returns the match data of `object`. */ -var getLength = baseProperty('length'); +function getMatchData(object) { + var result = pairs(object), + length = result.length; + + while (length--) { + result[length][2] = isStrictComparable(result[length][1]); + } + return result; +} /** - * Checks if `value` is a valid array-like length. + * Checks if `value` is a property name and not a property path. * - * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + */ +function isKey(value, object) { + var type = typeof value; + if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') { + return true; + } + if (isArray(value)) { + return false; + } + var result = !reIsDeepProp.test(value); + return result || (object != null && value in toObject(object)); +} + +/** + * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. * * @private * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @returns {boolean} Returns `true` if `value` if suitable for strict + * equality comparisons, else `false`. */ -function isLength(value) { - return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +function isStrictComparable(value) { + return value === value && !isObject(value); } /** @@ -12918,6 +11536,42 @@ function toObject(value) { return isObject(value) ? value : Object(value); } +/** + * Converts `value` to property path array if it's not one. + * + * @private + * @param {*} value The value to process. + * @returns {Array} Returns the property path array. + */ +function toPath(value) { + if (isArray(value)) { + return value; + } + var result = []; + baseToString(value).replace(rePropName, function(match, number, quote, string) { + result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; +} + +/** + * Gets the last element of `array`. + * + * @static + * @memberOf _ + * @category Array + * @param {Array} array The array to query. + * @returns {*} Returns the last element of `array`. + * @example + * + * _.last([1, 2, 3]); + * // => 3 + */ +function last(array) { + var length = array ? array.length : 0; + return length ? array[length - 1] : undefined; +} + /** * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) @@ -12945,47 +11599,84 @@ function isObject(value) { return !!value && (type == 'object' || type == 'function'); } -module.exports = baseEach; +/** + * This method returns the first argument provided to it. + * + * @static + * @memberOf _ + * @category Utility + * @param {*} value Any value. + * @returns {*} Returns `value`. + * @example + * + * var object = { 'user': 'fred' }; + * + * _.identity(object) === object; + * // => true + */ +function identity(value) { + return value; +} -},{"lodash.keys":66}],63:[function(require,module,exports){ /** - * lodash 3.1.4 (Custom Build) + * Creates a function that returns the property value at `path` on a + * given object. + * + * @static + * @memberOf _ + * @category Utility + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new function. + * @example + * + * var objects = [ + * { 'a': { 'b': { 'c': 2 } } }, + * { 'a': { 'b': { 'c': 1 } } } + * ]; + * + * _.map(objects, _.property('a.b.c')); + * // => [2, 1] + * + * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c'); + * // => [1, 2] + */ +function property(path) { + return isKey(path) ? baseProperty(path) : basePropertyDeep(path); +} + +module.exports = baseCallback; + +},{"lodash._baseisequal":73,"lodash._bindcallback":67,"lodash.isarray":40,"lodash.pairs":74}],61:[function(require,module,exports){ +/** + * lodash 3.1.2 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ -var arrayMap = require('lodash._arraymap'), - baseCallback = require('lodash._basecallback'), - baseEach = require('lodash._baseeach'), +var getNative = require('lodash._getnative'), + isArguments = require('lodash.isarguments'), isArray = require('lodash.isarray'); +/** Used to detect unsigned integer values. */ +var reIsUint = /^\d+$/; + +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeKeys = getNative(Object, 'keys'); + /** - * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) * of an array-like value. */ var MAX_SAFE_INTEGER = 9007199254740991; -/** - * The base implementation of `_.map` without support for callback shorthands - * and `this` binding. - * - * @private - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ -function baseMap(collection, iteratee) { - var index = -1, - result = isArrayLike(collection) ? Array(collection.length) : []; - - baseEach(collection, function(value, key, collection) { - result[++index] = iteratee(value, key, collection); - }); - return result; -} - /** * The base implementation of `_.property` without support for deep paths. * @@ -13022,10 +11713,24 @@ function isArrayLike(value) { return value != null && isLength(getLength(value)); } +/** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ +function isIndex(value, length) { + value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; + length = length == null ? MAX_SAFE_INTEGER : length; + return value > -1 && value % 1 == 0 && value < length; +} + /** * Checks if `value` is a valid array-like length. * - * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). * * @private * @param {*} value The value to check. @@ -13036,335 +11741,451 @@ function isLength(value) { } /** - * Creates an array of values by running each element in `collection` through - * `iteratee`. The `iteratee` is bound to `thisArg` and invoked with three - * arguments: (value, index|key, collection). + * A fallback implementation of `Object.keys` which creates an array of the + * own enumerable property names of `object`. * - * If a property name is provided for `iteratee` the created `_.property` - * style callback returns the property value of the given element. + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ +function shimKeys(object) { + var props = keysIn(object), + propsLength = props.length, + length = propsLength && object.length; + + var allowIndexes = !!length && isLength(length) && + (isArray(object) || isArguments(object)); + + var index = -1, + result = []; + + while (++index < propsLength) { + var key = props[index]; + if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { + result.push(key); + } + } + return result; +} + +/** + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * - * If a value is also provided for `thisArg` the created `_.matchesProperty` - * style callback returns `true` for elements that have a matching property - * value, else `false`. + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example * - * If an object is provided for `iteratee` the created `_.matches` style - * callback returns `true` for elements that have the properties of the given - * object, else `false`. + * _.isObject({}); + * // => true * - * Many lodash methods are guarded to work as iteratees for methods like - * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`. + * _.isObject([1, 2, 3]); + * // => true * - * The guarded methods are: - * `ary`, `callback`, `chunk`, `clone`, `create`, `curry`, `curryRight`, - * `drop`, `dropRight`, `every`, `fill`, `flatten`, `invert`, `max`, `min`, - * `parseInt`, `slice`, `sortBy`, `take`, `takeRight`, `template`, `trim`, - * `trimLeft`, `trimRight`, `trunc`, `random`, `range`, `sample`, `some`, - * `sum`, `uniq`, and `words` + * _.isObject(1); + * // => false + */ +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); +} + +/** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) + * for more details. * * @static * @memberOf _ - * @alias collect - * @category Collection - * @param {Array|Object|string} collection The collection to iterate over. - * @param {Function|Object|string} [iteratee=_.identity] The function invoked - * per iteration. - * @param {*} [thisArg] The `this` binding of `iteratee`. - * @returns {Array} Returns the new mapped array. + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. * @example * - * function timesThree(n) { - * return n * 3; + * function Foo() { + * this.a = 1; + * this.b = 2; * } * - * _.map([1, 2], timesThree); - * // => [3, 6] + * Foo.prototype.c = 3; * - * _.map({ 'a': 1, 'b': 2 }, timesThree); - * // => [3, 6] (iteration order is not guaranteed) + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) * - * var users = [ - * { 'user': 'barney' }, - * { 'user': 'fred' } - * ]; + * _.keys('hi'); + * // => ['0', '1'] + */ +var keys = !nativeKeys ? shimKeys : function(object) { + var Ctor = object == null ? undefined : object.constructor; + if ((typeof Ctor == 'function' && Ctor.prototype === object) || + (typeof object != 'function' && isArrayLike(object))) { + return shimKeys(object); + } + return isObject(object) ? nativeKeys(object) : []; +}; + +/** + * Creates an array of the own and inherited enumerable property names of `object`. * - * // using the `_.property` callback shorthand - * _.map(users, 'user'); - * // => ['barney', 'fred'] + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) */ -function map(collection, iteratee, thisArg) { - var func = isArray(collection) ? arrayMap : baseMap; - iteratee = baseCallback(iteratee, thisArg, 3); - return func(collection, iteratee); +function keysIn(object) { + if (object == null) { + return []; + } + if (!isObject(object)) { + object = Object(object); + } + var length = object.length; + length = (length && isLength(length) && + (isArray(object) || isArguments(object)) && length) || 0; + + var Ctor = object.constructor, + index = -1, + isProto = typeof Ctor == 'function' && Ctor.prototype === object, + result = Array(length), + skipIndexes = length > 0; + + while (++index < length) { + result[index] = (index + ''); + } + for (var key in object) { + if (!(skipIndexes && isIndex(key, length)) && + !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; } -module.exports = map; +module.exports = keys; + +},{"lodash._getnative":68,"lodash.isarguments":69,"lodash.isarray":44}],75:[function(require,module,exports){ +var global=self;/** + * Module dependencies + */ + +var XMLHttpRequest = require('xmlhttprequest'); +var XHR = require('./polling-xhr'); +var JSONP = require('./polling-jsonp'); +var websocket = require('./websocket'); + +/** + * Export transports. + */ + +exports.polling = polling; +exports.websocket = websocket; + +/** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + +function polling(opts){ + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + if (global.location) { + var isSSL = 'https:' == location.protocol; + var port = location.port; + + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? 443 : 80; + } + + xd = opts.hostname != location.hostname || port != opts.port; + xs = opts.secure != isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); -},{"lodash._arraymap":67,"lodash._basecallback":68,"lodash._baseeach":59,"lodash.isarray":60}],54:[function(require,module,exports){ + if ('open' in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error('JSONP disabled'); + return new JSONP(opts); + } +} -module.exports = require('./socket'); +},{"./polling-jsonp":78,"./polling-xhr":77,"./websocket":79,"xmlhttprequest":76}],80:[function(require,module,exports){ /** - * Exports parser - * - * @api public + * Gets the keys for an object. * + * @return {Array} keys + * @api private */ -module.exports.parser = require('engine.io-parser'); -},{"./socket":69,"engine.io-parser":70}],67:[function(require,module,exports){ +module.exports = Object.keys || function keys (obj){ + var arr = []; + var has = Object.prototype.hasOwnProperty; + + for (var i in obj) { + if (has.call(obj, i)) { + arr.push(i); + } + } + return arr; +}; + +},{}],81:[function(require,module,exports){ /** - * lodash 3.0.0 (Custom Build) + * lodash 3.0.2 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.7.0 + * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ +/** `Object#toString` result references. */ +var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + mapTag = '[object Map]', + numberTag = '[object Number]', + objectTag = '[object Object]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + weakMapTag = '[object WeakMap]'; + +var arrayBufferTag = '[object ArrayBuffer]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + +/** Used to identify `toStringTag` values of typed arrays. */ +var typedArrayTags = {}; +typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = +typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = +typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = +typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = +typedArrayTags[uint32Tag] = true; +typedArrayTags[argsTag] = typedArrayTags[arrayTag] = +typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = +typedArrayTags[dateTag] = typedArrayTags[errorTag] = +typedArrayTags[funcTag] = typedArrayTags[mapTag] = +typedArrayTags[numberTag] = typedArrayTags[objectTag] = +typedArrayTags[regexpTag] = typedArrayTags[setTag] = +typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; + /** - * A specialized version of `_.map` for arrays without support for callback - * shorthands or `this` binding. + * Checks if `value` is object-like. * * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ -function arrayMap(array, iteratee) { - var index = -1, - length = array.length, - result = Array(length); - - while (++index < length) { - result[index] = iteratee(array[index], index, array); - } - return result; +function isObjectLike(value) { + return !!value && typeof value == 'object'; } -module.exports = arrayMap; - -},{}],71:[function(require,module,exports){ +/** Used for native method references. */ +var objectProto = Object.prototype; /** - * Expose `Emitter`. + * Used to resolve the [`toStringTag`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring) + * of values. */ - -module.exports = Emitter; +var objToString = objectProto.toString; /** - * Initialize a new `Emitter`. - * - * @api public + * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer) + * of an array-like value. */ - -function Emitter(obj) { - if (obj) return mixin(obj); -}; +var MAX_SAFE_INTEGER = 9007199254740991; /** - * Mixin the emitter properties. + * Checks if `value` is a valid array-like length. * - * @param {Object} obj - * @return {Object} - * @api private + * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ - -function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - return obj; +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** - * Listen on the given `event` with `fn`. + * Checks if `value` is classified as a typed array. * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.on = -Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks[event] = this._callbacks[event] || []) - .push(fn); - return this; -}; - -/** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.once = function(event, fn){ - var self = this; - this._callbacks = this._callbacks || {}; - - function on() { - self.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; -}; - -/** - * Remove the given callback for `event` or all - * registered callbacks. + * _.isTypedArray(new Uint8Array); + * // => true * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public + * _.isTypedArray([]); + * // => false */ +function isTypedArray(value) { + return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)]; +} -Emitter.prototype.off = -Emitter.prototype.removeListener = -Emitter.prototype.removeAllListeners = -Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } - - // specific event - var callbacks = this._callbacks[event]; - if (!callbacks) return this; - - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks[event]; - return this; - } - - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } - return this; -}; +module.exports = isTypedArray; +},{}],82:[function(require,module,exports){ /** - * Emit `event` with the given args. + * Parses an URI * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} + * @author Steven Levithan (MIT license) + * @api private */ -Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - var args = [].slice.call(arguments, 1) - , callbacks = this._callbacks[event]; +var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } +var parts = [ + 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor' +]; - return this; -}; +module.exports = function parseuri(str) { + var src = str, + b = str.indexOf('['), + e = str.indexOf(']'); + + if (b != -1 && e != -1) { + str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); + } -/** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ + var m = re.exec(str || ''), + uri = {}, + i = 14; -Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks[event] || []; -}; + while (i--) { + uri[parts[i]] = m[i] || ''; + } -/** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ + if (b != -1 && e != -1) { + uri.source = src; + uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':'); + uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':'); + uri.ipv6uri = true; + } -Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; + return uri; }; -},{}],72:[function(require,module,exports){ +},{}],83:[function(require,module,exports){ var global=self;/** - * Module dependencies + * JSON parse. + * + * @see Based on jQuery#parseJSON (MIT) and JSON2 + * @api private */ -var XMLHttpRequest = require('xmlhttprequest-ssl'); -var XHR = require('./polling-xhr'); -var JSONP = require('./polling-jsonp'); -var websocket = require('./websocket'); +var rvalidchars = /^[\],:{}\s]*$/; +var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; +var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; +var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g; +var rtrimLeft = /^\s+/; +var rtrimRight = /\s+$/; -/** - * Export transports. - */ +module.exports = function parsejson(data) { + if ('string' != typeof data || !data) { + return null; + } -exports.polling = polling; -exports.websocket = websocket; + data = data.replace(rtrimLeft, '').replace(rtrimRight, ''); + + // Attempt to parse using the native JSON parser first + if (global.JSON && JSON.parse) { + return JSON.parse(data); + } + if (rvalidchars.test(data.replace(rvalidescape, '@') + .replace(rvalidtokens, ']') + .replace(rvalidbraces, ''))) { + return (new Function('return ' + data))(); + } +}; +},{}],84:[function(require,module,exports){ /** - * Polling transport polymorphic constructor. - * Decides on xhr vs jsonp based on feature detection. + * Compiles a querystring + * Returns string representation of the object * + * @param {Object} * @api private */ -function polling(opts){ - var xhr; - var xd = false; - var xs = false; - var jsonp = false !== opts.jsonp; - - if (global.location) { - var isSSL = 'https:' == location.protocol; - var port = location.port; +exports.encode = function (obj) { + var str = ''; - // some user agents have empty `location.port` - if (!port) { - port = isSSL ? 443 : 80; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + if (str.length) str += '&'; + str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]); } - - xd = opts.hostname != location.hostname || port != opts.port; - xs = opts.secure != isSSL; } - opts.xdomain = xd; - opts.xscheme = xs; - xhr = new XMLHttpRequest(opts); + return str; +}; - if ('open' in xhr && !opts.forceJSONP) { - return new XHR(opts); - } else { - if (!jsonp) throw new Error('JSONP disabled'); - return new JSONP(opts); +/** + * Parses a simple querystring into an object + * + * @param {String} qs + * @api private + */ + +exports.decode = function(qs){ + var qry = {}; + var pairs = qs.split('&'); + for (var i = 0, l = pairs.length; i < l; i++) { + var pair = pairs[i].split('='); + qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); } -} + return qry; +}; -},{"./polling-jsonp":75,"./polling-xhr":74,"./websocket":76,"xmlhttprequest-ssl":73}],69:[function(require,module,exports){ +},{}],71:[function(require,module,exports){ var global=self;/** * Module dependencies. */ @@ -13412,20 +12233,24 @@ function Socket(uri, opts){ if (uri) { uri = parseuri(uri); - opts.hostname = uri.host; + opts.host = uri.host; opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; opts.port = uri.port; if (uri.query) opts.query = uri.query; - } else if (opts.host) { - opts.hostname = parseuri(opts.host).host; } this.secure = null != opts.secure ? opts.secure : (global.location && 'https:' == location.protocol); - if (opts.hostname && !opts.port) { - // if no port is specified manually, use the protocol default - opts.port = this.secure ? '443' : '80'; + if (opts.host) { + var pieces = opts.host.split(':'); + opts.hostname = pieces.shift(); + if (pieces.length) { + opts.port = pieces.pop(); + } else if (!opts.port) { + // if no port is specified manually, use the protocol default + opts.port = this.secure ? '443' : '80'; + } } this.agent = opts.agent || false; @@ -13447,16 +12272,11 @@ function Socket(uri, opts){ this.transports = opts.transports || ['polling', 'websocket']; this.readyState = ''; this.writeBuffer = []; + this.callbackBuffer = []; this.policyPort = opts.policyPort || 843; this.rememberUpgrade = opts.rememberUpgrade || false; this.binaryType = null; this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; - this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false; - - if (true === this.perMessageDeflate) this.perMessageDeflate = {}; - if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { - this.perMessageDeflate.threshold = 1024; - } // SSL options for Node.js client this.pfx = opts.pfx || null; @@ -13465,15 +12285,7 @@ function Socket(uri, opts){ this.cert = opts.cert || null; this.ca = opts.ca || null; this.ciphers = opts.ciphers || null; - this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? null : opts.rejectUnauthorized; - - // other options for Node.js client - var freeGlobal = typeof global == 'object' && global; - if (freeGlobal.global === freeGlobal) { - if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { - this.extraHeaders = opts.extraHeaders; - } - } + this.rejectUnauthorized = opts.rejectUnauthorized || null; this.open(); } @@ -13546,9 +12358,7 @@ Socket.prototype.createTransport = function (name) { cert: this.cert, ca: this.ca, ciphers: this.ciphers, - rejectUnauthorized: this.rejectUnauthorized, - perMessageDeflate: this.perMessageDeflate, - extraHeaders: this.extraHeaders + rejectUnauthorized: this.rejectUnauthorized }); return transport; @@ -13573,7 +12383,7 @@ Socket.prototype.open = function () { var transport; if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) { transport = 'websocket'; - } else if (0 === this.transports.length) { + } else if (0 == this.transports.length) { // Emit error on next tick so it can be listened to var self = this; setTimeout(function() { @@ -13586,6 +12396,7 @@ Socket.prototype.open = function () { this.readyState = 'opening'; // Retry with the next transport if the transport is disabled (jsonp: false) + var transport; try { transport = this.createTransport(transport); } catch (e) { @@ -13795,13 +12606,12 @@ Socket.prototype.onPacket = function (packet) { case 'pong': this.setPing(); - this.emit('pong'); break; case 'error': var err = new Error('server error'); err.code = packet.data; - this.onError(err); + this.emit('error', err); break; case 'message': @@ -13873,14 +12683,11 @@ Socket.prototype.setPing = function () { /** * Sends a ping packet. * -* @api private +* @api public */ Socket.prototype.ping = function () { - var self = this; - this.sendPacket('ping', function(){ - self.emit('ping'); - }); + this.sendPacket('ping'); }; /** @@ -13890,14 +12697,21 @@ Socket.prototype.ping = function () { */ Socket.prototype.onDrain = function() { + for (var i = 0; i < this.prevBufferLen; i++) { + if (this.callbackBuffer[i]) { + this.callbackBuffer[i](); + } + } + this.writeBuffer.splice(0, this.prevBufferLen); + this.callbackBuffer.splice(0, this.prevBufferLen); // setting prevBufferLen = 0 is very important // for example, when upgrading, upgrade packet is sent over, // and a nonzero prevBufferLen could cause problems on `drain` this.prevBufferLen = 0; - if (0 === this.writeBuffer.length) { + if (this.writeBuffer.length == 0) { this.emit('drain'); } else { this.flush(); @@ -13927,14 +12741,13 @@ Socket.prototype.flush = function () { * * @param {String} message. * @param {Function} callback function. - * @param {Object} options. * @return {Socket} for chaining. * @api public */ Socket.prototype.write = -Socket.prototype.send = function (msg, options, fn) { - this.sendPacket('message', msg, options, fn); +Socket.prototype.send = function (msg, fn) { + this.sendPacket('message', msg, fn); return this; }; @@ -13943,37 +12756,19 @@ Socket.prototype.send = function (msg, options, fn) { * * @param {String} packet type. * @param {String} data. - * @param {Object} options. * @param {Function} callback function. * @api private */ -Socket.prototype.sendPacket = function (type, data, options, fn) { - if('function' == typeof data) { - fn = data; - data = undefined; - } - - if ('function' == typeof options) { - fn = options; - options = null; - } - +Socket.prototype.sendPacket = function (type, data, fn) { if ('closing' == this.readyState || 'closed' == this.readyState) { return; } - options = options || {}; - options.compress = false !== options.compress; - - var packet = { - type: type, - data: data, - options: options - }; + var packet = { type: type, data: data }; this.emit('packetCreate', packet); this.writeBuffer.push(packet); - if (fn) this.once('flush', fn); + this.callbackBuffer.push(fn); this.flush(); }; @@ -13989,6 +12784,24 @@ Socket.prototype.close = function () { var self = this; + function close() { + self.onClose('forced close'); + debug('socket closing - telling transport to close'); + self.transport.close(); + } + + function cleanupAndClose() { + self.removeListener('upgrade', cleanupAndClose); + self.removeListener('upgradeError', cleanupAndClose); + close(); + } + + function waitForUpgrade() { + // wait for upgrade to finish since we can't send packets while pausing a transport + self.once('upgrade', cleanupAndClose); + self.once('upgradeError', cleanupAndClose); + } + if (this.writeBuffer.length) { this.once('drain', function() { if (this.upgrading) { @@ -14004,24 +12817,6 @@ Socket.prototype.close = function () { } } - function close() { - self.onClose('forced close'); - debug('socket closing - telling transport to close'); - self.transport.close(); - } - - function cleanupAndClose() { - self.removeListener('upgrade', cleanupAndClose); - self.removeListener('upgradeError', cleanupAndClose); - close(); - } - - function waitForUpgrade() { - // wait for upgrade to finish since we can't send packets while pausing a transport - self.once('upgrade', cleanupAndClose); - self.once('upgradeError', cleanupAndClose); - } - return this; }; @@ -14053,6 +12848,14 @@ Socket.prototype.onClose = function (reason, desc) { clearTimeout(this.pingIntervalTimer); clearTimeout(this.pingTimeoutTimer); + // clean buffers in next tick, so developers can still + // grab the buffers on `close` event + setTimeout(function() { + self.writeBuffer = []; + self.callbackBuffer = []; + self.prevBufferLen = 0; + }, 0); + // stop event from firing again for transport this.transport.removeAllListeners('close'); @@ -14069,139 +12872,46 @@ Socket.prototype.onClose = function (reason, desc) { this.id = null; // emit close event - this.emit('close', reason, desc); - - // clean buffers after, so users can still - // grab the buffers on `close` event - self.writeBuffer = []; - self.prevBufferLen = 0; - } -}; - -/** - * Filters upgrades, returning only those matching client transports. - * - * @param {Array} server upgrades - * @api private - * - */ - -Socket.prototype.filterUpgrades = function (upgrades) { - var filteredUpgrades = []; - for (var i = 0, j = upgrades.length; i + * lodash 3.0.4 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ -var getNative = require('lodash._getnative'), - isArguments = require('lodash.isarguments'), - isArray = require('lodash.isarray'); -/** Used to detect unsigned integer values. */ -var reIsUint = /^\d+$/; +/** + * Checks if `value` is object-like. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + */ +function isObjectLike(value) { + return !!value && typeof value == 'object'; +} /** Used for native method references. */ var objectProto = Object.prototype; @@ -14209,8 +12919,8 @@ var objectProto = Object.prototype; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; -/* Native method references for those with the same name as other `lodash` methods. */ -var nativeKeys = getNative(Object, 'keys'); +/** Native method references. */ +var propertyIsEnumerable = objectProto.propertyIsEnumerable; /** * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) @@ -14255,58 +12965,123 @@ function isArrayLike(value) { } /** - * Checks if `value` is a valid array-like index. + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). * * @private * @param {*} value The value to check. - * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. - * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ -function isIndex(value, length) { - value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; - length = length == null ? MAX_SAFE_INTEGER : length; - return value > -1 && value % 1 == 0 && value < length; +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** - * Checks if `value` is a valid array-like length. + * Checks if `value` is classified as an `arguments` object. * - * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ +function isArguments(value) { + return isObjectLike(value) && isArrayLike(value) && + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); +} + +module.exports = isArguments; + +},{}],88:[function(require,module,exports){ +/** + * lodash 3.9.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ + +/** `Object#toString` result references. */ +var funcTag = '[object Function]'; + +/** Used to detect host constructors (Safari > 5). */ +var reIsHostCtor = /^\[object .+?Constructor\]$/; + +/** + * Checks if `value` is object-like. * * @private * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. */ -function isLength(value) { - return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +function isObjectLike(value) { + return !!value && typeof value == 'object'; } +/** Used for native method references. */ +var objectProto = Object.prototype; + +/** Used to resolve the decompiled source of functions. */ +var fnToString = Function.prototype.toString; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + /** - * A fallback implementation of `Object.keys` which creates an array of the - * own enumerable property names of `object`. + * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * of values. + */ +var objToString = objectProto.toString; + +/** Used to detect if a method is native. */ +var reIsNative = RegExp('^' + + fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +/** + * Gets the native function at `key` of `object`. * * @private * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. */ -function shimKeys(object) { - var props = keysIn(object), - propsLength = props.length, - length = propsLength && object.length; - - var allowIndexes = !!length && isLength(length) && - (isArray(object) || isArguments(object)); - - var index = -1, - result = []; +function getNative(object, key) { + var value = object == null ? undefined : object[key]; + return isNative(value) ? value : undefined; +} - while (++index < propsLength) { - var key = props[index]; - if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { - result.push(key); - } - } - return result; +/** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ +function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in older versions of Chrome and Safari which return 'function' for regexes + // and Safari 8 equivalents which return 'object' for typed array constructors. + return isObject(value) && objToString.call(value) == funcTag; } /** @@ -14337,533 +13112,531 @@ function isObject(value) { } /** - * Creates an array of the own enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. See the - * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) - * for more details. - * - * @static - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keys(new Foo); - * // => ['a', 'b'] (iteration order is not guaranteed) - * - * _.keys('hi'); - * // => ['0', '1'] - */ -var keys = !nativeKeys ? shimKeys : function(object) { - var Ctor = object == null ? undefined : object.constructor; - if ((typeof Ctor == 'function' && Ctor.prototype === object) || - (typeof object != 'function' && isArrayLike(object))) { - return shimKeys(object); - } - return isObject(object) ? nativeKeys(object) : []; -}; - -/** - * Creates an array of the own and inherited enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. + * Checks if `value` is a native function. * * @static * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, else `false`. * @example * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; + * _.isNative(Array.prototype.push); + * // => true * - * _.keysIn(new Foo); - * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + * _.isNative(_); + * // => false */ -function keysIn(object) { - if (object == null) { - return []; - } - if (!isObject(object)) { - object = Object(object); - } - var length = object.length; - length = (length && isLength(length) && - (isArray(object) || isArguments(object)) && length) || 0; - - var Ctor = object.constructor, - index = -1, - isProto = typeof Ctor == 'function' && Ctor.prototype === object, - result = Array(length), - skipIndexes = length > 0; - - while (++index < length) { - result[index] = (index + ''); +function isNative(value) { + if (value == null) { + return false; } - for (var key in object) { - if (!(skipIndexes && isIndex(key, length)) && - !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { - result.push(key); - } + if (isFunction(value)) { + return reIsNative.test(fnToString.call(value)); } - return result; + return isObjectLike(value) && reIsHostCtor.test(value); } -module.exports = keys; +module.exports = getNative; + +},{}],86:[function(require,module,exports){ -},{"lodash._getnative":81,"lodash.isarguments":82,"lodash.isarray":60}],68:[function(require,module,exports){ /** - * lodash 3.3.1 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. */ -var baseIsEqual = require('lodash._baseisequal'), - bindCallback = require('lodash._bindcallback'), - isArray = require('lodash.isarray'), - pairs = require('lodash.pairs'); - -/** Used to match property names within property paths. */ -var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/, - reIsPlainProp = /^\w*$/, - rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g; -/** Used to match backslashes in property paths. */ -var reEscapeChar = /\\(\\)?/g; +exports = module.exports = require('./debug'); +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; /** - * Converts `value` to a string if it's not one. An empty string is returned - * for `null` or `undefined` values. - * - * @private - * @param {*} value The value to process. - * @returns {string} Returns the string. + * Colors. */ -function baseToString(value) { - return value == null ? '' : (value + ''); -} + +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; /** - * The base implementation of `_.callback` which supports specifying the - * number of arguments to provide to `func`. + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. * - * @private - * @param {*} [func=_.identity] The value to convert to a callback. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {number} [argCount] The number of arguments to provide to `func`. - * @returns {Function} Returns the callback. + * TODO: add a `localStorage` variable to explicitly enable/disable colors */ -function baseCallback(func, thisArg, argCount) { - var type = typeof func; - if (type == 'function') { - return thisArg === undefined - ? func - : bindCallback(func, thisArg, argCount); - } - if (func == null) { - return identity; - } - if (type == 'object') { - return baseMatches(func); - } - return thisArg === undefined - ? property(func) - : baseMatchesProperty(func, thisArg); + +function useColors() { + // is webkit? http://stackoverflow.com/a/16459606/376773 + return ('WebkitAppearance' in document.documentElement.style) || + // is firebug? http://stackoverflow.com/a/398120/376773 + (window.console && (console.firebug || (console.exception && console.table))) || + // is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31); } /** - * The base implementation of `get` without support for string paths - * and default values. - * - * @private - * @param {Object} object The object to query. - * @param {Array} path The path of the property to get. - * @param {string} [pathKey] The key representation of path. - * @returns {*} Returns the resolved value. + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. */ -function baseGet(object, path, pathKey) { - if (object == null) { - return; - } - if (pathKey !== undefined && pathKey in toObject(object)) { - path = [pathKey]; - } - var index = 0, - length = path.length; - while (object != null && index < length) { - object = object[path[index++]]; - } - return (index && index == length) ? object : undefined; -} +exports.formatters.j = function(v) { + return JSON.stringify(v); +}; + /** - * The base implementation of `_.isMatch` without support for callback - * shorthands and `this` binding. + * Colorize log arguments if enabled. * - * @private - * @param {Object} object The object to inspect. - * @param {Array} matchData The propery names, values, and compare flags to match. - * @param {Function} [customizer] The function to customize comparing objects. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @api public */ -function baseIsMatch(object, matchData, customizer) { - var index = matchData.length, - length = index, - noCustomizer = !customizer; - if (object == null) { - return !length; - } - object = toObject(object); - while (index--) { - var data = matchData[index]; - if ((noCustomizer && data[2]) - ? data[1] !== object[data[0]] - : !(data[0] in object) - ) { - return false; - } - } - while (++index < length) { - data = matchData[index]; - var key = data[0], - objValue = object[key], - srcValue = data[1]; +function formatArgs() { + var args = arguments; + var useColors = this.useColors; - if (noCustomizer && data[2]) { - if (objValue === undefined && !(key in object)) { - return false; - } - } else { - var result = customizer ? customizer(objValue, srcValue, key) : undefined; - if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, true) : result)) { - return false; - } + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); + + if (!useColors) return args; + + var c = 'color: ' + this.color; + args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1)); + + // 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-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; } - } - return true; + }); + + args.splice(lastC, 0, c); + return args; } /** - * The base implementation of `_.matches` which does not clone `source`. + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". * - * @private - * @param {Object} source The object of property values to match. - * @returns {Function} Returns the new function. + * @api public */ -function baseMatches(source) { - var matchData = getMatchData(source); - if (matchData.length == 1 && matchData[0][2]) { - var key = matchData[0][0], - value = matchData[0][1]; - return function(object) { - if (object == null) { - return false; - } - return object[key] === value && (value !== undefined || (key in toObject(object))); - }; - } - return function(object) { - return baseIsMatch(object, matchData); - }; +function log() { + // This hackery is required for IE8, + // where the `console.log` function doesn't have 'apply' + return 'object' == typeof console + && 'function' == typeof console.log + && Function.prototype.apply.call(console.log, console, arguments); } /** - * The base implementation of `_.matchesProperty` which does not clone `srcValue`. + * Save `namespaces`. * - * @private - * @param {string} path The path of the property to get. - * @param {*} srcValue The value to compare. - * @returns {Function} Returns the new function. + * @param {String} namespaces + * @api private */ -function baseMatchesProperty(path, srcValue) { - var isArr = isArray(path), - isCommon = isKey(path) && isStrictComparable(srcValue), - pathKey = (path + ''); - path = toPath(path); - return function(object) { - if (object == null) { - return false; - } - var key = pathKey; - object = toObject(object); - if ((isArr || !isCommon) && !(key in object)) { - object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1)); - if (object == null) { - return false; - } - key = last(path); - object = toObject(object); +function save(namespaces) { + try { + if (null == namespaces) { + localStorage.removeItem('debug'); + } else { + localStorage.debug = namespaces; } - return object[key] === srcValue - ? (srcValue !== undefined || (key in object)) - : baseIsEqual(srcValue, object[key], undefined, true); - }; + } catch(e) {} +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + var r; + try { + r = localStorage.debug; + } catch(e) {} + return r; } /** - * The base implementation of `_.property` without support for deep paths. + * Enable namespaces listed in `localStorage.debug` initially. + */ + +exports.enable(load()); + +},{"./debug":89}],74:[function(require,module,exports){ +/** + * lodash 3.0.1 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation + * Based on Underscore.js 1.8.3 + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Available under MIT license + */ +var keys = require('lodash.keys'); + +/** + * Converts `value` to an object if it's not one. * * @private - * @param {string} key The key of the property to get. - * @returns {Function} Returns the new function. + * @param {*} value The value to process. + * @returns {Object} Returns the object. */ -function baseProperty(key) { - return function(object) { - return object == null ? undefined : object[key]; - }; +function toObject(value) { + return isObject(value) ? value : Object(value); } /** - * A specialized version of `baseProperty` which supports deep paths. + * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * - * @private - * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new function. + * @static + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(1); + * // => false */ -function basePropertyDeep(path) { - var pathKey = (path + ''); - path = toPath(path); - return function(object) { - return baseGet(object, path, pathKey); - }; +function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); } /** - * The base implementation of `_.slice` without an iteratee call guard. + * Creates a two dimensional array of the key-value pairs for `object`, + * e.g. `[[key1, value1], [key2, value2]]`. * - * @private - * @param {Array} array The array to slice. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the slice of `array`. + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the new array of key-value pairs. + * @example + * + * _.pairs({ 'barney': 36, 'fred': 40 }); + * // => [['barney', 36], ['fred', 40]] (iteration order is not guaranteed) */ -function baseSlice(array, start, end) { - var index = -1, - length = array.length; +function pairs(object) { + object = toObject(object); - start = start == null ? 0 : (+start || 0); - if (start < 0) { - start = -start > length ? 0 : (length + start); - } - end = (end === undefined || end > length) ? length : (+end || 0); - if (end < 0) { - end += length; - } - length = start > end ? 0 : ((end - start) >>> 0); - start >>>= 0; + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); - var result = Array(length); while (++index < length) { - result[index] = array[index + start]; + var key = props[index]; + result[index] = [key, object[key]]; } return result; } -/** - * Gets the propery names, values, and compare flags of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the match data of `object`. - */ -function getMatchData(object) { - var result = pairs(object), - length = result.length; +module.exports = pairs; + +},{"lodash.keys":70}],90:[function(require,module,exports){ +var global=self;/*! https://mths.be/utf8js v2.0.0 by @mathias */ +;(function(root) { + + // Detect free variables `exports` + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module` + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root` + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + // Taken from https://mths.be/punycode + function ucs2decode(string) { + var output = []; + var counter = 0; + var length = string.length; + var value; + var extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + // Taken from https://mths.be/punycode + function ucs2encode(array) { + var length = array.length; + var index = -1; + var value; + var output = ''; + while (++index < length) { + value = array[index]; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + } + return output; + } + + function checkScalarValue(codePoint) { + if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { + throw Error( + 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() + + ' is not a scalar value' + ); + } + } + /*--------------------------------------------------------------------------*/ + + function createByte(codePoint, shift) { + return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); + } + + function encodeCodePoint(codePoint) { + if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence + return stringFromCharCode(codePoint); + } + var symbol = ''; + if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence + symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); + } + else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence + checkScalarValue(codePoint); + symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); + symbol += createByte(codePoint, 6); + } + else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence + symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); + symbol += createByte(codePoint, 12); + symbol += createByte(codePoint, 6); + } + symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); + return symbol; + } + + function utf8encode(string) { + var codePoints = ucs2decode(string); + var length = codePoints.length; + var index = -1; + var codePoint; + var byteString = ''; + while (++index < length) { + codePoint = codePoints[index]; + byteString += encodeCodePoint(codePoint); + } + return byteString; + } + + /*--------------------------------------------------------------------------*/ + + function readContinuationByte() { + if (byteIndex >= byteCount) { + throw Error('Invalid byte index'); + } + + var continuationByte = byteArray[byteIndex] & 0xFF; + byteIndex++; + + if ((continuationByte & 0xC0) == 0x80) { + return continuationByte & 0x3F; + } + + // If we end up here, it’s not a continuation byte + throw Error('Invalid continuation byte'); + } + + function decodeSymbol() { + var byte1; + var byte2; + var byte3; + var byte4; + var codePoint; + + if (byteIndex > byteCount) { + throw Error('Invalid byte index'); + } - while (length--) { - result[length][2] = isStrictComparable(result[length][1]); - } - return result; -} + if (byteIndex == byteCount) { + return false; + } -/** - * Checks if `value` is a property name and not a property path. - * - * @private - * @param {*} value The value to check. - * @param {Object} [object] The object to query keys on. - * @returns {boolean} Returns `true` if `value` is a property name, else `false`. - */ -function isKey(value, object) { - var type = typeof value; - if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') { - return true; - } - if (isArray(value)) { - return false; - } - var result = !reIsDeepProp.test(value); - return result || (object != null && value in toObject(object)); -} + // Read first byte + byte1 = byteArray[byteIndex] & 0xFF; + byteIndex++; -/** - * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` if suitable for strict - * equality comparisons, else `false`. - */ -function isStrictComparable(value) { - return value === value && !isObject(value); -} + // 1-byte sequence (no continuation bytes) + if ((byte1 & 0x80) == 0) { + return byte1; + } -/** - * Converts `value` to an object if it's not one. - * - * @private - * @param {*} value The value to process. - * @returns {Object} Returns the object. - */ -function toObject(value) { - return isObject(value) ? value : Object(value); -} + // 2-byte sequence + if ((byte1 & 0xE0) == 0xC0) { + var byte2 = readContinuationByte(); + codePoint = ((byte1 & 0x1F) << 6) | byte2; + if (codePoint >= 0x80) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } -/** - * Converts `value` to property path array if it's not one. - * - * @private - * @param {*} value The value to process. - * @returns {Array} Returns the property path array. - */ -function toPath(value) { - if (isArray(value)) { - return value; - } - var result = []; - baseToString(value).replace(rePropName, function(match, number, quote, string) { - result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match)); - }); - return result; -} + // 3-byte sequence (may include unpaired surrogates) + if ((byte1 & 0xF0) == 0xE0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; + if (codePoint >= 0x0800) { + checkScalarValue(codePoint); + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } -/** - * Gets the last element of `array`. - * - * @static - * @memberOf _ - * @category Array - * @param {Array} array The array to query. - * @returns {*} Returns the last element of `array`. - * @example - * - * _.last([1, 2, 3]); - * // => 3 - */ -function last(array) { - var length = array ? array.length : 0; - return length ? array[length - 1] : undefined; -} + // 4-byte sequence + if ((byte1 & 0xF8) == 0xF0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + byte4 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | + (byte3 << 0x06) | byte4; + if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { + return codePoint; + } + } -/** - * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. - * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(1); - * // => false - */ -function isObject(value) { - // Avoid a V8 JIT bug in Chrome 19-20. - // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. - var type = typeof value; - return !!value && (type == 'object' || type == 'function'); -} + throw Error('Invalid UTF-8 detected'); + } -/** - * This method returns the first argument provided to it. - * - * @static - * @memberOf _ - * @category Utility - * @param {*} value Any value. - * @returns {*} Returns `value`. - * @example - * - * var object = { 'user': 'fred' }; - * - * _.identity(object) === object; - * // => true - */ -function identity(value) { - return value; -} + var byteArray; + var byteCount; + var byteIndex; + function utf8decode(byteString) { + byteArray = ucs2decode(byteString); + byteCount = byteArray.length; + byteIndex = 0; + var codePoints = []; + var tmp; + while ((tmp = decodeSymbol()) !== false) { + codePoints.push(tmp); + } + return ucs2encode(codePoints); + } -/** - * Creates a function that returns the property value at `path` on a - * given object. - * - * @static - * @memberOf _ - * @category Utility - * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new function. - * @example - * - * var objects = [ - * { 'a': { 'b': { 'c': 2 } } }, - * { 'a': { 'b': { 'c': 1 } } } - * ]; - * - * _.map(objects, _.property('a.b.c')); - * // => [2, 1] - * - * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c'); - * // => [1, 2] - */ -function property(path) { - return isKey(path) ? baseProperty(path) : basePropertyDeep(path); -} + /*--------------------------------------------------------------------------*/ -module.exports = baseCallback; + var utf8 = { + 'version': '2.0.0', + 'encode': utf8encode, + 'decode': utf8decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return utf8; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = utf8; + } else { // in Narwhal or RingoJS v0.7.0- + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + for (var key in utf8) { + hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]); + } + } + } else { // in Rhino or a web browser + root.utf8 = utf8; + } + +}(this)); -},{"lodash._baseisequal":83,"lodash._bindcallback":58,"lodash.isarray":60,"lodash.pairs":84}],81:[function(require,module,exports){ +},{}],73:[function(require,module,exports){ /** - * lodash 3.9.1 (Custom Build) + * lodash 3.0.7 (Custom Build) * Build: `lodash modern modularize exports="npm" -o ./` * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ +var isArray = require('lodash.isarray'), + isTypedArray = require('lodash.istypedarray'), + keys = require('lodash.keys'); /** `Object#toString` result references. */ -var funcTag = '[object Function]'; - -/** Used to detect host constructors (Safari > 5). */ -var reIsHostCtor = /^\[object .+?Constructor\]$/; +var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + numberTag = '[object Number]', + objectTag = '[object Object]', + regexpTag = '[object RegExp]', + stringTag = '[object String]'; /** * Checks if `value` is object-like. @@ -14879,58 +13652,281 @@ function isObjectLike(value) { /** Used for native method references. */ var objectProto = Object.prototype; -/** Used to resolve the decompiled source of functions. */ -var fnToString = Function.prototype.toString; - /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** - * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) + * Used to resolve the [`toStringTag`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring) * of values. */ var objToString = objectProto.toString; -/** Used to detect if a method is native. */ -var reIsNative = RegExp('^' + - fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') - .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' -); +/** + * A specialized version of `_.some` for arrays without support for callback + * shorthands and `this` binding. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ +function arraySome(array, predicate) { + var index = -1, + length = array.length; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; +} /** - * Gets the native function at `key` of `object`. + * The base implementation of `_.isEqual` without support for `this` binding + * `customizer` functions. * * @private - * @param {Object} object The object to query. - * @param {string} key The key of the method to get. - * @returns {*} Returns the function if it's native, else `undefined`. + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize comparing values. + * @param {boolean} [isLoose] Specify performing partial comparisons. + * @param {Array} [stackA] Tracks traversed `value` objects. + * @param {Array} [stackB] Tracks traversed `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. */ -function getNative(object, key) { - var value = object == null ? undefined : object[key]; - return isNative(value) ? value : undefined; +function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) { + if (value === other) { + return true; + } + if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) { + return value !== value && other !== other; + } + return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB); +} + +/** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Function} [customizer] The function to customize comparing objects. + * @param {boolean} [isLoose] Specify performing partial comparisons. + * @param {Array} [stackA=[]] Tracks traversed `value` objects. + * @param {Array} [stackB=[]] Tracks traversed `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) { + var objIsArr = isArray(object), + othIsArr = isArray(other), + objTag = arrayTag, + othTag = arrayTag; + + if (!objIsArr) { + objTag = objToString.call(object); + if (objTag == argsTag) { + objTag = objectTag; + } else if (objTag != objectTag) { + objIsArr = isTypedArray(object); + } + } + if (!othIsArr) { + othTag = objToString.call(other); + if (othTag == argsTag) { + othTag = objectTag; + } else if (othTag != objectTag) { + othIsArr = isTypedArray(other); + } + } + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, + isSameTag = objTag == othTag; + + if (isSameTag && !(objIsArr || objIsObj)) { + return equalByTag(object, other, objTag); + } + if (!isLoose) { + var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB); + } + } + if (!isSameTag) { + return false; + } + // Assume cyclic values are equal. + // For more information on detecting circular references see https://es5.github.io/#JO. + stackA || (stackA = []); + stackB || (stackB = []); + + var length = stackA.length; + while (length--) { + if (stackA[length] == object) { + return stackB[length] == other; + } + } + // Add `object` and `other` to the stack of traversed objects. + stackA.push(object); + stackB.push(other); + + var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB); + + stackA.pop(); + stackB.pop(); + + return result; +} + +/** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Function} [customizer] The function to customize comparing arrays. + * @param {boolean} [isLoose] Specify performing partial comparisons. + * @param {Array} [stackA] Tracks traversed `value` objects. + * @param {Array} [stackB] Tracks traversed `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ +function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) { + var index = -1, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isLoose && othLength > arrLength)) { + return false; + } + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index], + result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined; + + if (result !== undefined) { + if (result) { + continue; + } + return false; + } + // Recursively compare arrays (susceptible to call stack limits). + if (isLoose) { + if (!arraySome(other, function(othValue) { + return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB); + })) { + return false; + } + } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) { + return false; + } + } + return true; +} + +/** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} value The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function equalByTag(object, other, tag) { + switch (tag) { + case boolTag: + case dateTag: + // Coerce dates and booleans to numbers, dates to milliseconds and booleans + // to `1` or `0` treating invalid dates coerced to `NaN` as not equal. + return +object == +other; + + case errorTag: + return object.name == other.name && object.message == other.message; + + case numberTag: + // Treat `NaN` vs. `NaN` as equal. + return (object != +object) + ? other != +other + : object == +other; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings primitives and string + // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details. + return object == (other + ''); + } + return false; } -/** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. - * @example - * - * _.isFunction(_); - * // => true - * - * _.isFunction(/abc/); - * // => false - */ -function isFunction(value) { - // The use of `Object#toString` avoids issues with the `typeof` operator - // in older versions of Chrome and Safari which return 'function' for regexes - // and Safari 8 equivalents which return 'object' for typed array constructors. - return isObject(value) && objToString.call(value) == funcTag; +/** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Function} [customizer] The function to customize comparing values. + * @param {boolean} [isLoose] Specify performing partial comparisons. + * @param {Array} [stackA] Tracks traversed `value` objects. + * @param {Array} [stackB] Tracks traversed `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ +function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) { + var objProps = keys(object), + objLength = objProps.length, + othProps = keys(other), + othLength = othProps.length; + + if (objLength != othLength && !isLoose) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) { + return false; + } + } + var skipCtor = isLoose; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key], + result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined; + + // Recursively compare objects (susceptible to call stack limits). + if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) { + return false; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (!skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && + ('constructor' in object && 'constructor' in other) && + !(typeof objCtor == 'function' && objCtor instanceof objCtor && + typeof othCtor == 'function' && othCtor instanceof othCtor)) { + return false; + } + } + return true; } /** @@ -14960,66 +13956,38 @@ function isObject(value) { return !!value && (type == 'object' || type == 'function'); } -/** - * Checks if `value` is a native function. - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, else `false`. - * @example - * - * _.isNative(Array.prototype.push); - * // => true - * - * _.isNative(_); - * // => false - */ -function isNative(value) { - if (value == null) { - return false; - } - if (isFunction(value)) { - return reIsNative.test(fnToString.call(value)); - } - return isObjectLike(value) && reIsHostCtor.test(value); -} - -module.exports = getNative; +module.exports = baseIsEqual; -},{}],82:[function(require,module,exports){ +},{"lodash.isarray":40,"lodash.istypedarray":81,"lodash.keys":70}],70:[function(require,module,exports){ /** - * lodash 3.0.8 (Custom Build) - * Build: `lodash modularize exports="npm" -o ./` - * Copyright 2012-2016 The Dojo Foundation + * lodash 3.1.2 (Custom Build) + * Build: `lodash modern modularize exports="npm" -o ./` + * Copyright 2012-2015 The Dojo Foundation * Based on Underscore.js 1.8.3 - * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ +var getNative = require('lodash._getnative'), + isArguments = require('lodash.isarguments'), + isArray = require('lodash.isarray'); -/** Used as references for various `Number` constants. */ -var MAX_SAFE_INTEGER = 9007199254740991; - -/** `Object#toString` result references. */ -var argsTag = '[object Arguments]', - funcTag = '[object Function]', - genTag = '[object GeneratorFunction]'; +/** Used to detect unsigned integer values. */ +var reIsUint = /^\d+$/; -/** Used for built-in method references. */ +/** Used for native method references. */ var objectProto = Object.prototype; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeKeys = getNative(Object, 'keys'); + /** - * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) - * of values. + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. */ -var objectToString = objectProto.toString; - -/** Built-in value references. */ -var propertyIsEnumerable = objectProto.propertyIsEnumerable; +var MAX_SAFE_INTEGER = 9007199254740991; /** * The base implementation of `_.property` without support for deep paths. @@ -15047,133 +14015,69 @@ function baseProperty(key) { var getLength = baseProperty('length'); /** - * Checks if `value` is likely an `arguments` object. - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. - * @example - * - * _.isArguments(function() { return arguments; }()); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ -function isArguments(value) { - // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode. - return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && - (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); -} - -/** - * Checks if `value` is array-like. A value is considered array-like if it's - * not a function and has a `value.length` that's an integer greater than or - * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * Checks if `value` is array-like. * - * @static - * @memberOf _ - * @category Lang + * @private * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is array-like, else `false`. - * @example - * - * _.isArrayLike([1, 2, 3]); - * // => true - * - * _.isArrayLike(document.body.children); - * // => true - * - * _.isArrayLike('abc'); - * // => true - * - * _.isArrayLike(_.noop); - * // => false */ function isArrayLike(value) { - return value != null && isLength(getLength(value)) && !isFunction(value); + return value != null && isLength(getLength(value)); } /** - * This method is like `_.isArrayLike` except that it also checks if `value` - * is an object. + * Checks if `value` is a valid array-like index. * - * @static - * @memberOf _ - * @category Lang + * @private * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array-like object, else `false`. - * @example - * - * _.isArrayLikeObject([1, 2, 3]); - * // => true - * - * _.isArrayLikeObject(document.body.children); - * // => true - * - * _.isArrayLikeObject('abc'); - * // => false - * - * _.isArrayLikeObject(_.noop); - * // => false + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. */ -function isArrayLikeObject(value) { - return isObjectLike(value) && isArrayLike(value); +function isIndex(value, length) { + value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; + length = length == null ? MAX_SAFE_INTEGER : length; + return value > -1 && value % 1 == 0 && value < length; } /** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. - * @example + * Checks if `value` is a valid array-like length. * - * _.isFunction(_); - * // => true + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). * - * _.isFunction(/abc/); - * // => false + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. */ -function isFunction(value) { - // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 8 which returns 'object' for typed array and weak map constructors, - // and PhantomJS 1.9 which returns 'function' for `NodeList` instances. - var tag = isObject(value) ? objectToString.call(value) : ''; - return tag == funcTag || tag == genTag; +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; } /** - * Checks if `value` is a valid array-like length. - * - * **Note:** This function is loosely based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. - * @example - * - * _.isLength(3); - * // => true - * - * _.isLength(Number.MIN_VALUE); - * // => false - * - * _.isLength(Infinity); - * // => false + * A fallback implementation of `Object.keys` which creates an array of the + * own enumerable property names of `object`. * - * _.isLength('3'); - * // => false + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. */ -function isLength(value) { - return typeof value == 'number' && - value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +function shimKeys(object) { + var props = keysIn(object), + propsLength = props.length, + length = propsLength && object.length; + + var allowIndexes = !!length && isLength(length) && + (isArray(object) || isArguments(object)); + + var index = -1, + result = []; + + while (++index < propsLength) { + var key = props[index]; + if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { + result.push(key); + } + } + return result; } /** @@ -15193,47 +14097,106 @@ function isLength(value) { * _.isObject([1, 2, 3]); * // => true * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); + * _.isObject(1); * // => false */ function isObject(value) { + // Avoid a V8 JIT bug in Chrome 19-20. + // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. var type = typeof value; return !!value && (type == 'object' || type == 'function'); } /** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) + * for more details. * * @static * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. * @example * - * _.isObjectLike({}); - * // => true + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ +var keys = !nativeKeys ? shimKeys : function(object) { + var Ctor = object == null ? undefined : object.constructor; + if ((typeof Ctor == 'function' && Ctor.prototype === object) || + (typeof object != 'function' && isArrayLike(object))) { + return shimKeys(object); + } + return isObject(object) ? nativeKeys(object) : []; +}; + +/** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example * - * _.isObjectLike([1, 2, 3]); - * // => true + * function Foo() { + * this.a = 1; + * this.b = 2; + * } * - * _.isObjectLike(_.noop); - * // => false + * Foo.prototype.c = 3; * - * _.isObjectLike(null); - * // => false + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) */ -function isObjectLike(value) { - return !!value && typeof value == 'object'; +function keysIn(object) { + if (object == null) { + return []; + } + if (!isObject(object)) { + object = Object(object); + } + var length = object.length; + length = (length && isLength(length) && + (isArray(object) || isArguments(object)) && length) || 0; + + var Ctor = object.constructor, + index = -1, + isProto = typeof Ctor == 'function' && Ctor.prototype === object, + result = Array(length), + skipIndexes = length > 0; + + while (++index < length) { + result[index] = (index + ''); + } + for (var key in object) { + if (!(skipIndexes && isIndex(key, length)) && + !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; } -module.exports = isArguments; +module.exports = keys; -},{}],77:[function(require,module,exports){ +},{"lodash._getnative":88,"lodash.isarguments":87,"lodash.isarray":40}],85:[function(require,module,exports){ /** * Module dependencies. */ @@ -15275,9 +14238,6 @@ function Transport (opts) { this.ca = opts.ca; this.ciphers = opts.ciphers; this.rejectUnauthorized = opts.rejectUnauthorized; - - // other options for Node.js client - this.extraHeaders = opts.extraHeaders; } /** @@ -15286,6 +14246,13 @@ function Transport (opts) { Emitter(Transport.prototype); +/** + * A counter used to prevent collisions in the timestamps used + * for cache busting. + */ + +Transport.timestamps = 0; + /** * Emits an error. * @@ -15385,14 +14352,232 @@ Transport.prototype.onPacket = function (packet) { * @api private */ -Transport.prototype.onClose = function () { - this.readyState = 'closed'; - this.emit('close'); +Transport.prototype.onClose = function () { + this.readyState = 'closed'; + this.emit('close'); +}; + +},{"component-emitter":49,"engine.io-parser":72}],91:[function(require,module,exports){ +/** + * An abstraction for slicing an arraybuffer even when + * ArrayBuffer.prototype.slice is not supported + * + * @api public + */ + +module.exports = function(arraybuffer, start, end) { + var bytes = arraybuffer.byteLength; + start = start || 0; + end = end || bytes; + + if (arraybuffer.slice) { return arraybuffer.slice(start, end); } + + if (start < 0) { start += bytes; } + if (end < 0) { end += bytes; } + if (end > bytes) { end = bytes; } + + if (start >= bytes || start >= end || bytes === 0) { + return new ArrayBuffer(0); + } + + var abv = new Uint8Array(arraybuffer); + var result = new Uint8Array(end - start); + for (var i = start, ii = 0; i < end; i++, ii++) { + result[ii] = abv[i]; + } + return result.buffer; +}; + +},{}],92:[function(require,module,exports){ +/* + * base64-arraybuffer + * https://github.com/niklasvh/base64-arraybuffer + * + * Copyright (c) 2012 Niklas von Hertzen + * Licensed under the MIT license. + */ +(function(chars){ + "use strict"; + + exports.encode = function(arraybuffer) { + var bytes = new Uint8Array(arraybuffer), + i, len = bytes.length, base64 = ""; + + for (i = 0; i < len; i+=3) { + base64 += chars[bytes[i] >> 2]; + base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64 += chars[bytes[i + 2] & 63]; + } + + if ((len % 3) === 2) { + base64 = base64.substring(0, base64.length - 1) + "="; + } else if (len % 3 === 1) { + base64 = base64.substring(0, base64.length - 2) + "=="; + } + + return base64; + }; + + exports.decode = function(base64) { + var bufferLength = base64.length * 0.75, + len = base64.length, i, p = 0, + encoded1, encoded2, encoded3, encoded4; + + if (base64[base64.length - 1] === "=") { + bufferLength--; + if (base64[base64.length - 2] === "=") { + bufferLength--; + } + } + + var arraybuffer = new ArrayBuffer(bufferLength), + bytes = new Uint8Array(arraybuffer); + + for (i = 0; i < len; i+=4) { + encoded1 = chars.indexOf(base64[i]); + encoded2 = chars.indexOf(base64[i+1]); + encoded3 = chars.indexOf(base64[i+2]); + encoded4 = chars.indexOf(base64[i+3]); + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return arraybuffer; + }; +})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + +},{}],93:[function(require,module,exports){ +module.exports = after + +function after(count, callback, err_cb) { + var bail = false + err_cb = err_cb || noop + proxy.count = count + + return (count === 0) ? callback() : proxy + + function proxy(err, result) { + if (proxy.count <= 0) { + throw new Error('after called too many times') + } + --proxy.count + + // after first error, rest are passed to err_cb + if (err) { + bail = true + callback(err) + // future error callbacks will go to error handler + callback = err_cb + } else if (proxy.count === 0 && !bail) { + callback(null, result) + } + } +} + +function noop() {} + +},{}],94:[function(require,module,exports){ +var global=self;/** + * Create a blob builder even when vendor prefixes exist + */ + +var BlobBuilder = global.BlobBuilder + || global.WebKitBlobBuilder + || global.MSBlobBuilder + || global.MozBlobBuilder; + +/** + * Check if Blob constructor is supported + */ + +var blobSupported = (function() { + try { + var a = new Blob(['hi']); + return a.size === 2; + } catch(e) { + return false; + } +})(); + +/** + * Check if Blob constructor supports ArrayBufferViews + * Fails in Safari 6, so we need to map to ArrayBuffers there. + */ + +var blobSupportsArrayBufferView = blobSupported && (function() { + try { + var b = new Blob([new Uint8Array([1,2])]); + return b.size === 2; + } catch(e) { + return false; + } +})(); + +/** + * Check if BlobBuilder is supported + */ + +var blobBuilderSupported = BlobBuilder + && BlobBuilder.prototype.append + && BlobBuilder.prototype.getBlob; + +/** + * Helper function that maps ArrayBufferViews to ArrayBuffers + * Used by BlobBuilder constructor and old browsers that didn't + * support it in the Blob constructor. + */ + +function mapArrayBufferViews(ary) { + for (var i = 0; i < ary.length; i++) { + var chunk = ary[i]; + if (chunk.buffer instanceof ArrayBuffer) { + var buf = chunk.buffer; + + // if this is a subarray, make a copy so we only + // include the subarray region from the underlying buffer + if (chunk.byteLength !== buf.byteLength) { + var copy = new Uint8Array(chunk.byteLength); + copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); + buf = copy.buffer; + } + + ary[i] = buf; + } + } +} + +function BlobBuilderConstructor(ary, options) { + options = options || {}; + + var bb = new BlobBuilder(); + mapArrayBufferViews(ary); + + for (var i = 0; i < ary.length; i++) { + bb.append(ary[i]); + } + + return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); +}; + +function BlobConstructor(ary, options) { + mapArrayBufferViews(ary); + return new Blob(ary, options || {}); }; -},{"component-emitter":71,"engine.io-parser":70}],85:[function(require,module,exports){ +module.exports = (function() { + if (blobSupported) { + return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; + } else if (blobBuilderSupported) { + return BlobBuilderConstructor; + } else { + return undefined; + } +})(); -},{}],70:[function(require,module,exports){ +},{}],72:[function(require,module,exports){ var global=self;/** * Module dependencies. */ @@ -15580,7 +14765,7 @@ function encodeBlob(packet, supportsBinary, callback) { exports.encodeBase64Packet = function(packet, callback) { var message = 'b' + exports.packets[packet.type]; - if (Blob && packet.data instanceof global.Blob) { + if (Blob && packet.data instanceof Blob) { var fr = new FileReader(); fr.onload = function() { var b64 = fr.result.split(',')[1]; @@ -15649,1294 +14834,932 @@ exports.decodePacket = function (data, binaryType, utf8decode) { }; /** - * Decodes a packet encoded in a base64 string - * - * @param {String} base64 encoded message - * @return {Object} with `type` and `data` (if any) - */ - -exports.decodeBase64Packet = function(msg, binaryType) { - var type = packetslist[msg.charAt(0)]; - if (!global.ArrayBuffer) { - return { type: type, data: { base64: true, data: msg.substr(1) } }; - } - - var data = base64encoder.decode(msg.substr(1)); - - if (binaryType === 'blob' && Blob) { - data = new Blob([data]); - } - - return { type: type, data: data }; -}; - -/** - * Encodes multiple messages (payload). - * - * :data - * - * Example: - * - * 11:hello world2:hi - * - * If any contents are binary, they will be encoded as base64 strings. Base64 - * encoded strings are marked with a b before the length specifier - * - * @param {Array} packets - * @api private - */ - -exports.encodePayload = function (packets, supportsBinary, callback) { - if (typeof supportsBinary == 'function') { - callback = supportsBinary; - supportsBinary = null; - } - - var isBinary = hasBinary(packets); - - if (supportsBinary && isBinary) { - if (Blob && !dontSendBlobs) { - return exports.encodePayloadAsBlob(packets, callback); - } - - return exports.encodePayloadAsArrayBuffer(packets, callback); - } - - if (!packets.length) { - return callback('0:'); - } - - function setLengthHeader(message) { - return message.length + ':' + message; - } - - function encodeOne(packet, doneCallback) { - exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) { - doneCallback(null, setLengthHeader(message)); - }); - } - - map(packets, encodeOne, function(err, results) { - return callback(results.join('')); - }); -}; - -/** - * Async array map using after - */ - -function map(ary, each, done) { - var result = new Array(ary.length); - var next = after(ary.length, done); - - var eachWithIndex = function(i, el, cb) { - each(el, function(error, msg) { - result[i] = msg; - cb(error, result); - }); - }; - - for (var i = 0; i < ary.length; i++) { - eachWithIndex(i, ary[i], next); - } -} - -/* - * Decodes data when a payload is maybe expected. Possible binary contents are - * decoded from their base64 representation - * - * @param {String} data, callback method - * @api public - */ - -exports.decodePayload = function (data, binaryType, callback) { - if (typeof data != 'string') { - return exports.decodePayloadAsBinary(data, binaryType, callback); - } - - if (typeof binaryType === 'function') { - callback = binaryType; - binaryType = null; - } - - var packet; - if (data == '') { - // parser error - ignoring payload - return callback(err, 0, 1); - } - - var length = '' - , n, msg; - - for (var i = 0, l = data.length; i < l; i++) { - var chr = data.charAt(i); - - if (':' != chr) { - length += chr; - } else { - if ('' == length || (length != (n = Number(length)))) { - // parser error - ignoring payload - return callback(err, 0, 1); - } - - msg = data.substr(i + 1, n); - - if (length != msg.length) { - // parser error - ignoring payload - return callback(err, 0, 1); - } - - if (msg.length) { - packet = exports.decodePacket(msg, binaryType, true); - - if (err.type == packet.type && err.data == packet.data) { - // parser error in individual packet - ignoring payload - return callback(err, 0, 1); - } - - var ret = callback(packet, i + n, l); - if (false === ret) return; - } - - // advance cursor - i += n; - length = ''; - } - } - - if (length != '') { - // parser error - ignoring payload - return callback(err, 0, 1); - } - -}; - -/** - * Encodes multiple messages (payload) as binary. - * - * <1 = binary, 0 = string>[...] - * - * Example: - * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers - * - * @param {Array} packets - * @return {ArrayBuffer} encoded payload - * @api private - */ - -exports.encodePayloadAsArrayBuffer = function(packets, callback) { - if (!packets.length) { - return callback(new ArrayBuffer(0)); - } - - function encodeOne(packet, doneCallback) { - exports.encodePacket(packet, true, true, function(data) { - return doneCallback(null, data); - }); - } - - map(packets, encodeOne, function(err, encodedPackets) { - var totalLength = encodedPackets.reduce(function(acc, p) { - var len; - if (typeof p === 'string'){ - len = p.length; - } else { - len = p.byteLength; - } - return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2 - }, 0); - - var resultArray = new Uint8Array(totalLength); - - var bufferIndex = 0; - encodedPackets.forEach(function(p) { - var isString = typeof p === 'string'; - var ab = p; - if (isString) { - var view = new Uint8Array(p.length); - for (var i = 0; i < p.length; i++) { - view[i] = p.charCodeAt(i); - } - ab = view.buffer; - } - - if (isString) { // not true binary - resultArray[bufferIndex++] = 0; - } else { // true binary - resultArray[bufferIndex++] = 1; - } - - var lenStr = ab.byteLength.toString(); - for (var i = 0; i < lenStr.length; i++) { - resultArray[bufferIndex++] = parseInt(lenStr[i]); - } - resultArray[bufferIndex++] = 255; - - var view = new Uint8Array(ab); - for (var i = 0; i < view.length; i++) { - resultArray[bufferIndex++] = view[i]; - } - }); - - return callback(resultArray.buffer); - }); -}; - -/** - * Encode as Blob + * Decodes a packet encoded in a base64 string + * + * @param {String} base64 encoded message + * @return {Object} with `type` and `data` (if any) */ -exports.encodePayloadAsBlob = function(packets, callback) { - function encodeOne(packet, doneCallback) { - exports.encodePacket(packet, true, true, function(encoded) { - var binaryIdentifier = new Uint8Array(1); - binaryIdentifier[0] = 1; - if (typeof encoded === 'string') { - var view = new Uint8Array(encoded.length); - for (var i = 0; i < encoded.length; i++) { - view[i] = encoded.charCodeAt(i); - } - encoded = view.buffer; - binaryIdentifier[0] = 0; - } - - var len = (encoded instanceof ArrayBuffer) - ? encoded.byteLength - : encoded.size; +exports.decodeBase64Packet = function(msg, binaryType) { + var type = packetslist[msg.charAt(0)]; + if (!global.ArrayBuffer) { + return { type: type, data: { base64: true, data: msg.substr(1) } }; + } - var lenStr = len.toString(); - var lengthAry = new Uint8Array(lenStr.length + 1); - for (var i = 0; i < lenStr.length; i++) { - lengthAry[i] = parseInt(lenStr[i]); - } - lengthAry[lenStr.length] = 255; + var data = base64encoder.decode(msg.substr(1)); - if (Blob) { - var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]); - doneCallback(null, blob); - } - }); + if (binaryType === 'blob' && Blob) { + data = new Blob([data]); } - map(packets, encodeOne, function(err, results) { - return callback(new Blob(results)); - }); + return { type: type, data: data }; }; -/* - * Decodes data when a payload is maybe expected. Strings are decoded by - * interpreting each byte as a key code for entries marked to start with 0. See - * description of encodePayloadAsBinary +/** + * Encodes multiple messages (payload). * - * @param {ArrayBuffer} data, callback method - * @api public + * :data + * + * Example: + * + * 11:hello world2:hi + * + * If any contents are binary, they will be encoded as base64 strings. Base64 + * encoded strings are marked with a b before the length specifier + * + * @param {Array} packets + * @api private */ -exports.decodePayloadAsBinary = function (data, binaryType, callback) { - if (typeof binaryType === 'function') { - callback = binaryType; - binaryType = null; +exports.encodePayload = function (packets, supportsBinary, callback) { + if (typeof supportsBinary == 'function') { + callback = supportsBinary; + supportsBinary = null; } - var bufferTail = data; - var buffers = []; - - var numberTooLong = false; - while (bufferTail.byteLength > 0) { - var tailArray = new Uint8Array(bufferTail); - var isString = tailArray[0] === 0; - var msgLength = ''; - - for (var i = 1; ; i++) { - if (tailArray[i] == 255) break; - - if (msgLength.length > 310) { - numberTooLong = true; - break; - } + var isBinary = hasBinary(packets); - msgLength += tailArray[i]; + if (supportsBinary && isBinary) { + if (Blob && !dontSendBlobs) { + return exports.encodePayloadAsBlob(packets, callback); } - if(numberTooLong) return callback(err, 0, 1); + return exports.encodePayloadAsArrayBuffer(packets, callback); + } - bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length); - msgLength = parseInt(msgLength); + if (!packets.length) { + return callback('0:'); + } - var msg = sliceBuffer(bufferTail, 0, msgLength); - if (isString) { - try { - msg = String.fromCharCode.apply(null, new Uint8Array(msg)); - } catch (e) { - // iPhone Safari doesn't let you apply to typed arrays - var typed = new Uint8Array(msg); - msg = ''; - for (var i = 0; i < typed.length; i++) { - msg += String.fromCharCode(typed[i]); - } - } - } + function setLengthHeader(message) { + return message.length + ':' + message; + } - buffers.push(msg); - bufferTail = sliceBuffer(bufferTail, msgLength); + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) { + doneCallback(null, setLengthHeader(message)); + }); } - var total = buffers.length; - buffers.forEach(function(buffer, i) { - callback(exports.decodePacket(buffer, binaryType, true), i, total); + map(packets, encodeOne, function(err, results) { + return callback(results.join('')); }); }; -},{"./keys":80,"after":90,"arraybuffer.slice":87,"base64-arraybuffer":89,"blob":91,"has-binary":86,"utf8":88}],87:[function(require,module,exports){ /** - * An abstraction for slicing an arraybuffer even when - * ArrayBuffer.prototype.slice is not supported - * - * @api public + * Async array map using after */ -module.exports = function(arraybuffer, start, end) { - var bytes = arraybuffer.byteLength; - start = start || 0; - end = end || bytes; - - if (arraybuffer.slice) { return arraybuffer.slice(start, end); } - - if (start < 0) { start += bytes; } - if (end < 0) { end += bytes; } - if (end > bytes) { end = bytes; } +function map(ary, each, done) { + var result = new Array(ary.length); + var next = after(ary.length, done); - if (start >= bytes || start >= end || bytes === 0) { - return new ArrayBuffer(0); - } + var eachWithIndex = function(i, el, cb) { + each(el, function(error, msg) { + result[i] = msg; + cb(error, result); + }); + }; - var abv = new Uint8Array(arraybuffer); - var result = new Uint8Array(end - start); - for (var i = start, ii = 0; i < end; i++, ii++) { - result[ii] = abv[i]; + for (var i = 0; i < ary.length; i++) { + eachWithIndex(i, ary[i], next); } - return result.buffer; -}; - -},{}],88:[function(require,module,exports){ -var global=self;/*! https://mths.be/utf8js v2.0.0 by @mathias */ -;(function(root) { - - // Detect free variables `exports` - var freeExports = typeof exports == 'object' && exports; - - // Detect free variable `module` - var freeModule = typeof module == 'object' && module && - module.exports == freeExports && module; - - // Detect free variable `global`, from Node.js or Browserified code, - // and use it as `root` - var freeGlobal = typeof global == 'object' && global; - if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { - root = freeGlobal; - } - - /*--------------------------------------------------------------------------*/ +} - var stringFromCharCode = String.fromCharCode; +/* + * Decodes data when a payload is maybe expected. Possible binary contents are + * decoded from their base64 representation + * + * @param {String} data, callback method + * @api public + */ - // Taken from https://mths.be/punycode - function ucs2decode(string) { - var output = []; - var counter = 0; - var length = string.length; - var value; - var extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } +exports.decodePayload = function (data, binaryType, callback) { + if (typeof data != 'string') { + return exports.decodePayloadAsBinary(data, binaryType, callback); + } - // Taken from https://mths.be/punycode - function ucs2encode(array) { - var length = array.length; - var index = -1; - var value; - var output = ''; - while (++index < length) { - value = array[index]; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - } - return output; - } + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } - function checkScalarValue(codePoint) { - if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { - throw Error( - 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() + - ' is not a scalar value' - ); - } - } - /*--------------------------------------------------------------------------*/ + var packet; + if (data == '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } - function createByte(codePoint, shift) { - return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); - } + var length = '' + , n, msg; - function encodeCodePoint(codePoint) { - if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence - return stringFromCharCode(codePoint); - } - var symbol = ''; - if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence - symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); - } - else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence - checkScalarValue(codePoint); - symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); - symbol += createByte(codePoint, 6); - } - else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence - symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); - symbol += createByte(codePoint, 12); - symbol += createByte(codePoint, 6); - } - symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); - return symbol; - } + for (var i = 0, l = data.length; i < l; i++) { + var chr = data.charAt(i); - function utf8encode(string) { - var codePoints = ucs2decode(string); - var length = codePoints.length; - var index = -1; - var codePoint; - var byteString = ''; - while (++index < length) { - codePoint = codePoints[index]; - byteString += encodeCodePoint(codePoint); - } - return byteString; - } + if (':' != chr) { + length += chr; + } else { + if ('' == length || (length != (n = Number(length)))) { + // parser error - ignoring payload + return callback(err, 0, 1); + } - /*--------------------------------------------------------------------------*/ + msg = data.substr(i + 1, n); - function readContinuationByte() { - if (byteIndex >= byteCount) { - throw Error('Invalid byte index'); - } + if (length != msg.length) { + // parser error - ignoring payload + return callback(err, 0, 1); + } - var continuationByte = byteArray[byteIndex] & 0xFF; - byteIndex++; + if (msg.length) { + packet = exports.decodePacket(msg, binaryType, true); - if ((continuationByte & 0xC0) == 0x80) { - return continuationByte & 0x3F; - } + if (err.type == packet.type && err.data == packet.data) { + // parser error in individual packet - ignoring payload + return callback(err, 0, 1); + } - // If we end up here, it’s not a continuation byte - throw Error('Invalid continuation byte'); - } + var ret = callback(packet, i + n, l); + if (false === ret) return; + } - function decodeSymbol() { - var byte1; - var byte2; - var byte3; - var byte4; - var codePoint; + // advance cursor + i += n; + length = ''; + } + } - if (byteIndex > byteCount) { - throw Error('Invalid byte index'); - } + if (length != '') { + // parser error - ignoring payload + return callback(err, 0, 1); + } - if (byteIndex == byteCount) { - return false; - } +}; - // Read first byte - byte1 = byteArray[byteIndex] & 0xFF; - byteIndex++; +/** + * Encodes multiple messages (payload) as binary. + * + * <1 = binary, 0 = string>[...] + * + * Example: + * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers + * + * @param {Array} packets + * @return {ArrayBuffer} encoded payload + * @api private + */ - // 1-byte sequence (no continuation bytes) - if ((byte1 & 0x80) == 0) { - return byte1; - } +exports.encodePayloadAsArrayBuffer = function(packets, callback) { + if (!packets.length) { + return callback(new ArrayBuffer(0)); + } - // 2-byte sequence - if ((byte1 & 0xE0) == 0xC0) { - var byte2 = readContinuationByte(); - codePoint = ((byte1 & 0x1F) << 6) | byte2; - if (codePoint >= 0x80) { - return codePoint; - } else { - throw Error('Invalid continuation byte'); - } - } + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(data) { + return doneCallback(null, data); + }); + } - // 3-byte sequence (may include unpaired surrogates) - if ((byte1 & 0xF0) == 0xE0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; - if (codePoint >= 0x0800) { - checkScalarValue(codePoint); - return codePoint; - } else { - throw Error('Invalid continuation byte'); - } - } + map(packets, encodeOne, function(err, encodedPackets) { + var totalLength = encodedPackets.reduce(function(acc, p) { + var len; + if (typeof p === 'string'){ + len = p.length; + } else { + len = p.byteLength; + } + return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2 + }, 0); - // 4-byte sequence - if ((byte1 & 0xF8) == 0xF0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - byte4 = readContinuationByte(); - codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | - (byte3 << 0x06) | byte4; - if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { - return codePoint; - } - } + var resultArray = new Uint8Array(totalLength); - throw Error('Invalid UTF-8 detected'); - } + var bufferIndex = 0; + encodedPackets.forEach(function(p) { + var isString = typeof p === 'string'; + var ab = p; + if (isString) { + var view = new Uint8Array(p.length); + for (var i = 0; i < p.length; i++) { + view[i] = p.charCodeAt(i); + } + ab = view.buffer; + } - var byteArray; - var byteCount; - var byteIndex; - function utf8decode(byteString) { - byteArray = ucs2decode(byteString); - byteCount = byteArray.length; - byteIndex = 0; - var codePoints = []; - var tmp; - while ((tmp = decodeSymbol()) !== false) { - codePoints.push(tmp); - } - return ucs2encode(codePoints); - } + if (isString) { // not true binary + resultArray[bufferIndex++] = 0; + } else { // true binary + resultArray[bufferIndex++] = 1; + } - /*--------------------------------------------------------------------------*/ + var lenStr = ab.byteLength.toString(); + for (var i = 0; i < lenStr.length; i++) { + resultArray[bufferIndex++] = parseInt(lenStr[i]); + } + resultArray[bufferIndex++] = 255; - var utf8 = { - 'version': '2.0.0', - 'encode': utf8encode, - 'decode': utf8decode - }; + var view = new Uint8Array(ab); + for (var i = 0; i < view.length; i++) { + resultArray[bufferIndex++] = view[i]; + } + }); - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define(function() { - return utf8; - }); - } else if (freeExports && !freeExports.nodeType) { - if (freeModule) { // in Node.js or RingoJS v0.8.0+ - freeModule.exports = utf8; - } else { // in Narwhal or RingoJS v0.7.0- - var object = {}; - var hasOwnProperty = object.hasOwnProperty; - for (var key in utf8) { - hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]); - } - } - } else { // in Rhino or a web browser - root.utf8 = utf8; - } + return callback(resultArray.buffer); + }); +}; + +/** + * Encode as Blob + */ + +exports.encodePayloadAsBlob = function(packets, callback) { + function encodeOne(packet, doneCallback) { + exports.encodePacket(packet, true, true, function(encoded) { + var binaryIdentifier = new Uint8Array(1); + binaryIdentifier[0] = 1; + if (typeof encoded === 'string') { + var view = new Uint8Array(encoded.length); + for (var i = 0; i < encoded.length; i++) { + view[i] = encoded.charCodeAt(i); + } + encoded = view.buffer; + binaryIdentifier[0] = 0; + } -}(this)); + var len = (encoded instanceof ArrayBuffer) + ? encoded.byteLength + : encoded.size; + + var lenStr = len.toString(); + var lengthAry = new Uint8Array(lenStr.length + 1); + for (var i = 0; i < lenStr.length; i++) { + lengthAry[i] = parseInt(lenStr[i]); + } + lengthAry[lenStr.length] = 255; + + if (Blob) { + var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]); + doneCallback(null, blob); + } + }); + } + + map(packets, encodeOne, function(err, results) { + return callback(new Blob(results)); + }); +}; -},{}],89:[function(require,module,exports){ /* - * base64-arraybuffer - * https://github.com/niklasvh/base64-arraybuffer + * Decodes data when a payload is maybe expected. Strings are decoded by + * interpreting each byte as a key code for entries marked to start with 0. See + * description of encodePayloadAsBinary * - * Copyright (c) 2012 Niklas von Hertzen - * Licensed under the MIT license. + * @param {ArrayBuffer} data, callback method + * @api public */ -(function(chars){ - "use strict"; - exports.encode = function(arraybuffer) { - var bytes = new Uint8Array(arraybuffer), - i, len = bytes.length, base64 = ""; - - for (i = 0; i < len; i+=3) { - base64 += chars[bytes[i] >> 2]; - base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; - base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; - base64 += chars[bytes[i + 2] & 63]; - } +exports.decodePayloadAsBinary = function (data, binaryType, callback) { + if (typeof binaryType === 'function') { + callback = binaryType; + binaryType = null; + } - if ((len % 3) === 2) { - base64 = base64.substring(0, base64.length - 1) + "="; - } else if (len % 3 === 1) { - base64 = base64.substring(0, base64.length - 2) + "=="; - } + var bufferTail = data; + var buffers = []; - return base64; - }; + var numberTooLong = false; + while (bufferTail.byteLength > 0) { + var tailArray = new Uint8Array(bufferTail); + var isString = tailArray[0] === 0; + var msgLength = ''; - exports.decode = function(base64) { - var bufferLength = base64.length * 0.75, - len = base64.length, i, p = 0, - encoded1, encoded2, encoded3, encoded4; + for (var i = 1; ; i++) { + if (tailArray[i] == 255) break; - if (base64[base64.length - 1] === "=") { - bufferLength--; - if (base64[base64.length - 2] === "=") { - bufferLength--; + if (msgLength.length > 310) { + numberTooLong = true; + break; } + + msgLength += tailArray[i]; } - var arraybuffer = new ArrayBuffer(bufferLength), - bytes = new Uint8Array(arraybuffer); + if(numberTooLong) return callback(err, 0, 1); - for (i = 0; i < len; i+=4) { - encoded1 = chars.indexOf(base64[i]); - encoded2 = chars.indexOf(base64[i+1]); - encoded3 = chars.indexOf(base64[i+2]); - encoded4 = chars.indexOf(base64[i+3]); + bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length); + msgLength = parseInt(msgLength); - bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); - bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); - bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + var msg = sliceBuffer(bufferTail, 0, msgLength); + if (isString) { + try { + msg = String.fromCharCode.apply(null, new Uint8Array(msg)); + } catch (e) { + // iPhone Safari doesn't let you apply to typed arrays + var typed = new Uint8Array(msg); + msg = ''; + for (var i = 0; i < typed.length; i++) { + msg += String.fromCharCode(typed[i]); + } + } } - return arraybuffer; - }; -})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + buffers.push(msg); + bufferTail = sliceBuffer(bufferTail, msgLength); + } -},{}],90:[function(require,module,exports){ -module.exports = after + var total = buffers.length; + buffers.forEach(function(buffer, i) { + callback(exports.decodePacket(buffer, binaryType, true), i, total); + }); +}; -function after(count, callback, err_cb) { - var bail = false - err_cb = err_cb || noop - proxy.count = count +},{"./keys":80,"after":93,"arraybuffer.slice":91,"base64-arraybuffer":92,"blob":94,"has-binary":56,"utf8":90}],76:[function(require,module,exports){ +// browser shim for xmlhttprequest module +var hasCORS = require('has-cors'); - return (count === 0) ? callback() : proxy +module.exports = function(opts) { + var xdomain = opts.xdomain; - function proxy(err, result) { - if (proxy.count <= 0) { - throw new Error('after called too many times') - } - --proxy.count + // scheme must be same when usign XDomainRequest + // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx + var xscheme = opts.xscheme; - // after first error, rest are passed to err_cb - if (err) { - bail = true - callback(err) - // future error callbacks will go to error handler - callback = err_cb - } else if (proxy.count === 0 && !bail) { - callback(null, result) - } + // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default. + // https://github.com/Automattic/engine.io-client/pull/217 + var enablesXDR = opts.enablesXDR; + + // XMLHttpRequest can be disabled on IE + try { + if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) { + return new XMLHttpRequest(); + } + } catch (e) { } + + // Use XDomainRequest for IE8 if enablesXDR is true + // because loading bar keeps flashing when using jsonp-polling + // https://github.com/yujiosaka/socke.io-ie8-loading-example + try { + if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) { + return new XDomainRequest(); } + } catch (e) { } + + if (!xdomain) { + try { + return new ActiveXObject('Microsoft.XMLHTTP'); + } catch(e) { } + } } -function noop() {} +},{"has-cors":95}],89:[function(require,module,exports){ -},{}],91:[function(require,module,exports){ -var global=self;/** - * Create a blob builder even when vendor prefixes exist +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. */ -var BlobBuilder = global.BlobBuilder - || global.WebKitBlobBuilder - || global.MSBlobBuilder - || global.MozBlobBuilder; +exports = module.exports = debug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = require('ms'); /** - * Check if Blob constructor is supported + * The currently active debug mode names, and names to skip. */ -var blobSupported = (function() { - try { - var a = new Blob(['hi']); - return a.size === 2; - } catch(e) { - return false; - } -})(); +exports.names = []; +exports.skips = []; /** - * Check if Blob constructor supports ArrayBufferViews - * Fails in Safari 6, so we need to map to ArrayBuffers there. + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lowercased letter, i.e. "n". + */ + +exports.formatters = {}; + +/** + * Previously assigned color. + */ + +var prevColor = 0; + +/** + * Previous log timestamp. + */ + +var prevTime; + +/** + * Select a color. + * + * @return {Number} + * @api private + */ + +function selectColor() { + return exports.colors[prevColor++ % exports.colors.length]; +} + +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public */ -var blobSupportsArrayBufferView = blobSupported && (function() { - try { - var b = new Blob([new Uint8Array([1,2])]); - return b.size === 2; - } catch(e) { - return false; - } -})(); +function debug(namespace) { + + // define the `disabled` version + function disabled() { + } + disabled.enabled = false; + + // define the `enabled` version + function enabled() { + + var self = enabled; + + // set `diff` timestamp + var curr = +new Date(); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + // add the `color` if not set + if (null == self.useColors) self.useColors = exports.useColors(); + if (null == self.color && self.useColors) self.color = selectColor(); -/** - * Check if BlobBuilder is supported - */ + var args = Array.prototype.slice.call(arguments); -var blobBuilderSupported = BlobBuilder - && BlobBuilder.prototype.append - && BlobBuilder.prototype.getBlob; + args[0] = exports.coerce(args[0]); -/** - * Helper function that maps ArrayBufferViews to ArrayBuffers - * Used by BlobBuilder constructor and old browsers that didn't - * support it in the Blob constructor. - */ + if ('string' !== typeof args[0]) { + // anything else let's inspect with %o + args = ['%o'].concat(args); + } -function mapArrayBufferViews(ary) { - for (var i = 0; i < ary.length; i++) { - var chunk = ary[i]; - if (chunk.buffer instanceof ArrayBuffer) { - var buf = chunk.buffer; + // apply any `formatters` transformations + var index = 0; + args[0] = args[0].replace(/%([a-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); - // if this is a subarray, make a copy so we only - // include the subarray region from the underlying buffer - if (chunk.byteLength !== buf.byteLength) { - var copy = new Uint8Array(chunk.byteLength); - copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength)); - buf = copy.buffer; + // now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; } + return match; + }); - ary[i] = buf; + if ('function' === typeof exports.formatArgs) { + args = exports.formatArgs.apply(self, args); } + var logFn = enabled.log || exports.log || console.log.bind(console); + logFn.apply(self, args); } -} + enabled.enabled = true; -function BlobBuilderConstructor(ary, options) { - options = options || {}; + var fn = exports.enabled(namespace) ? enabled : disabled; - var bb = new BlobBuilder(); - mapArrayBufferViews(ary); + fn.namespace = namespace; - for (var i = 0; i < ary.length; i++) { - bb.append(ary[i]); - } + return fn; +} - return (options.type) ? bb.getBlob(options.type) : bb.getBlob(); -}; +/** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ -function BlobConstructor(ary, options) { - mapArrayBufferViews(ary); - return new Blob(ary, options || {}); -}; +function enable(namespaces) { + exports.save(namespaces); -module.exports = (function() { - if (blobSupported) { - return blobSupportsArrayBufferView ? global.Blob : BlobConstructor; - } else if (blobBuilderSupported) { - return BlobBuilderConstructor; - } else { - return undefined; - } -})(); + var split = (namespaces || '').split(/[\s,]+/); + var len = split.length; -},{}],73:[function(require,module,exports){ -// browser shim for xmlhttprequest module -var hasCORS = require('has-cors'); + 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 + '$')); + } + } +} -module.exports = function(opts) { - var xdomain = opts.xdomain; +/** + * Disable debug output. + * + * @api public + */ - // scheme must be same when usign XDomainRequest - // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx - var xscheme = opts.xscheme; +function disable() { + exports.enable(''); +} - // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default. - // https://github.com/Automattic/engine.io-client/pull/217 - var enablesXDR = opts.enablesXDR; +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ - // XMLHttpRequest can be disabled on IE - try { - if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) { - return new XMLHttpRequest(); +function enabled(name) { + var i, len; + for (i = 0, len = exports.skips.length; i < len; i++) { + if (exports.skips[i].test(name)) { + return false; } - } catch (e) { } - - // Use XDomainRequest for IE8 if enablesXDR is true - // because loading bar keeps flashing when using jsonp-polling - // https://github.com/yujiosaka/socke.io-ie8-loading-example - try { - if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) { - return new XDomainRequest(); + } + for (i = 0, len = exports.names.length; i < len; i++) { + if (exports.names[i].test(name)) { + return true; } - } catch (e) { } - - if (!xdomain) { - try { - return new ActiveXObject('Microsoft.XMLHTTP'); - } catch(e) { } } + return false; } -},{"has-cors":92}],92:[function(require,module,exports){ - /** - * Module exports. - * - * Logic borrowed from Modernizr: + * Coerce `val`. * - * - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js + * @param {Mixed} val + * @return {Mixed} + * @api private */ -try { - module.exports = typeof XMLHttpRequest !== 'undefined' && - 'withCredentials' in new XMLHttpRequest(); -} catch (err) { - // if XMLHttp support is disabled in IE then it will throw - // when trying to create - module.exports = false; +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; } -},{}],84:[function(require,module,exports){ +},{"ms":96}],96:[function(require,module,exports){ /** - * lodash 3.0.1 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license + * Helpers. */ -var keys = require('lodash.keys'); + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; /** - * Converts `value` to an object if it's not one. + * Parse or format the given `val`. * - * @private - * @param {*} value The value to process. - * @returns {Object} Returns the object. + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} options + * @return {String|Number} + * @api public */ -function toObject(value) { - return isObject(value) ? value : Object(value); + +module.exports = function(val, options){ + options = options || {}; + if ('string' == typeof val) return parse(val); + return options.long + ? long(val) + : short(val); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|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 'y': + return n * y; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 's': + return n * s; + case 'ms': + return n; + } } /** - * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. - * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true + * Short format for `ms`. * - * _.isObject(1); - * // => false + * @param {Number} ms + * @return {String} + * @api private */ -function isObject(value) { - // Avoid a V8 JIT bug in Chrome 19-20. - // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. - var type = typeof value; - return !!value && (type == 'object' || type == 'function'); + +function short(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'; } /** - * Creates a two dimensional array of the key-value pairs for `object`, - * e.g. `[[key1, value1], [key2, value2]]`. - * - * @static - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the new array of key-value pairs. - * @example + * Long format for `ms`. * - * _.pairs({ 'barney': 36, 'fred': 40 }); - * // => [['barney', 36], ['fred', 40]] (iteration order is not guaranteed) + * @param {Number} ms + * @return {String} + * @api private */ -function pairs(object) { - object = toObject(object); - - var index = -1, - props = keys(object), - length = props.length, - result = Array(length); - while (++index < length) { - var key = props[index]; - result[index] = [key, object[key]]; - } - return result; +function long(ms) { + return plural(ms, d, 'day') + || plural(ms, h, 'hour') + || plural(ms, m, 'minute') + || plural(ms, s, 'second') + || ms + ' ms'; } -module.exports = pairs; +/** + * 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'; +} -},{"lodash.keys":66}],83:[function(require,module,exports){ +},{}],78:[function(require,module,exports){ +var global=self; /** - * lodash 3.0.7 (Custom Build) - * Build: `lodash modern modularize exports="npm" -o ./` - * Copyright 2012-2015 The Dojo Foundation - * Based on Underscore.js 1.8.3 - * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Available under MIT license + * Module requirements. */ -var isArray = require('lodash.isarray'), - isTypedArray = require('lodash.istypedarray'), - keys = require('lodash.keys'); -/** `Object#toString` result references. */ -var argsTag = '[object Arguments]', - arrayTag = '[object Array]', - boolTag = '[object Boolean]', - dateTag = '[object Date]', - errorTag = '[object Error]', - numberTag = '[object Number]', - objectTag = '[object Object]', - regexpTag = '[object RegExp]', - stringTag = '[object String]'; +var Polling = require('./polling'); +var inherit = require('component-inherit'); /** - * Checks if `value` is object-like. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * Module exports. */ -function isObjectLike(value) { - return !!value && typeof value == 'object'; -} -/** Used for native method references. */ -var objectProto = Object.prototype; +module.exports = JSONPPolling; -/** Used to check objects for own properties. */ -var hasOwnProperty = objectProto.hasOwnProperty; +/** + * Cached regular expressions. + */ + +var rNewline = /\n/g; +var rEscapedNewline = /\\n/g; /** - * Used to resolve the [`toStringTag`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring) - * of values. + * Global JSONP callbacks. */ -var objToString = objectProto.toString; + +var callbacks; /** - * A specialized version of `_.some` for arrays without support for callback - * shorthands and `this` binding. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. + * Callbacks count. */ -function arraySome(array, predicate) { - var index = -1, - length = array.length; - while (++index < length) { - if (predicate(array[index], index, array)) { - return true; - } - } - return false; -} +var index = 0; /** - * The base implementation of `_.isEqual` without support for `this` binding - * `customizer` functions. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {Function} [customizer] The function to customize comparing values. - * @param {boolean} [isLoose] Specify performing partial comparisons. - * @param {Array} [stackA] Tracks traversed `value` objects. - * @param {Array} [stackB] Tracks traversed `other` objects. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * Noop. */ -function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) { - if (value === other) { - return true; - } - if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) { - return value !== value && other !== other; - } - return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB); -} + +function empty () { } /** - * A specialized version of `baseIsEqual` for arrays and objects which performs - * deep comparisons and tracks traversed objects enabling objects with circular - * references to be compared. + * JSONP Polling constructor. * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Function} [customizer] The function to customize comparing objects. - * @param {boolean} [isLoose] Specify performing partial comparisons. - * @param {Array} [stackA=[]] Tracks traversed `value` objects. - * @param {Array} [stackB=[]] Tracks traversed `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + * @param {Object} opts. + * @api public */ -function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) { - var objIsArr = isArray(object), - othIsArr = isArray(other), - objTag = arrayTag, - othTag = arrayTag; - if (!objIsArr) { - objTag = objToString.call(object); - if (objTag == argsTag) { - objTag = objectTag; - } else if (objTag != objectTag) { - objIsArr = isTypedArray(object); - } - } - if (!othIsArr) { - othTag = objToString.call(other); - if (othTag == argsTag) { - othTag = objectTag; - } else if (othTag != objectTag) { - othIsArr = isTypedArray(other); - } - } - var objIsObj = objTag == objectTag, - othIsObj = othTag == objectTag, - isSameTag = objTag == othTag; +function JSONPPolling (opts) { + Polling.call(this, opts); - if (isSameTag && !(objIsArr || objIsObj)) { - return equalByTag(object, other, objTag); - } - if (!isLoose) { - var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), - othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); + this.query = this.query || {}; - if (objIsWrapped || othIsWrapped) { - return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB); - } - } - if (!isSameTag) { - return false; + // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + if (!callbacks) { + // we need to consider multiple engines in the same page + if (!global.___eio) global.___eio = []; + callbacks = global.___eio; } - // Assume cyclic values are equal. - // For more information on detecting circular references see https://es5.github.io/#JO. - stackA || (stackA = []); - stackB || (stackB = []); - var length = stackA.length; - while (length--) { - if (stackA[length] == object) { - return stackB[length] == other; - } + // callback identifier + this.index = callbacks.length; + + // add callback to jsonp global + var self = this; + callbacks.push(function (msg) { + self.onData(msg); + }); + + // append to query string + this.query.j = this.index; + + // prevent spurious errors from being emitted when the window is unloaded + if (global.document && global.addEventListener) { + global.addEventListener('beforeunload', function () { + if (self.script) self.script.onerror = empty; + }, false); } - // Add `object` and `other` to the stack of traversed objects. - stackA.push(object); - stackB.push(other); +} - var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB); +/** + * Inherits from Polling. + */ - stackA.pop(); - stackB.pop(); +inherit(JSONPPolling, Polling); - return result; -} +/* + * JSONP only supports binary as base64 encoded strings + */ + +JSONPPolling.prototype.supportsBinary = false; /** - * A specialized version of `baseIsEqualDeep` for arrays with support for - * partial deep comparisons. + * Closes the socket. * - * @private - * @param {Array} array The array to compare. - * @param {Array} other The other array to compare. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Function} [customizer] The function to customize comparing arrays. - * @param {boolean} [isLoose] Specify performing partial comparisons. - * @param {Array} [stackA] Tracks traversed `value` objects. - * @param {Array} [stackB] Tracks traversed `other` objects. - * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + * @api private */ -function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) { - var index = -1, - arrLength = array.length, - othLength = other.length; - if (arrLength != othLength && !(isLoose && othLength > arrLength)) { - return false; +JSONPPolling.prototype.doClose = function () { + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; } - // Ignore non-index properties. - while (++index < arrLength) { - var arrValue = array[index], - othValue = other[index], - result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined; - if (result !== undefined) { - if (result) { - continue; - } - return false; - } - // Recursively compare arrays (susceptible to call stack limits). - if (isLoose) { - if (!arraySome(other, function(othValue) { - return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB); - })) { - return false; - } - } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) { - return false; - } + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; } - return true; -} + + Polling.prototype.doClose.call(this); +}; /** - * A specialized version of `baseIsEqualDeep` for comparing objects of - * the same `toStringTag`. - * - * **Note:** This function only supports comparing values with tags of - * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * Starts a poll cycle. * - * @private - * @param {Object} value The object to compare. - * @param {Object} other The other object to compare. - * @param {string} tag The `toStringTag` of the objects to compare. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + * @api private */ -function equalByTag(object, other, tag) { - switch (tag) { - case boolTag: - case dateTag: - // Coerce dates and booleans to numbers, dates to milliseconds and booleans - // to `1` or `0` treating invalid dates coerced to `NaN` as not equal. - return +object == +other; - case errorTag: - return object.name == other.name && object.message == other.message; +JSONPPolling.prototype.doPoll = function () { + var self = this; + var script = document.createElement('script'); - case numberTag: - // Treat `NaN` vs. `NaN` as equal. - return (object != +object) - ? other != +other - : object == +other; + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } - case regexpTag: - case stringTag: - // Coerce regexes to strings and treat strings primitives and string - // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details. - return object == (other + ''); + script.async = true; + script.src = this.uri(); + script.onerror = function(e){ + self.onError('jsonp poll error',e); + }; + + var insertAt = document.getElementsByTagName('script')[0]; + insertAt.parentNode.insertBefore(script, insertAt); + this.script = script; + + var isUAgecko = 'undefined' != typeof navigator && /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); } - return false; -} +}; /** - * A specialized version of `baseIsEqualDeep` for objects with support for - * partial deep comparisons. + * Writes with a hidden iframe. * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Function} [customizer] The function to customize comparing values. - * @param {boolean} [isLoose] Specify performing partial comparisons. - * @param {Array} [stackA] Tracks traversed `value` objects. - * @param {Array} [stackB] Tracks traversed `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + * @param {String} data to send + * @param {Function} called upon flush. + * @api private */ -function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) { - var objProps = keys(object), - objLength = objProps.length, - othProps = keys(other), - othLength = othProps.length; - if (objLength != othLength && !isLoose) { - return false; +JSONPPolling.prototype.doWrite = function (data, fn) { + var self = this; + + if (!this.form) { + var form = document.createElement('form'); + var area = document.createElement('textarea'); + var id = this.iframeId = 'eio_iframe_' + this.index; + var iframe; + + form.className = 'socketio'; + form.style.position = 'absolute'; + form.style.top = '-1000px'; + form.style.left = '-1000px'; + form.target = id; + form.method = 'POST'; + form.setAttribute('accept-charset', 'utf-8'); + area.name = 'd'; + form.appendChild(area); + document.body.appendChild(form); + + this.form = form; + this.area = area; } - var index = objLength; - while (index--) { - var key = objProps[index]; - if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) { - return false; - } + + this.form.action = this.uri(); + + function complete () { + initIframe(); + fn(); } - var skipCtor = isLoose; - while (++index < objLength) { - key = objProps[index]; - var objValue = object[key], - othValue = other[key], - result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined; - // Recursively compare objects (susceptible to call stack limits). - if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) { - return false; + function initIframe () { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError('jsonp polling iframe removal error', e); + } } - skipCtor || (skipCtor = key == 'constructor'); - } - if (!skipCtor) { - var objCtor = object.constructor, - othCtor = other.constructor; - // Non `Object` object instances with different constructors are not equal. - if (objCtor != othCtor && - ('constructor' in object && 'constructor' in other) && - !(typeof objCtor == 'function' && objCtor instanceof objCtor && - typeof othCtor == 'function' && othCtor instanceof othCtor)) { - return false; + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = '