From 175264b7f9ec245b02dadff8a2a1f80e3606a7ac Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Fri, 22 Jan 2016 11:33:32 -0600 Subject: [PATCH] Add callstats support --- JitsiConference.js | 97 +- JitsiMeetJS.js | 10 +- lib-jitsi-meet.js | 8379 ++++++++++++++++++++++- lib-jitsi-meet.min.js | 28 +- modules/RTC/JitsiRemoteTrack.js | 2 - modules/settings/Settings.js | 28 +- modules/statistics/CallStats.js | 276 +- modules/statistics/statistics.js | 283 +- modules/util/UsernameGenerator.js | 429 ++ modules/xmpp/ChatRoom.js | 5 +- modules/xmpp/TraceablePeerConnection.js | 35 +- modules/xmpp/moderator.js | 9 +- modules/xmpp/strophe.emuc.js | 5 +- modules/xmpp/xmpp.js | 4 +- service/xmpp/XMPPEvents.js | 22 +- 15 files changed, 9220 insertions(+), 392 deletions(-) create mode 100644 modules/util/UsernameGenerator.js diff --git a/JitsiConference.js b/JitsiConference.js index 9ec36612f..6da34f765 100644 --- a/JitsiConference.js +++ b/JitsiConference.js @@ -12,6 +12,7 @@ var JitsiParticipant = require("./JitsiParticipant"); var Statistics = require("./modules/statistics/statistics"); var JitsiDTMFManager = require('./modules/DTMF/JitsiDTMFManager'); var JitsiTrackEvents = require("./JitsiTrackEvents"); +var Settings = require("./modules/settings/Settings"); /** * Creates a JitsiConference object with the given name and properties. @@ -32,12 +33,22 @@ function JitsiConference(options) { this.connection = this.options.connection; this.xmpp = this.connection.xmpp; this.eventEmitter = new EventEmitter(); - this.room = this.xmpp.createRoom(this.options.name, this.options.config); + var confID = this.options.name + '@' + this.xmpp.options.hosts.muc; + this.settings = new Settings(confID); + this.room = this.xmpp.createRoom(this.options.name, this.options.config, + this.settings); this.room.updateDeviceAvailability(RTC.getDeviceAvailability()); this.rtc = new RTC(this.room, options); - if(!RTC.options.disableAudioLevels) - this.statistics = new Statistics(); + this.statistics = new Statistics({ + disableAudioLevels: RTC.options.disableAudioLevels, + callStatsID: this.options.config.callStatsID, + callStatsSecret: this.options.config.callStatsSecret, + disableThirdPartyRequests: this.options.config.disableThirdPartyRequests + }); setupListeners(this); + JitsiMeetJS._gumFailedHandler.push(function(error) { + this.statistics.sendGetUserMediaFailed(error); + }.bind(this)); this.participants = {}; this.lastDominantSpeaker = null; this.dtmfManager = null; @@ -259,9 +270,12 @@ JitsiConference.prototype.addTrack = function (track) { track.muteHandler = this._fireMuteChangeEvent.bind(this, track); track.stopHandler = this.removeTrack.bind(this, track); track.audioLevelHandler = this._fireAudioLevelChangeEvent.bind(this); - track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, track.muteHandler); - track.addEventListener(JitsiTrackEvents.TRACK_STOPPED, track.stopHandler); - track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, track.audioLevelHandler); + track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, + track.muteHandler); + track.addEventListener(JitsiTrackEvents.TRACK_STOPPED, + track.stopHandler); + track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, + track.audioLevelHandler); this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, track); }.bind(this)); }; @@ -717,6 +731,29 @@ JitsiConference.prototype.getLogs = function () { return data; }; +/** + * Sends the given feedback through CallStats if enabled. + * + * @param overallFeedback an integer between 1 and 5 indicating the + * user feedback + * @param detailedFeedback detailed feedback from the user. Not yet used + */ +JitsiConference.prototype.sendFeedback = +function(overallFeedback, detailedFeedback){ + this.statistics.sendFeedback(overallFeedback, detailedFeedback); +} + +/** + * Returns true if the callstats integration is enabled, otherwise returns + * false. + * + * @returns true if the callstats integration is enabled, otherwise returns + * false. + */ +JitsiConference.prototype.isCallstatsEnabled = function () { + return this.statistics.isCallstatsEnabled(); +} + /** * Setups the listeners needed for the conference. * @param conference the conference @@ -724,8 +761,7 @@ JitsiConference.prototype.getLogs = function () { function setupListeners(conference) { conference.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) { conference.rtc.onIncommingCall(event); - if(conference.statistics) - conference.statistics.startRemoteStats(event.peerconnection); + conference.statistics.startRemoteStats(event.peerconnection); }); conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, @@ -907,6 +943,51 @@ function setupListeners(conference) { // RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { // conference.room.updateDeviceAvailability(devices); // }); + + conference.room.addListener(XMPPEvents.PEERCONNECTION_READY, + function (session) { + conference.statistics.startCallStats( + session, conference.settings); + }); + + conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, + function () { + conference.statistics.sendSetupFailedEvent(); + }); + + conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, + function (track) { + if(!track.isLocal()) + return; + var type = (track.getType() === "audio")? "audio" : "video"; + conference.statistics.sendMuteEvent(track.isMuted(), type); + }); + + conference.room.addListener(XMPPEvents.CREATE_OFFER_FAILED, function (e, pc) { + conference.statistics.sendCreateOfferFailed(e, pc); + }); + + conference.room.addListener(XMPPEvents.CREATE_ANSWER_FAILED, function (e, pc) { + conference.statistics.sendCreateAnswerFailed(e, pc); + }); + + conference.room.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED, + function (e, pc) { + conference.statistics.sendSetLocalDescFailed(e, pc); + } + ); + + conference.room.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED, + function (e, pc) { + conference.statistics.sendSetRemoteDescFailed(e, pc); + } + ); + + conference.room.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED, + function (e, pc) { + conference.statistics.sendAddIceCandidateFailed(e, pc); + } + ); } } diff --git a/JitsiMeetJS.js b/JitsiMeetJS.js index 02a08d814..3561ab315 100644 --- a/JitsiMeetJS.js +++ b/JitsiMeetJS.js @@ -43,6 +43,10 @@ var LibJitsiMeet = { track: JitsiTrackErrors }, logLevels: Logger.levels, + /** + * Array of functions that will receive the GUM error. + */ + _gumFailedHandler: [], init: function (options) { return RTC.init(options || {}); }, @@ -90,6 +94,10 @@ var LibJitsiMeet = { } return tracks; }).catch(function (error) { + this._gumFailedHandler.forEach(function (handler) { + handler(error); + }); + Statistics.sendGetUserMediaFailed(error); if(error === JitsiTrackErrors.UNSUPPORTED_RESOLUTION) { var oldResolution = options.resolution || '360'; var newResolution = getLowerResolution(oldResolution); @@ -99,7 +107,7 @@ var LibJitsiMeet = { return LibJitsiMeet.createLocalTracks(options); } return Promise.reject(error); - }); + }.bind(this)); }, /** * Checks if its possible to enumerate available cameras/micropones. diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index 4e10f5330..10d936168 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -14,6 +14,7 @@ var JitsiParticipant = require("./JitsiParticipant"); var Statistics = require("./modules/statistics/statistics"); var JitsiDTMFManager = require('./modules/DTMF/JitsiDTMFManager'); var JitsiTrackEvents = require("./JitsiTrackEvents"); +var Settings = require("./modules/settings/Settings"); /** * Creates a JitsiConference object with the given name and properties. @@ -34,12 +35,22 @@ function JitsiConference(options) { this.connection = this.options.connection; this.xmpp = this.connection.xmpp; this.eventEmitter = new EventEmitter(); - this.room = this.xmpp.createRoom(this.options.name, this.options.config); + var confID = this.options.name + '@' + this.xmpp.options.hosts.muc; + this.settings = new Settings(confID); + this.room = this.xmpp.createRoom(this.options.name, this.options.config, + this.settings); this.room.updateDeviceAvailability(RTC.getDeviceAvailability()); this.rtc = new RTC(this.room, options); - if(!RTC.options.disableAudioLevels) - this.statistics = new Statistics(); + this.statistics = new Statistics({ + disableAudioLevels: RTC.options.disableAudioLevels, + callStatsID: this.options.config.callStatsID, + callStatsSecret: this.options.config.callStatsSecret, + disableThirdPartyRequests: this.options.config.disableThirdPartyRequests + }); setupListeners(this); + JitsiMeetJS._gumFailedHandler.push(function(error) { + this.statistics.sendGetUserMediaFailed(error); + }.bind(this)); this.participants = {}; this.lastDominantSpeaker = null; this.dtmfManager = null; @@ -261,9 +272,12 @@ JitsiConference.prototype.addTrack = function (track) { track.muteHandler = this._fireMuteChangeEvent.bind(this, track); track.stopHandler = this.removeTrack.bind(this, track); track.audioLevelHandler = this._fireAudioLevelChangeEvent.bind(this); - track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, track.muteHandler); - track.addEventListener(JitsiTrackEvents.TRACK_STOPPED, track.stopHandler); - track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, track.audioLevelHandler); + track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, + track.muteHandler); + track.addEventListener(JitsiTrackEvents.TRACK_STOPPED, + track.stopHandler); + track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, + track.audioLevelHandler); this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, track); }.bind(this)); }; @@ -719,6 +733,29 @@ JitsiConference.prototype.getLogs = function () { return data; }; +/** + * Sends the given feedback through CallStats if enabled. + * + * @param overallFeedback an integer between 1 and 5 indicating the + * user feedback + * @param detailedFeedback detailed feedback from the user. Not yet used + */ +JitsiConference.prototype.sendFeedback = +function(overallFeedback, detailedFeedback){ + this.statistics.sendFeedback(overallFeedback, detailedFeedback); +} + +/** + * Returns true if the callstats integration is enabled, otherwise returns + * false. + * + * @returns true if the callstats integration is enabled, otherwise returns + * false. + */ +JitsiConference.prototype.isCallstatsEnabled = function () { + return this.statistics.isCallstatsEnabled(); +} + /** * Setups the listeners needed for the conference. * @param conference the conference @@ -726,8 +763,7 @@ JitsiConference.prototype.getLogs = function () { function setupListeners(conference) { conference.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) { conference.rtc.onIncommingCall(event); - if(conference.statistics) - conference.statistics.startRemoteStats(event.peerconnection); + conference.statistics.startRemoteStats(event.peerconnection); }); conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, @@ -909,6 +945,51 @@ function setupListeners(conference) { // RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { // conference.room.updateDeviceAvailability(devices); // }); + + conference.room.addListener(XMPPEvents.PEERCONNECTION_READY, + function (session) { + conference.statistics.startCallStats( + session, conference.settings); + }); + + conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, + function () { + conference.statistics.sendSetupFailedEvent(); + }); + + conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, + function (track) { + if(!track.isLocal()) + return; + var type = (track.getType() === "audio")? "audio" : "video"; + conference.statistics.sendMuteEvent(track.isMuted(), type); + }); + + conference.room.addListener(XMPPEvents.CREATE_OFFER_FAILED, function (e, pc) { + conference.statistics.sendCreateOfferFailed(e, pc); + }); + + conference.room.addListener(XMPPEvents.CREATE_ANSWER_FAILED, function (e, pc) { + conference.statistics.sendCreateAnswerFailed(e, pc); + }); + + conference.room.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED, + function (e, pc) { + conference.statistics.sendSetLocalDescFailed(e, pc); + } + ); + + conference.room.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED, + function (e, pc) { + conference.statistics.sendSetRemoteDescFailed(e, pc); + } + ); + + conference.room.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED, + function (e, pc) { + conference.statistics.sendAddIceCandidateFailed(e, pc); + } + ); } } @@ -916,7 +997,7 @@ function setupListeners(conference) { module.exports = JitsiConference; }).call(this,"/JitsiConference.js") -},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./JitsiTrackEvents":10,"./modules/DTMF/JitsiDTMFManager":11,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"./service/RTC/RTCEvents":79,"./service/authentication/AuthenticationEvents":81,"./service/xmpp/XMPPEvents":85,"events":43,"jitsi-meet-logger":47}],2:[function(require,module,exports){ +},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./JitsiTrackEvents":10,"./modules/DTMF/JitsiDTMFManager":11,"./modules/RTC/RTC":16,"./modules/settings/Settings":21,"./modules/statistics/statistics":25,"./service/RTC/RTCEvents":132,"./service/authentication/AuthenticationEvents":134,"./service/xmpp/XMPPEvents":138,"events":45,"jitsi-meet-logger":49}],2:[function(require,module,exports){ /** * Enumeration with the errors for the conference. * @type {{string: string}} @@ -1145,7 +1226,7 @@ JitsiConnection.prototype.removeEventListener = function (event, listener) { module.exports = JitsiConnection; -},{"./JitsiConference":1,"./modules/xmpp/xmpp":41}],5:[function(require,module,exports){ +},{"./JitsiConference":1,"./modules/xmpp/xmpp":43}],5:[function(require,module,exports){ /** * Enumeration with the errors for the connection. * @type {{string: string}} @@ -1241,6 +1322,10 @@ var LibJitsiMeet = { track: JitsiTrackErrors }, logLevels: Logger.levels, + /** + * Array of functions that will receive the GUM error. + */ + _gumFailedHandler: [], init: function (options) { return RTC.init(options || {}); }, @@ -1288,6 +1373,10 @@ var LibJitsiMeet = { } return tracks; }).catch(function (error) { + this._gumFailedHandler.forEach(function (handler) { + handler(error); + }); + Statistics.sendGetUserMediaFailed(error); if(error === JitsiTrackErrors.UNSUPPORTED_RESOLUTION) { var oldResolution = options.resolution || '360'; var newResolution = getLowerResolution(oldResolution); @@ -1297,7 +1386,7 @@ var LibJitsiMeet = { return LibJitsiMeet.createLocalTracks(options); } return Promise.reject(error); - }); + }.bind(this)); }, /** * Checks if its possible to enumerate available cameras/micropones. @@ -1324,7 +1413,7 @@ window.Promise = window.Promise || require("es6-promise").Promise; module.exports = LibJitsiMeet; -},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./JitsiTrackErrors":9,"./JitsiTrackEvents":10,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"./service/RTC/Resolutions":80,"es6-promise":45,"jitsi-meet-logger":47}],8:[function(require,module,exports){ +},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./JitsiTrackErrors":9,"./JitsiTrackEvents":10,"./modules/RTC/RTC":16,"./modules/statistics/statistics":25,"./service/RTC/Resolutions":133,"es6-promise":47,"jitsi-meet-logger":49}],8:[function(require,module,exports){ /* global Strophe */ /** @@ -1548,7 +1637,7 @@ JitsiDTMFManager.prototype.sendTones = function (tones, duration, pause) { }; }).call(this,"/modules/DTMF/JitsiDTMFManager.js") -},{"jitsi-meet-logger":47}],12:[function(require,module,exports){ +},{"jitsi-meet-logger":49}],12:[function(require,module,exports){ (function (__filename){ /* global config, APP, Strophe */ @@ -1772,7 +1861,7 @@ module.exports = DataChannels; }).call(this,"/modules/RTC/DataChannels.js") -},{"../../service/RTC/RTCEvents":79,"jitsi-meet-logger":47}],13:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":132,"jitsi-meet-logger":49}],13:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var RTCBrowserType = require("./RTCBrowserType"); var JitsiTrackEvents = require('../../JitsiTrackEvents'); @@ -2011,8 +2100,6 @@ JitsiRemoteTrack.prototype.isLocal = function () { delete JitsiRemoteTrack.prototype.stop; -delete JitsiRemoteTrack.prototype.start; - module.exports = JitsiRemoteTrack; },{"../../JitsiTrackEvents":10,"./JitsiTrack":15}],15:[function(require,module,exports){ @@ -2285,7 +2372,7 @@ JitsiTrack.prototype.setAudioLevel = function (audioLevel) { module.exports = JitsiTrack; -},{"../../JitsiTrackEvents":10,"./RTCBrowserType":17,"./RTCUtils":18,"events":43}],16:[function(require,module,exports){ +},{"../../JitsiTrackEvents":10,"./RTCBrowserType":17,"./RTCUtils":18,"events":45}],16:[function(require,module,exports){ /* global APP */ var EventEmitter = require("events"); var RTCBrowserType = require("./RTCBrowserType"); @@ -2543,7 +2630,7 @@ RTC.prototype.setAudioLevel = function (jid, audioLevel) { } module.exports = RTC; -},{"../../service/RTC/MediaStreamTypes":78,"../../service/RTC/RTCEvents.js":79,"../../service/desktopsharing/DesktopSharingEventTypes":82,"./DataChannels":12,"./JitsiLocalTrack.js":13,"./JitsiRemoteTrack.js":14,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils.js":18,"events":43}],17:[function(require,module,exports){ +},{"../../service/RTC/MediaStreamTypes":131,"../../service/RTC/RTCEvents.js":132,"../../service/desktopsharing/DesktopSharingEventTypes":135,"./DataChannels":12,"./JitsiLocalTrack.js":13,"./JitsiRemoteTrack.js":14,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils.js":18,"events":45}],17:[function(require,module,exports){ var currentBrowser; @@ -3536,7 +3623,7 @@ var RTCUtils = { module.exports = RTCUtils; }).call(this,"/modules/RTC/RTCUtils.js") -},{"../../JitsiTrackErrors":9,"../../service/RTC/RTCEvents":79,"../../service/RTC/Resolutions":80,"../xmpp/SDPUtil":31,"./RTCBrowserType":17,"./ScreenObtainer":19,"./adapter.screenshare":20,"events":43,"jitsi-meet-logger":47}],19:[function(require,module,exports){ +},{"../../JitsiTrackErrors":9,"../../service/RTC/RTCEvents":132,"../../service/RTC/Resolutions":133,"../xmpp/SDPUtil":33,"./RTCBrowserType":17,"./ScreenObtainer":19,"./adapter.screenshare":20,"events":45,"jitsi-meet-logger":49}],19:[function(require,module,exports){ (function (__filename){ /* global chrome, $, alert */ /* jshint -W003 */ @@ -3952,7 +4039,7 @@ function initFirefoxExtensionDetection(options) { module.exports = ScreenObtainer; }).call(this,"/modules/RTC/ScreenObtainer.js") -},{"../../JitsiTrackErrors":9,"../../service/desktopsharing/DesktopSharingEventTypes":82,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":47}],20:[function(require,module,exports){ +},{"../../JitsiTrackErrors":9,"../../service/desktopsharing/DesktopSharingEventTypes":135,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":49}],20:[function(require,module,exports){ (function (__filename){ /*! adapterjs - v0.12.3 - 2015-11-16 */ var console = require("jitsi-meet-logger").getLogger(__filename); @@ -5136,10 +5223,12 @@ if (navigator.mozGetUserMedia) { } }).call(this,"/modules/RTC/adapter.screenshare.js") -},{"jitsi-meet-logger":47}],21:[function(require,module,exports){ +},{"jitsi-meet-logger":49}],21:[function(require,module,exports){ (function (__filename){ - var logger = require("jitsi-meet-logger").getLogger(__filename); + +var UsernameGenerator = require('../util/UsernameGenerator'); + function supportsLocalStorage() { try { return 'localStorage' in window && window.localStorage !== null; @@ -5162,6 +5251,7 @@ function Settings(conferenceID) { this.userId; this.confSettings = null; this.conferenceID = conferenceID; + this.callStatsUserName; if (supportsLocalStorage()) { if(!window.localStorage.getItem(conferenceID)) this.confSettings = {}; @@ -5173,11 +5263,21 @@ function Settings(conferenceID) { this.confSettings.jitsiMeetId); this.save(); } + if (!this.confSettings.callStatsUserName) { + this.confSettings.callStatsUserName + = UsernameGenerator.generateUsername(); + logger.log('generated callstats uid', + this.confSettings.callStatsUserName); + this.save(); + } + this.userId = this.confSettings.jitsiMeetId || ''; this.displayName = this.confSettings.displayname || ''; + this.callStatsUserName = this.confSettings.callStatsUserName || ''; } else { logger.log("local storage is not supported"); this.userId = generateUniqueId(); + this.callStatsUserName = UsernameGenerator.generateUsername(); } } @@ -5192,18 +5292,288 @@ Settings.prototype.setDisplayName = function (newDisplayName) { this.confSettings.displayname = displayName; this.save(); return this.displayName; -}, +} + Settings.prototype.getSettings = function () { return { displayName: this.displayName, uid: this.userId }; -}, +} + +/** + * Returns fake username for callstats + * @returns {string} fake username for callstats + */ +Settings.prototype.getCallStatsUserName = function () { + return this.callStatsUserName; +} module.exports = Settings; }).call(this,"/modules/settings/Settings.js") -},{"jitsi-meet-logger":47}],22:[function(require,module,exports){ +},{"../util/UsernameGenerator":27,"jitsi-meet-logger":49}],22:[function(require,module,exports){ +(function (__filename){ +/* global $, Strophe, callstats */ +var logger = require("jitsi-meet-logger").getLogger(__filename); + +var jsSHA = require('jssha'); +var io = require('socket.io-client'); + +/** + * @const + * @see http://www.callstats.io/api/#enumeration-of-wrtcfuncnames + */ +var wrtcFuncNames = { + createOffer: "createOffer", + createAnswer: "createAnswer", + setLocalDescription: "setLocalDescription", + setRemoteDescription: "setRemoteDescription", + addIceCandidate: "addIceCandidate", + getUserMedia: "getUserMedia" +}; + +var callStats = null; + +function initCallback (err, msg) { + logger.log("CallStats Status: err=" + err + " msg=" + msg); +} + +/** + * Returns a function which invokes f in a try/catch block, logs any exception + * to the console, and then swallows it. + * + * @param f the function to invoke in a try/catch block + * @return a function which invokes f in a try/catch block, logs any exception + * to the console, and then swallows it + */ +function _try_catch (f) { + return function () { + try { + f.apply(this, arguments); + } catch (e) { + logger.error(e); + } + }; +} + +/** + * Creates new CallStats instance that handles all callstats API calls. + * @param peerConnection {JingleSessionPC} the session object + * @param Settings {Settings} the settings instance. Declared in + * /modules/settings/Settings.js + * @param options {object} credentials for callstats. + */ +var CallStats = _try_catch(function(jingleSession, Settings, options) { + try{ + //check weather that should work with more than 1 peerconnection + if(!callStats) { + callStats = new callstats($, io, jsSHA); + } else { + return; + } + + this.session = jingleSession; + this.peerconnection = jingleSession.peerconnection.peerconnection; + + this.userID = Settings.getCallStatsUserName(); + + //FIXME: change it to something else (maybe roomName) + var location = window.location; + this.confID = location.hostname + location.pathname; + + //userID is generated or given by the origin server + callStats.initialize(options.callStatsID, + options.callStatsSecret, + this.userID, + initCallback); + + callStats.addNewFabric(this.peerconnection, + Strophe.getResourceFromJid(jingleSession.peerjid), + callStats.fabricUsage.multiplex, + this.confID, + this.pcCallback.bind(this)); + } catch (e) { + // The callstats.io API failed to initialize (e.g. because its + // download failed to succeed in general or on time). Further + // attempts to utilize it cannot possibly succeed. + callStats = null; + logger.error(e); + } + // notify callstats about failures if there were any + if (CallStats.pendingErrors.length) { + CallStats.pendingErrors.forEach(function (error) { + CallStats._reportError.call(this, error.type, error.error, + error.pc); + }, this); + CallStats.pendingErrors.length = 0; + } +}); + +// some errors may happen before CallStats init +// in this case we accumulate them in this array +// and send them to callstats on init +CallStats.pendingErrors = []; + +CallStats.prototype.pcCallback = _try_catch(function (err, msg) { + if (!callStats) { + return; + } + logger.log("Monitoring status: "+ err + " msg: " + msg); + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricSetup, this.confID); +}); + +/** + * Notifies CallStats for mute events + * @param mute {boolean} true for muted and false for not muted + * @param type {String} "audio"/"video" + */ +CallStats.prototype.sendMuteEvent = _try_catch(function (mute, type) { + if (!callStats) { + return; + } + var event = null; + if (type === "video") { + event = (mute? callStats.fabricEvent.videoPause : + callStats.fabricEvent.videoResume); + } + else { + event = (mute? callStats.fabricEvent.audioMute : + callStats.fabricEvent.audioUnmute); + } + callStats.sendFabricEvent(this.peerconnection, event, this.confID); +}); + +/** + * Notifies CallStats for connection setup errors + */ +CallStats.prototype.sendTerminateEvent = _try_catch(function () { + if(!callStats) { + return; + } + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricTerminated, this.confID); +}); + +/** + * Notifies CallStats for connection setup errors + */ +CallStats.prototype.sendSetupFailedEvent = _try_catch(function () { + if(!callStats) { + return; + } + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricSetupFailed, this.confID); +}); + +/** + * Sends the given feedback through CallStats. + * + * @param overallFeedback an integer between 1 and 5 indicating the + * user feedback + * @param detailedFeedback detailed feedback from the user. Not yet used + */ +CallStats.prototype.sendFeedback = _try_catch( +function(overallFeedback, detailedFeedback) { + if(!callStats) { + return; + } + var feedbackString = '{"userID":"' + this.userID + '"' + + ', "overall":' + overallFeedback + + ', "comment": "' + detailedFeedback + '"}'; + + var feedbackJSON = JSON.parse(feedbackString); + + callStats.sendUserFeedback(this.confID, feedbackJSON); +}); + +/** + * Reports an error to callstats. + * + * @param type the type of the error, which will be one of the wrtcFuncNames + * @param e the error + * @param pc the peerconnection + * @private + */ +CallStats._reportError = function (type, e, pc) { + if (callStats) { + callStats.reportError(pc, this.confID, type, e); + } else { + CallStats.pendingErrors.push({ type: type, error: e, pc: pc}); + } + // else just ignore it +}; + +/** + * Notifies CallStats that getUserMedia failed. + * + * @param {Error} e error to send + * @param {CallStats} cs callstats instance related to the error (optional) + */ +CallStats.sendGetUserMediaFailed = _try_catch(function (e, cs) { + CallStats._reportError.call(cs, wrtcFuncNames.getUserMedia, e, null); +}); + +/** + * Notifies CallStats that peer connection failed to create offer. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + * @param {CallStats} cs callstats instance related to the error (optional) + */ +CallStats.sendCreateOfferFailed = _try_catch(function (e, pc, cs) { + CallStats._reportError.call(cs, wrtcFuncNames.createOffer, e, pc); +}); + +/** + * Notifies CallStats that peer connection failed to create answer. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + * @param {CallStats} cs callstats instance related to the error (optional) + */ +CallStats.sendCreateAnswerFailed = _try_catch(function (e, pc, cs) { + CallStats._reportError.call(cs, wrtcFuncNames.createAnswer, e, pc); +}); + +/** + * Notifies CallStats that peer connection failed to set local description. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + * @param {CallStats} cs callstats instance related to the error (optional) + */ +CallStats.sendSetLocalDescFailed = _try_catch(function (e, pc, cs) { + CallStats._reportError.call(cs, wrtcFuncNames.setLocalDescription, e, pc); +}); + +/** + * Notifies CallStats that peer connection failed to set remote description. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + * @param {CallStats} cs callstats instance related to the error (optional) + */ +CallStats.sendSetRemoteDescFailed = _try_catch(function (e, pc, cs) { + CallStats._reportError.call(cs, wrtcFuncNames.setRemoteDescription, e, pc); +}); + +/** + * Notifies CallStats that peer connection failed to add ICE candidate. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + * @param {CallStats} cs callstats instance related to the error (optional) + */ +CallStats.sendAddIceCandidateFailed = _try_catch(function (e, pc, cs) { + CallStats._reportError.call(cs, wrtcFuncNames.addIceCandidate, e, pc); +}); + +module.exports = CallStats; + +}).call(this,"/modules/statistics/CallStats.js") +},{"jitsi-meet-logger":49,"jssha":50,"socket.io-client":81}],23:[function(require,module,exports){ /* global config */ /** * Provides statistics for the local stream. @@ -5333,7 +5703,7 @@ LocalStatsCollector.prototype.stop = function () { module.exports = LocalStatsCollector; -},{"../RTC/RTCBrowserType":17}],23:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":17}],24:[function(require,module,exports){ (function (__filename){ /* global require, ssrc2jid */ /* jshint -W117 */ @@ -6060,35 +6430,57 @@ StatsCollector.prototype.processAudioLevelReport = function () { }; }).call(this,"/modules/statistics/RTPStatsCollector.js") -},{"../../service/statistics/Events":83,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],24:[function(require,module,exports){ +},{"../../service/statistics/Events":136,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":49}],25:[function(require,module,exports){ /* global require, APP */ var LocalStats = require("./LocalStatsCollector.js"); var RTPStats = require("./RTPStatsCollector.js"); var EventEmitter = require("events"); var StatisticsEvents = require("../../service/statistics/Events"); -//var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); -//var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -//var CallStats = require("./CallStats"); -//var RTCEvents = require("../../service/RTC/RTCEvents"); +var CallStats = require("./CallStats"); -// -//function onDisposeConference(onUnload) { -// CallStats.sendTerminateEvent(); -// stopRemote(); -// if(onUnload) { -// stopLocal(); -// eventEmitter.removeAllListeners(); -// } -//} +// Since callstats.io is a third party, we cannot guarantee the quality of +// their service. More specifically, their server may take noticeably long +// time to respond. Consequently, it is in our best interest (in the sense +// that the intergration of callstats.io is pretty important to us but not +// enough to allow it to prevent people from joining a conference) to (1) +// start downloading their API as soon as possible and (2) do the +// downloading asynchronously. +function loadCallStatsAPI() { + (function (d, src) { + var elementName = 'script'; + var newScript = d.createElement(elementName); + var referenceNode = d.getElementsByTagName(elementName)[0]; + + newScript.async = true; + newScript.src = src; + referenceNode.parentNode.insertBefore(newScript, referenceNode); + })(document, 'https://api.callstats.io/static/callstats.min.js'); + // FIXME At the time of this writing, we hope that the callstats.io API will + // have loaded by the time we needed it (i.e. CallStats.init is invoked). +} var eventEmitter = new EventEmitter(); -function Statistics() { +function Statistics(options) { this.rtpStats = null; this.eventEmitter = new EventEmitter(); + this.options = options || {}; + this.callStatsIntegrationEnabled + = this.options.callStatsID && this.options.callStatsSecret + // Even though AppID and AppSecret may be specified, the integration of + // callstats.io may be disabled because of globally-disallowed requests + // to any third parties. + && (this.options.disableThirdPartyRequests !== true); + if(this.callStatsIntegrationEnabled) + loadCallStatsAPI(); + this.audioLevelsEnabled = !this.disableAudioLevels || true; + this.callStats = null; } Statistics.prototype.startRemoteStats = function (peerconnection) { + if(!this.audioLevelsEnabled) + return; + if (this.rtpStats) { this.rtpStats.stop(); } @@ -6100,6 +6492,8 @@ Statistics.prototype.startRemoteStats = function (peerconnection) { Statistics.localStats = []; Statistics.startLocalStats = function (stream, callback) { + if(!this.audioLevelsEnabled) + return; var localStats = new LocalStats(stream, 200, callback); this.localStats.push(localStats); localStats.start(); @@ -6107,32 +6501,50 @@ Statistics.startLocalStats = function (stream, callback) { Statistics.prototype.addAudioLevelListener = function(listener) { + if(!this.audioLevelsEnabled) + return; this.eventEmitter.on(StatisticsEvents.AUDIO_LEVEL, listener); } Statistics.prototype.removeAudioLevelListener = function(listener) { + if(!this.audioLevelsEnabled) + return; this.eventEmitter.removeListener(StatisticsEvents.AUDIO_LEVEL, listener); } Statistics.prototype.dispose = function () { - Statistics.stopAllLocalStats(); - this.stopRemote(); - if(this.eventEmitter) - this.eventEmitter.removeAllListeners(); + if(this.audioLevelsEnabled) { + Statistics.stopAllLocalStats(); + this.stopRemote(); + if(this.eventEmitter) + this.eventEmitter.removeAllListeners(); - if(eventEmitter) - eventEmitter.removeAllListeners(); + if(eventEmitter) + eventEmitter.removeAllListeners(); + } + + if(this.callstats) + { + this.callstats.sendTerminateEvent(); + this.callstats = null; + } } Statistics.stopAllLocalStats = function () { + if(!this.audioLevelsEnabled) + return; + for(var i = 0; i < this.localStats.length; i++) this.localStats[i].stop(); this.localStats = []; } Statistics.stopLocalStats = function (stream) { + if(!this.audioLevelsEnabled) + return; + for(var i = 0; i < Statistics.localStats.length; i++) if(Statistics.localStats[i].stream === stream){ var localStats = Statistics.localStats.splice(i, 1); @@ -6142,7 +6554,7 @@ Statistics.stopLocalStats = function (stream) { } Statistics.prototype.stopRemote = function () { - if (this.rtpStats) { + if (this.rtpStats && this.audioLevelsEnabled) { this.rtpStats.stop(); this.eventEmitter.emit(StatisticsEvents.STOP); this.rtpStats = null; @@ -6160,82 +6572,149 @@ Statistics.prototype.stopRemote = function () { * at this time. */ Statistics.prototype.getPeerSSRCAudioLevel = function (peerJid, ssrc) { - + if(!this.audioLevelsEnabled) + return; var peerStats = this.rtpStats.jid2stats[peerJid]; return peerStats ? peerStats.ssrc2AudioLevel[ssrc] : null; }; + +//CALSTATS METHODS + +/** + * Initializes the callstats.io API. + * @param peerConnection {JingleSessionPC} the session object + * @param Settings {Settings} the settings instance. Declared in + * /modules/settings/Settings.js + */ +Statistics.prototype.startCallStats = function (session, settings) { + if(this.callStatsIntegrationEnabled) { + this.callstats = new CallStats(session, settings, this.options); + } +} + +/** + * Returns true if the callstats integration is enabled, otherwise returns + * false. + * + * @returns true if the callstats integration is enabled, otherwise returns + * false. + */ +Statistics.prototype.isCallstatsEnabled = function () { + return this.callStatsIntegrationEnabled; +} + +/** + * Notifies CallStats for connection setup errors + */ +Statistics.prototype.sendSetupFailedEvent = function () { + if(this.callStatsIntegrationEnabled && this.callstats) + this.callstats.sendSetupFailedEvent(); +} + +/** + * Notifies CallStats for mute events + * @param mute {boolean} true for muted and false for not muted + * @param type {String} "audio"/"video" + */ +Statistics.prototype.sendMuteEvent = function (muted, type) { + if(this.callStatsIntegrationEnabled && this.callstats) + this.callstats.sendMuteEvent(muted, type); +} + +/** + * Notifies CallStats that getUserMedia failed. + * + * @param {Error} e error to send + */ +Statistics.prototype.sendGetUserMediaFailed = function (e) { + if(this.callStatsIntegrationEnabled) + CallStats.sendGetUserMediaFailed(e, this.callstats); +}; + +/** + * Notifies CallStats that getUserMedia failed. + * + * @param {Error} e error to send + */ +Statistics.sendGetUserMediaFailed = function (e) { + CallStats.sendGetUserMediaFailed(e, null); +}; + +/** + * Notifies CallStats that peer connection failed to create offer. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + */ +Statistics.prototype.sendCreateOfferFailed = function (e, pc) { + if(this.callStatsIntegrationEnabled) + CallStats.sendCreateOfferFailed(e, pc, this.callstats); +}; + +/** + * Notifies CallStats that peer connection failed to create answer. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + */ +Statistics.prototype.sendCreateAnswerFailed = function (e, pc) { + if(this.callStatsIntegrationEnabled) + CallStats.sendCreateAnswerFailed(e, pc, this.callstats); +}; + +/** + * Notifies CallStats that peer connection failed to set local description. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + */ +Statistics.prototype.sendSetLocalDescFailed = function (e, pc) { + if(this.callStatsIntegrationEnabled) + CallStats.sendSetLocalDescFailed(e, pc, this.callstats); +} + +/** + * Notifies CallStats that peer connection failed to set remote description. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + */ +Statistics.prototype.sendSetRemoteDescFailed = function (e, pc) { + if(this.callStatsIntegrationEnabled) + CallStats.sendSetRemoteDescFailed(e, pc, this.callstats); +} + +/** + * Notifies CallStats that peer connection failed to add ICE candidate. + * + * @param {Error} e error to send + * @param {RTCPeerConnection} pc connection on which failure occured. + */ +Statistics.prototype.sendAddIceCandidateFailed = function (e, pc) { + if(this.callStatsIntegrationEnabled) + CallStats.sendAddIceCandidateFailed(e, pc, this.callstats); +} + +/** + * Sends the given feedback through CallStats. + * + * @param overallFeedback an integer between 1 and 5 indicating the + * user feedback + * @param detailedFeedback detailed feedback from the user. Not yet used + */ +Statistics.prototype.sendFeedback = +function(overallFeedback, detailedFeedback){ + if(this.callStatsIntegrationEnabled && this.callstats) + this.callstats.sendFeedback(overallFeedback, detailedFeedback); +} + Statistics.LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID; -// -//var statistics = { -// /** -// * Indicates that this audio level is for local jid. -// * @type {string} -// */ -// LOCAL_JID: 'local', -// -// addConnectionStatsListener: function(listener) -// { -// eventEmitter.on("statistics.connectionstats", listener); -// }, -// -// removeConnectionStatsListener: function(listener) -// { -// eventEmitter.removeListener("statistics.connectionstats", listener); -// }, -// -// -// addRemoteStatsStopListener: function(listener) -// { -// eventEmitter.on("statistics.stop", listener); -// }, -// -// removeRemoteStatsStopListener: function(listener) -// { -// eventEmitter.removeListener("statistics.stop", listener); -// }, -// -// -// stopRemoteStatistics: function() -// { -// stopRemote(); -// }, -// -//// Already implemented with the constructor -// start: function () { -// APP.RTC.addStreamListener(onStreamCreated, -// StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); -// APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference); -// //FIXME: we may want to change CALL INCOMING event to onnegotiationneeded -// APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) { -// startRemoteStats(event.peerconnection); -//// CallStats.init(event); -// }); -//// APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) { -//// CallStats.init(session); -//// }); -// //FIXME: that event is changed to TRACK_MUTE_CHANGED -//// APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) { -//// CallStats.sendMuteEvent(mute, "audio"); -//// }); -//// APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { -//// CallStats.sendSetupFailedEvent(); -//// }); -// //FIXME: that event is changed to TRACK_MUTE_CHANGED -//// APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) { -//// CallStats.sendMuteEvent(mute, "video"); -//// }); -// } -//}; - - - - module.exports = Statistics; -},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"./LocalStatsCollector.js":22,"./RTPStatsCollector.js":23,"events":43}],25:[function(require,module,exports){ +},{"../../service/statistics/Events":136,"../../service/statistics/constants":137,"./CallStats":22,"./LocalStatsCollector.js":23,"./RTPStatsCollector.js":24,"events":45}],26:[function(require,module,exports){ /** /** * @const @@ -6311,7 +6790,438 @@ var RandomUtil = { module.exports = RandomUtil; -},{}],26:[function(require,module,exports){ +},{}],27:[function(require,module,exports){ +var RandomUtil = require('./RandomUtil'); + +/** + * from faker.js - Copyright (c) 2014-2015 Matthew Bergman & Marak Squires + * MIT License + * http://github.com/marak/faker.js/ + * + * @const + */ +var names = [ + "Aaliyah", "Aaron", "Abagail", "Abbey", "Abbie", "Abbigail", + "Abby", "Abdiel", "Abdul", "Abdullah", "Abe", "Abel", "Abelardo", "Abigail", + "Abigale", "Abigayle", "Abner", "Abraham", "Ada", "Adah", "Adalberto", + "Adaline", "Adam", "Adan", "Addie", "Addison", "Adela", "Adelbert", "Adele", + "Adelia", "Adeline", "Adell", "Adella", "Adelle", "Aditya", "Adolf", "Adolfo", + "Adolph", "Adolphus", "Adonis", "Adrain", "Adrian", "Adriana", "Adrianna", + "Adriel", "Adrien", "Adrienne", "Afton", "Aglae", "Agnes", "Agustin", + "Agustina", "Ahmad", "Ahmed", "Aida", "Aidan", "Aiden", "Aileen", "Aimee", + "Aisha", "Aiyana", "Akeem", "Al", "Alaina", "Alan", "Alana", "Alanis", + "Alanna", "Alayna", "Alba", "Albert", "Alberta", "Albertha", "Alberto", + "Albin", "Albina", "Alda", "Alden", "Alec", "Aleen", "Alejandra", + "Alejandrin", "Alek", "Alena", "Alene", "Alessandra", "Alessandro", "Alessia", + "Aletha", "Alex", "Alexa", "Alexander", "Alexandra", "Alexandre", + "Alexandrea", "Alexandria", "Alexandrine", "Alexandro", "Alexane", "Alexanne", + "Alexie", "Alexis", "Alexys", "Alexzander", "Alf", "Alfonso", "Alfonzo", + "Alford", "Alfred", "Alfreda", "Alfredo", "Ali", "Alia", "Alice", "Alicia", + "Alisa", "Alisha", "Alison", "Alivia", "Aliya", "Aliyah", "Aliza", "Alize", + "Allan", "Allen", "Allene", "Allie", "Allison", "Ally", "Alphonso", "Alta", + "Althea", "Alva", "Alvah", "Alvena", "Alvera", "Alverta", "Alvina", "Alvis", + "Alyce", "Alycia", "Alysa", "Alysha", "Alyson", "Alysson", "Amalia", "Amanda", + "Amani", "Amara", "Amari", "Amaya", "Amber", "Ambrose", "Amelia", "Amelie", + "Amely", "America", "Americo", "Amie", "Amina", "Amir", "Amira", "Amiya", + "Amos", "Amparo", "Amy", "Amya", "Ana", "Anabel", "Anabelle", "Anahi", + "Anais", "Anastacio", "Anastasia", "Anderson", "Andre", "Andreane", + "Andreanne", "Andres", "Andrew", "Andy", "Angel", "Angela", "Angelica", + "Angelina", "Angeline", "Angelita", "Angelo", "Angie", "Angus", "Anibal", + "Anika", "Anissa", "Anita", "Aniya", "Aniyah", "Anjali", "Anna", "Annabel", + "Annabell", "Annabelle", "Annalise", "Annamae", "Annamarie", "Anne", + "Annetta", "Annette", "Annie", "Ansel", "Ansley", "Anthony", "Antoinette", + "Antone", "Antonetta", "Antonette", "Antonia", "Antonietta", "Antonina", + "Antonio", "Antwan", "Antwon", "Anya", "April", "Ara", "Araceli", "Aracely", + "Arch", "Archibald", "Ardella", "Arden", "Ardith", "Arely", "Ari", "Ariane", + "Arianna", "Aric", "Ariel", "Arielle", "Arjun", "Arlene", "Arlie", "Arlo", + "Armand", "Armando", "Armani", "Arnaldo", "Arne", "Arno", "Arnold", "Arnoldo", + "Arnulfo", "Aron", "Art", "Arthur", "Arturo", "Arvel", "Arvid", "Arvilla", + "Aryanna", "Asa", "Asha", "Ashlee", "Ashleigh", "Ashley", "Ashly", "Ashlynn", + "Ashton", "Ashtyn", "Asia", "Assunta", "Astrid", "Athena", "Aubree", "Aubrey", + "Audie", "Audra", "Audreanne", "Audrey", "August", "Augusta", "Augustine", + "Augustus", "Aurelia", "Aurelie", "Aurelio", "Aurore", "Austen", "Austin", + "Austyn", "Autumn", "Ava", "Avery", "Avis", "Axel", "Ayana", "Ayden", "Ayla", + "Aylin", "Baby", "Bailee", "Bailey", "Barbara", "Barney", "Baron", "Barrett", + "Barry", "Bart", "Bartholome", "Barton", "Baylee", "Beatrice", "Beau", + "Beaulah", "Bell", "Bella", "Belle", "Ben", "Benedict", "Benjamin", "Bennett", + "Bennie", "Benny", "Benton", "Berenice", "Bernadette", "Bernadine", "Bernard", + "Bernardo", "Berneice", "Bernhard", "Bernice", "Bernie", "Berniece", + "Bernita", "Berry", "Bert", "Berta", "Bertha", "Bertram", "Bertrand", "Beryl", + "Bessie", "Beth", "Bethany", "Bethel", "Betsy", "Bette", "Bettie", "Betty", + "Bettye", "Beulah", "Beverly", "Bianka", "Bill", "Billie", "Billy", "Birdie", + "Blair", "Blaise", "Blake", "Blanca", "Blanche", "Blaze", "Bo", "Bobbie", + "Bobby", "Bonita", "Bonnie", "Boris", "Boyd", "Brad", "Braden", "Bradford", + "Bradley", "Bradly", "Brady", "Braeden", "Brain", "Brandi", "Brando", + "Brandon", "Brandt", "Brandy", "Brandyn", "Brannon", "Branson", "Brant", + "Braulio", "Braxton", "Brayan", "Breana", "Breanna", "Breanne", "Brenda", + "Brendan", "Brenden", "Brendon", "Brenna", "Brennan", "Brennon", "Brent", + "Bret", "Brett", "Bria", "Brian", "Briana", "Brianne", "Brice", "Bridget", + "Bridgette", "Bridie", "Brielle", "Brigitte", "Brionna", "Brisa", "Britney", + "Brittany", "Brock", "Broderick", "Brody", "Brook", "Brooke", "Brooklyn", + "Brooks", "Brown", "Bruce", "Bryana", "Bryce", "Brycen", "Bryon", "Buck", + "Bud", "Buddy", "Buford", "Bulah", "Burdette", "Burley", "Burnice", "Buster", + "Cade", "Caden", "Caesar", "Caitlyn", "Cale", "Caleb", "Caleigh", "Cali", + "Calista", "Callie", "Camden", "Cameron", "Camila", "Camilla", "Camille", + "Camren", "Camron", "Camryn", "Camylle", "Candace", "Candelario", "Candice", + "Candida", "Candido", "Cara", "Carey", "Carissa", "Carlee", "Carleton", + "Carley", "Carli", "Carlie", "Carlo", "Carlos", "Carlotta", "Carmel", + "Carmela", "Carmella", "Carmelo", "Carmen", "Carmine", "Carol", "Carolanne", + "Carole", "Carolina", "Caroline", "Carolyn", "Carolyne", "Carrie", "Carroll", + "Carson", "Carter", "Cary", "Casandra", "Casey", "Casimer", "Casimir", + "Casper", "Cassandra", "Cassandre", "Cassidy", "Cassie", "Catalina", + "Caterina", "Catharine", "Catherine", "Cathrine", "Cathryn", "Cathy", "Cayla", + "Ceasar", "Cecelia", "Cecil", "Cecile", "Cecilia", "Cedrick", "Celestine", + "Celestino", "Celia", "Celine", "Cesar", "Chad", "Chadd", "Chadrick", "Chaim", + "Chance", "Chandler", "Chanel", "Chanelle", "Charity", "Charlene", "Charles", + "Charley", "Charlie", "Charlotte", "Chase", "Chasity", "Chauncey", "Chaya", + "Chaz", "Chelsea", "Chelsey", "Chelsie", "Chesley", "Chester", "Chet", + "Cheyanne", "Cheyenne", "Chloe", "Chris", "Christ", "Christa", "Christelle", + "Christian", "Christiana", "Christina", "Christine", "Christop", "Christophe", + "Christopher", "Christy", "Chyna", "Ciara", "Cicero", "Cielo", "Cierra", + "Cindy", "Citlalli", "Clair", "Claire", "Clara", "Clarabelle", "Clare", + "Clarissa", "Clark", "Claud", "Claude", "Claudia", "Claudie", "Claudine", + "Clay", "Clemens", "Clement", "Clementina", "Clementine", "Clemmie", "Cleo", + "Cleora", "Cleta", "Cletus", "Cleve", "Cleveland", "Clifford", "Clifton", + "Clint", "Clinton", "Clotilde", "Clovis", "Cloyd", "Clyde", "Coby", "Cody", + "Colby", "Cole", "Coleman", "Colin", "Colleen", "Collin", "Colt", "Colten", + "Colton", "Columbus", "Concepcion", "Conner", "Connie", "Connor", "Conor", + "Conrad", "Constance", "Constantin", "Consuelo", "Cooper", "Cora", "Coralie", + "Corbin", "Cordelia", "Cordell", "Cordia", "Cordie", "Corene", "Corine", + "Cornelius", "Cornell", "Corrine", "Cortez", "Cortney", "Cory", "Coty", + "Courtney", "Coy", "Craig", "Crawford", "Creola", "Cristal", "Cristian", + "Cristina", "Cristobal", "Cristopher", "Cruz", "Crystal", "Crystel", "Cullen", + "Curt", "Curtis", "Cydney", "Cynthia", "Cyril", "Cyrus", "Dagmar", "Dahlia", + "Daija", "Daisha", "Daisy", "Dakota", "Dale", "Dallas", "Dallin", "Dalton", + "Damaris", "Dameon", "Damian", "Damien", "Damion", "Damon", "Dan", "Dana", + "Dandre", "Dane", "D'angelo", "Dangelo", "Danial", "Daniela", "Daniella", + "Danielle", "Danika", "Dannie", "Danny", "Dante", "Danyka", "Daphne", + "Daphnee", "Daphney", "Darby", "Daren", "Darian", "Dariana", "Darien", + "Dario", "Darion", "Darius", "Darlene", "Daron", "Darrel", "Darrell", + "Darren", "Darrick", "Darrin", "Darrion", "Darron", "Darryl", "Darwin", + "Daryl", "Dashawn", "Dasia", "Dave", "David", "Davin", "Davion", "Davon", + "Davonte", "Dawn", "Dawson", "Dax", "Dayana", "Dayna", "Dayne", "Dayton", + "Dean", "Deangelo", "Deanna", "Deborah", "Declan", "Dedric", "Dedrick", "Dee", + "Deion", "Deja", "Dejah", "Dejon", "Dejuan", "Delaney", "Delbert", "Delfina", + "Delia", "Delilah", "Dell", "Della", "Delmer", "Delores", "Delpha", "Delphia", + "Delphine", "Delta", "Demarco", "Demarcus", "Demario", "Demetris", + "Demetrius", "Demond", "Dena", "Denis", "Dennis", "Deon", "Deondre", + "Deontae", "Deonte", "Dereck", "Derek", "Derick", "Deron", "Derrick", + "Deshaun", "Deshawn", "Desiree", "Desmond", "Dessie", "Destany", "Destin", + "Destinee", "Destiney", "Destini", "Destiny", "Devan", "Devante", "Deven", + "Devin", "Devon", "Devonte", "Devyn", "Dewayne", "Dewitt", "Dexter", + "Diamond", "Diana", "Dianna", "Diego", "Dillan", "Dillon", "Dimitri", "Dina", + "Dino", "Dion", "Dixie", "Dock", "Dolly", "Dolores", "Domenic", "Domenica", + "Domenick", "Domenico", "Domingo", "Dominic", "Dominique", "Don", "Donald", + "Donato", "Donavon", "Donna", "Donnell", "Donnie", "Donny", "Dora", "Dorcas", + "Dorian", "Doris", "Dorothea", "Dorothy", "Dorris", "Dortha", "Dorthy", + "Doug", "Douglas", "Dovie", "Doyle", "Drake", "Drew", "Duane", "Dudley", + "Dulce", "Duncan", "Durward", "Dustin", "Dusty", "Dwight", "Dylan", "Earl", + "Earlene", "Earline", "Earnest", "Earnestine", "Easter", "Easton", "Ebba", + "Ebony", "Ed", "Eda", "Edd", "Eddie", "Eden", "Edgar", "Edgardo", "Edison", + "Edmond", "Edmund", "Edna", "Eduardo", "Edward", "Edwardo", "Edwin", "Edwina", + "Edyth", "Edythe", "Effie", "Efrain", "Efren", "Eileen", "Einar", "Eino", + "Eladio", "Elaina", "Elbert", "Elda", "Eldon", "Eldora", "Eldred", "Eldridge", + "Eleanora", "Eleanore", "Eleazar", "Electa", "Elena", "Elenor", "Elenora", + "Eleonore", "Elfrieda", "Eli", "Elian", "Eliane", "Elias", "Eliezer", + "Elijah", "Elinor", "Elinore", "Elisa", "Elisabeth", "Elise", "Eliseo", + "Elisha", "Elissa", "Eliza", "Elizabeth", "Ella", "Ellen", "Ellie", "Elliot", + "Elliott", "Ellis", "Ellsworth", "Elmer", "Elmira", "Elmo", "Elmore", "Elna", + "Elnora", "Elody", "Eloisa", "Eloise", "Elouise", "Eloy", "Elroy", "Elsa", + "Else", "Elsie", "Elta", "Elton", "Elva", "Elvera", "Elvie", "Elvis", "Elwin", + "Elwyn", "Elyse", "Elyssa", "Elza", "Emanuel", "Emelia", "Emelie", "Emely", + "Emerald", "Emerson", "Emery", "Emie", "Emil", "Emile", "Emilia", "Emiliano", + "Emilie", "Emilio", "Emily", "Emma", "Emmalee", "Emmanuel", "Emmanuelle", + "Emmet", "Emmett", "Emmie", "Emmitt", "Emmy", "Emory", "Ena", "Enid", "Enoch", + "Enola", "Enos", "Enrico", "Enrique", "Ephraim", "Era", "Eriberto", "Eric", + "Erica", "Erich", "Erick", "Ericka", "Erik", "Erika", "Erin", "Erling", + "Erna", "Ernest", "Ernestina", "Ernestine", "Ernesto", "Ernie", "Ervin", + "Erwin", "Eryn", "Esmeralda", "Esperanza", "Esta", "Esteban", "Estefania", + "Estel", "Estell", "Estella", "Estelle", "Estevan", "Esther", "Estrella", + "Etha", "Ethan", "Ethel", "Ethelyn", "Ethyl", "Ettie", "Eudora", "Eugene", + "Eugenia", "Eula", "Eulah", "Eulalia", "Euna", "Eunice", "Eusebio", "Eva", + "Evalyn", "Evan", "Evangeline", "Evans", "Eve", "Eveline", "Evelyn", + "Everardo", "Everett", "Everette", "Evert", "Evie", "Ewald", "Ewell", + "Ezekiel", "Ezequiel", "Ezra", "Fabian", "Fabiola", "Fae", "Fannie", "Fanny", + "Fatima", "Faustino", "Fausto", "Favian", "Fay", "Faye", "Federico", + "Felicia", "Felicita", "Felicity", "Felipa", "Felipe", "Felix", "Felton", + "Fermin", "Fern", "Fernando", "Ferne", "Fidel", "Filiberto", "Filomena", + "Finn", "Fiona", "Flavie", "Flavio", "Fleta", "Fletcher", "Flo", "Florence", + "Florencio", "Florian", "Florida", "Florine", "Flossie", "Floy", "Floyd", + "Ford", "Forest", "Forrest", "Foster", "Frances", "Francesca", "Francesco", + "Francis", "Francisca", "Francisco", "Franco", "Frank", "Frankie", "Franz", + "Fred", "Freda", "Freddie", "Freddy", "Frederic", "Frederick", "Frederik", + "Frederique", "Fredrick", "Fredy", "Freeda", "Freeman", "Freida", "Frida", + "Frieda", "Friedrich", "Fritz", "Furman", "Gabe", "Gabriel", "Gabriella", + "Gabrielle", "Gaetano", "Gage", "Gail", "Gardner", "Garett", "Garfield", + "Garland", "Garnet", "Garnett", "Garret", "Garrett", "Garrick", "Garrison", + "Garry", "Garth", "Gaston", "Gavin", "Gay", "Gayle", "Gaylord", "Gene", + "General", "Genesis", "Genevieve", "Gennaro", "Genoveva", "Geo", "Geoffrey", + "George", "Georgette", "Georgiana", "Georgianna", "Geovanni", "Geovanny", + "Geovany", "Gerald", "Geraldine", "Gerard", "Gerardo", "Gerda", "Gerhard", + "Germaine", "German", "Gerry", "Gerson", "Gertrude", "Gia", "Gianni", + "Gideon", "Gilbert", "Gilberto", "Gilda", "Giles", "Gillian", "Gina", "Gino", + "Giovani", "Giovanna", "Giovanni", "Giovanny", "Gisselle", "Giuseppe", + "Gladyce", "Gladys", "Glen", "Glenda", "Glenna", "Glennie", "Gloria", + "Godfrey", "Golda", "Golden", "Gonzalo", "Gordon", "Grace", "Gracie", + "Graciela", "Grady", "Graham", "Grant", "Granville", "Grayce", "Grayson", + "Green", "Greg", "Gregg", "Gregoria", "Gregorio", "Gregory", "Greta", + "Gretchen", "Greyson", "Griffin", "Grover", "Guadalupe", "Gudrun", "Guido", + "Guillermo", "Guiseppe", "Gunnar", "Gunner", "Gus", "Gussie", "Gust", + "Gustave", "Guy", "Gwen", "Gwendolyn", "Hadley", "Hailee", "Hailey", "Hailie", + "Hal", "Haleigh", "Haley", "Halie", "Halle", "Hallie", "Hank", "Hanna", + "Hannah", "Hans", "Hardy", "Harley", "Harmon", "Harmony", "Harold", + "Harrison", "Harry", "Harvey", "Haskell", "Hassan", "Hassie", "Hattie", + "Haven", "Hayden", "Haylee", "Hayley", "Haylie", "Hazel", "Hazle", "Heath", + "Heather", "Heaven", "Heber", "Hector", "Heidi", "Helen", "Helena", "Helene", + "Helga", "Hellen", "Helmer", "Heloise", "Henderson", "Henri", "Henriette", + "Henry", "Herbert", "Herman", "Hermann", "Hermina", "Herminia", "Herminio", + "Hershel", "Herta", "Hertha", "Hester", "Hettie", "Hilario", "Hilbert", + "Hilda", "Hildegard", "Hillard", "Hillary", "Hilma", "Hilton", "Hipolito", + "Hiram", "Hobart", "Holden", "Hollie", "Hollis", "Holly", "Hope", "Horace", + "Horacio", "Hortense", "Hosea", "Houston", "Howard", "Howell", "Hoyt", + "Hubert", "Hudson", "Hugh", "Hulda", "Humberto", "Hunter", "Hyman", "Ian", + "Ibrahim", "Icie", "Ida", "Idell", "Idella", "Ignacio", "Ignatius", "Ike", + "Ila", "Ilene", "Iliana", "Ima", "Imani", "Imelda", "Immanuel", "Imogene", + "Ines", "Irma", "Irving", "Irwin", "Isaac", "Isabel", "Isabell", "Isabella", + "Isabelle", "Isac", "Isadore", "Isai", "Isaiah", "Isaias", "Isidro", "Ismael", + "Isobel", "Isom", "Israel", "Issac", "Itzel", "Iva", "Ivah", "Ivory", "Ivy", + "Izabella", "Izaiah", "Jabari", "Jace", "Jacey", "Jacinthe", "Jacinto", + "Jack", "Jackeline", "Jackie", "Jacklyn", "Jackson", "Jacky", "Jaclyn", + "Jacquelyn", "Jacques", "Jacynthe", "Jada", "Jade", "Jaden", "Jadon", "Jadyn", + "Jaeden", "Jaida", "Jaiden", "Jailyn", "Jaime", "Jairo", "Jakayla", "Jake", + "Jakob", "Jaleel", "Jalen", "Jalon", "Jalyn", "Jamaal", "Jamal", "Jamar", + "Jamarcus", "Jamel", "Jameson", "Jamey", "Jamie", "Jamil", "Jamir", "Jamison", + "Jammie", "Jan", "Jana", "Janae", "Jane", "Janelle", "Janessa", "Janet", + "Janice", "Janick", "Janie", "Janis", "Janiya", "Jannie", "Jany", "Jaquan", + "Jaquelin", "Jaqueline", "Jared", "Jaren", "Jarod", "Jaron", "Jarred", + "Jarrell", "Jarret", "Jarrett", "Jarrod", "Jarvis", "Jasen", "Jasmin", + "Jason", "Jasper", "Jaunita", "Javier", "Javon", "Javonte", "Jay", "Jayce", + "Jaycee", "Jayda", "Jayde", "Jayden", "Jaydon", "Jaylan", "Jaylen", "Jaylin", + "Jaylon", "Jayme", "Jayne", "Jayson", "Jazlyn", "Jazmin", "Jazmyn", "Jazmyne", + "Jean", "Jeanette", "Jeanie", "Jeanne", "Jed", "Jedediah", "Jedidiah", "Jeff", + "Jefferey", "Jeffery", "Jeffrey", "Jeffry", "Jena", "Jenifer", "Jennie", + "Jennifer", "Jennings", "Jennyfer", "Jensen", "Jerad", "Jerald", "Jeramie", + "Jeramy", "Jerel", "Jeremie", "Jeremy", "Jermain", "Jermaine", "Jermey", + "Jerod", "Jerome", "Jeromy", "Jerrell", "Jerrod", "Jerrold", "Jerry", "Jess", + "Jesse", "Jessica", "Jessie", "Jessika", "Jessy", "Jessyca", "Jesus", "Jett", + "Jettie", "Jevon", "Jewel", "Jewell", "Jillian", "Jimmie", "Jimmy", "Jo", + "Joan", "Joana", "Joanie", "Joanne", "Joannie", "Joanny", "Joany", "Joaquin", + "Jocelyn", "Jodie", "Jody", "Joe", "Joel", "Joelle", "Joesph", "Joey", + "Johan", "Johann", "Johanna", "Johathan", "John", "Johnathan", "Johnathon", + "Johnnie", "Johnny", "Johnpaul", "Johnson", "Jolie", "Jon", "Jonas", + "Jonatan", "Jonathan", "Jonathon", "Jordan", "Jordane", "Jordi", "Jordon", + "Jordy", "Jordyn", "Jorge", "Jose", "Josefa", "Josefina", "Joseph", + "Josephine", "Josh", "Joshua", "Joshuah", "Josiah", "Josiane", "Josianne", + "Josie", "Josue", "Jovan", "Jovani", "Jovanny", "Jovany", "Joy", "Joyce", + "Juana", "Juanita", "Judah", "Judd", "Jude", "Judge", "Judson", "Judy", + "Jules", "Julia", "Julian", "Juliana", "Julianne", "Julie", "Julien", + "Juliet", "Julio", "Julius", "June", "Junior", "Junius", "Justen", "Justice", + "Justina", "Justine", "Juston", "Justus", "Justyn", "Juvenal", "Juwan", + "Kacey", "Kaci", "Kacie", "Kade", "Kaden", "Kadin", "Kaela", "Kaelyn", "Kaia", + "Kailee", "Kailey", "Kailyn", "Kaitlin", "Kaitlyn", "Kale", "Kaleb", + "Kaleigh", "Kaley", "Kali", "Kallie", "Kameron", "Kamille", "Kamren", + "Kamron", "Kamryn", "Kane", "Kara", "Kareem", "Karelle", "Karen", "Kari", + "Kariane", "Karianne", "Karina", "Karine", "Karl", "Karlee", "Karley", + "Karli", "Karlie", "Karolann", "Karson", "Kasandra", "Kasey", "Kassandra", + "Katarina", "Katelin", "Katelyn", "Katelynn", "Katharina", "Katherine", + "Katheryn", "Kathleen", "Kathlyn", "Kathryn", "Kathryne", "Katlyn", "Katlynn", + "Katrina", "Katrine", "Kattie", "Kavon", "Kay", "Kaya", "Kaycee", "Kayden", + "Kayla", "Kaylah", "Kaylee", "Kayleigh", "Kayley", "Kayli", "Kaylie", + "Kaylin", "Keagan", "Keanu", "Keara", "Keaton", "Keegan", "Keeley", "Keely", + "Keenan", "Keira", "Keith", "Kellen", "Kelley", "Kelli", "Kellie", "Kelly", + "Kelsi", "Kelsie", "Kelton", "Kelvin", "Ken", "Kendall", "Kendra", "Kendrick", + "Kenna", "Kennedi", "Kennedy", "Kenneth", "Kennith", "Kenny", "Kenton", + "Kenya", "Kenyatta", "Kenyon", "Keon", "Keshaun", "Keshawn", "Keven", "Kevin", + "Kevon", "Keyon", "Keyshawn", "Khalid", "Khalil", "Kian", "Kiana", "Kianna", + "Kiara", "Kiarra", "Kiel", "Kiera", "Kieran", "Kiley", "Kim", "Kimberly", + "King", "Kip", "Kira", "Kirk", "Kirsten", "Kirstin", "Kitty", "Kobe", "Koby", + "Kody", "Kolby", "Kole", "Korbin", "Korey", "Kory", "Kraig", "Kris", "Krista", + "Kristian", "Kristin", "Kristina", "Kristofer", "Kristoffer", "Kristopher", + "Kristy", "Krystal", "Krystel", "Krystina", "Kurt", "Kurtis", "Kyla", "Kyle", + "Kylee", "Kyleigh", "Kyler", "Kylie", "Kyra", "Lacey", "Lacy", "Ladarius", + "Lafayette", "Laila", "Laisha", "Lamar", "Lambert", "Lamont", "Lance", + "Landen", "Lane", "Laney", "Larissa", "Laron", "Larry", "Larue", "Laura", + "Laurel", "Lauren", "Laurence", "Lauretta", "Lauriane", "Laurianne", "Laurie", + "Laurine", "Laury", "Lauryn", "Lavada", "Lavern", "Laverna", "Laverne", + "Lavina", "Lavinia", "Lavon", "Lavonne", "Lawrence", "Lawson", "Layla", + "Layne", "Lazaro", "Lea", "Leann", "Leanna", "Leanne", "Leatha", "Leda", + "Lee", "Leif", "Leila", "Leilani", "Lela", "Lelah", "Leland", "Lelia", + "Lempi", "Lemuel", "Lenna", "Lennie", "Lenny", "Lenora", "Lenore", "Leo", + "Leola", "Leon", "Leonard", "Leonardo", "Leone", "Leonel", "Leonie", "Leonor", + "Leonora", "Leopold", "Leopoldo", "Leora", "Lera", "Lesley", "Leslie", + "Lesly", "Lessie", "Lester", "Leta", "Letha", "Letitia", "Levi", "Lew", + "Lewis", "Lexi", "Lexie", "Lexus", "Lia", "Liam", "Liana", "Libbie", "Libby", + "Lila", "Lilian", "Liliana", "Liliane", "Lilla", "Lillian", "Lilliana", + "Lillie", "Lilly", "Lily", "Lilyan", "Lina", "Lincoln", "Linda", "Lindsay", + "Lindsey", "Linnea", "Linnie", "Linwood", "Lionel", "Lisa", "Lisandro", + "Lisette", "Litzy", "Liza", "Lizeth", "Lizzie", "Llewellyn", "Lloyd", "Logan", + "Lois", "Lola", "Lolita", "Loma", "Lon", "London", "Lonie", "Lonnie", "Lonny", + "Lonzo", "Lora", "Loraine", "Loren", "Lorena", "Lorenz", "Lorenza", "Lorenzo", + "Lori", "Lorine", "Lorna", "Lottie", "Lou", "Louie", "Louisa", "Lourdes", + "Louvenia", "Lowell", "Loy", "Loyal", "Loyce", "Lucas", "Luciano", "Lucie", + "Lucienne", "Lucile", "Lucinda", "Lucio", "Lucious", "Lucius", "Lucy", + "Ludie", "Ludwig", "Lue", "Luella", "Luigi", "Luis", "Luisa", "Lukas", "Lula", + "Lulu", "Luna", "Lupe", "Lura", "Lurline", "Luther", "Luz", "Lyda", "Lydia", + "Lyla", "Lynn", "Lyric", "Lysanne", "Mabel", "Mabelle", "Mable", "Mac", + "Macey", "Maci", "Macie", "Mack", "Mackenzie", "Macy", "Madaline", "Madalyn", + "Maddison", "Madeline", "Madelyn", "Madelynn", "Madge", "Madie", "Madilyn", + "Madisen", "Madison", "Madisyn", "Madonna", "Madyson", "Mae", "Maegan", + "Maeve", "Mafalda", "Magali", "Magdalen", "Magdalena", "Maggie", "Magnolia", + "Magnus", "Maia", "Maida", "Maiya", "Major", "Makayla", "Makenna", "Makenzie", + "Malachi", "Malcolm", "Malika", "Malinda", "Mallie", "Mallory", "Malvina", + "Mandy", "Manley", "Manuel", "Manuela", "Mara", "Marc", "Marcel", "Marcelina", + "Marcelino", "Marcella", "Marcelle", "Marcellus", "Marcelo", "Marcia", + "Marco", "Marcos", "Marcus", "Margaret", "Margarete", "Margarett", + "Margaretta", "Margarette", "Margarita", "Marge", "Margie", "Margot", + "Margret", "Marguerite", "Maria", "Mariah", "Mariam", "Marian", "Mariana", + "Mariane", "Marianna", "Marianne", "Mariano", "Maribel", "Marie", "Mariela", + "Marielle", "Marietta", "Marilie", "Marilou", "Marilyne", "Marina", "Mario", + "Marion", "Marisa", "Marisol", "Maritza", "Marjolaine", "Marjorie", "Marjory", + "Mark", "Markus", "Marlee", "Marlen", "Marlene", "Marley", "Marlin", "Marlon", + "Marques", "Marquis", "Marquise", "Marshall", "Marta", "Martin", "Martina", + "Martine", "Marty", "Marvin", "Mary", "Maryam", "Maryjane", "Maryse", "Mason", + "Mateo", "Mathew", "Mathias", "Mathilde", "Matilda", "Matilde", "Matt", + "Matteo", "Mattie", "Maud", "Maude", "Maudie", "Maureen", "Maurice", + "Mauricio", "Maurine", "Maverick", "Mavis", "Max", "Maxie", "Maxime", + "Maximilian", "Maximillia", "Maximillian", "Maximo", "Maximus", "Maxine", + "Maxwell", "May", "Maya", "Maybell", "Maybelle", "Maye", "Maymie", "Maynard", + "Mayra", "Mazie", "Mckayla", "Mckenna", "Mckenzie", "Meagan", "Meaghan", + "Meda", "Megane", "Meggie", "Meghan", "Mekhi", "Melany", "Melba", "Melisa", + "Melissa", "Mellie", "Melody", "Melvin", "Melvina", "Melyna", "Melyssa", + "Mercedes", "Meredith", "Merl", "Merle", "Merlin", "Merritt", "Mertie", + "Mervin", "Meta", "Mia", "Micaela", "Micah", "Michael", "Michaela", "Michale", + "Micheal", "Michel", "Michele", "Michelle", "Miguel", "Mikayla", "Mike", + "Mikel", "Milan", "Miles", "Milford", "Miller", "Millie", "Milo", "Milton", + "Mina", "Minerva", "Minnie", "Miracle", "Mireille", "Mireya", "Misael", + "Missouri", "Misty", "Mitchel", "Mitchell", "Mittie", "Modesta", "Modesto", + "Mohamed", "Mohammad", "Mohammed", "Moises", "Mollie", "Molly", "Mona", + "Monica", "Monique", "Monroe", "Monserrat", "Monserrate", "Montana", "Monte", + "Monty", "Morgan", "Moriah", "Morris", "Mortimer", "Morton", "Mose", "Moses", + "Moshe", "Mossie", "Mozell", "Mozelle", "Muhammad", "Muriel", "Murl", + "Murphy", "Murray", "Mustafa", "Mya", "Myah", "Mylene", "Myles", "Myra", + "Myriam", "Myrl", "Myrna", "Myron", "Myrtice", "Myrtie", "Myrtis", "Myrtle", + "Nadia", "Nakia", "Name", "Nannie", "Naomi", "Naomie", "Napoleon", "Narciso", + "Nash", "Nasir", "Nat", "Natalia", "Natalie", "Natasha", "Nathan", + "Nathanael", "Nathanial", "Nathaniel", "Nathen", "Nayeli", "Neal", "Ned", + "Nedra", "Neha", "Neil", "Nelda", "Nella", "Nelle", "Nellie", "Nels", + "Nelson", "Neoma", "Nestor", "Nettie", "Neva", "Newell", "Newton", "Nia", + "Nicholas", "Nicholaus", "Nichole", "Nick", "Nicklaus", "Nickolas", "Nico", + "Nicola", "Nicolas", "Nicole", "Nicolette", "Nigel", "Nikita", "Nikki", + "Nikko", "Niko", "Nikolas", "Nils", "Nina", "Noah", "Noble", "Noe", "Noel", + "Noelia", "Noemi", "Noemie", "Noemy", "Nola", "Nolan", "Nona", "Nora", + "Norbert", "Norberto", "Norene", "Norma", "Norris", "Norval", "Norwood", + "Nova", "Novella", "Nya", "Nyah", "Nyasia", "Obie", "Oceane", "Ocie", + "Octavia", "Oda", "Odell", "Odessa", "Odie", "Ofelia", "Okey", "Ola", "Olaf", + "Ole", "Olen", "Oleta", "Olga", "Olin", "Oliver", "Ollie", "Oma", "Omari", + "Omer", "Ona", "Onie", "Opal", "Ophelia", "Ora", "Oral", "Oran", "Oren", + "Orie", "Orin", "Orion", "Orland", "Orlando", "Orlo", "Orpha", "Orrin", + "Orval", "Orville", "Osbaldo", "Osborne", "Oscar", "Osvaldo", "Oswald", + "Oswaldo", "Otha", "Otho", "Otilia", "Otis", "Ottilie", "Ottis", "Otto", + "Ova", "Owen", "Ozella", "Pablo", "Paige", "Palma", "Pamela", "Pansy", + "Paolo", "Paris", "Parker", "Pascale", "Pasquale", "Pat", "Patience", + "Patricia", "Patrick", "Patsy", "Pattie", "Paul", "Paula", "Pauline", + "Paxton", "Payton", "Pearl", "Pearlie", "Pearline", "Pedro", "Peggie", + "Penelope", "Percival", "Percy", "Perry", "Pete", "Peter", "Petra", "Peyton", + "Philip", "Phoebe", "Phyllis", "Pierce", "Pierre", "Pietro", "Pink", "Pinkie", + "Piper", "Polly", "Porter", "Precious", "Presley", "Preston", "Price", + "Prince", "Princess", "Priscilla", "Providenci", "Prudence", "Queen", + "Queenie", "Quentin", "Quincy", "Quinn", "Quinten", "Quinton", "Rachael", + "Rachel", "Rachelle", "Rae", "Raegan", "Rafael", "Rafaela", "Raheem", + "Rahsaan", "Rahul", "Raina", "Raleigh", "Ralph", "Ramiro", "Ramon", "Ramona", + "Randal", "Randall", "Randi", "Randy", "Ransom", "Raoul", "Raphael", + "Raphaelle", "Raquel", "Rashad", "Rashawn", "Rasheed", "Raul", "Raven", "Ray", + "Raymond", "Raymundo", "Reagan", "Reanna", "Reba", "Rebeca", "Rebecca", + "Rebeka", "Rebekah", "Reece", "Reed", "Reese", "Regan", "Reggie", "Reginald", + "Reid", "Reilly", "Reina", "Reinhold", "Remington", "Rene", "Renee", "Ressie", + "Reta", "Retha", "Retta", "Reuben", "Reva", "Rex", "Rey", "Reyes", "Reymundo", + "Reyna", "Reynold", "Rhea", "Rhett", "Rhianna", "Rhiannon", "Rhoda", + "Ricardo", "Richard", "Richie", "Richmond", "Rick", "Rickey", "Rickie", + "Ricky", "Rico", "Rigoberto", "Riley", "Rita", "River", "Robb", "Robbie", + "Robert", "Roberta", "Roberto", "Robin", "Robyn", "Rocio", "Rocky", "Rod", + "Roderick", "Rodger", "Rodolfo", "Rodrick", "Rodrigo", "Roel", "Rogelio", + "Roger", "Rogers", "Rolando", "Rollin", "Roma", "Romaine", "Roman", "Ron", + "Ronaldo", "Ronny", "Roosevelt", "Rory", "Rosa", "Rosalee", "Rosalia", + "Rosalind", "Rosalinda", "Rosalyn", "Rosamond", "Rosanna", "Rosario", + "Roscoe", "Rose", "Rosella", "Roselyn", "Rosemarie", "Rosemary", "Rosendo", + "Rosetta", "Rosie", "Rosina", "Roslyn", "Ross", "Rossie", "Rowan", "Rowena", + "Rowland", "Roxane", "Roxanne", "Roy", "Royal", "Royce", "Rozella", "Ruben", + "Rubie", "Ruby", "Rubye", "Rudolph", "Rudy", "Rupert", "Russ", "Russel", + "Russell", "Rusty", "Ruth", "Ruthe", "Ruthie", "Ryan", "Ryann", "Ryder", + "Rylan", "Rylee", "Ryleigh", "Ryley", "Sabina", "Sabrina", "Sabryna", "Sadie", + "Sadye", "Sage", "Saige", "Sallie", "Sally", "Salma", "Salvador", "Salvatore", + "Sam", "Samanta", "Samantha", "Samara", "Samir", "Sammie", "Sammy", "Samson", + "Sandra", "Sandrine", "Sandy", "Sanford", "Santa", "Santiago", "Santina", + "Santino", "Santos", "Sarah", "Sarai", "Sarina", "Sasha", "Saul", "Savanah", + "Savanna", "Savannah", "Savion", "Scarlett", "Schuyler", "Scot", "Scottie", + "Scotty", "Seamus", "Sean", "Sebastian", "Sedrick", "Selena", "Selina", + "Selmer", "Serena", "Serenity", "Seth", "Shad", "Shaina", "Shakira", "Shana", + "Shane", "Shanel", "Shanelle", "Shania", "Shanie", "Shaniya", "Shanna", + "Shannon", "Shanny", "Shanon", "Shany", "Sharon", "Shaun", "Shawn", "Shawna", + "Shaylee", "Shayna", "Shayne", "Shea", "Sheila", "Sheldon", "Shemar", + "Sheridan", "Sherman", "Sherwood", "Shirley", "Shyann", "Shyanne", "Sibyl", + "Sid", "Sidney", "Sienna", "Sierra", "Sigmund", "Sigrid", "Sigurd", "Silas", + "Sim", "Simeon", "Simone", "Sincere", "Sister", "Skye", "Skyla", "Skylar", + "Sofia", "Soledad", "Solon", "Sonia", "Sonny", "Sonya", "Sophia", "Sophie", + "Spencer", "Stacey", "Stacy", "Stan", "Stanford", "Stanley", "Stanton", + "Stefan", "Stefanie", "Stella", "Stephan", "Stephania", "Stephanie", + "Stephany", "Stephen", "Stephon", "Sterling", "Steve", "Stevie", "Stewart", + "Stone", "Stuart", "Summer", "Sunny", "Susan", "Susana", "Susanna", "Susie", + "Suzanne", "Sven", "Syble", "Sydnee", "Sydney", "Sydni", "Sydnie", "Sylvan", + "Sylvester", "Sylvia", "Tabitha", "Tad", "Talia", "Talon", "Tamara", "Tamia", + "Tania", "Tanner", "Tanya", "Tara", "Taryn", "Tate", "Tatum", "Tatyana", + "Taurean", "Tavares", "Taya", "Taylor", "Teagan", "Ted", "Telly", "Terence", + "Teresa", "Terrance", "Terrell", "Terrence", "Terrill", "Terry", "Tess", + "Tessie", "Tevin", "Thad", "Thaddeus", "Thalia", "Thea", "Thelma", "Theo", + "Theodora", "Theodore", "Theresa", "Therese", "Theresia", "Theron", "Thomas", + "Thora", "Thurman", "Tia", "Tiana", "Tianna", "Tiara", "Tierra", "Tiffany", + "Tillman", "Timmothy", "Timmy", "Timothy", "Tina", "Tito", "Titus", "Tobin", + "Toby", "Tod", "Tom", "Tomas", "Tomasa", "Tommie", "Toney", "Toni", "Tony", + "Torey", "Torrance", "Torrey", "Toy", "Trace", "Tracey", "Tracy", "Travis", + "Travon", "Tre", "Tremaine", "Tremayne", "Trent", "Trenton", "Tressa", + "Tressie", "Treva", "Trever", "Trevion", "Trevor", "Trey", "Trinity", + "Trisha", "Tristian", "Tristin", "Triston", "Troy", "Trudie", "Trycia", + "Trystan", "Turner", "Twila", "Tyler", "Tyra", "Tyree", "Tyreek", "Tyrel", + "Tyrell", "Tyrese", "Tyrique", "Tyshawn", "Tyson", "Ubaldo", "Ulices", + "Ulises", "Una", "Unique", "Urban", "Uriah", "Uriel", "Ursula", "Vada", + "Valentin", "Valentina", "Valentine", "Valerie", "Vallie", "Van", "Vance", + "Vanessa", "Vaughn", "Veda", "Velda", "Vella", "Velma", "Velva", "Vena", + "Verda", "Verdie", "Vergie", "Verla", "Verlie", "Vern", "Verna", "Verner", + "Vernice", "Vernie", "Vernon", "Verona", "Veronica", "Vesta", "Vicenta", + "Vicente", "Vickie", "Vicky", "Victor", "Victoria", "Vida", "Vidal", "Vilma", + "Vince", "Vincent", "Vincenza", "Vincenzo", "Vinnie", "Viola", "Violet", + "Violette", "Virgie", "Virgil", "Virginia", "Virginie", "Vita", "Vito", + "Viva", "Vivian", "Viviane", "Vivianne", "Vivien", "Vivienne", "Vladimir", + "Wade", "Waino", "Waldo", "Walker", "Wallace", "Walter", "Walton", "Wanda", + "Ward", "Warren", "Watson", "Wava", "Waylon", "Wayne", "Webster", "Weldon", + "Wellington", "Wendell", "Wendy", "Werner", "Westley", "Weston", "Whitney", + "Wilber", "Wilbert", "Wilburn", "Wiley", "Wilford", "Wilfred", "Wilfredo", + "Wilfrid", "Wilhelm", "Wilhelmine", "Will", "Willa", "Willard", "William", + "Willie", "Willis", "Willow", "Willy", "Wilma", "Wilmer", "Wilson", "Wilton", + "Winfield", "Winifred", "Winnifred", "Winona", "Winston", "Woodrow", "Wyatt", + "Wyman", "Xander", "Xavier", "Xzavier", "Yadira", "Yasmeen", "Yasmin", + "Yasmine", "Yazmin", "Yesenia", "Yessenia", "Yolanda", "Yoshiko", "Yvette", + "Yvonne", "Zachariah", "Zachary", "Zachery", "Zack", "Zackary", "Zackery", + "Zakary", "Zander", "Zane", "Zaria", "Zechariah", "Zelda", "Zella", "Zelma", + "Zena", "Zetta", "Zion", "Zita", "Zoe", "Zoey", "Zoie", "Zoila", "Zola", + "Zora", "Zula" +]; + +/** + * Generate random username. + * @returns {string} random username + */ +function generateUsername () { + var name = RandomUtil.randomElement(names); + var suffix = RandomUtil.randomAlphanumStr(3); + + return name + '-' + suffix; +} + +module.exports = { + generateUsername: generateUsername +}; + +},{"./RandomUtil":26}],28:[function(require,module,exports){ (function (__filename){ /* global Strophe, $, $pres, $iq, $msg */ /* jshint -W101,-W069 */ @@ -6374,7 +7284,7 @@ function filterNodeFromPresenceJSON(pres, nodeName){ return res; } -function ChatRoom(connection, jid, password, XMPP, options) { +function ChatRoom(connection, jid, password, XMPP, options, settings) { this.eventEmitter = new EventEmitter(); this.xmpp = XMPP; this.connection = connection; @@ -6390,7 +7300,8 @@ function ChatRoom(connection, jid, password, XMPP, options) { this.focusMucJid = null; this.bridgeIsDown = false; this.options = options || {}; - this.moderator = new Moderator(this.roomjid, this.xmpp, this.eventEmitter); + this.moderator = new Moderator(this.roomjid, this.xmpp, this.eventEmitter, + settings); this.initPresenceMap(); this.session = null; var self = this; @@ -7123,7 +8034,7 @@ ChatRoom.prototype.onMute = function (iq) { module.exports = ChatRoom; }).call(this,"/modules/xmpp/ChatRoom.js") -},{"../../service/xmpp/XMPPEvents":85,"./moderator":33,"./recording":34,"events":43,"jitsi-meet-logger":47}],27:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":138,"./moderator":35,"./recording":36,"events":45,"jitsi-meet-logger":49}],29:[function(require,module,exports){ (function (__filename){ /* * JingleSession provides an API to manage a single Jingle session. We will @@ -7259,7 +8170,7 @@ JingleSession.prototype.setAnswer = function(jingle) {}; module.exports = JingleSession; }).call(this,"/modules/xmpp/JingleSession.js") -},{"jitsi-meet-logger":47}],28:[function(require,module,exports){ +},{"jitsi-meet-logger":49}],30:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -8922,7 +9833,7 @@ JingleSessionPC.prototype.getIceConnectionState = function () { module.exports = JingleSessionPC; }).call(this,"/modules/xmpp/JingleSessionPC.js") -},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":16,"../RTC/RTCBrowserType":17,"./JingleSession":27,"./SDP":29,"./SDPDiffer":30,"./SDPUtil":31,"./TraceablePeerConnection":32,"async":42,"jitsi-meet-logger":47,"sdp-transform":75}],29:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":138,"../RTC/RTC":16,"../RTC/RTCBrowserType":17,"./JingleSession":29,"./SDP":31,"./SDPDiffer":32,"./SDPUtil":33,"./TraceablePeerConnection":34,"async":44,"jitsi-meet-logger":49,"sdp-transform":78}],31:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -9573,7 +10484,7 @@ SDP.prototype.jingle2media = function (content) { module.exports = SDP; }).call(this,"/modules/xmpp/SDP.js") -},{"./SDPUtil":31,"jitsi-meet-logger":47}],30:[function(require,module,exports){ +},{"./SDPUtil":33,"jitsi-meet-logger":49}],32:[function(require,module,exports){ var SDPUtil = require("./SDPUtil"); function SDPDiffer(mySDP, otherSDP) @@ -9742,7 +10653,7 @@ SDPDiffer.prototype.toJingle = function(modify) { }; module.exports = SDPDiffer; -},{"./SDPUtil":31}],31:[function(require,module,exports){ +},{"./SDPUtil":33}],33:[function(require,module,exports){ (function (__filename){ var logger = require("jitsi-meet-logger").getLogger(__filename); var RTCBrowserType = require("../RTC/RTCBrowserType"); @@ -10110,7 +11021,7 @@ SDPUtil = { module.exports = SDPUtil; }).call(this,"/modules/xmpp/SDPUtil.js") -},{"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],32:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":17,"jitsi-meet-logger":49}],34:[function(require,module,exports){ (function (__filename){ /* global $ */ var RTC = require('../RTC/RTC'); @@ -10137,7 +11048,9 @@ function TraceablePeerConnection(ice_config, constraints, session) { var Interop = require('sdp-interop').Interop; this.interop = new Interop(); var Simulcast = require('sdp-simulcast'); - this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false}); + this.simulcast = new Simulcast({numOfLayers: 3, + explodeRemoteSimulcast: false}); + this.eventEmitter = this.session.room.eventEmitter; // override as desired this.trace = function (what, info) { @@ -10438,7 +11351,8 @@ TraceablePeerConnection.prototype.setLocalDescription // if we're running on FF, transform to Plan A first. if (RTCBrowserType.usesUnifiedPlan()) { description = this.interop.toUnifiedPlan(description); - this.trace('setLocalDescription::postTransform (Plan A)', dumpSDP(description)); + this.trace('setLocalDescription::postTransform (Plan A)', + dumpSDP(description)); } var self = this; @@ -10449,14 +11363,11 @@ TraceablePeerConnection.prototype.setLocalDescription }, function (err) { self.trace('setLocalDescriptionOnFailure', err); + self.eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED, + err, self.peerconnection); failureCallback(err); } ); - /* - if (this.statsinterval === null && this.maxstats > 0) { - // start gathering stats - } - */ }; TraceablePeerConnection.prototype.setRemoteDescription @@ -10484,6 +11395,8 @@ TraceablePeerConnection.prototype.setRemoteDescription }, function (err) { self.trace('setRemoteDescriptionOnFailure', err); + self.eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED, + err, self.peerconnection); failureCallback(err); } ); @@ -10525,14 +11438,18 @@ TraceablePeerConnection.prototype.createOffer self.trace('createOfferOnSuccess::mungeLocalVideoSSRC', dumpSDP(offer)); } - if (!self.session.room.options.disableSimulcast && self.simulcast.isSupported()) { + if (!self.session.room.options.disableSimulcast + && self.simulcast.isSupported()) { offer = self.simulcast.mungeLocalDescription(offer); - self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer)); + self.trace('createOfferOnSuccess::postTransform (simulcast)', + dumpSDP(offer)); } successCallback(offer); }, function(err) { self.trace('createOfferOnFailure', err); + self.eventEmitter.emit(XMPPEvents.CREATE_OFFER_FAILED, err, + self.peerconnection); failureCallback(err); }, constraints @@ -10549,7 +11466,8 @@ TraceablePeerConnection.prototype.createAnswer // if we're running on FF, transform to Plan A first. if (RTCBrowserType.usesUnifiedPlan()) { answer = self.interop.toPlanB(answer); - self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer)); + self.trace('createAnswerOnSuccess::postTransform (Plan B)', + dumpSDP(answer)); } if (RTCBrowserType.isChrome()) @@ -10558,14 +11476,18 @@ TraceablePeerConnection.prototype.createAnswer self.trace('createAnswerOnSuccess::mungeLocalVideoSSRC', dumpSDP(answer)); } - if (!self.session.room.options.disableSimulcast && self.simulcast.isSupported()) { + if (!self.session.room.options.disableSimulcast + && self.simulcast.isSupported()) { answer = self.simulcast.mungeLocalDescription(answer); - self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer)); + self.trace('createAnswerOnSuccess::postTransform (simulcast)', + dumpSDP(answer)); } successCallback(answer); }, function(err) { self.trace('createAnswerOnFailure', err); + self.eventEmitter.emit(XMPPEvents.CREATE_ANSWER_FAILED, err, + self.peerconnection); failureCallback(err); }, constraints @@ -10606,13 +11528,12 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { module.exports = TraceablePeerConnection; }).call(this,"/modules/xmpp/TraceablePeerConnection.js") -},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":16,"../RTC/RTCBrowserType.js":17,"../util/RandomUtil":25,"jitsi-meet-logger":47,"sdp-interop":65,"sdp-simulcast":72,"sdp-transform":75}],33:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":138,"../RTC/RTC":16,"../RTC/RTCBrowserType.js":17,"../util/RandomUtil":26,"jitsi-meet-logger":49,"sdp-interop":68,"sdp-simulcast":75,"sdp-transform":78}],35:[function(require,module,exports){ (function (__filename){ /* global $, $iq, Promise, Strophe */ var logger = require("jitsi-meet-logger").getLogger(__filename); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var Settings = require("../settings/Settings"); var AuthenticationEvents = require("../../service/authentication/AuthenticationEvents"); @@ -10632,18 +11553,14 @@ function createExpBackoffTimer(step) { }; } - - - - -function Moderator(roomName, xmpp, emitter) { +function Moderator(roomName, xmpp, emitter, settings) { this.roomName = roomName; this.xmppService = xmpp; this.getNextTimeout = createExpBackoffTimer(1000); this.getNextErrorTimeout = createExpBackoffTimer(1000); // External authentication stuff this.externalAuthEnabled = false; - this.settings = new Settings(roomName); + this.settings = settings; // Sip gateway can be enabled by configuring Jigasi host in config.js or // it will be enabled automatically if focus detects the component through // service discovery. @@ -11067,7 +11984,7 @@ Moderator.prototype.logout = function (callback) { module.exports = Moderator; }).call(this,"/modules/xmpp/moderator.js") -},{"../../service/authentication/AuthenticationEvents":81,"../../service/xmpp/XMPPEvents":85,"../settings/Settings":21,"jitsi-meet-logger":47}],34:[function(require,module,exports){ +},{"../../service/authentication/AuthenticationEvents":134,"../../service/xmpp/XMPPEvents":138,"jitsi-meet-logger":49}],36:[function(require,module,exports){ (function (__filename){ /* global $, $iq, config, connection, focusMucJid, messageHandler, Toolbar, Util, Promise */ @@ -11294,7 +12211,7 @@ Recording.prototype.getURL = function () { module.exports = Recording; }).call(this,"/modules/xmpp/recording.js") -},{"../../service/xmpp/XMPPEvents":85,"jitsi-meet-logger":47}],35:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":138,"jitsi-meet-logger":49}],37:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ /* a simple MUC connection plugin @@ -11322,13 +12239,14 @@ module.exports = function(XMPP) { this.connection.addHandler(this.onMute.bind(this), 'http://jitsi.org/jitmeet/audio', 'iq', 'set',null,null); }, - createRoom: function (jid, password, options) { + createRoom: function (jid, password, options, settings) { var roomJid = Strophe.getBareJidFromJid(jid); if (this.rooms[roomJid]) { logger.error("You are already in the room!"); return; } - this.rooms[roomJid] = new ChatRoom(this.connection, jid, password, XMPP, options); + this.rooms[roomJid] = new ChatRoom(this.connection, jid, + password, XMPP, options, settings); return this.rooms[roomJid]; }, doLeave: function (jid) { @@ -11406,7 +12324,7 @@ module.exports = function(XMPP) { }; }).call(this,"/modules/xmpp/strophe.emuc.js") -},{"./ChatRoom":26,"jitsi-meet-logger":47}],36:[function(require,module,exports){ +},{"./ChatRoom":28,"jitsi-meet-logger":49}],38:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -11703,7 +12621,7 @@ module.exports = function(XMPP, eventEmitter) { }).call(this,"/modules/xmpp/strophe.jingle.js") -},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTCBrowserType":17,"./JingleSessionPC":28,"jitsi-meet-logger":47}],37:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":138,"../RTC/RTCBrowserType":17,"./JingleSessionPC":30,"jitsi-meet-logger":49}],39:[function(require,module,exports){ /* global Strophe */ module.exports = function () { @@ -11724,7 +12642,7 @@ module.exports = function () { } }); }; -},{}],38:[function(require,module,exports){ +},{}],40:[function(require,module,exports){ (function (__filename){ /* global $, $iq, Strophe */ @@ -11851,7 +12769,7 @@ module.exports = function (XMPP, eventEmitter) { }; }).call(this,"/modules/xmpp/strophe.ping.js") -},{"../../service/xmpp/XMPPEvents":85,"jitsi-meet-logger":47}],39:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":138,"jitsi-meet-logger":49}],41:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -11968,7 +12886,7 @@ module.exports = function() { }; }).call(this,"/modules/xmpp/strophe.rayo.js") -},{"jitsi-meet-logger":47}],40:[function(require,module,exports){ +},{"jitsi-meet-logger":49}],42:[function(require,module,exports){ (function (__filename){ /* global Strophe */ /** @@ -12017,7 +12935,7 @@ module.exports = function () { }; }).call(this,"/modules/xmpp/strophe.util.js") -},{"jitsi-meet-logger":47}],41:[function(require,module,exports){ +},{"jitsi-meet-logger":49}],43:[function(require,module,exports){ (function (__filename){ /* global $, APP, config, Strophe*/ @@ -12207,7 +13125,7 @@ XMPP.prototype.connect = function (jid, password) { return this._connect(jid, password); }; -XMPP.prototype.createRoom = function (roomName, options) { +XMPP.prototype.createRoom = function (roomName, options, settings) { var roomjid = roomName + '@' + this.options.hosts.muc; if (options.useNicks) { @@ -12225,7 +13143,7 @@ XMPP.prototype.createRoom = function (roomName, options) { roomjid += '/' + tmpJid; } - return this.connection.emuc.createRoom(roomjid, null, options); + return this.connection.emuc.createRoom(roomjid, null, options, settings); } XMPP.prototype.addListener = function(type, listener) { @@ -12329,7 +13247,7 @@ XMPP.prototype.disconnect = function () { module.exports = XMPP; }).call(this,"/modules/xmpp/xmpp.js") -},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":79,"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":16,"./strophe.emuc":35,"./strophe.jingle":36,"./strophe.logger":37,"./strophe.ping":38,"./strophe.rayo":39,"./strophe.util":40,"events":43,"jitsi-meet-logger":47,"pako":48}],42:[function(require,module,exports){ +},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":132,"../../service/xmpp/XMPPEvents":138,"../RTC/RTC":16,"./strophe.emuc":37,"./strophe.jingle":38,"./strophe.logger":39,"./strophe.ping":40,"./strophe.rayo":41,"./strophe.util":42,"events":45,"jitsi-meet-logger":49,"pako":51}],44:[function(require,module,exports){ (function (process){ /*! * async @@ -13456,7 +14374,7 @@ module.exports = XMPP; }()); }).call(this,require('_process')) -},{"_process":44}],43:[function(require,module,exports){ +},{"_process":46}],45:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -13759,7 +14677,7 @@ function isUndefined(arg) { return arg === void 0; } -},{}],44:[function(require,module,exports){ +},{}],46:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; @@ -13852,7 +14770,7 @@ process.chdir = function (dir) { }; process.umask = function() { return 0; }; -},{}],45:[function(require,module,exports){ +},{}],47:[function(require,module,exports){ (function (process,global){ /*! * @overview es6-promise - a tiny implementation of Promises/A+. @@ -14823,7 +15741,7 @@ process.umask = function() { return 0; }; }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"_process":44}],46:[function(require,module,exports){ +},{"_process":46}],48:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14967,7 +15885,7 @@ Logger.levels = { ERROR: "error" }; -},{}],47:[function(require,module,exports){ +},{}],49:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15052,7 +15970,43 @@ module.exports = { levels: Logger.levels }; -},{"./Logger":46}],48:[function(require,module,exports){ +},{"./Logger":48}],50:[function(require,module,exports){ +/* + A JavaScript implementation of the SHA family of hashes, as + defined in FIPS PUB 180-2 as well as the corresponding HMAC implementation + as defined in FIPS PUB 198a + + Copyright Brian Turek 2008-2013 + Distributed under the BSD License + See http://caligatio.github.com/jsSHA/ for more information + + Several functions taken from Paul Johnston +*/ +(function(T){function z(a,c,b){var g=0,f=[0],h="",l=null,h=b||"UTF8";if("UTF8"!==h&&"UTF16"!==h)throw"encoding must be UTF8 or UTF16";if("HEX"===c){if(0!==a.length%2)throw"srcString of HEX type must be in byte increments";l=B(a);g=l.binLen;f=l.value}else if("ASCII"===c||"TEXT"===c)l=J(a,h),g=l.binLen,f=l.value;else if("B64"===c)l=K(a),g=l.binLen,f=l.value;else throw"inputFormat must be HEX, TEXT, ASCII, or B64";this.getHash=function(a,c,b,h){var l=null,d=f.slice(),n=g,p;3===arguments.length?"number"!== +typeof b&&(h=b,b=1):2===arguments.length&&(b=1);if(b!==parseInt(b,10)||1>b)throw"numRounds must a integer >= 1";switch(c){case "HEX":l=L;break;case "B64":l=M;break;default:throw"format must be HEX or B64";}if("SHA-1"===a)for(p=0;pp/8&&(d[b]&=4294967040);for(n=0;n<=b;n+=1)w[n]=d[n]^909522486,x[n]=d[n]^1549556828;c="SHA-1"===c?y(x.concat(y(w.concat(f),a+g)),a+m):v(x.concat(v(w.concat(f),a+g,c)),a+m,c);return l(c,N(s))}}function s(a,c){this.a=a;this.b=c}function J(a,c){var b=[],g,f=[],h=0,l;if("UTF8"===c)for(l=0;l>>12,f[1]=128|(g&4032)>>>6,f[2]=128|g&63):128>>6,f[1]=128|g&63):f[0]=g,g=0;g>>2]|=f[g]<<24-h%4*8,h+=1;else if("UTF16"===c)for(l=0;l>>2]|=a.charCodeAt(l)<<16-h%4*8,h+=2;return{value:b,binLen:8*h}}function B(a){var c=[],b=a.length,g,f;if(0!==b%2)throw"String of HEX type must be in byte increments";for(g=0;g>>3]|=f<<24-g%8*4}return{value:c, +binLen:4*b}}function K(a){var c=[],b=0,g,f,h,l,r;if(-1===a.search(/^[a-zA-Z0-9=+\/]+$/))throw"Invalid character in base-64 string";g=a.indexOf("=");a=a.replace(/\=/g,"");if(-1!==g&&g max) ? pos : max; }; -},{"./common":51}],53:[function(require,module,exports){ +},{"./common":54}],56:[function(require,module,exports){ 'use strict'; // Note: adler32 takes 12% for level 0 and 2% for level 6. @@ -16173,7 +17127,7 @@ function adler32(adler, buf, len, pos) { module.exports = adler32; -},{}],54:[function(require,module,exports){ +},{}],57:[function(require,module,exports){ module.exports = { /* Allowed flush values; see deflate() and inflate() below for details */ @@ -16222,7 +17176,7 @@ module.exports = { //Z_NULL: null // Use -1 or null inline, depending on var type }; -},{}],55:[function(require,module,exports){ +},{}],58:[function(require,module,exports){ 'use strict'; // Note: we can't get significant speed boost here. @@ -16265,7 +17219,7 @@ function crc32(crc, buf, len, pos) { module.exports = crc32; -},{}],56:[function(require,module,exports){ +},{}],59:[function(require,module,exports){ 'use strict'; var utils = require('../utils/common'); @@ -18032,7 +18986,7 @@ exports.deflatePrime = deflatePrime; exports.deflateTune = deflateTune; */ -},{"../utils/common":51,"./adler32":53,"./crc32":55,"./messages":61,"./trees":62}],57:[function(require,module,exports){ +},{"../utils/common":54,"./adler32":56,"./crc32":58,"./messages":64,"./trees":65}],60:[function(require,module,exports){ 'use strict'; @@ -18074,7 +19028,7 @@ function GZheader() { module.exports = GZheader; -},{}],58:[function(require,module,exports){ +},{}],61:[function(require,module,exports){ 'use strict'; // See state defs from inflate.js @@ -18402,7 +19356,7 @@ module.exports = function inflate_fast(strm, start) { return; }; -},{}],59:[function(require,module,exports){ +},{}],62:[function(require,module,exports){ 'use strict'; @@ -19907,7 +20861,7 @@ exports.inflateSyncPoint = inflateSyncPoint; exports.inflateUndermine = inflateUndermine; */ -},{"../utils/common":51,"./adler32":53,"./crc32":55,"./inffast":58,"./inftrees":60}],60:[function(require,module,exports){ +},{"../utils/common":54,"./adler32":56,"./crc32":58,"./inffast":61,"./inftrees":63}],63:[function(require,module,exports){ 'use strict'; @@ -20236,7 +21190,7 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta return 0; }; -},{"../utils/common":51}],61:[function(require,module,exports){ +},{"../utils/common":54}],64:[function(require,module,exports){ 'use strict'; module.exports = { @@ -20251,7 +21205,7 @@ module.exports = { '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ }; -},{}],62:[function(require,module,exports){ +},{}],65:[function(require,module,exports){ 'use strict'; @@ -21452,7 +22406,7 @@ exports._tr_flush_block = _tr_flush_block; exports._tr_tally = _tr_tally; exports._tr_align = _tr_align; -},{"../utils/common":51}],63:[function(require,module,exports){ +},{"../utils/common":54}],66:[function(require,module,exports){ 'use strict'; @@ -21483,7 +22437,7 @@ function ZStream() { module.exports = ZStream; -},{}],64:[function(require,module,exports){ +},{}],67:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21524,7 +22478,7 @@ module.exports = function arrayEquals(array) { }; -},{}],65:[function(require,module,exports){ +},{}],68:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21542,7 +22496,7 @@ module.exports = function arrayEquals(array) { exports.Interop = require('./interop'); -},{"./interop":66}],66:[function(require,module,exports){ +},{"./interop":69}],69:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22265,7 +23219,7 @@ Interop.prototype.toUnifiedPlan = function(desc) { //#endregion }; -},{"./array-equals":64,"./transform":67}],67:[function(require,module,exports){ +},{"./array-equals":67,"./transform":70}],70:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22379,7 +23333,7 @@ exports.parse = function(sdp) { }; -},{"sdp-transform":69}],68:[function(require,module,exports){ +},{"sdp-transform":72}],71:[function(require,module,exports){ var grammar = module.exports = { v: [{ name: 'version', @@ -22638,7 +23592,7 @@ Object.keys(grammar).forEach(function (key) { }); }); -},{}],69:[function(require,module,exports){ +},{}],72:[function(require,module,exports){ var parser = require('./parser'); var writer = require('./writer'); @@ -22648,7 +23602,7 @@ exports.parseFmtpConfig = parser.parseFmtpConfig; exports.parsePayloads = parser.parsePayloads; exports.parseRemoteCandidates = parser.parseRemoteCandidates; -},{"./parser":70,"./writer":71}],70:[function(require,module,exports){ +},{"./parser":73,"./writer":74}],73:[function(require,module,exports){ var toIntIfInt = function (v) { return String(Number(v)) === v ? Number(v) : v; }; @@ -22743,7 +23697,7 @@ exports.parseRemoteCandidates = function (str) { return candidates; }; -},{"./grammar":68}],71:[function(require,module,exports){ +},{"./grammar":71}],74:[function(require,module,exports){ var grammar = require('./grammar'); // customized util.format - discards excess arguments and can void middle ones @@ -22859,7 +23813,7 @@ module.exports = function (session, opts) { return sdp.join('\r\n') + '\r\n'; }; -},{"./grammar":68}],72:[function(require,module,exports){ +},{"./grammar":71}],75:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23283,7 +24237,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) { module.exports = Simulcast; -},{"./transform-utils":73,"sdp-transform":75}],73:[function(require,module,exports){ +},{"./transform-utils":76,"sdp-transform":78}],76:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23349,22 +24303,7019 @@ exports.parseSsrcs = function (mLine) { }; -},{}],74:[function(require,module,exports){ -arguments[4][68][0].apply(exports,arguments) -},{"dup":68}],75:[function(require,module,exports){ -arguments[4][69][0].apply(exports,arguments) -},{"./parser":76,"./writer":77,"dup":69}],76:[function(require,module,exports){ -arguments[4][70][0].apply(exports,arguments) -},{"./grammar":74,"dup":70}],77:[function(require,module,exports){ +},{}],77:[function(require,module,exports){ arguments[4][71][0].apply(exports,arguments) -},{"./grammar":74,"dup":71}],78:[function(require,module,exports){ +},{"dup":71}],78:[function(require,module,exports){ +arguments[4][72][0].apply(exports,arguments) +},{"./parser":79,"./writer":80,"dup":72}],79:[function(require,module,exports){ +arguments[4][73][0].apply(exports,arguments) +},{"./grammar":77,"dup":73}],80:[function(require,module,exports){ +arguments[4][74][0].apply(exports,arguments) +},{"./grammar":77,"dup":74}],81:[function(require,module,exports){ + +module.exports = require('./lib/'); + +},{"./lib/":82}],82:[function(require,module,exports){ + +/** + * Module dependencies. + */ + +var url = require('./url'); +var parser = require('socket.io-parser'); +var Manager = require('./manager'); +var debug = require('debug')('socket.io-client'); + +/** + * Module exports. + */ + +module.exports = exports = lookup; + +/** + * Managers cache. + */ + +var cache = exports.managers = {}; + +/** + * 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 + */ + +function lookup(uri, opts) { + if (typeof uri == 'object') { + opts = uri; + uri = undefined; + } + + opts = opts || {}; + + var parsed = url(uri); + var source = parsed.source; + var id = parsed.id; + var io; + + if (opts.forceNew || opts['force new connection'] || false === opts.multiplex) { + 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]; + } + + 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":83,"./socket":85,"./url":86,"debug":90,"socket.io-parser":126}],83:[function(require,module,exports){ + +/** + * Module dependencies. + */ + +var url = require('./url'); +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 object = require('object-component'); +var debug = require('debug')('socket.io-client:manager'); +var indexOf = require('indexof'); +var Backoff = require('backo2'); + +/** + * Module exports + */ + +module.exports = Manager; + +/** + * `Manager` constructor. + * + * @param {String} engine instance or engine uri/opts + * @param {Object} options + * @api public + */ + +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(); +} + +/** + * Propagate given event to sockets and emit on `this` + * + * @api private + */ + +Manager.prototype.emitAll = function() { + this.emit.apply(this, arguments); + for (var nsp in this.nsps) { + this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); + } +}; + +/** + * Update `socket.id` of all sockets + * + * @api private + */ + +Manager.prototype.updateSocketIds = function(){ + for (var nsp in this.nsps) { + this.nsps[nsp].id = this.engine.id; + } +}; + +/** + * Mix in `Emitter`. + */ + +Emitter(Manager.prototype); + +/** + * Sets the `reconnection` config. + * + * @param {Boolean} true/false if it should automatically reconnect + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.reconnection = function(v){ + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + return this; +}; + +/** + * Sets the reconnection attempts config. + * + * @param {Number} max reconnection attempts before giving up + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.reconnectionAttempts = function(v){ + if (!arguments.length) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; +}; + +/** + * Sets the delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + +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; +}; + +/** + * Sets the maximum delay between reconnections. + * + * @param {Number} delay + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.reconnectionDelayMax = function(v){ + if (!arguments.length) return this._reconnectionDelayMax; + this._reconnectionDelayMax = v; + this.backoff && this.backoff.setMax(v); + return this; +}; + +/** + * Sets the connection timeout. `false` to disable + * + * @return {Manager} self or value + * @api public + */ + +Manager.prototype.timeout = function(v){ + if (!arguments.length) return this._timeout; + this._timeout = v; + return this; +}; + +/** + * Starts trying to reconnect if reconnection is enabled and we have not + * started reconnecting yet + * + * @api private + */ + +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(); + } +}; + + +/** + * Sets the current transport `socket`. + * + * @param {Function} optional, callback + * @return {Manager} self + * @api public + */ + +Manager.prototype.open = +Manager.prototype.connect = function(fn){ + debug('readyState %s', this.readyState); + if (~this.readyState.indexOf('open')) return this; + + 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; + + // emit `open` + var openSub = on(socket, 'open', function() { + self.onopen(); + fn && fn(); + }); + + // 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; +}; + +/** + * Called upon transport open. + * + * @api private + */ + +Manager.prototype.onopen = function(){ + debug('open'); + + // clear old subs + this.cleanup(); + + // mark as open + this.readyState = 'open'; + this.emit('open'); + + // 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'))); +}; + +/** + * Called with data. + * + * @api private + */ + +Manager.prototype.ondata = function(data){ + this.decoder.add(data); +}; + +/** + * Called when parser fully decodes a packet. + * + * @api private + */ + +Manager.prototype.ondecoded = function(packet) { + this.emit('packet', packet); +}; + +/** + * Called upon socket error. + * + * @api private + */ + +Manager.prototype.onerror = function(err){ + debug('error', err); + this.emitAll('error', err); +}; + +/** + * 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('connect', function(){ + socket.id = self.engine.id; + if (!~indexOf(self.connected, socket)) { + self.connected.push(socket); + } + }); + } + return socket; +}; + +/** + * Called upon a socket close. + * + * @param {Socket} socket + */ + +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(); +}; + +/** + * Writes a packet. + * + * @param {Object} packet + * @api private + */ + +Manager.prototype.packet = function(packet){ + debug('writing packet %j', packet); + var self = this; + + 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]); + } + self.encoding = false; + self.processPacketQueue(); + }); + } else { // add packet to the queue + self.packetBuffer.push(packet); + } +}; + +/** + * If packet buffer is non-empty, begins encoding the + * next packet in line. + * + * @api private + */ + +Manager.prototype.processPacketQueue = function() { + if (this.packetBuffer.length > 0 && !this.encoding) { + var pack = this.packetBuffer.shift(); + this.packet(pack); + } +}; + +/** + * Clean up transport subscriptions and packet buffer. + * + * @api private + */ + +Manager.prototype.cleanup = function(){ + var sub; + while (sub = this.subs.shift()) sub.destroy(); + + this.packetBuffer = []; + this.encoding = false; + + this.decoder.destroy(); +}; + +/** + * Close the current socket. + * + * @api private + */ + +Manager.prototype.close = +Manager.prototype.disconnect = function(){ + this.skipReconnect = true; + this.backoff.reset(); + this.readyState = 'closed'; + this.engine && this.engine.close(); +}; + +/** + * Called upon engine close. + * + * @api private + */ + +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(); + } +}; + +/** + * Attempt a reconnection. + * + * @api private + */ + +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); + } + }); + } +}; + +/** + * 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":84,"./socket":85,"./url":86,"backo2":87,"component-bind":88,"component-emitter":89,"debug":90,"engine.io-client":91,"indexof":122,"object-component":123,"socket.io-parser":126}],84:[function(require,module,exports){ + +/** + * Module exports. + */ + +module.exports = on; + +/** + * Helper for subscriptions. + * + * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter` + * @param {String} event name + * @param {Function} callback + * @api public + */ + +function on(obj, ev, fn) { + obj.on(ev, fn); + return { + destroy: function(){ + obj.removeListener(ev, fn); + } + }; +} + +},{}],85:[function(require,module,exports){ + +/** + * Module dependencies. + */ + +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'); + +/** + * Module exports. + */ + +module.exports = exports = Socket; + +/** + * Internal events (blacklisted). + * These events can't be emitted by the user. + * + * @api private + */ + +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 +}; + +/** + * Shortcut to `Emitter#emit`. + */ + +var emit = Emitter.prototype.emit; + +/** + * `Socket` constructor. + * + * @api public + */ + +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; +} + +/** + * Mix in `Emitter`. + */ + +Emitter(Socket.prototype); + +/** + * Subscribe to open, close and packet events + * + * @api private + */ + +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')) + ]; +}; + +/** + * "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; +}; + +/** + * Sends a `message` event. + * + * @return {Socket} self + * @api public + */ + +Socket.prototype.send = function(){ + var args = toArray(arguments); + args.unshift('message'); + this.emit.apply(this, args); + return this; +}; + +/** + * Override `emit`. + * If the event is in `events`, it's emitted normally. + * + * @param {String} event name + * @return {Socket} self + * @api public + */ + +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 }; + + // 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++; + } + + if (this.connected) { + this.packet(packet); + } else { + this.sendBuffer.push(packet); + } + + return this; +}; + +/** + * Sends a packet. + * + * @param {Object} packet + * @api private + */ + +Socket.prototype.packet = function(packet){ + packet.nsp = this.nsp; + this.io.packet(packet); +}; + +/** + * Called upon engine `open`. + * + * @api private + */ + +Socket.prototype.onopen = function(){ + debug('transport is open - connecting'); + + // write connect packet if necessary + if ('/' != this.nsp) { + this.packet({ type: parser.CONNECT }); + } +}; + +/** + * Called upon engine `close`. + * + * @param {String} reason + * @api private + */ + +Socket.prototype.onclose = function(reason){ + debug('close (%s)', reason); + this.connected = false; + this.disconnected = true; + delete this.id; + this.emit('disconnect', reason); +}; + +/** + * Called with socket packet. + * + * @param {Object} packet + * @api private + */ + +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; + } +}; + +/** + * Called upon a server event. + * + * @param {Object} packet + * @api private + */ + +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)); + } + + if (this.connected) { + emit.apply(this, args); + } else { + this.receiveBuffer.push(args); + } +}; + +/** + * Produces an ack callback to emit with an event. + * + * @api private + */ + +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 + }); + }; +}; + +/** + * Called upon a server acknowlegement. + * + * @param {Object} packet + * @api private + */ + +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]; +}; + +/** + * Called upon server connect. + * + * @api private + */ + +Socket.prototype.onconnect = function(){ + this.connected = true; + this.disconnected = false; + this.emit('connect'); + this.emitBuffered(); +}; + +/** + * Emit buffered events (received and emitted). + * + * @api private + */ + +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 = []; +}; + +/** + * Called upon server disconnect. + * + * @api private + */ + +Socket.prototype.ondisconnect = function(){ + debug('server disconnect (%s)', this.nsp); + this.destroy(); + this.onclose('io server disconnect'); +}; + +/** + * 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. + */ + +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); +}; + +/** + * 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(); + + if (this.connected) { + // fire events + this.onclose('io client disconnect'); + } + return this; +}; + +},{"./on":84,"component-bind":88,"component-emitter":89,"debug":90,"has-binary":120,"socket.io-parser":126,"to-array":130}],86:[function(require,module,exports){ +(function (global){ + +/** + * Module dependencies. + */ + +var parseuri = require('parseuri'); +var debug = require('debug')('socket.io-client:url'); + +/** + * Module exports. + */ + +module.exports = url; + +/** + * URL parser. + * + * @param {String} url + * @param {Object} An object meant to mimic window.location. + * Defaults to window.location. + * @api public + */ + +function url(uri, loc){ + var obj = uri; + + // default to window.location + var loc = loc || global.location; + if (null == uri) uri = loc.protocol + '//' + loc.host; + + // relative path support + if ('string' == typeof uri) { + if ('/' == uri.charAt(0)) { + if ('/' == uri.charAt(1)) { + uri = loc.protocol + uri; + } else { + uri = loc.hostname + uri; + } + } + + if (!/^(https?|wss?):\/\//.test(uri)) { + debug('protocol-less url %s', uri); + if ('undefined' != typeof loc) { + uri = loc.protocol + '//' + uri; + } else { + uri = 'https://' + uri; + } + } + + // parse + debug('parse %s', uri); + obj = parseuri(uri); + } + + // 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'; + } + } + + obj.path = obj.path || '/'; + + // 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)); + + return obj; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"debug":90,"parseuri":124}],87:[function(require,module,exports){ + +/** + * Expose `Backoff`. + */ + +module.exports = Backoff; + +/** + * Initialize backoff timer with `opts`. + * + * - `min` initial timeout in milliseconds [100] + * - `max` max timeout [10000] + * - `jitter` [0] + * - `factor` [2] + * + * @param {Object} opts + * @api public + */ + +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; +} + +/** + * Return the backoff duration. + * + * @return {Number} + * @api public + */ + +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; +}; + +/** + * Reset the number of attempts. + * + * @api public + */ + +Backoff.prototype.reset = function(){ + this.attempts = 0; +}; + +/** + * Set the minimum duration + * + * @api public + */ + +Backoff.prototype.setMin = function(min){ + this.ms = min; +}; + +/** + * Set the maximum duration + * + * @api public + */ + +Backoff.prototype.setMax = function(max){ + this.max = max; +}; + +/** + * Set the jitter + * + * @api public + */ + +Backoff.prototype.setJitter = function(jitter){ + this.jitter = jitter; +}; + + +},{}],88:[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))); + } +}; + +},{}],89:[function(require,module,exports){ + +/** + * Expose `Emitter`. + */ + +module.exports = Emitter; + +/** + * Initialize a new `Emitter`. + * + * @api public + */ + +function Emitter(obj) { + if (obj) return mixin(obj); +}; + +/** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + +function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; +} + +/** + * Listen on the given `event` with `fn`. + * + * @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. + * + * @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. + * + * @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; + } + + // 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; +}; + +/** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + +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; +}; + +/** + * 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] || []; +}; + +/** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + +Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; +}; + +},{}],90:[function(require,module,exports){ + +/** + * Expose `debug()` as the module. + */ + +module.exports = debug; + +/** + * Create a debugger with the given `name`. + * + * @param {String} name + * @return {Type} + * @api public + */ + +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); + } +} + +/** + * The currently active debug mode names. + */ + +debug.names = []; +debug.skips = []; + +/** + * Enables a debug mode by name. This can include modes + * separated by a colon and wildcards. + * + * @param {String} name + * @api public + */ + +debug.enable = function(name) { + try { + localStorage.debug = name; + } catch(e){} + + 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 + '$')); + } + } +}; + +/** + * Disable debug output. + * + * @api public + */ + +debug.disable = function(){ + debug.enable(''); +}; + +/** + * Humanize the given `ms`. + * + * @param {Number} m + * @return {String} + * @api private + */ + +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'; +}; + +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + +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; +}; + +/** + * Coerce `val`. + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} + +// persist + +try { + if (window.localStorage) debug.enable(localStorage.debug); +} catch(e){} + +},{}],91:[function(require,module,exports){ + +module.exports = require('./lib/'); + +},{"./lib/":92}],92:[function(require,module,exports){ + +module.exports = require('./socket'); + +/** + * Exports parser + * + * @api public + * + */ +module.exports.parser = require('engine.io-parser'); + +},{"./socket":93,"engine.io-parser":105}],93:[function(require,module,exports){ +(function (global){ +/** + * Module dependencies. + */ + +var transports = require('./transports'); +var Emitter = require('component-emitter'); +var debug = require('debug')('engine.io-client:socket'); +var index = require('indexof'); +var parser = require('engine.io-parser'); +var parseuri = require('parseuri'); +var parsejson = require('parsejson'); +var parseqs = require('parseqs'); + +/** + * Module exports. + */ + +module.exports = Socket; + +/** + * Noop function. + * + * @api private + */ + +function noop(){} + +/** + * Socket constructor. + * + * @param {String|Object} uri or options + * @param {Object} options + * @api public + */ + +function Socket(uri, opts){ + if (!(this instanceof Socket)) return new Socket(uri, opts); + + opts = opts || {}; + + if (uri && 'object' == typeof uri) { + opts = uri; + uri = null; + } + + if (uri) { + uri = parseuri(uri); + opts.host = uri.host; + opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; + opts.port = uri.port; + if (uri.query) opts.query = uri.query; + } + + this.secure = null != opts.secure ? opts.secure : + (global.location && 'https:' == location.protocol); + + 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; + this.hostname = opts.hostname || + (global.location ? location.hostname : 'localhost'); + this.port = opts.port || (global.location && location.port ? + location.port : + (this.secure ? 443 : 80)); + this.query = opts.query || {}; + if ('string' == typeof this.query) this.query = parseqs.decode(this.query); + this.upgrade = false !== opts.upgrade; + this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; + this.forceJSONP = !!opts.forceJSONP; + this.jsonp = false !== opts.jsonp; + this.forceBase64 = !!opts.forceBase64; + this.enablesXDR = !!opts.enablesXDR; + this.timestampParam = opts.timestampParam || 't'; + this.timestampRequests = opts.timestampRequests; + 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; + + // SSL options for Node.js client + this.pfx = opts.pfx || null; + this.key = opts.key || null; + this.passphrase = opts.passphrase || null; + this.cert = opts.cert || null; + this.ca = opts.ca || null; + this.ciphers = opts.ciphers || null; + this.rejectUnauthorized = opts.rejectUnauthorized || null; + + this.open(); +} + +Socket.priorWebsocketSuccess = false; + +/** + * Mix in `Emitter`. + */ + +Emitter(Socket.prototype); + +/** + * Protocol version. + * + * @api public + */ + +Socket.protocol = parser.protocol; // this is an int + +/** + * Expose deps for legacy compatibility + * and standalone browser access. + */ + +Socket.Socket = Socket; +Socket.Transport = require('./transport'); +Socket.transports = require('./transports'); +Socket.parser = require('engine.io-parser'); + +/** + * Creates transport of the given type. + * + * @param {String} transport name + * @return {Transport} + * @api private + */ + +Socket.prototype.createTransport = function (name) { + debug('creating transport "%s"', name); + var query = clone(this.query); + + // append engine.io protocol identifier + query.EIO = parser.protocol; + + // transport name + query.transport = name; + + // session id if we already have one + if (this.id) query.sid = this.id; + + var transport = new transports[name]({ + agent: this.agent, + hostname: this.hostname, + port: this.port, + secure: this.secure, + path: this.path, + query: query, + forceJSONP: this.forceJSONP, + jsonp: this.jsonp, + forceBase64: this.forceBase64, + enablesXDR: this.enablesXDR, + timestampRequests: this.timestampRequests, + timestampParam: this.timestampParam, + policyPort: this.policyPort, + socket: this, + pfx: this.pfx, + key: this.key, + passphrase: this.passphrase, + cert: this.cert, + ca: this.ca, + ciphers: this.ciphers, + rejectUnauthorized: this.rejectUnauthorized + }); + + return transport; +}; + +function clone (obj) { + var o = {}; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = obj[i]; + } + } + return o; +} + +/** + * Initializes transport to use and starts probe. + * + * @api private + */ +Socket.prototype.open = function () { + var transport; + if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) { + transport = 'websocket'; + } else if (0 == this.transports.length) { + // Emit error on next tick so it can be listened to + var self = this; + setTimeout(function() { + self.emit('error', 'No transports available'); + }, 0); + return; + } else { + transport = this.transports[0]; + } + this.readyState = 'opening'; + + // Retry with the next transport if the transport is disabled (jsonp: false) + var transport; + try { + transport = this.createTransport(transport); + } catch (e) { + this.transports.shift(); + this.open(); + return; + } + + transport.open(); + this.setTransport(transport); +}; + +/** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + +Socket.prototype.setTransport = function(transport){ + debug('setting transport %s', transport.name); + var self = this; + + if (this.transport) { + debug('clearing existing transport %s', this.transport.name); + this.transport.removeAllListeners(); + } + + // set up transport + this.transport = transport; + + // set up transport listeners + transport + .on('drain', function(){ + self.onDrain(); + }) + .on('packet', function(packet){ + self.onPacket(packet); + }) + .on('error', function(e){ + self.onError(e); + }) + .on('close', function(){ + self.onClose('transport close'); + }); +}; + +/** + * Probes a transport. + * + * @param {String} transport name + * @api private + */ + +Socket.prototype.probe = function (name) { + debug('probing transport "%s"', name); + var transport = this.createTransport(name, { probe: 1 }) + , failed = false + , self = this; + + Socket.priorWebsocketSuccess = false; + + function onTransportOpen(){ + if (self.onlyBinaryUpgrades) { + var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; + failed = failed || upgradeLosesBinary; + } + if (failed) return; + + debug('probe transport "%s" opened', name); + transport.send([{ type: 'ping', data: 'probe' }]); + transport.once('packet', function (msg) { + if (failed) return; + if ('pong' == msg.type && 'probe' == msg.data) { + debug('probe transport "%s" pong', name); + self.upgrading = true; + self.emit('upgrading', transport); + if (!transport) return; + Socket.priorWebsocketSuccess = 'websocket' == transport.name; + + debug('pausing current transport "%s"', self.transport.name); + self.transport.pause(function () { + if (failed) return; + if ('closed' == self.readyState) return; + debug('changing transport and sending upgrade packet'); + + cleanup(); + + self.setTransport(transport); + transport.send([{ type: 'upgrade' }]); + self.emit('upgrade', transport); + transport = null; + self.upgrading = false; + self.flush(); + }); + } else { + debug('probe transport "%s" failed', name); + var err = new Error('probe error'); + err.transport = transport.name; + self.emit('upgradeError', err); + } + }); + } + + function freezeTransport() { + if (failed) return; + + // Any callback called by transport should be ignored since now + failed = true; + + cleanup(); + + transport.close(); + transport = null; + } + + //Handle any error that happens while probing + function onerror(err) { + var error = new Error('probe error: ' + err); + error.transport = transport.name; + + freezeTransport(); + + debug('probe transport "%s" failed because of error: %s', name, err); + + self.emit('upgradeError', error); + } + + function onTransportClose(){ + onerror("transport closed"); + } + + //When the socket is closed while we're probing + function onclose(){ + onerror("socket closed"); + } + + //When the socket is upgraded while we're probing + function onupgrade(to){ + if (transport && to.name != transport.name) { + debug('"%s" works - aborting "%s"', to.name, transport.name); + freezeTransport(); + } + } + + //Remove all listeners on the transport and on self + function cleanup(){ + transport.removeListener('open', onTransportOpen); + transport.removeListener('error', onerror); + transport.removeListener('close', onTransportClose); + self.removeListener('close', onclose); + self.removeListener('upgrading', onupgrade); + } + + transport.once('open', onTransportOpen); + transport.once('error', onerror); + transport.once('close', onTransportClose); + + this.once('close', onclose); + this.once('upgrading', onupgrade); + + transport.open(); + +}; + +/** + * Called when connection is deemed open. + * + * @api public + */ + +Socket.prototype.onOpen = function () { + debug('socket open'); + this.readyState = 'open'; + Socket.priorWebsocketSuccess = 'websocket' == this.transport.name; + this.emit('open'); + this.flush(); + + // we check for `readyState` in case an `open` + // listener already closed the socket + if ('open' == this.readyState && this.upgrade && this.transport.pause) { + debug('starting upgrade probes'); + for (var i = 0, l = this.upgrades.length; i < l; i++) { + this.probe(this.upgrades[i]); + } + } +}; + +/** + * Handles a packet. + * + * @api private + */ + +Socket.prototype.onPacket = function (packet) { + if ('opening' == this.readyState || 'open' == this.readyState) { + debug('socket receive: type "%s", data "%s"', packet.type, packet.data); + + this.emit('packet', packet); + + // Socket is live - any packet counts + this.emit('heartbeat'); + + switch (packet.type) { + case 'open': + this.onHandshake(parsejson(packet.data)); + break; + + case 'pong': + this.setPing(); + break; + + case 'error': + var err = new Error('server error'); + err.code = packet.data; + this.emit('error', err); + break; + + case 'message': + this.emit('data', packet.data); + this.emit('message', packet.data); + break; + } + } else { + debug('packet received with socket readyState "%s"', this.readyState); + } +}; + +/** + * Called upon handshake completion. + * + * @param {Object} handshake obj + * @api private + */ + +Socket.prototype.onHandshake = function (data) { + this.emit('handshake', data); + this.id = data.sid; + this.transport.query.sid = data.sid; + this.upgrades = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.onOpen(); + // In case open handler closes socket + if ('closed' == this.readyState) return; + this.setPing(); + + // Prolong liveness of socket on heartbeat + this.removeListener('heartbeat', this.onHeartbeat); + this.on('heartbeat', this.onHeartbeat); +}; + +/** + * Resets ping timeout. + * + * @api private + */ + +Socket.prototype.onHeartbeat = function (timeout) { + clearTimeout(this.pingTimeoutTimer); + var self = this; + self.pingTimeoutTimer = setTimeout(function () { + if ('closed' == self.readyState) return; + self.onClose('ping timeout'); + }, timeout || (self.pingInterval + self.pingTimeout)); +}; + +/** + * Pings server every `this.pingInterval` and expects response + * within `this.pingTimeout` or closes connection. + * + * @api private + */ + +Socket.prototype.setPing = function () { + var self = this; + clearTimeout(self.pingIntervalTimer); + self.pingIntervalTimer = setTimeout(function () { + debug('writing ping packet - expecting pong within %sms', self.pingTimeout); + self.ping(); + self.onHeartbeat(self.pingTimeout); + }, self.pingInterval); +}; + +/** +* Sends a ping packet. +* +* @api public +*/ + +Socket.prototype.ping = function () { + this.sendPacket('ping'); +}; + +/** + * Called on `drain` event + * + * @api private + */ + +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 (this.writeBuffer.length == 0) { + this.emit('drain'); + } else { + this.flush(); + } +}; + +/** + * Flush write buffers. + * + * @api private + */ + +Socket.prototype.flush = function () { + if ('closed' != this.readyState && this.transport.writable && + !this.upgrading && this.writeBuffer.length) { + debug('flushing %d packets in socket', this.writeBuffer.length); + this.transport.send(this.writeBuffer); + // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + this.prevBufferLen = this.writeBuffer.length; + this.emit('flush'); + } +}; + +/** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @return {Socket} for chaining. + * @api public + */ + +Socket.prototype.write = +Socket.prototype.send = function (msg, fn) { + this.sendPacket('message', msg, fn); + return this; +}; + +/** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Function} callback function. + * @api private + */ + +Socket.prototype.sendPacket = function (type, data, fn) { + if ('closing' == this.readyState || 'closed' == this.readyState) { + return; + } + + var packet = { type: type, data: data }; + this.emit('packetCreate', packet); + this.writeBuffer.push(packet); + this.callbackBuffer.push(fn); + this.flush(); +}; + +/** + * Closes the connection. + * + * @api private + */ + +Socket.prototype.close = function () { + if ('opening' == this.readyState || 'open' == this.readyState) { + this.readyState = 'closing'; + + 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) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + + return this; +}; + +/** + * Called upon transport error + * + * @api private + */ + +Socket.prototype.onError = function (err) { + debug('socket error %j', err); + Socket.priorWebsocketSuccess = false; + this.emit('error', err); + this.onClose('transport error', err); +}; + +/** + * Called upon transport close. + * + * @api private + */ + +Socket.prototype.onClose = function (reason, desc) { + if ('opening' == this.readyState || 'open' == this.readyState || 'closing' == this.readyState) { + debug('socket close with reason: "%s"', reason); + var self = this; + + // clear timers + 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'); + + // ensure transport won't stay open + this.transport.close(); + + // ignore further transport communication + this.transport.removeAllListeners(); + + // set ready state + this.readyState = 'closed'; + + // clear session id + this.id = null; + + // emit close event + this.emit('close', reason, desc); + } +}; + +/** + * 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