{"version":3,"file":"lib-jitsi-meet.min.js","mappings":"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","sources":["webpack://JitsiMeetJS/webpack/universalModuleDefinition","webpack://JitsiMeetJS/./index.js","webpack://JitsiMeetJS/./modules/statistics/SpeakerStats.js","webpack://JitsiMeetJS/./modules/util/AuthUtil.js","webpack://JitsiMeetJS/./modules/util/EventEmitterForwarder.js","webpack://JitsiMeetJS/./modules/util/GlobalOnErrorHandler.js","webpack://JitsiMeetJS/./modules/util/RandomUtil.js","webpack://JitsiMeetJS/./modules/util/ScriptUtil.js","webpack://JitsiMeetJS/./modules/util/StringUtils.js","webpack://JitsiMeetJS/./modules/util/UsernameGenerator.js","webpack://JitsiMeetJS/./node_modules/@jitsi/logger/lib/LogCollector.js","webpack://JitsiMeetJS/./node_modules/@jitsi/logger/lib/Logger.js","webpack://JitsiMeetJS/./node_modules/@jitsi/logger/lib/index.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-interop/node_modules/sdp-transform/lib/grammar.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-interop/node_modules/sdp-transform/lib/index.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-interop/node_modules/sdp-transform/lib/parser.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-interop/node_modules/sdp-transform/lib/writer.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-simulcast/lib/index.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-simulcast/lib/transform-utils.js","webpack://JitsiMeetJS/./node_modules/base64-js/index.js","webpack://JitsiMeetJS/./node_modules/bowser/es5.js","webpack://JitsiMeetJS/./node_modules/current-executing-script/dist/currentExecutingScript.js","webpack://JitsiMeetJS/./node_modules/events/events.js","webpack://JitsiMeetJS/./node_modules/js-md5/src/md5.js","webpack://JitsiMeetJS/./node_modules/lodash.clonedeep/index.js","webpack://JitsiMeetJS/./node_modules/lodash.debounce/index.js","webpack://JitsiMeetJS/./node_modules/lodash.isequal/index.js","webpack://JitsiMeetJS/./node_modules/sdp-transform/lib/grammar.js","webpack://JitsiMeetJS/./node_modules/sdp-transform/lib/index.js","webpack://JitsiMeetJS/./node_modules/sdp-transform/lib/parser.js","webpack://JitsiMeetJS/./node_modules/sdp-transform/lib/writer.js","webpack://JitsiMeetJS/./node_modules/sdp/sdp.js","webpack://JitsiMeetJS/./node_modules/strophe.js/dist/strophe.umd.js","webpack://JitsiMeetJS/../src/strophe.disco.js","webpack://JitsiMeetJS/../src/strophe.stream-management.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/bytesToUuid.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/index.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/md5-browser.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/rng-browser.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/sha1-browser.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/v1.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/v3.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/v35.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/v4.js","webpack://JitsiMeetJS/./node_modules/uuid/dist/v5.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/utils.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/chrome/getusermedia.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/chrome/getdisplaymedia.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/chrome/chrome_shim.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/firefox/getusermedia.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/firefox/getdisplaymedia.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/firefox/firefox_shim.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/safari/safari_shim.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/common_shim.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/adapter_core.js","webpack://JitsiMeetJS/./node_modules/webrtc-adapter/src/js/adapter_factory.js","webpack://JitsiMeetJS/./service/RTC/BridgeVideoType.js","webpack://JitsiMeetJS/./service/RTC/CameraFacingMode.js","webpack://JitsiMeetJS/./service/RTC/CodecMimeType.js","webpack://JitsiMeetJS/./service/RTC/RTCEvents.ts","webpack://JitsiMeetJS/./service/RTC/Resolutions.js","webpack://JitsiMeetJS/./service/RTC/VideoType.ts","webpack://JitsiMeetJS/./service/authentication/AuthenticationEvents.js","webpack://JitsiMeetJS/./service/statistics/constants.ts","webpack://JitsiMeetJS/./service/xmpp/XMPPEvents.ts","webpack://JitsiMeetJS/./JitsiConferenceErrors.ts","webpack://JitsiMeetJS/./JitsiConferenceEvents.ts","webpack://JitsiMeetJS/./modules/statistics/constants.js","webpack://JitsiMeetJS/./JitsiTrackErrors.ts","webpack://JitsiMeetJS/./JitsiTrackError.js","webpack://JitsiMeetJS/./service/statistics/AnalyticsEvents.ts","webpack://JitsiMeetJS/./service/statistics/Events.ts","webpack://JitsiMeetJS/./node_modules/@jitsi/js-utils/browser-detection/browsers.js","webpack://JitsiMeetJS/./node_modules/@jitsi/js-utils/browser-detection/BrowserDetection.js","webpack://JitsiMeetJS/./node_modules/@jitsi/js-utils/jitsi-local-storage/index.js","webpack://JitsiMeetJS/./modules/browser/BrowserCapabilities.js","webpack://JitsiMeetJS/./modules/browser/index.js","webpack://JitsiMeetJS/./modules/statistics/AnalyticsAdapter.js","webpack://JitsiMeetJS/./modules/statistics/CallStats.js","webpack://JitsiMeetJS/./modules/statistics/LocalStatsCollector.js","webpack://JitsiMeetJS/./modules/util/MathUtil.js","webpack://JitsiMeetJS/./modules/statistics/PerformanceObserverStats.js","webpack://JitsiMeetJS/./service/RTC/MediaType.ts","webpack://JitsiMeetJS/./modules/flags/FeatureFlags.js","webpack://JitsiMeetJS/./modules/statistics/RTPStatsCollector.js","webpack://JitsiMeetJS/./modules/statistics/statistics.js","webpack://JitsiMeetJS/./JitsiConferenceEventManager.js","webpack://JitsiMeetJS/./JitsiTrackEvents.ts","webpack://JitsiMeetJS/./modules/connectivity/ParticipantConnectionStatus.js","webpack://JitsiMeetJS/./JitsiParticipant.js","webpack://JitsiMeetJS/./JitsiConnectionEvents.ts","webpack://JitsiMeetJS/./JitsiConnectionErrors.ts","webpack://JitsiMeetJS/./modules/util/Deferred.js","webpack://JitsiMeetJS/./modules/util/Listenable.js","webpack://JitsiMeetJS/./modules/e2ee/E2EEContext.js","webpack://JitsiMeetJS/./modules/e2ee/KeyHandler.js","webpack://JitsiMeetJS/./modules/e2ee/ExternallyManagedKeyHandler.js","webpack://JitsiMeetJS/./node_modules/uuid/wrapper.mjs","webpack://JitsiMeetJS/./modules/e2ee/OlmAdapter.js","webpack://JitsiMeetJS/./modules/e2ee/ManagedKeyHandler.js","webpack://JitsiMeetJS/./modules/e2ee/crypto-utils.js","webpack://JitsiMeetJS/./modules/e2ee/E2EEncryption.js","webpack://JitsiMeetJS/./modules/xmpp/Caps.js","webpack://JitsiMeetJS/./modules/connectivity/NetworkInfo.js","webpack://JitsiMeetJS/./modules/xmpp/ResumeTask.js","webpack://JitsiMeetJS/./modules/util/Retry.js","webpack://JitsiMeetJS/./modules/xmpp/StropheLastSuccess.js","webpack://JitsiMeetJS/./modules/xmpp/ConnectionPlugin.js","webpack://JitsiMeetJS/./modules/xmpp/strophe.ping.js","webpack://JitsiMeetJS/./modules/xmpp/XmppConnection.js","webpack://JitsiMeetJS/./JitsiTranscriptionStatus.ts","webpack://JitsiMeetJS/./modules/xmpp/AVModeration.js","webpack://JitsiMeetJS/./modules/xmpp/BreakoutRooms.js","webpack://JitsiMeetJS/./modules/xmpp/Lobby.js","webpack://JitsiMeetJS/./modules/settings/Settings.js","webpack://JitsiMeetJS/./modules/xmpp/moderator.js","webpack://JitsiMeetJS/./modules/xmpp/ChatRoom.js","webpack://JitsiMeetJS/./modules/xmpp/strophe.emuc.js","webpack://JitsiMeetJS/./modules/xmpp/JingleHelperFunctions.js","webpack://JitsiMeetJS/./service/RTC/MediaDirection.ts","webpack://JitsiMeetJS/./modules/RTC/ScreenObtainer.js","webpack://JitsiMeetJS/./modules/sdp/SDPUtil.js","webpack://JitsiMeetJS/./modules/sdp/SDP.js","webpack://JitsiMeetJS/./modules/sdp/SDPDiffer.js","webpack://JitsiMeetJS/./node_modules/async/dist/async.mjs","webpack://JitsiMeetJS/./modules/util/AsyncQueue.js","webpack://JitsiMeetJS/./modules/xmpp/JingleSessionState.ts","webpack://JitsiMeetJS/./modules/xmpp/JingleSession.js","webpack://JitsiMeetJS/./modules/xmpp/MediaSessionEvents.ts","webpack://JitsiMeetJS/./modules/xmpp/JingleSessionPC.js","webpack://JitsiMeetJS/./modules/xmpp/strophe.jingle.js","webpack://JitsiMeetJS/./modules/xmpp/strophe.logger.js","webpack://JitsiMeetJS/./modules/xmpp/strophe.rayo.js","webpack://JitsiMeetJS/./modules/xmpp/strophe.util.js","webpack://JitsiMeetJS/./modules/xmpp/xmpp.js","webpack://JitsiMeetJS/./authenticateAndUpgradeRole.js","webpack://JitsiMeetJS/./modules/RTC/CodecSelection.js","webpack://JitsiMeetJS/./modules/RTC/BridgeChannel.js","webpack://JitsiMeetJS/./modules/RTC/RTCUtils.js","webpack://JitsiMeetJS/./modules/RTC/JitsiTrack.js","webpack://JitsiMeetJS/./modules/RTC/JitsiLocalTrack.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-interop/lib/transform.js","webpack://JitsiMeetJS/./node_modules/@jitsi/sdp-interop/lib/interop.js","webpack://JitsiMeetJS/./service/RTC/SignalingEvents.ts","webpack://JitsiMeetJS/./service/RTC/SignalingLayer.js","webpack://JitsiMeetJS/./modules/sdp/SdpTransformUtil.js","webpack://JitsiMeetJS/./modules/sdp/LocalSdpMunger.js","webpack://JitsiMeetJS/./modules/sdp/RtxModifier.js","webpack://JitsiMeetJS/./modules/sdp/SdpConsistency.js","webpack://JitsiMeetJS/./modules/sdp/SdpSimulcast.ts","webpack://JitsiMeetJS/./modules/connectivity/TrackStreamingStatus.ts","webpack://JitsiMeetJS/./modules/RTC/JitsiRemoteTrack.js","webpack://JitsiMeetJS/./modules/RTC/TPCUtils.js","webpack://JitsiMeetJS/./modules/RTC/TraceablePeerConnection.js","webpack://JitsiMeetJS/./modules/RTC/RTC.js","webpack://JitsiMeetJS/./service/connectivity/ConnectionQualityEvents.ts","webpack://JitsiMeetJS/./modules/connectivity/ConnectionQuality.js","webpack://JitsiMeetJS/./modules/connectivity/IceFailedHandling.js","webpack://JitsiMeetJS/./modules/detection/DetectionEvents.ts","webpack://JitsiMeetJS/./modules/detection/NoAudioSignalDetection.js","webpack://JitsiMeetJS/./modules/detection/P2PDominantSpeakerDetection.js","webpack://JitsiMeetJS/./modules/webaudio/WebAudioUtils.js","webpack://JitsiMeetJS/./modules/detection/TrackVADEmitter.js","webpack://JitsiMeetJS/./modules/detection/VADAudioAnalyser.js","webpack://JitsiMeetJS/./modules/detection/VADNoiseDetection.js","webpack://JitsiMeetJS/./modules/detection/VADTalkMutedDetection.js","webpack://JitsiMeetJS/./service/e2eping/E2ePingEvents.ts","webpack://JitsiMeetJS/./modules/e2eping/e2eping.js","webpack://JitsiMeetJS/./modules/event/Jvb121EventGenerator.js","webpack://JitsiMeetJS/./modules/qualitycontrol/ReceiveVideoController.js","webpack://JitsiMeetJS/./modules/qualitycontrol/SendVideoController.js","webpack://JitsiMeetJS/./modules/recording/recordingXMLUtils.js","webpack://JitsiMeetJS/./modules/recording/JibriSession.js","webpack://JitsiMeetJS/./modules/recording/RecordingManager.js","webpack://JitsiMeetJS/./modules/statistics/AudioOutputProblemDetector.js","webpack://JitsiMeetJS/./modules/statistics/AvgRTPStatsReporter.js","webpack://JitsiMeetJS/./modules/statistics/SpeakerStatsCollector.js","webpack://JitsiMeetJS/./modules/transcription/recordingResult.js","webpack://JitsiMeetJS/./modules/transcription/trackRecorder.js","webpack://JitsiMeetJS/./modules/transcription/audioRecorder.js","webpack://JitsiMeetJS/./modules/transcription/word.js","webpack://JitsiMeetJS/./modules/transcription/transcriptionServices/SphinxTranscriptionService.js","webpack://JitsiMeetJS/./modules/transcription/transcriptionServices/AbstractTranscriptionService.js","webpack://JitsiMeetJS/./modules/transcription/transcriber.js","webpack://JitsiMeetJS/./modules/version/ComponentsVersions.js","webpack://JitsiMeetJS/./modules/videosipgw/VideoSIPGWConstants.ts","webpack://JitsiMeetJS/./modules/videosipgw/JitsiVideoSIPGWSession.js","webpack://JitsiMeetJS/./modules/videosipgw/VideoSIPGW.js","webpack://JitsiMeetJS/./modules/xmpp/SignalingLayerImpl.js","webpack://JitsiMeetJS/./JitsiConference.js","webpack://JitsiMeetJS/./JitsiConnection.js","webpack://JitsiMeetJS/./JitsiMediaDevicesEvents.ts","webpack://JitsiMeetJS/./JitsiMediaDevices.js","webpack://JitsiMeetJS/./modules/detection/ActiveDeviceDetector.js","webpack://JitsiMeetJS/./modules/proxyconnection/constants.ts","webpack://JitsiMeetJS/./modules/proxyconnection/ProxyConnectionPC.js","webpack://JitsiMeetJS/./modules/proxyconnection/ProxyConnectionService.js","webpack://JitsiMeetJS/./modules/statistics/PrecallTest.js","webpack://JitsiMeetJS/./modules/webaudio/AudioMixer.js","webpack://JitsiMeetJS/./JitsiMeetJS.js","webpack://JitsiMeetJS/./modules/recording/recordingConstants.js","webpack://JitsiMeetJS/webpack/bootstrap","webpack://JitsiMeetJS/webpack/runtime/amd options","webpack://JitsiMeetJS/webpack/runtime/compat get default export","webpack://JitsiMeetJS/webpack/runtime/define property getters","webpack://JitsiMeetJS/webpack/runtime/global","webpack://JitsiMeetJS/webpack/runtime/hasOwnProperty shorthand","webpack://JitsiMeetJS/webpack/runtime/make namespace object","webpack://JitsiMeetJS/webpack/runtime/node module decorator","webpack://JitsiMeetJS/webpack/startup"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"JitsiMeetJS\"] = factory();\n\telse\n\t\troot[\"JitsiMeetJS\"] = factory();\n})(self, function() {\nreturn ","// For legacy purposes, preserve the UMD of the public API of the Jitsi Meet\r\n// library (a.k.a. JitsiMeetJS).\r\nmodule.exports = require('./JitsiMeetJS').default;\r\n","/**\r\n * A model for keeping track of each user's total\r\n * time as a dominant speaker. The model also\r\n * keeps track of the user's last known name\r\n * in case the user has left the meeting,\r\n * which is also tracked.\r\n */\r\nclass SpeakerStats {\r\n /**\r\n * Initializes a new SpeakerStats instance.\r\n *\r\n * @constructor\r\n * @param {string} userId - The id of the user being tracked.\r\n * @param {string} displayName - The name of the user being tracked.\r\n * @param {boolean} isLocalStats - True if the stats model tracks\r\n * the local user.\r\n * @returns {void}\r\n */\r\n constructor(userId, displayName, isLocalStats) {\r\n this._userId = userId;\r\n this.setDisplayName(displayName);\r\n this._isLocalStats = isLocalStats || false;\r\n this.setDominantSpeaker(false);\r\n this.totalDominantSpeakerTime = 0;\r\n this._dominantSpeakerStart = 0;\r\n this._hasLeft = false;\r\n this._faceExpressions = {\r\n happy: 0,\r\n neutral: 0,\r\n surprised: 0,\r\n angry: 0,\r\n fearful: 0,\r\n disgusted: 0,\r\n sad: 0\r\n };\r\n }\r\n\r\n /**\r\n * Get the user id being tracked.\r\n *\r\n * @returns {string} The user id.\r\n */\r\n getUserId() {\r\n return this._userId;\r\n }\r\n\r\n /**\r\n * Get the name of the user being tracked.\r\n *\r\n * @returns {string} The user name.\r\n */\r\n getDisplayName() {\r\n return this.displayName;\r\n }\r\n\r\n /**\r\n * Updates the last known name of the user being tracked.\r\n *\r\n * @param {string} - The user name.\r\n * @returns {void}\r\n */\r\n setDisplayName(newName) {\r\n this.displayName = newName;\r\n }\r\n\r\n /**\r\n * Returns true if the stats are tracking the local user.\r\n *\r\n * @returns {boolean}\r\n */\r\n isLocalStats() {\r\n return this._isLocalStats;\r\n }\r\n\r\n /**\r\n * Returns true if the tracked user is currently a dominant speaker.\r\n *\r\n * @returns {boolean}\r\n */\r\n isDominantSpeaker() {\r\n return this._dominantSpeakerStart > 0;\r\n }\r\n\r\n /**\r\n * Returns true if the tracked user is currently a dominant speaker.\r\n *\r\n * @param {boolean} - If true, the user will being accumulating time\r\n * as dominant speaker. If false, the user will not accumulate time\r\n * and will record any time accumulated since starting as dominant speaker.\r\n * @returns {void}\r\n */\r\n setDominantSpeaker(isNowDominantSpeaker) {\r\n if (!this.isDominantSpeaker() && isNowDominantSpeaker) {\r\n this._dominantSpeakerStart = Date.now();\r\n } else if (this.isDominantSpeaker() && !isNowDominantSpeaker) {\r\n const now = Date.now();\r\n const timeElapsed = now - this._dominantSpeakerStart;\r\n\r\n this.totalDominantSpeakerTime += timeElapsed;\r\n this._dominantSpeakerStart = 0;\r\n }\r\n }\r\n\r\n /**\r\n * Get how long the tracked user has been dominant speaker.\r\n *\r\n * @returns {number} - The speaker time in milliseconds.\r\n */\r\n getTotalDominantSpeakerTime() {\r\n let total = this.totalDominantSpeakerTime;\r\n\r\n if (this.isDominantSpeaker()) {\r\n total += Date.now() - this._dominantSpeakerStart;\r\n }\r\n\r\n return total;\r\n }\r\n\r\n /**\r\n * Get whether or not the user is still in the meeting.\r\n *\r\n * @returns {boolean} True if the user is no longer in the meeting.\r\n */\r\n hasLeft() {\r\n return this._hasLeft;\r\n }\r\n\r\n /**\r\n * Set the user as having left the meeting.\r\n *\r\n * @returns {void}\r\n */\r\n markAsHasLeft() {\r\n this._hasLeft = true;\r\n this.setDominantSpeaker(false);\r\n }\r\n\r\n /**\r\n * Gets the face expressions of the user.\r\n *\r\n * @returns {Object}\r\n */\r\n getFaceExpressions() {\r\n return this._faceExpressions;\r\n }\r\n\r\n /**\r\n * Sets the face expressions of the user.\r\n *\r\n * @param {Object} faceExpressions - object with face expressions.\r\n * @returns {void}\r\n */\r\n setFaceExpressions(faceExpressions) {\r\n this._faceExpressions = faceExpressions;\r\n }\r\n\r\n /**\r\n * Adds a new face expression to speaker stats.\r\n *\r\n * @param {string} faceExpression\r\n * @param {number} duration\r\n */\r\n addFaceExpression(faceExpression, duration) {\r\n this._faceExpressions[faceExpression] += duration;\r\n }\r\n}\r\n\r\nmodule.exports = SpeakerStats;\r\n","const AuthUtil = {\r\n /**\r\n * Creates the URL pointing to JWT token authentication service. It is\r\n * formatted from the 'urlPattern' argument which can contain the following\r\n * constants:\r\n * '{room}' - name of the conference room passed as roomName\r\n * argument to this method.\r\n * '{roleUpgrade}' - will contain 'true' if the URL will be used for\r\n * the role upgrade scenario, where user connects from anonymous domain and\r\n * then gets upgraded to the moderator by logging-in from the popup window.\r\n *\r\n * @param urlPattern a URL pattern pointing to the login service\r\n * @param roomName the name of the conference room for which the user will\r\n * be authenticated\r\n * @param {bool} roleUpgrade true if the URL will be used for role\r\n * upgrade scenario, where the user logs-in from the popup window in order\r\n * to have the moderator rights granted\r\n *\r\n * @returns {string|null} the URL pointing to JWT login service or\r\n * null if 'urlPattern' is not a string and the URL can not be\r\n * constructed.\r\n */\r\n getTokenAuthUrl(urlPattern, roomName, roleUpgrade) {\r\n const url = urlPattern;\r\n\r\n if (typeof url !== 'string') {\r\n return null;\r\n }\r\n\r\n return url.replace('{room}', roomName)\r\n .replace('{roleUpgrade}', roleUpgrade === true);\r\n }\r\n};\r\n\r\nmodule.exports = AuthUtil;\r\n","/**\r\n * Implements utility to forward events from one eventEmitter to another.\r\n * @param src {object} instance of EventEmitter or another class that implements\r\n * addListener method which will register listener to EventEmitter instance.\r\n * @param dest {object} instance of EventEmitter or another class that\r\n * implements emit method which will emit an event.\r\n */\r\nfunction EventEmitterForwarder(src, dest) {\r\n if (!src || !dest || typeof src.addListener !== 'function'\r\n || typeof dest.emit !== 'function') {\r\n throw new Error('Invalid arguments passed to EventEmitterForwarder');\r\n }\r\n this.src = src;\r\n this.dest = dest;\r\n}\r\n\r\n/**\r\n * Adds event to be forwarded from src to dest.\r\n * @param srcEvent {string} the event that EventEmitterForwarder is listening\r\n * for.\r\n * @param dstEvent {string} the event that will be fired from dest.\r\n * @param arguments all other passed arguments are going to be fired with\r\n * dstEvent.\r\n */\r\nEventEmitterForwarder.prototype.forward = function(...args) {\r\n const srcEvent = args[0];\r\n\r\n // This will be the \"this\" value for emit function.\r\n\r\n args[0] = this.dest;\r\n\r\n // Using bind.apply to pass the arguments as Array-like object (\"arguments\")\r\n this.src.addListener(\r\n srcEvent,\r\n Function.prototype.bind.apply(this.dest.emit, args));\r\n};\r\n\r\nmodule.exports = EventEmitterForwarder;\r\n","/**\r\n * This utility class defines custom onerror and onunhandledrejection functions.\r\n * The custom error handlers respect the previously-defined error handlers.\r\n * GlobalOnErrorHandler class provides utilities to add many custom error\r\n * handlers and to execute the error handlers directly.\r\n */\r\n\r\n\r\n/**\r\n * List with global error handlers that will be executed.\r\n */\r\nconst handlers = [];\r\n\r\n// If an old handler exists, also fire its events.\r\nconst oldOnErrorHandler = window.onerror;\r\n\r\n/**\r\n * Custom error handler that calls the old global error handler and executes\r\n * all handlers that were previously added.\r\n */\r\nfunction JitsiGlobalErrorHandler(...args) {\r\n handlers.forEach(handler => handler(...args));\r\n oldOnErrorHandler && oldOnErrorHandler(...args);\r\n}\r\n\r\n// If an old handler exists, also fire its events.\r\nconst oldOnUnhandledRejection = window.onunhandledrejection;\r\n\r\n/**\r\n * Custom handler that calls the old global handler and executes all handlers\r\n * that were previously added. This handler handles rejected Promises.\r\n */\r\nfunction JitsiGlobalUnhandledRejection(event) {\r\n handlers.forEach(handler => handler(null, null, null, null, event.reason));\r\n oldOnUnhandledRejection && oldOnUnhandledRejection(event);\r\n}\r\n\r\n// Setting the custom error handlers.\r\nwindow.onerror = JitsiGlobalErrorHandler;\r\nwindow.onunhandledrejection = JitsiGlobalUnhandledRejection;\r\n\r\nconst GlobalOnErrorHandler = {\r\n /**\r\n * Adds new error handlers.\r\n * @param handler the new handler.\r\n */\r\n addHandler(handler) {\r\n handlers.push(handler);\r\n },\r\n\r\n /**\r\n * Calls the global error handler if there is one.\r\n * @param error the error to pass to the error handler\r\n */\r\n callErrorHandler(error) {\r\n const errHandler = window.onerror;\r\n\r\n if (!errHandler) {\r\n return;\r\n }\r\n errHandler(null, null, null, null, error);\r\n },\r\n\r\n /**\r\n * Calls the global rejection handler if there is one.\r\n * @param error the error to pass to the rejection handler.\r\n */\r\n callUnhandledRejectionHandler(error) {\r\n const errHandler = window.onunhandledrejection;\r\n\r\n if (!errHandler) {\r\n return;\r\n }\r\n errHandler(error);\r\n }\r\n};\r\n\r\n\r\nmodule.exports = GlobalOnErrorHandler;\r\n","/**\r\n * @const\r\n */\r\nconst ALPHANUM\r\n = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';\r\n\r\n/**\r\n * Hexadecimal digits.\r\n * @const\r\n */\r\nconst HEX_DIGITS = '0123456789abcdef';\r\n\r\n/**\r\n * Generates random int within the range [min, max]\r\n * @param min the minimum value for the generated number\r\n * @param max the maximum value for the generated number\r\n * @returns random int number\r\n */\r\nfunction randomInt(min, max) {\r\n return Math.floor(Math.random() * (max - min + 1)) + min;\r\n}\r\n\r\n/**\r\n * Get random element from array or string.\r\n * @param {Array|string} arr source\r\n * @returns array element or string character\r\n */\r\nfunction randomElement(arr) {\r\n return arr[randomInt(0, arr.length - 1)];\r\n}\r\n\r\n/**\r\n * Generate random alphanumeric string.\r\n * @param {number} length expected string length\r\n * @returns {string} random string of specified length\r\n */\r\nfunction randomAlphanumStr(length) {\r\n let result = '';\r\n\r\n for (let i = 0; i < length; i += 1) {\r\n result += randomElement(ALPHANUM);\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Exported interface.\r\n */\r\nconst RandomUtil = {\r\n /**\r\n * Returns a random hex digit.\r\n * @returns {*}\r\n */\r\n randomHexDigit() {\r\n return randomElement(HEX_DIGITS);\r\n },\r\n\r\n /**\r\n * Returns a random string of hex digits with length 'len'.\r\n * @param len the length.\r\n */\r\n randomHexString(len) {\r\n let ret = '';\r\n\r\n while (len--) { // eslint-disable-line no-param-reassign\r\n ret += this.randomHexDigit();\r\n }\r\n\r\n return ret;\r\n },\r\n randomElement,\r\n randomAlphanumStr,\r\n randomInt\r\n};\r\n\r\nmodule.exports = RandomUtil;\r\n","const currentExecutingScript = require('current-executing-script');\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * Implements utility functions which facilitate the dealing with scripts such\r\n * as the download and execution of a JavaScript file.\r\n */\r\nconst ScriptUtil = {\r\n /**\r\n * Loads a script from a specific source.\r\n *\r\n * @param src the source from the which the script is to be (down)loaded\r\n * @param async true to asynchronously load the script or false to\r\n * synchronously load the script\r\n * @param prepend true to schedule the loading of the script as soon as\r\n * possible or false to schedule the loading of the script at the end of the\r\n * scripts known at the time\r\n * @param relativeURL whether we need load the library from url relative\r\n * to the url that lib-jitsi-meet was loaded. Useful when sourcing the\r\n * library from different location than the app that is using it\r\n * @param loadCallback on load callback function\r\n * @param errorCallback callback to be called on error loading the script\r\n */\r\n loadScript(\r\n src,\r\n async,\r\n prepend,\r\n relativeURL,\r\n loadCallback,\r\n errorCallback) {\r\n const d = document;\r\n const tagName = 'script';\r\n const script = d.createElement(tagName);\r\n const referenceNode = d.getElementsByTagName(tagName)[0];\r\n\r\n script.async = async;\r\n\r\n if (relativeURL) {\r\n // finds the src url of the current loaded script\r\n // and use it as base of the src supplied argument\r\n const scriptEl = currentExecutingScript();\r\n\r\n if (scriptEl) {\r\n const scriptSrc = scriptEl.src;\r\n const baseScriptSrc\r\n = scriptSrc.substring(0, scriptSrc.lastIndexOf('/') + 1);\r\n\r\n if (scriptSrc && baseScriptSrc) {\r\n // eslint-disable-next-line no-param-reassign\r\n src = baseScriptSrc + src;\r\n }\r\n }\r\n }\r\n\r\n if (loadCallback) {\r\n script.onload = loadCallback;\r\n }\r\n if (errorCallback) {\r\n script.onerror = errorCallback;\r\n }\r\n\r\n script.src = src;\r\n if (prepend) {\r\n referenceNode.parentNode.insertBefore(script, referenceNode);\r\n } else {\r\n referenceNode.parentNode.appendChild(script);\r\n }\r\n }\r\n};\r\n\r\n/* eslint-enable max-params */\r\n\r\nmodule.exports = ScriptUtil;\r\n","/**\r\n * Implements a simple hash code for a string (see\r\n * https://en.wikipedia.org/wiki/Java_hashCode()).\r\n *\r\n * @param {string} The string to return a hash of.\r\n * @return {Number} the integer hash code of the string.\r\n */\r\nfunction integerHash(string) {\r\n if (!string) {\r\n return 0;\r\n }\r\n\r\n let char, hash = 0, i;\r\n\r\n for (i = 0; i < string.length; i++) {\r\n char = string.charCodeAt(i);\r\n hash += char * Math.pow(31, string.length - 1 - i);\r\n hash = Math.abs(hash | 0); // eslint-disable-line no-bitwise\r\n }\r\n\r\n return hash;\r\n}\r\n\r\nmodule.exports = { integerHash };\r\n","const RandomUtil = require('./RandomUtil');\r\n\r\n/**\r\n * from faker.js - Copyright (c) 2014-2015 Matthew Bergman & Marak Squires\r\n * MIT License\r\n * http://github.com/marak/faker.js/\r\n *\r\n * @const\r\n */\r\nconst names = [\r\n 'Aaliyah', 'Aaron', 'Abagail', 'Abbey', 'Abbie', 'Abbigail', 'Abby',\r\n 'Abdiel', 'Abdul', 'Abdullah', 'Abe', 'Abel', 'Abelardo', 'Abigail',\r\n 'Abigale', 'Abigayle', 'Abner', 'Abraham', 'Ada', 'Adah', 'Adalberto',\r\n 'Adaline', 'Adam', 'Adan', 'Addie', 'Addison', 'Adela', 'Adelbert', 'Adele',\r\n 'Adelia', 'Adeline', 'Adell', 'Adella', 'Adelle', 'Aditya', 'Adolf',\r\n 'Adolfo', 'Adolph', 'Adolphus', 'Adonis', 'Adrain', 'Adrian', 'Adriana',\r\n 'Adrianna', 'Adriel', 'Adrien', 'Adrienne', 'Afton', 'Aglae', 'Agnes',\r\n 'Agustin', 'Agustina', 'Ahmad', 'Ahmed', 'Aida', 'Aidan', 'Aiden', 'Aileen',\r\n 'Aisha', 'Aiyana', 'Akeem', 'Al', 'Alaina', 'Alan', 'Alana', 'Alanis',\r\n 'Alanna', 'Alayna', 'Alba', 'Albert', 'Alberta', 'Albertha', 'Alberto',\r\n 'Albin', 'Albina', 'Alda', 'Alden', 'Alec', 'Aleen', 'Alejandra',\r\n 'Alejandrin', 'Alek', 'Alena', 'Alene', 'Alessandra', 'Alessandro',\r\n 'Alessia', 'Aletha', 'Alex', 'Alexa', 'Alexander', 'Alexandra', 'Alexandre',\r\n 'Alexandrea', 'Alexandria', 'Alexandrine', 'Alexandro', 'Alexane',\r\n 'Alexanne', 'Alexie', 'Alexis', 'Alexys', 'Alexzander', 'Alf', 'Alfonso',\r\n 'Alfonzo', 'Alford', 'Alfred', 'Alfreda', 'Alfredo', 'Ali', 'Alia', 'Alice',\r\n 'Alicia', 'Alisa', 'Alisha', 'Alison', 'Alivia', 'Aliya', 'Aliyah', 'Aliza',\r\n 'Alize', 'Allan', 'Allen', 'Allene', 'Allie', 'Allison', 'Ally', 'Alphonso',\r\n 'Alta', 'Althea', 'Alva', 'Alvah', 'Alvena', 'Alvera', 'Alverta', 'Alvina',\r\n 'Alvis', 'Alyce', 'Alycia', 'Alysa', 'Alysha', 'Alyson', 'Alysson',\r\n 'Amalia', 'Amanda', 'Amani', 'Amara', 'Amari', 'Amaya', 'Amber', 'Ambrose',\r\n 'Amelia', 'Amelie', 'Amely', 'America', 'Americo', 'Amie', 'Amina', 'Amir',\r\n 'Amira', 'Amiya', 'Amos', 'Amparo', 'Amy', 'Amya', 'Ana', 'Anabel',\r\n 'Anabelle', 'Anahi', 'Anais', 'Anastacio', 'Anastasia', 'Anderson', 'Andre',\r\n 'Andreane', 'Andreanne', 'Andres', 'Andrew', 'Andy', 'Angel', 'Angela',\r\n 'Angelica', 'Angelina', 'Angeline', 'Angelita', 'Angelo', 'Angie', 'Angus',\r\n 'Anibal', 'Anika', 'Anissa', 'Anita', 'Aniya', 'Aniyah', 'Anjali', 'Anna',\r\n 'Annabel', 'Annabell', 'Annabelle', 'Annalise', 'Annamae', 'Annamarie',\r\n 'Anne', 'Annetta', 'Annette', 'Annie', 'Ansel', 'Ansley', 'Anthony',\r\n 'Antoinette', 'Antone', 'Antonetta', 'Antonette', 'Antonia', 'Antonietta',\r\n 'Antonina', 'Antonio', 'Antwan', 'Antwon', 'Anya', 'April', 'Ara',\r\n 'Araceli', 'Aracely', 'Arch', 'Archibald', 'Ardella', 'Arden', 'Ardith',\r\n 'Arely', 'Ari', 'Ariane', 'Arianna', 'Aric', 'Ariel', 'Arielle', 'Arjun',\r\n 'Arlene', 'Arlie', 'Arlo', 'Armand', 'Armando', 'Armani', 'Arnaldo', 'Arne',\r\n 'Arno', 'Arnold', 'Arnoldo', 'Arnulfo', 'Aron', 'Art', 'Arthur', 'Arturo',\r\n 'Arvel', 'Arvid', 'Arvilla', 'Aryanna', 'Asa', 'Asha', 'Ashlee', 'Ashleigh',\r\n 'Ashley', 'Ashly', 'Ashlynn', 'Ashton', 'Ashtyn', 'Asia', 'Assunta',\r\n 'Astrid', 'Athena', 'Aubree', 'Aubrey', 'Audie', 'Audra', 'Audreanne',\r\n 'Audrey', 'August', 'Augusta', 'Augustine', 'Augustus', 'Aurelia',\r\n 'Aurelie', 'Aurelio', 'Aurore', 'Austen', 'Austin', 'Austyn', 'Autumn',\r\n 'Ava', 'Avery', 'Avis', 'Axel', 'Ayana', 'Ayden', 'Ayla', 'Aylin', 'Baby',\r\n 'Bailee', 'Bailey', 'Barbara', 'Barney', 'Baron', 'Barrett', 'Barry',\r\n 'Bart', 'Bartholome', 'Barton', 'Baylee', 'Beatrice', 'Beau', 'Beaulah',\r\n 'Bell', 'Bella', 'Belle', 'Ben', 'Benedict', 'Benjamin', 'Bennett',\r\n 'Bennie', 'Benny', 'Benton', 'Berenice', 'Bernadette', 'Bernadine',\r\n 'Bernard', 'Bernardo', 'Berneice', 'Bernhard', 'Bernice', 'Bernie',\r\n 'Berniece', 'Bernita', 'Berry', 'Bert', 'Berta', 'Bertha', 'Bertram',\r\n 'Bertrand', 'Beryl', 'Bessie', 'Beth', 'Bethany', 'Bethel', 'Betsy',\r\n 'Bette', 'Bettie', 'Betty', 'Bettye', 'Beulah', 'Beverly', 'Bianka', 'Bill',\r\n 'Billie', 'Billy', 'Birdie', 'Blair', 'Blaise', 'Blake', 'Blanca',\r\n 'Blanche', 'Blaze', 'Bo', 'Bobbie', 'Bobby', 'Bonita', 'Bonnie', 'Boris',\r\n 'Boyd', 'Brad', 'Braden', 'Bradford', 'Bradley', 'Bradly', 'Brady',\r\n 'Braeden', 'Brain', 'Brandi', 'Brando', 'Brandon', 'Brandt', 'Brandy',\r\n 'Brandyn', 'Brannon', 'Branson', 'Brant', 'Braulio', 'Braxton', 'Brayan',\r\n 'Breana', 'Breanna', 'Breanne', 'Brenda', 'Brendan', 'Brenden', 'Brendon',\r\n 'Brenna', 'Brennan', 'Brennon', 'Brent', 'Bret', 'Brett', 'Bria', 'Brian',\r\n 'Briana', 'Brianne', 'Brice', 'Bridget', 'Bridgette', 'Bridie', 'Brielle',\r\n 'Brigitte', 'Brionna', 'Brisa', 'Britney', 'Brittany', 'Brock', 'Broderick',\r\n 'Brody', 'Brook', 'Brooke', 'Brooklyn', 'Brooks', 'Brown', 'Bruce',\r\n 'Bryana', 'Bryce', 'Brycen', 'Bryon', 'Buck', 'Bud', 'Buddy', 'Buford',\r\n 'Bulah', 'Burdette', 'Burley', 'Burnice', 'Buster', 'Cade', 'Caden',\r\n 'Caesar', 'Caitlyn', 'Cale', 'Caleb', 'Caleigh', 'Cali', 'Calista',\r\n 'Callie', 'Camden', 'Cameron', 'Camila', 'Camilla', 'Camille', 'Camren',\r\n 'Camron', 'Camryn', 'Camylle', 'Candace', 'Candelario', 'Candice',\r\n 'Candida', 'Candido', 'Cara', 'Carey', 'Carissa', 'Carlee', 'Carleton',\r\n 'Carley', 'Carli', 'Carlie', 'Carlo', 'Carlos', 'Carlotta', 'Carmel',\r\n 'Carmela', 'Carmella', 'Carmelo', 'Carmen', 'Carmine', 'Carol', 'Carolanne',\r\n 'Carole', 'Carolina', 'Caroline', 'Carolyn', 'Carolyne', 'Carrie',\r\n 'Carroll', 'Carson', 'Carter', 'Cary', 'Casandra', 'Casey', 'Casimer',\r\n 'Casimir', 'Casper', 'Cassandra', 'Cassandre', 'Cassidy', 'Cassie',\r\n 'Catalina', 'Caterina', 'Catharine', 'Catherine', 'Cathrine', 'Cathryn',\r\n 'Cathy', 'Cayla', 'Ceasar', 'Cecelia', 'Cecil', 'Cecile', 'Cecilia',\r\n 'Cedrick', 'Celestine', 'Celestino', 'Celia', 'Celine', 'Cesar', 'Chad',\r\n 'Chadd', 'Chadrick', 'Chaim', 'Chance', 'Chandler', 'Chanel', 'Chanelle',\r\n 'Charity', 'Charlene', 'Charles', 'Charley', 'Charlie', 'Charlotte',\r\n 'Chase', 'Chasity', 'Chauncey', 'Chaya', 'Chaz', 'Chelsea', 'Chelsey',\r\n 'Chelsie', 'Chesley', 'Chester', 'Chet', 'Cheyanne', 'Cheyenne', 'Chloe',\r\n 'Chris', 'Christ', 'Christa', 'Christelle', 'Christian', 'Christiana',\r\n 'Christina', 'Christine', 'Christop', 'Christophe', 'Christopher',\r\n 'Christy', 'Chyna', 'Ciara', 'Cicero', 'Cielo', 'Cierra', 'Cindy',\r\n 'Citlalli', 'Clair', 'Claire', 'Clara', 'Clarabelle', 'Clare', 'Clarissa',\r\n 'Clark', 'Claud', 'Claude', 'Claudia', 'Claudie', 'Claudine', 'Clay',\r\n 'Clemens', 'Clement', 'Clementina', 'Clementine', 'Clemmie', 'Cleo',\r\n 'Cleora', 'Cleta', 'Cletus', 'Cleve', 'Cleveland', 'Clifford', 'Clifton',\r\n 'Clint', 'Clinton', 'Clotilde', 'Clovis', 'Cloyd', 'Clyde', 'Coby', 'Cody',\r\n 'Colby', 'Cole', 'Coleman', 'Colin', 'Colleen', 'Collin', 'Colt', 'Colten',\r\n 'Colton', 'Columbus', 'Concepcion', 'Conner', 'Connie', 'Connor', 'Conor',\r\n 'Conrad', 'Constance', 'Constantin', 'Consuelo', 'Cooper', 'Cora',\r\n 'Coralie', 'Corbin', 'Cordelia', 'Cordell', 'Cordia', 'Cordie', 'Corene',\r\n 'Corine', 'Cornelius', 'Cornell', 'Corrine', 'Cortez', 'Cortney', 'Cory',\r\n 'Coty', 'Courtney', 'Coy', 'Craig', 'Crawford', 'Creola', 'Cristal',\r\n 'Cristian', 'Cristina', 'Cristobal', 'Cristopher', 'Cruz', 'Crystal',\r\n 'Crystel', 'Cullen', 'Curt', 'Curtis', 'Cydney', 'Cynthia', 'Cyril',\r\n 'Cyrus', 'Dagmar', 'Dahlia', 'Daija', 'Daisha', 'Daisy', 'Dakota', 'Dale',\r\n 'Dallas', 'Dallin', 'Dalton', 'Damaris', 'Dameon', 'Damian', 'Damien',\r\n 'Damion', 'Damon', 'Dan', 'Dana', 'Dandre', 'Dane', 'D\\'angelo', 'Dangelo',\r\n 'Danial', 'Daniela', 'Daniella', 'Danielle', 'Danika', 'Dannie', 'Danny',\r\n 'Dante', 'Danyka', 'Daphne', 'Daphnee', 'Daphney', 'Darby', 'Daren',\r\n 'Darian', 'Dariana', 'Darien', 'Dario', 'Darion', 'Darius', 'Darlene',\r\n 'Daron', 'Darrel', 'Darrell', 'Darren', 'Darrick', 'Darrin', 'Darrion',\r\n 'Darron', 'Darryl', 'Darwin', 'Daryl', 'Dashawn', 'Dasia', 'Dave', 'David',\r\n 'Davin', 'Davion', 'Davon', 'Davonte', 'Dawn', 'Dawson', 'Dax', 'Dayana',\r\n 'Dayna', 'Dayne', 'Dayton', 'Dean', 'Deangelo', 'Deanna', 'Deborah',\r\n 'Declan', 'Dedric', 'Dedrick', 'Dee', 'Deion', 'Deja', 'Dejah', 'Dejon',\r\n 'Dejuan', 'Delaney', 'Delbert', 'Delfina', 'Delia', 'Delilah', 'Dell',\r\n 'Della', 'Delmer', 'Delores', 'Delpha', 'Delphia', 'Delphine', 'Delta',\r\n 'Demarco', 'Demarcus', 'Demario', 'Demetris', 'Demetrius', 'Demond', 'Dena',\r\n 'Denis', 'Dennis', 'Deon', 'Deondre', 'Deontae', 'Deonte', 'Dereck',\r\n 'Derek', 'Derick', 'Deron', 'Derrick', 'Deshaun', 'Deshawn', 'Desiree',\r\n 'Desmond', 'Dessie', 'Destany', 'Destin', 'Destinee', 'Destiney', 'Destini',\r\n 'Destiny', 'Devan', 'Devante', 'Deven', 'Devin', 'Devon', 'Devonte',\r\n 'Devyn', 'Dewayne', 'Dewitt', 'Dexter', 'Diamond', 'Diana', 'Dianna',\r\n 'Diego', 'Dillan', 'Dillon', 'Dimitri', 'Dina', 'Dino', 'Dion', 'Dixie',\r\n 'Dock', 'Dolly', 'Dolores', 'Domenic', 'Domenica', 'Domenick', 'Domenico',\r\n 'Domingo', 'Dominic', 'Dominique', 'Don', 'Donald', 'Donato', 'Donavon',\r\n 'Donna', 'Donnell', 'Donnie', 'Donny', 'Dora', 'Dorcas', 'Dorian', 'Doris',\r\n 'Dorothea', 'Dorothy', 'Dorris', 'Dortha', 'Dorthy', 'Doug', 'Douglas',\r\n 'Dovie', 'Doyle', 'Drake', 'Drew', 'Duane', 'Dudley', 'Dulce', 'Duncan',\r\n 'Durward', 'Dustin', 'Dusty', 'Dwight', 'Dylan', 'Earl', 'Earlene',\r\n 'Earline', 'Earnest', 'Earnestine', 'Easter', 'Easton', 'Ebba', 'Ebony',\r\n 'Ed', 'Eda', 'Edd', 'Eddie', 'Eden', 'Edgar', 'Edgardo', 'Edison', 'Edmond',\r\n 'Edmund', 'Edna', 'Eduardo', 'Edward', 'Edwardo', 'Edwin', 'Edwina',\r\n 'Edyth', 'Edythe', 'Effie', 'Efrain', 'Efren', 'Eileen', 'Einar', 'Eino',\r\n 'Eladio', 'Elaina', 'Elbert', 'Elda', 'Eldon', 'Eldora', 'Eldred',\r\n 'Eldridge', 'Eleanora', 'Eleanore', 'Eleazar', 'Electa', 'Elena', 'Elenor',\r\n 'Elenora', 'Eleonore', 'Elfrieda', 'Eli', 'Elian', 'Eliane', 'Elias',\r\n 'Eliezer', 'Elijah', 'Elinor', 'Elinore', 'Elisa', 'Elisabeth', 'Elise',\r\n 'Eliseo', 'Elisha', 'Elissa', 'Eliza', 'Elizabeth', 'Ella', 'Ellen',\r\n 'Ellie', 'Elliot', 'Elliott', 'Ellis', 'Ellsworth', 'Elmer', 'Elmira',\r\n 'Elmo', 'Elmore', 'Elna', 'Elnora', 'Elody', 'Eloisa', 'Eloise', 'Elouise',\r\n 'Eloy', 'Elroy', 'Elsa', 'Else', 'Elsie', 'Elta', 'Elton', 'Elva', 'Elvera',\r\n 'Elvie', 'Elvis', 'Elwin', 'Elwyn', 'Elyse', 'Elyssa', 'Elza', 'Emanuel',\r\n 'Emelia', 'Emelie', 'Emely', 'Emerald', 'Emerson', 'Emery', 'Emie', 'Emil',\r\n 'Emile', 'Emilia', 'Emiliano', 'Emilie', 'Emilio', 'Emily', 'Emma',\r\n 'Emmalee', 'Emmanuel', 'Emmanuelle', 'Emmet', 'Emmett', 'Emmie', 'Emmitt',\r\n 'Emmy', 'Emory', 'Ena', 'Enid', 'Enoch', 'Enola', 'Enos', 'Enrico',\r\n 'Enrique', 'Ephraim', 'Era', 'Eriberto', 'Eric', 'Erica', 'Erich', 'Erick',\r\n 'Ericka', 'Erik', 'Erika', 'Erin', 'Erling', 'Erna', 'Ernest', 'Ernestina',\r\n 'Ernestine', 'Ernesto', 'Ernie', 'Ervin', 'Erwin', 'Eryn', 'Esmeralda',\r\n 'Esperanza', 'Esta', 'Esteban', 'Estefania', 'Estel', 'Estell', 'Estella',\r\n 'Estelle', 'Estevan', 'Esther', 'Estrella', 'Etha', 'Ethan', 'Ethel',\r\n 'Ethelyn', 'Ethyl', 'Ettie', 'Eudora', 'Eugene', 'Eugenia', 'Eula', 'Eulah',\r\n 'Eulalia', 'Euna', 'Eunice', 'Eusebio', 'Eva', 'Evalyn', 'Evan',\r\n 'Evangeline', 'Evans', 'Eve', 'Eveline', 'Evelyn', 'Everardo', 'Everett',\r\n 'Everette', 'Evert', 'Evie', 'Ewald', 'Ewell', 'Ezekiel', 'Ezequiel',\r\n 'Ezra', 'Fabian', 'Fabiola', 'Fae', 'Fannie', 'Fanny', 'Fatima', 'Faustino',\r\n 'Fausto', 'Favian', 'Fay', 'Faye', 'Federico', 'Felicia', 'Felicita',\r\n 'Felicity', 'Felipa', 'Felipe', 'Felix', 'Felton', 'Fermin', 'Fern',\r\n 'Fernando', 'Ferne', 'Fidel', 'Filiberto', 'Filomena', 'Finn', 'Fiona',\r\n 'Flavie', 'Flavio', 'Fleta', 'Fletcher', 'Flo', 'Florence', 'Florencio',\r\n 'Florian', 'Florida', 'Florine', 'Flossie', 'Floy', 'Floyd', 'Ford',\r\n 'Forest', 'Forrest', 'Foster', 'Frances', 'Francesca', 'Francesco',\r\n 'Francis', 'Francisca', 'Francisco', 'Franco', 'Frank', 'Frankie', 'Franz',\r\n 'Fred', 'Freda', 'Freddie', 'Freddy', 'Frederic', 'Frederick', 'Frederik',\r\n 'Frederique', 'Fredrick', 'Fredy', 'Freeda', 'Freeman', 'Freida', 'Frida',\r\n 'Frieda', 'Friedrich', 'Fritz', 'Furman', 'Gabe', 'Gabriel', 'Gabriella',\r\n 'Gabrielle', 'Gaetano', 'Gage', 'Gail', 'Gardner', 'Garett', 'Garfield',\r\n 'Garland', 'Garnet', 'Garnett', 'Garret', 'Garrett', 'Garrick', 'Garrison',\r\n 'Garry', 'Garth', 'Gaston', 'Gavin', 'Gay', 'Gayle', 'Gaylord', 'Gene',\r\n 'General', 'Genesis', 'Genevieve', 'Gennaro', 'Genoveva', 'Geo', 'Geoffrey',\r\n 'George', 'Georgette', 'Georgiana', 'Georgianna', 'Geovanni', 'Geovanny',\r\n 'Geovany', 'Gerald', 'Geraldine', 'Gerard', 'Gerardo', 'Gerda', 'Gerhard',\r\n 'Germaine', 'German', 'Gerry', 'Gerson', 'Gertrude', 'Gia', 'Gianni',\r\n 'Gideon', 'Gilbert', 'Gilberto', 'Gilda', 'Giles', 'Gillian', 'Gina',\r\n 'Gino', 'Giovani', 'Giovanna', 'Giovanni', 'Giovanny', 'Gisselle',\r\n 'Giuseppe', 'Gladyce', 'Gladys', 'Glen', 'Glenda', 'Glenna', 'Glennie',\r\n 'Gloria', 'Godfrey', 'Golda', 'Golden', 'Gonzalo', 'Gordon', 'Grace',\r\n 'Gracie', 'Graciela', 'Grady', 'Graham', 'Grant', 'Granville', 'Grayce',\r\n 'Grayson', 'Green', 'Greg', 'Gregg', 'Gregoria', 'Gregorio', 'Gregory',\r\n 'Greta', 'Gretchen', 'Greyson', 'Griffin', 'Grover', 'Guadalupe', 'Gudrun',\r\n 'Guido', 'Guillermo', 'Guiseppe', 'Gunnar', 'Gunner', 'Gus', 'Gussie',\r\n 'Gust', 'Gustave', 'Guy', 'Gwen', 'Gwendolyn', 'Hadley', 'Hailee', 'Hailey',\r\n 'Hailie', 'Hal', 'Haleigh', 'Haley', 'Halie', 'Halle', 'Hallie', 'Hank',\r\n 'Hanna', 'Hannah', 'Hans', 'Hardy', 'Harley', 'Harmon', 'Harmony', 'Harold',\r\n 'Harrison', 'Harry', 'Harvey', 'Haskell', 'Hassan', 'Hassie', 'Hattie',\r\n 'Haven', 'Hayden', 'Haylee', 'Hayley', 'Haylie', 'Hazel', 'Hazle', 'Heath',\r\n 'Heather', 'Heaven', 'Heber', 'Hector', 'Heidi', 'Helen', 'Helena',\r\n 'Helene', 'Helga', 'Hellen', 'Helmer', 'Heloise', 'Henderson', 'Henri',\r\n 'Henriette', 'Henry', 'Herbert', 'Herman', 'Hermann', 'Hermina', 'Herminia',\r\n 'Herminio', 'Hershel', 'Herta', 'Hertha', 'Hester', 'Hettie', 'Hilario',\r\n 'Hilbert', 'Hilda', 'Hildegard', 'Hillard', 'Hillary', 'Hilma', 'Hilton',\r\n 'Hipolito', 'Hiram', 'Hobart', 'Holden', 'Hollie', 'Hollis', 'Holly',\r\n 'Hope', 'Horace', 'Horacio', 'Hortense', 'Hosea', 'Houston', 'Howard',\r\n 'Howell', 'Hoyt', 'Hubert', 'Hudson', 'Hugh', 'Hulda', 'Humberto', 'Hunter',\r\n 'Hyman', 'Ian', 'Ibrahim', 'Icie', 'Ida', 'Idell', 'Idella', 'Ignacio',\r\n 'Ignatius', 'Ike', 'Ila', 'Ilene', 'Iliana', 'Ima', 'Imani', 'Imelda',\r\n 'Immanuel', 'Imogene', 'Ines', 'Irma', 'Irving', 'Irwin', 'Isaac', 'Isabel',\r\n 'Isabell', 'Isabella', 'Isabelle', 'Isac', 'Isadore', 'Isai', 'Isaiah',\r\n 'Isaias', 'Isidro', 'Ismael', 'Isobel', 'Isom', 'Israel', 'Issac', 'Itzel',\r\n 'Iva', 'Ivah', 'Ivory', 'Ivy', 'Izabella', 'Izaiah', 'Jabari', 'Jace',\r\n 'Jacey', 'Jacinthe', 'Jacinto', 'Jack', 'Jackeline', 'Jackie', 'Jacklyn',\r\n 'Jackson', 'Jacky', 'Jaclyn', 'Jacquelyn', 'Jacques', 'Jacynthe', 'Jada',\r\n 'Jade', 'Jaden', 'Jadon', 'Jadyn', 'Jaeden', 'Jaida', 'Jaiden', 'Jailyn',\r\n 'Jaime', 'Jairo', 'Jakayla', 'Jake', 'Jakob', 'Jaleel', 'Jalen', 'Jalon',\r\n 'Jalyn', 'Jamaal', 'Jamal', 'Jamar', 'Jamarcus', 'Jamel', 'Jameson',\r\n 'Jamey', 'Jamie', 'Jamil', 'Jamir', 'Jamison', 'Jammie', 'Jan', 'Jana',\r\n 'Janae', 'Jane', 'Janelle', 'Janessa', 'Janet', 'Janice', 'Janick', 'Janie',\r\n 'Janis', 'Janiya', 'Jannie', 'Jany', 'Jaquan', 'Jaquelin', 'Jaqueline',\r\n 'Jared', 'Jaren', 'Jarod', 'Jaron', 'Jarred', 'Jarrell', 'Jarret',\r\n 'Jarrett', 'Jarrod', 'Jarvis', 'Jasen', 'Jasmin', 'Jason', 'Jasper',\r\n 'Jaunita', 'Javier', 'Javon', 'Javonte', 'Jay', 'Jayce', 'Jaycee', 'Jayda',\r\n 'Jayde', 'Jayden', 'Jaydon', 'Jaylan', 'Jaylen', 'Jaylin', 'Jaylon',\r\n 'Jayme', 'Jayne', 'Jayson', 'Jazlyn', 'Jazmin', 'Jazmyn', 'Jazmyne', 'Jean',\r\n 'Jeanette', 'Jeanie', 'Jeanne', 'Jed', 'Jedediah', 'Jedidiah', 'Jeff',\r\n 'Jefferey', 'Jeffery', 'Jeffrey', 'Jeffry', 'Jena', 'Jenifer', 'Jennie',\r\n 'Jennifer', 'Jennings', 'Jennyfer', 'Jensen', 'Jerad', 'Jerald', 'Jeramie',\r\n 'Jeramy', 'Jerel', 'Jeremie', 'Jeremy', 'Jermain', 'Jermaine', 'Jermey',\r\n 'Jerod', 'Jerome', 'Jeromy', 'Jerrell', 'Jerrod', 'Jerrold', 'Jerry',\r\n 'Jess', 'Jesse', 'Jessica', 'Jessie', 'Jessika', 'Jessy', 'Jessyca',\r\n 'Jesus', 'Jett', 'Jettie', 'Jevon', 'Jewel', 'Jewell', 'Jillian', 'Jimmie',\r\n 'Jimmy', 'Jo', 'Joan', 'Joana', 'Joanie', 'Joanne', 'Joannie', 'Joanny',\r\n 'Joany', 'Joaquin', 'Jocelyn', 'Jodie', 'Jody', 'Joe', 'Joel', 'Joelle',\r\n 'Joesph', 'Joey', 'Johan', 'Johann', 'Johanna', 'Johathan', 'John',\r\n 'Johnathan', 'Johnathon', 'Johnnie', 'Johnny', 'Johnpaul', 'Johnson',\r\n 'Jolie', 'Jon', 'Jonas', 'Jonatan', 'Jonathan', 'Jonathon', 'Jordan',\r\n 'Jordane', 'Jordi', 'Jordon', 'Jordy', 'Jordyn', 'Jorge', 'Jose', 'Josefa',\r\n 'Josefina', 'Joseph', 'Josephine', 'Josh', 'Joshua', 'Joshuah', 'Josiah',\r\n 'Josiane', 'Josianne', 'Josie', 'Josue', 'Jovan', 'Jovani', 'Jovanny',\r\n 'Jovany', 'Joy', 'Joyce', 'Juana', 'Juanita', 'Judah', 'Judd', 'Jude',\r\n 'Judge', 'Judson', 'Judy', 'Jules', 'Julia', 'Julian', 'Juliana',\r\n 'Julianne', 'Julie', 'Julien', 'Juliet', 'Julio', 'Julius', 'June',\r\n 'Junior', 'Junius', 'Justen', 'Justice', 'Justina', 'Justine', 'Juston',\r\n 'Justus', 'Justyn', 'Juvenal', 'Juwan', 'Kacey', 'Kaci', 'Kacie', 'Kade',\r\n 'Kaden', 'Kadin', 'Kaela', 'Kaelyn', 'Kaia', 'Kailee', 'Kailey', 'Kailyn',\r\n 'Kaitlin', 'Kaitlyn', 'Kale', 'Kaleb', 'Kaleigh', 'Kaley', 'Kali', 'Kallie',\r\n 'Kameron', 'Kamille', 'Kamren', 'Kamron', 'Kamryn', 'Kane', 'Kara',\r\n 'Kareem', 'Karelle', 'Karen', 'Kari', 'Kariane', 'Karianne', 'Karina',\r\n 'Karine', 'Karl', 'Karlee', 'Karley', 'Karli', 'Karlie', 'Karolann',\r\n 'Karson', 'Kasandra', 'Kasey', 'Kassandra', 'Katarina', 'Katelin',\r\n 'Katelyn', 'Katelynn', 'Katharina', 'Katherine', 'Katheryn', 'Kathleen',\r\n 'Kathlyn', 'Kathryn', 'Kathryne', 'Katlyn', 'Katlynn', 'Katrina', 'Katrine',\r\n 'Kattie', 'Kavon', 'Kay', 'Kaya', 'Kaycee', 'Kayden', 'Kayla', 'Kaylah',\r\n 'Kaylee', 'Kayleigh', 'Kayley', 'Kayli', 'Kaylie', 'Kaylin', 'Keagan',\r\n 'Keanu', 'Keara', 'Keaton', 'Keegan', 'Keeley', 'Keely', 'Keenan', 'Keira',\r\n 'Keith', 'Kellen', 'Kelley', 'Kelli', 'Kellie', 'Kelly', 'Kelsi', 'Kelsie',\r\n 'Kelton', 'Kelvin', 'Ken', 'Kendall', 'Kendra', 'Kendrick', 'Kenna',\r\n 'Kennedi', 'Kennedy', 'Kenneth', 'Kennith', 'Kenny', 'Kenton', 'Kenya',\r\n 'Kenyatta', 'Kenyon', 'Keon', 'Keshaun', 'Keshawn', 'Keven', 'Kevin',\r\n 'Kevon', 'Keyon', 'Keyshawn', 'Khalid', 'Khalil', 'Kian', 'Kiana', 'Kianna',\r\n 'Kiara', 'Kiarra', 'Kiel', 'Kiera', 'Kieran', 'Kiley', 'Kim', 'Kimberly',\r\n 'King', 'Kip', 'Kira', 'Kirk', 'Kirsten', 'Kirstin', 'Kitty', 'Kobe',\r\n 'Koby', 'Kody', 'Kolby', 'Kole', 'Korbin', 'Korey', 'Kory', 'Kraig', 'Kris',\r\n 'Krista', 'Kristian', 'Kristin', 'Kristina', 'Kristofer', 'Kristoffer',\r\n 'Kristopher', 'Kristy', 'Krystal', 'Krystel', 'Krystina', 'Kurt', 'Kurtis',\r\n 'Kyla', 'Kyle', 'Kylee', 'Kyleigh', 'Kyler', 'Kylie', 'Kyra', 'Lacey',\r\n 'Lacy', 'Ladarius', 'Lafayette', 'Laila', 'Laisha', 'Lamar', 'Lambert',\r\n 'Lamont', 'Lance', 'Landen', 'Lane', 'Laney', 'Larissa', 'Laron', 'Larry',\r\n 'Larue', 'Laura', 'Laurel', 'Lauren', 'Laurence', 'Lauretta', 'Lauriane',\r\n 'Laurianne', 'Laurie', 'Laurine', 'Laury', 'Lauryn', 'Lavada', 'Lavern',\r\n 'Laverna', 'Laverne', 'Lavina', 'Lavinia', 'Lavon', 'Lavonne', 'Lawrence',\r\n 'Lawson', 'Layla', 'Layne', 'Lazaro', 'Lea', 'Leann', 'Leanna', 'Leanne',\r\n 'Leatha', 'Leda', 'Lee', 'Leif', 'Leila', 'Leilani', 'Lela', 'Lelah',\r\n 'Leland', 'Lelia', 'Lempi', 'Lemuel', 'Lenna', 'Lennie', 'Lenny', 'Lenora',\r\n 'Lenore', 'Leo', 'Leola', 'Leon', 'Leonard', 'Leonardo', 'Leone', 'Leonel',\r\n 'Leonie', 'Leonor', 'Leonora', 'Leopold', 'Leopoldo', 'Leora', 'Lera',\r\n 'Lesley', 'Leslie', 'Lesly', 'Lessie', 'Lester', 'Leta', 'Letha', 'Letitia',\r\n 'Levi', 'Lew', 'Lewis', 'Lexi', 'Lexie', 'Lexus', 'Lia', 'Liam', 'Liana',\r\n 'Libbie', 'Libby', 'Lila', 'Lilian', 'Liliana', 'Liliane', 'Lilla',\r\n 'Lillian', 'Lilliana', 'Lillie', 'Lilly', 'Lily', 'Lilyan', 'Lina',\r\n 'Lincoln', 'Linda', 'Lindsay', 'Lindsey', 'Linnea', 'Linnie', 'Linwood',\r\n 'Lionel', 'Lisa', 'Lisandro', 'Lisette', 'Litzy', 'Liza', 'Lizeth',\r\n 'Lizzie', 'Llewellyn', 'Lloyd', 'Logan', 'Lois', 'Lola', 'Lolita', 'Loma',\r\n 'Lon', 'London', 'Lonie', 'Lonnie', 'Lonny', 'Lonzo', 'Lora', 'Loraine',\r\n 'Loren', 'Lorena', 'Lorenz', 'Lorenza', 'Lorenzo', 'Lori', 'Lorine',\r\n 'Lorna', 'Lottie', 'Lou', 'Louie', 'Louisa', 'Lourdes', 'Louvenia',\r\n 'Lowell', 'Loy', 'Loyal', 'Loyce', 'Lucas', 'Luciano', 'Lucie', 'Lucienne',\r\n 'Lucile', 'Lucinda', 'Lucio', 'Lucious', 'Lucius', 'Lucy', 'Ludie',\r\n 'Ludwig', 'Lue', 'Luella', 'Luigi', 'Luis', 'Luisa', 'Lukas', 'Lula',\r\n 'Lulu', 'Luna', 'Lupe', 'Lura', 'Lurline', 'Luther', 'Luz', 'Lyda', 'Lydia',\r\n 'Lyla', 'Lynn', 'Lyric', 'Lysanne', 'Mabel', 'Mabelle', 'Mable', 'Mac',\r\n 'Macey', 'Maci', 'Macie', 'Mack', 'Mackenzie', 'Macy', 'Madaline',\r\n 'Madalyn', 'Maddison', 'Madeline', 'Madelyn', 'Madelynn', 'Madge', 'Madie',\r\n 'Madilyn', 'Madisen', 'Madison', 'Madisyn', 'Madonna', 'Madyson', 'Mae',\r\n 'Maegan', 'Maeve', 'Mafalda', 'Magali', 'Magdalen', 'Magdalena', 'Maggie',\r\n 'Magnolia', 'Magnus', 'Maia', 'Maida', 'Maiya', 'Major', 'Makayla',\r\n 'Makenna', 'Makenzie', 'Malachi', 'Malcolm', 'Malika', 'Malinda', 'Mallie',\r\n 'Mallory', 'Malvina', 'Mandy', 'Manley', 'Manuel', 'Manuela', 'Mara',\r\n 'Marc', 'Marcel', 'Marcelina', 'Marcelino', 'Marcella', 'Marcelle',\r\n 'Marcellus', 'Marcelo', 'Marcia', 'Marco', 'Marcos', 'Marcus', 'Margaret',\r\n 'Margarete', 'Margarett', 'Margaretta', 'Margarette', 'Margarita', 'Marge',\r\n 'Margie', 'Margot', 'Margret', 'Marguerite', 'Maria', 'Mariah', 'Mariam',\r\n 'Marian', 'Mariana', 'Mariane', 'Marianna', 'Marianne', 'Mariano',\r\n 'Maribel', 'Marie', 'Mariela', 'Marielle', 'Marietta', 'Marilie', 'Marilou',\r\n 'Marilyne', 'Marina', 'Mario', 'Marion', 'Marisa', 'Marisol', 'Maritza',\r\n 'Marjolaine', 'Marjorie', 'Marjory', 'Mark', 'Markus', 'Marlee', 'Marlen',\r\n 'Marlene', 'Marley', 'Marlin', 'Marlon', 'Marques', 'Marquis', 'Marquise',\r\n 'Marshall', 'Marta', 'Martin', 'Martina', 'Martine', 'Marty', 'Marvin',\r\n 'Mary', 'Maryam', 'Maryjane', 'Maryse', 'Mason', 'Mateo', 'Mathew',\r\n 'Mathias', 'Mathilde', 'Matilda', 'Matilde', 'Matt', 'Matteo', 'Mattie',\r\n 'Maud', 'Maude', 'Maudie', 'Maureen', 'Maurice', 'Mauricio', 'Maurine',\r\n 'Maverick', 'Mavis', 'Max', 'Maxie', 'Maxime', 'Maximilian', 'Maximillia',\r\n 'Maximillian', 'Maximo', 'Maximus', 'Maxine', 'Maxwell', 'May', 'Maya',\r\n 'Maybell', 'Maybelle', 'Maye', 'Maymie', 'Maynard', 'Mayra', 'Mazie',\r\n 'Mckayla', 'Mckenna', 'Mckenzie', 'Meagan', 'Meaghan', 'Meda', 'Megane',\r\n 'Meggie', 'Meghan', 'Mekhi', 'Melany', 'Melba', 'Melisa', 'Melissa',\r\n 'Mellie', 'Melody', 'Melvin', 'Melvina', 'Melyna', 'Melyssa', 'Mercedes',\r\n 'Meredith', 'Merl', 'Merle', 'Merlin', 'Merritt', 'Mertie', 'Mervin',\r\n 'Meta', 'Mia', 'Micaela', 'Micah', 'Michael', 'Michaela', 'Michale',\r\n 'Micheal', 'Michel', 'Michele', 'Michelle', 'Miguel', 'Mikayla', 'Mike',\r\n 'Mikel', 'Milan', 'Miles', 'Milford', 'Miller', 'Millie', 'Milo', 'Milton',\r\n 'Mina', 'Minerva', 'Minnie', 'Miracle', 'Mireille', 'Mireya', 'Misael',\r\n 'Missouri', 'Misty', 'Mitchel', 'Mitchell', 'Mittie', 'Modesta', 'Modesto',\r\n 'Mohamed', 'Mohammad', 'Mohammed', 'Moises', 'Mollie', 'Molly', 'Mona',\r\n 'Monica', 'Monique', 'Monroe', 'Monserrat', 'Monserrate', 'Montana',\r\n 'Monte', 'Monty', 'Morgan', 'Moriah', 'Morris', 'Mortimer', 'Morton',\r\n 'Mose', 'Moses', 'Moshe', 'Mossie', 'Mozell', 'Mozelle', 'Muhammad',\r\n 'Muriel', 'Murl', 'Murphy', 'Murray', 'Mustafa', 'Mya', 'Myah', 'Mylene',\r\n 'Myles', 'Myra', 'Myriam', 'Myrl', 'Myrna', 'Myron', 'Myrtice', 'Myrtie',\r\n 'Myrtis', 'Myrtle', 'Nadia', 'Nakia', 'Name', 'Nannie', 'Naomi', 'Naomie',\r\n 'Napoleon', 'Narciso', 'Nash', 'Nasir', 'Nat', 'Natalia', 'Natalie',\r\n 'Natasha', 'Nathan', 'Nathanael', 'Nathanial', 'Nathaniel', 'Nathen',\r\n 'Nayeli', 'Neal', 'Ned', 'Nedra', 'Neha', 'Neil', 'Nelda', 'Nella', 'Nelle',\r\n 'Nellie', 'Nels', 'Nelson', 'Neoma', 'Nestor', 'Nettie', 'Neva', 'Newell',\r\n 'Newton', 'Nia', 'Nicholas', 'Nicholaus', 'Nichole', 'Nick', 'Nicklaus',\r\n 'Nickolas', 'Nico', 'Nicola', 'Nicolas', 'Nicole', 'Nicolette', 'Nigel',\r\n 'Nikita', 'Nikki', 'Nikko', 'Niko', 'Nikolas', 'Nils', 'Nina', 'Noah',\r\n 'Noble', 'Noe', 'Noel', 'Noelia', 'Noemi', 'Noemie', 'Noemy', 'Nola',\r\n 'Nolan', 'Nona', 'Nora', 'Norbert', 'Norberto', 'Norene', 'Norma', 'Norris',\r\n 'Norval', 'Norwood', 'Nova', 'Novella', 'Nya', 'Nyah', 'Nyasia', 'Obie',\r\n 'Oceane', 'Ocie', 'Octavia', 'Oda', 'Odell', 'Odessa', 'Odie', 'Ofelia',\r\n 'Okey', 'Ola', 'Olaf', 'Ole', 'Olen', 'Oleta', 'Olga', 'Olin', 'Oliver',\r\n 'Ollie', 'Oma', 'Omari', 'Omer', 'Ona', 'Onie', 'Opal', 'Ophelia', 'Ora',\r\n 'Oral', 'Oran', 'Oren', 'Orie', 'Orin', 'Orion', 'Orland', 'Orlando',\r\n 'Orlo', 'Orpha', 'Orrin', 'Orval', 'Orville', 'Osbaldo', 'Osborne', 'Oscar',\r\n 'Osvaldo', 'Oswald', 'Oswaldo', 'Otha', 'Otho', 'Otilia', 'Otis', 'Ottilie',\r\n 'Ottis', 'Otto', 'Ova', 'Owen', 'Ozella', 'Pablo', 'Paige', 'Palma',\r\n 'Pamela', 'Pansy', 'Paolo', 'Paris', 'Parker', 'Pascale', 'Pasquale', 'Pat',\r\n 'Patience', 'Patricia', 'Patrick', 'Patsy', 'Pattie', 'Paul', 'Paula',\r\n 'Pauline', 'Paxton', 'Payton', 'Pearl', 'Pearlie', 'Pearline', 'Pedro',\r\n 'Peggie', 'Penelope', 'Percival', 'Percy', 'Perry', 'Pete', 'Peter',\r\n 'Petra', 'Peyton', 'Philip', 'Phoebe', 'Phyllis', 'Pierce', 'Pierre',\r\n 'Pietro', 'Pink', 'Pinkie', 'Piper', 'Polly', 'Porter', 'Precious',\r\n 'Presley', 'Preston', 'Price', 'Prince', 'Princess', 'Priscilla',\r\n 'Providenci', 'Prudence', 'Queen', 'Queenie', 'Quentin', 'Quincy', 'Quinn',\r\n 'Quinten', 'Quinton', 'Rachael', 'Rachel', 'Rachelle', 'Rae', 'Raegan',\r\n 'Rafael', 'Rafaela', 'Raheem', 'Rahsaan', 'Rahul', 'Raina', 'Raleigh',\r\n 'Ralph', 'Ramiro', 'Ramon', 'Ramona', 'Randal', 'Randall', 'Randi', 'Randy',\r\n 'Ransom', 'Raoul', 'Raphael', 'Raphaelle', 'Raquel', 'Rashad', 'Rashawn',\r\n 'Rasheed', 'Raul', 'Raven', 'Ray', 'Raymond', 'Raymundo', 'Reagan',\r\n 'Reanna', 'Reba', 'Rebeca', 'Rebecca', 'Rebeka', 'Rebekah', 'Reece', 'Reed',\r\n 'Reese', 'Regan', 'Reggie', 'Reginald', 'Reid', 'Reilly', 'Reina',\r\n 'Reinhold', 'Remington', 'Rene', 'Renee', 'Ressie', 'Reta', 'Retha',\r\n 'Retta', 'Reuben', 'Reva', 'Rex', 'Rey', 'Reyes', 'Reymundo', 'Reyna',\r\n 'Reynold', 'Rhea', 'Rhett', 'Rhianna', 'Rhiannon', 'Rhoda', 'Ricardo',\r\n 'Richard', 'Richie', 'Richmond', 'Rick', 'Rickey', 'Rickie', 'Ricky',\r\n 'Rico', 'Rigoberto', 'Riley', 'Rita', 'River', 'Robb', 'Robbie', 'Robert',\r\n 'Roberta', 'Roberto', 'Robin', 'Robyn', 'Rocio', 'Rocky', 'Rod', 'Roderick',\r\n 'Rodger', 'Rodolfo', 'Rodrick', 'Rodrigo', 'Roel', 'Rogelio', 'Roger',\r\n 'Rogers', 'Rolando', 'Rollin', 'Roma', 'Romaine', 'Roman', 'Ron', 'Ronaldo',\r\n 'Ronny', 'Roosevelt', 'Rory', 'Rosa', 'Rosalee', 'Rosalia', 'Rosalind',\r\n 'Rosalinda', 'Rosalyn', 'Rosamond', 'Rosanna', 'Rosario', 'Roscoe', 'Rose',\r\n 'Rosella', 'Roselyn', 'Rosemarie', 'Rosemary', 'Rosendo', 'Rosetta',\r\n 'Rosie', 'Rosina', 'Roslyn', 'Ross', 'Rossie', 'Rowan', 'Rowena', 'Rowland',\r\n 'Roxane', 'Roxanne', 'Roy', 'Royal', 'Royce', 'Rozella', 'Ruben', 'Rubie',\r\n 'Ruby', 'Rubye', 'Rudolph', 'Rudy', 'Rupert', 'Russ', 'Russel', 'Russell',\r\n 'Rusty', 'Ruth', 'Ruthe', 'Ruthie', 'Ryan', 'Ryann', 'Ryder', 'Rylan',\r\n 'Rylee', 'Ryleigh', 'Ryley', 'Sabina', 'Sabrina', 'Sabryna', 'Sadie',\r\n 'Sadye', 'Sage', 'Saige', 'Sallie', 'Sally', 'Salma', 'Salvador',\r\n 'Salvatore', 'Sam', 'Samanta', 'Samantha', 'Samara', 'Samir', 'Sammie',\r\n 'Sammy', 'Samson', 'Sandra', 'Sandrine', 'Sandy', 'Sanford', 'Santa',\r\n 'Santiago', 'Santina', 'Santino', 'Santos', 'Sarah', 'Sarai', 'Sarina',\r\n 'Sasha', 'Saul', 'Savanah', 'Savanna', 'Savannah', 'Savion', 'Scarlett',\r\n 'Schuyler', 'Scot', 'Scottie', 'Scotty', 'Seamus', 'Sean', 'Sebastian',\r\n 'Sedrick', 'Selena', 'Selina', 'Selmer', 'Serena', 'Serenity', 'Seth',\r\n 'Shad', 'Shaina', 'Shakira', 'Shana', 'Shane', 'Shanel', 'Shanelle',\r\n 'Shania', 'Shanie', 'Shaniya', 'Shanna', 'Shannon', 'Shanny', 'Shanon',\r\n 'Shany', 'Sharon', 'Shaun', 'Shawn', 'Shawna', 'Shaylee', 'Shayna',\r\n 'Shayne', 'Shea', 'Sheila', 'Sheldon', 'Shemar', 'Sheridan', 'Sherman',\r\n 'Sherwood', 'Shirley', 'Shyann', 'Shyanne', 'Sibyl', 'Sid', 'Sidney',\r\n 'Sienna', 'Sierra', 'Sigmund', 'Sigrid', 'Sigurd', 'Silas', 'Sim', 'Simeon',\r\n 'Simone', 'Sincere', 'Sister', 'Skye', 'Skyla', 'Skylar', 'Sofia',\r\n 'Soledad', 'Solon', 'Sonia', 'Sonny', 'Sonya', 'Sophia', 'Sophie',\r\n 'Spencer', 'Stacey', 'Stacy', 'Stan', 'Stanford', 'Stanley', 'Stanton',\r\n 'Stefan', 'Stefanie', 'Stella', 'Stephan', 'Stephania', 'Stephanie',\r\n 'Stephany', 'Stephen', 'Stephon', 'Sterling', 'Steve', 'Stevie', 'Stewart',\r\n 'Stone', 'Stuart', 'Summer', 'Sunny', 'Susan', 'Susana', 'Susanna', 'Susie',\r\n 'Suzanne', 'Sven', 'Syble', 'Sydnee', 'Sydney', 'Sydni', 'Sydnie', 'Sylvan',\r\n 'Sylvester', 'Sylvia', 'Tabitha', 'Tad', 'Talia', 'Talon', 'Tamara',\r\n 'Tamia', 'Tania', 'Tanner', 'Tanya', 'Tara', 'Taryn', 'Tate', 'Tatum',\r\n 'Tatyana', 'Taurean', 'Tavares', 'Taya', 'Taylor', 'Teagan', 'Ted', 'Telly',\r\n 'Terence', 'Teresa', 'Terrance', 'Terrell', 'Terrence', 'Terrill', 'Terry',\r\n 'Tess', 'Tessie', 'Tevin', 'Thad', 'Thaddeus', 'Thalia', 'Thea', 'Thelma',\r\n 'Theo', 'Theodora', 'Theodore', 'Theresa', 'Therese', 'Theresia', 'Theron',\r\n 'Thomas', 'Thora', 'Thurman', 'Tia', 'Tiana', 'Tianna', 'Tiara', 'Tierra',\r\n 'Tiffany', 'Tillman', 'Timmothy', 'Timmy', 'Timothy', 'Tina', 'Tito',\r\n 'Titus', 'Tobin', 'Toby', 'Tod', 'Tom', 'Tomas', 'Tomasa', 'Tommie',\r\n 'Toney', 'Toni', 'Tony', 'Torey', 'Torrance', 'Torrey', 'Toy', 'Trace',\r\n 'Tracey', 'Tracy', 'Travis', 'Travon', 'Tre', 'Tremaine', 'Tremayne',\r\n 'Trent', 'Trenton', 'Tressa', 'Tressie', 'Treva', 'Trever', 'Trevion',\r\n 'Trevor', 'Trey', 'Trinity', 'Trisha', 'Tristian', 'Tristin', 'Triston',\r\n 'Troy', 'Trudie', 'Trycia', 'Trystan', 'Turner', 'Twila', 'Tyler', 'Tyra',\r\n 'Tyree', 'Tyreek', 'Tyrel', 'Tyrell', 'Tyrese', 'Tyrique', 'Tyshawn',\r\n 'Tyson', 'Ubaldo', 'Ulices', 'Ulises', 'Una', 'Unique', 'Urban', 'Uriah',\r\n 'Uriel', 'Ursula', 'Vada', 'Valentin', 'Valentina', 'Valentine', 'Valerie',\r\n 'Vallie', 'Van', 'Vance', 'Vanessa', 'Vaughn', 'Veda', 'Velda', 'Vella',\r\n 'Velma', 'Velva', 'Vena', 'Verda', 'Verdie', 'Vergie', 'Verla', 'Verlie',\r\n 'Vern', 'Verna', 'Verner', 'Vernice', 'Vernie', 'Vernon', 'Verona',\r\n 'Veronica', 'Vesta', 'Vicenta', 'Vicente', 'Vickie', 'Vicky', 'Victor',\r\n 'Victoria', 'Vida', 'Vidal', 'Vilma', 'Vince', 'Vincent', 'Vincenza',\r\n 'Vincenzo', 'Vinnie', 'Viola', 'Violet', 'Violette', 'Virgie', 'Virgil',\r\n 'Virginia', 'Virginie', 'Vita', 'Vito', 'Viva', 'Vivian', 'Viviane',\r\n 'Vivianne', 'Vivien', 'Vivienne', 'Vladimir', 'Wade', 'Waino', 'Waldo',\r\n 'Walker', 'Wallace', 'Walter', 'Walton', 'Wanda', 'Ward', 'Warren',\r\n 'Watson', 'Wava', 'Waylon', 'Wayne', 'Webster', 'Weldon', 'Wellington',\r\n 'Wendell', 'Wendy', 'Werner', 'Westley', 'Weston', 'Whitney', 'Wilber',\r\n 'Wilbert', 'Wilburn', 'Wiley', 'Wilford', 'Wilfred', 'Wilfredo', 'Wilfrid',\r\n 'Wilhelm', 'Wilhelmine', 'Will', 'Willa', 'Willard', 'William', 'Willie',\r\n 'Willis', 'Willow', 'Willy', 'Wilma', 'Wilmer', 'Wilson', 'Wilton',\r\n 'Winfield', 'Winifred', 'Winnifred', 'Winona', 'Winston', 'Woodrow',\r\n 'Wyatt', 'Wyman', 'Xander', 'Xavier', 'Xzavier', 'Yadira', 'Yasmeen',\r\n 'Yasmin', 'Yasmine', 'Yazmin', 'Yesenia', 'Yessenia', 'Yolanda', 'Yoshiko',\r\n 'Yvette', 'Yvonne', 'Zachariah', 'Zachary', 'Zachery', 'Zack', 'Zackary',\r\n 'Zackery', 'Zakary', 'Zander', 'Zane', 'Zaria', 'Zechariah', 'Zelda',\r\n 'Zella', 'Zelma', 'Zena', 'Zetta', 'Zion', 'Zita', 'Zoe', 'Zoey', 'Zoie',\r\n 'Zoila', 'Zola', 'Zora', 'Zula'\r\n];\r\n\r\n/**\r\n * Generate random username.\r\n * @returns {string} random username\r\n */\r\nfunction generateUsername() {\r\n const name = RandomUtil.randomElement(names);\r\n const suffix = RandomUtil.randomAlphanumStr(3);\r\n\r\n return `${name}-${suffix}`;\r\n}\r\n\r\nmodule.exports = {\r\n generateUsername\r\n};\r\n","/* Copyright @ 2016-present 8x8, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nvar Logger = require('./Logger.js');\n\n/**\n * Creates new LogCollector. Class implements LoggerTransport\n * and thus can be added as global transport in order to capture all the logs.\n *\n * It captures subsequent log lines created whenever Logger logs\n * a message and stores them in a queue in order to batch log entries. There are\n * time and size limit constraints which determine how often batch entries are\n * stored. Whenever one of these limits is exceeded the LogCollector\n * will use the logStorage object given as an argument to save\n * the batch log entry.\n *\n * @param {Object} logStorage an object which allows to store the logs collected\n * @param {function(string|object[])} logStorage.storeLogs a method called when\n * this LogCollector requests log entry storage. The method's argument\n * is an array which can contain strings and objects. If given\n * item is an object it means that it's an aggregated message. That is a message\n * which is the same as the previous one and it's representation has\n * the following format:\n * {\n * {string} text: 'the text of some duplicated message'\n * {number} count: 3 // how many times the message appeared in a row\n * }\n * If a message \"B\" after an aggregated message \"A\" is different, then it breaks\n * the sequence of \"A\". Which means that even if the next message \"C\" is\n * the same as \"A\" it will start a new aggregated message \"C\".\n * @param {function()} logStorage.isReady a method which should return\n * a boolean to tell the collector that it's ready to store. During the\n * time storage is not ready log batches will be cached and stored on the next\n * occasion (flush or interval timeout).\n *\n * @param {Object} options the LogCollector configuration options.\n * @param {number} options.maxEntryLength the size limit for a single log entry\n * to be stored. The LogCollector will push the entry as soon as it\n * reaches or exceeds this limit given that logStorage.isReady\n * returns true. Otherwise the log entry will be cached until the log\n * storage becomes ready. Note that the \"is ready\" condition is checked every\n * options.storeInterval milliseconds.\n * @param {number} options.storeInterval how often the logs should be stored in\n * case maxEntryLength was not exceeded.\n * @param {boolean} options.stringifyObjects indicates whether or not object\n * arguments should be \"stringified\" with JSON.stringify when a log\n * message is composed. Note that objects logged on the error log level are\n * always stringified.\n *\n * @constructor\n */\nfunction LogCollector(logStorage, options) {\n this.logStorage = logStorage;\n this.stringifyObjects = options && options.stringifyObjects ? options.stringifyObjects : false;\n this.storeInterval = options && options.storeInterval ? options.storeInterval: 30000;\n this.maxEntryLength = options && options.maxEntryLength ? options.maxEntryLength : 10000;\n // Bind the log method for each level to the corresponding method name\n // in order to implement \"global log transport\" object.\n Object.keys(Logger.levels).forEach(\n function (logLevel) {\n var methodName = Logger.levels[logLevel];\n this[methodName] = function () {\n this._log.apply(this, arguments);\n }.bind(this, logLevel);\n }.bind(this));\n /**\n * The ID of store logs interval if one is currently scheduled or\n * null otherwise.\n * @type {number|null}\n */\n this.storeLogsIntervalID = null;\n /**\n * The log messages that are to be batched into log entry when\n * {@link LogCollector._flush} method is called.\n * @type {string[]}\n */\n this.queue = [];\n /**\n * The total length of all messages currently stored in the {@link queue}.\n * @type {number}\n */\n this.totalLen = 0;\n /**\n * An array used to temporarily store log batches, before the storage gets\n * ready.\n * @type {string[]}\n */\n this.outputCache = [];\n}\n\n/**\n * Method called inside of {@link formatLogMessage} in order to covert an\n * Object argument to string. The conversion will happen when either\n * 'stringifyObjects' option is enabled or on the {@link Logger.levels.ERROR}\n * log level. The default implementation uses JSON.stringify and\n * returns \"[object with circular refs?]\" instead of an object if it fails.\n *\n * @param {object} someObject the object to be stringified.\n *\n * @return {string} the result of JSON.stringify or\n * \"[object with circular refs?]\" if any error occurs during \"stringification\".\n *\n * @protected\n */\nLogCollector.prototype.stringify = function (someObject) {\n try {\n return JSON.stringify(someObject);\n } catch (error) {\n return '[object with circular refs?]';\n }\n};\n\n/**\n * Formats log entry for the given logging level and arguments passed to the\n * Logger's log method. The first argument is log level and the next\n * arguments have to be captured using JS built-in 'arguments' variable.\n *\n * @param {Logger.levels} logLevel provides the logging level of the message to\n * be logged.\n * @param {Date} timestamp - The {@code Date} when a message has been logged.\n *\n * @return {string|null} a non-empty string representation of the log entry\n * crafted from the log arguments. If the return value is null then\n * the message wil be discarded by this LogCollector.\n *\n * @protected\n */\nLogCollector.prototype.formatLogMessage = function (\nlogLevel /* timestamp, arg2, arg3, arg4... */) {\n var msg = '';\n for (var i = 1, len = arguments.length; i < len; i++) {\n var arg = arguments[i];\n // objects logged on error level are always converted to JSON\n if ((this.stringifyObjects || logLevel === Logger.levels.ERROR) &&\n typeof arg === 'object') {\n arg = this.stringify(arg);\n }\n msg += arg;\n if (i !== len - 1) {\n msg += ' ';\n }\n }\n return msg.length ? msg : null;\n};\n\n/**\n * The log method bound to each of the logging levels in order to implement\n * \"global log transport\" object.\n *\n * @private\n */\nLogCollector.prototype._log = function() {\n\n // var logLevel = arguments[0]; first argument is the log level\n var timestamp = arguments[1];\n var msg = this.formatLogMessage.apply(this, arguments);\n if (msg) {\n // The same as the previous message aggregation logic\n var prevMessage = this.queue[this.queue.length - 1];\n var prevMessageText = prevMessage && prevMessage.text;\n if (prevMessageText === msg) {\n prevMessage.count += 1;\n } else {\n this.queue.push({\n text: msg,\n timestamp: timestamp,\n count: 1\n });\n this.totalLen += msg.length;\n }\n }\n\n if (this.totalLen >= this.maxEntryLength) {\n this._flush(true /* force */, true /* reschedule */);\n }\n};\n\n/**\n * Starts periodical \"store logs\" task which will be triggered at the interval\n * specified in the constructor options.\n */\nLogCollector.prototype.start = function () {\n this._reschedulePublishInterval();\n};\n\n/**\n * Reschedules the periodical \"store logs\" task which will store the next batch\n * log entry in the storage.\n * @private\n */\nLogCollector.prototype._reschedulePublishInterval = function () {\n if (this.storeLogsIntervalID) {\n window.clearTimeout(this.storeLogsIntervalID);\n this.storeLogsIntervalID = null;\n }\n // It's actually a timeout, because it is rescheduled on every flush\n this.storeLogsIntervalID = window.setTimeout(\n this._flush.bind(\n this, false /* do not force */, true /* reschedule */),\n this.storeInterval);\n};\n\n/**\n * Call this method to flush the log entry buffer and store it in the log\n * storage immediately (given that the storage is ready).\n */\nLogCollector.prototype.flush = function() {\n this._flush(\n false /* do not force, as it will not be stored anyway */,\n true /* reschedule next update */ );\n};\n\n/**\n * Stores the next batch log entry in the log storage.\n * @param {boolean} force enforce current logs batch to be stored or cached if\n * there is anything to be logged, but the storage is not ready yet. One of\n * legitimate reasons to force is when the logs length exceeds size limit which\n * could result in truncation.\n * @param {boolean} reschedule true if the next periodic task should be\n * scheduled after the log entry is stored. false will end the periodic\n * task cycle.\n * @private\n */\nLogCollector.prototype._flush = function(force, reschedule) {\n // Publish only if there's anything to be logged\n if (this.totalLen > 0 && (this.logStorage.isReady() || force)) {\n //FIXME avoid truncating\n // right now we don't care if the message size is \"slightly\" exceeded\n if (this.logStorage.isReady()) {\n // Sends all cached logs\n if (this.outputCache.length) {\n this.outputCache.forEach(\n function (cachedQueue) {\n this.logStorage.storeLogs(cachedQueue);\n }.bind(this)\n );\n // Clear the cache\n this.outputCache = [];\n }\n // Send current batch\n this.logStorage.storeLogs(this.queue);\n } else {\n this.outputCache.push(this.queue);\n }\n\n this.queue = [];\n this.totalLen = 0;\n }\n\n if (reschedule) {\n this._reschedulePublishInterval();\n }\n};\n\n/**\n * Stops the periodical \"store logs\" task and immediately stores any pending\n * log entries as a batch.\n */\nLogCollector.prototype.stop = function() {\n // Flush and stop publishing logs\n this._flush(false /* do not force */, false /* do not reschedule */);\n};\n\nmodule.exports = LogCollector;\n","/* Copyright @ 2015-present 8x8, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/*jslint latedef:false*/\n\n/**\n * Ordered log levels.\n */\nvar levels = {\n \"trace\": 0,\n \"debug\": 1,\n \"info\": 2,\n \"log\": 3,\n \"warn\": 4,\n \"error\": 5\n};\n\n/**\n * The default transport - console\n * @type LoggerTransport\n */\nLogger.consoleTransport = console;\n\n/**\n * The array which stores currently registered global transports.\n * @type {[LoggerTransport]}\n */\nvar globalTransports = [ Logger.consoleTransport ];\n\n/**\n * Adds given {@link LoggerTransport} instance to the list of global\n * transports which means that it'll be used by all {@link Logger}s\n * @param {LoggerTransport} transport\n */\nLogger.addGlobalTransport = function(transport) {\n if (globalTransports.indexOf(transport) === -1) {\n globalTransports.push(transport);\n }\n};\n\n/**\n * Removes given {@link LoggerTransport} instance from the list of global\n * transports\n * @param {LoggerTransport} transport\n */\nLogger.removeGlobalTransport = function(transport) {\n var transportIdx = globalTransports.indexOf(transport);\n if (transportIdx !== -1) {\n globalTransports.splice(transportIdx, 1);\n }\n};\n\n/**\n * The global configuration options.\n */\nvar globalOptions = {};\n\n/**\n * Sets global options which will be used by all loggers. Changing these works\n * even after other loggers are created.\n */\nLogger.setGlobalOptions = function(options) {\n globalOptions = options || {};\n};\n\n/**\n * Parses Error's object stack trace and extracts information about the last\n * caller before the log method was called.\n * @returns JS object with info about the caller - method name, file location,\n * line and column.\n */\nfunction getCallerInfo() {\n var callerInfo = {\n methodName: \"\",\n fileLocation: \"\",\n line: null,\n column: null\n };\n //gets the part of the stack without the logger wrappers\n var error = new Error();\n var stack = error.stack? error.stack.split(\"\\n\") : [];\n if(!stack || stack.length < 3) {\n return callerInfo;\n }\n var m = null;\n if(stack[3]) {\n m = stack[3].match(/\\s*at\\s*(.+?)\\s*\\((\\S*)\\s*:(\\d*)\\s*:(\\d*)\\)/);\n }\n if(!m || m.length <= 4) {\n //Firefox && Safari\n if(stack[2].indexOf(\"log@\") === 0){\n //Safari\n callerInfo.methodName = stack[3].substr(0, stack[3].indexOf(\"@\"));\n } else {\n //Firefox\n callerInfo.methodName = stack[2].substr(0, stack[2].indexOf(\"@\"));\n }\n return callerInfo;\n }\n\n callerInfo.methodName = m[1];\n callerInfo.fileLocation = m[2];\n callerInfo.line = m[3];\n callerInfo.column = m[4];\n return callerInfo;\n}\n\n/**\n * Logs messages using the transports and level from the logger.\n * @param logger a logger instance.\n * @param level the log level of the message. See the levels variable.\n * @param arguments array with arguments that will be logged.\n */\nfunction log() {\n var logger = arguments[0], level = arguments[1],\n args = Array.prototype.slice.call(arguments, 2);\n if(levels[level] < logger.level) {\n return;\n }\n\n var callerInfo\n = !(logger.options.disableCallerInfo || globalOptions.disableCallerInfo) &&\n getCallerInfo();\n var transports = globalTransports.concat(logger.transports);\n for(var i = 0; i < transports.length; i++) {\n var t = transports[i];\n var l = t[level];\n if(l && typeof(l) === \"function\") {\n var logPrefixes = [];\n\n logPrefixes.push(new Date().toISOString());\n\n if (logger.id) {\n logPrefixes.push(\"[\" + logger.id + \"]\");\n }\n\n if (callerInfo && callerInfo.methodName.length > 1) {\n logPrefixes.push(\"<\" + callerInfo.methodName + \">: \");\n }\n\n var fullLogParts = logPrefixes.concat(args);\n\n l.bind(t).apply(t, fullLogParts);\n }\n }\n}\n\n/**\n *\n * Constructs new logger object.\n * @param level the logging level for the new logger\n * @param id optional identifier for the logger instance.\n * @param {LoggerTransport} transports optional list of handlers(objects) for\n * the logs. The handlers must support - log, warn, error, debug, info, trace.\n * @param options optional configuration file for how the logger should behave.\n * @param {boolean} options.disableCallerInfo Whether the call site of a logger\n * method invocation should be included in the log. Defaults to false, so the\n * call site will be included.\n */\nfunction Logger(level, id, transports, options) {\n this.id = id;\n this.options = options || {};\n this.transports = transports;\n if(!this.transports) {\n this.transports = [];\n }\n this.level = levels[level];\n var methods = Object.keys(levels);\n for(var i = 0; i < methods.length; i++){\n this[methods[i]] =\n log.bind(null, this, methods[i]);\n }\n}\n\n/**\n * Sets the log level for the logger.\n * @param level the new log level.\n */\nLogger.prototype.setLevel = function (level) {\n this.level = levels[level];\n};\nmodule.exports = Logger;\n\n/**\n * Enum for the supported log levels.\n */\nLogger.levels = {\n TRACE: \"trace\",\n DEBUG: \"debug\",\n INFO: \"info\",\n LOG: \"log\",\n WARN: \"warn\",\n ERROR: \"error\"\n};\n","/* Copyright @ 2015-present 8x8, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nvar Logger = require(\"./Logger\");\nvar LogCollector = require(\"./LogCollector\");\n\n/**\n * Definition of the log method\n * @name log_method\n * @function\n * @param {...*} log_args the arguments to be logged\n */\n/**\n * The logger's transport type definition.\n *\n * @typedef {object} LoggerTransport\n *\n * @property {log_method} trace method called to log on {@link Logger.levels.TRACE} logging level\n * @property {log_method} debug method called to log on {@link Logger.levels.DEBUG} logging level\n * @property {log_method} info method called to log on {@link Logger.levels.INFO} logging level\n * @property {log_method} log method called to log on {@link Logger.levels.LOG} logging level\n * @property {log_method} warn method called to log on {@link Logger.levels.WARN} logging level\n * @property {log_method} error method called to log on {@link Logger.levels.ERROR} logging level\n */\n\n/**\n * Map with the created loggers with ID.\n */\nvar idLoggers = {};\n\n/**\n * Array with the loggers without id.\n */\nvar loggers = [];\n\n/**\n * Log level for the lbrary.\n */\nvar curLevel = Logger.levels.TRACE;\n\n\nmodule.exports = {\n /**\n * Adds given {@link LoggerTransport} instance to the list of global\n * transports which means that it'll be used by all {@link Logger}s\n * @param {LoggerTransport} transport\n */\n addGlobalTransport: function(transport) {\n Logger.addGlobalTransport(transport);\n },\n /**\n * Removes given {@link LoggerTransport} instance from the list of global\n * transports\n * @param {LoggerTransport} transport\n */\n removeGlobalTransport: function(transport) {\n Logger.removeGlobalTransport(transport);\n },\n /**\n * Sets global options which will be used by all loggers. Changing these\n * works even after other loggers are created.\n */\n setGlobalOptions: function(options) {\n Logger.setGlobalOptions(options);\n },\n /**\n * Creates new logger.\n * @arguments the same as Logger constructor\n */\n getLogger: function(id, transports, options) {\n var logger = new Logger(curLevel, id, transports, options);\n if(id) {\n idLoggers[id] = idLoggers[id] || [];\n idLoggers[id].push(logger);\n } else {\n loggers.push(logger);\n }\n return logger;\n },\n /**\n * Changes the log level for the existing loggers by id.\n * @param level the new log level.\n * @param id if specified the level will be changed only for loggers with the\n * same id. Otherwise the operation will affect all loggers that don't\n * have id.\n */\n setLogLevelById: function(level, id) {\n var l = id? (idLoggers[id] || []) : loggers;\n for(var i = 0; i < l.length; i++) {\n l[i].setLevel(level);\n }\n },\n /**\n * Changes the log level for all existing loggers.\n * @param level the new log level.\n */\n setLogLevel: function (level) {\n curLevel = level;\n var i = 0;\n for(; i < loggers.length; i++) {\n loggers[i].setLevel(level);\n }\n\n for(var id in idLoggers) {\n var l = idLoggers[id] || [];\n for(i = 0; i < l.length; i++) {\n l[i].setLevel(level);\n }\n }\n },\n /**\n * The supported log levels.\n */\n levels: Logger.levels,\n /**\n * Exports the LogCollector.\n */\n LogCollector: LogCollector\n};\n","var grammar = module.exports = {\n v: [{\n name: 'version',\n reg: /^(\\d*)$/\n }],\n o: [{\n // o=- 20518 0 IN IP4 203.0.113.1\n // NB: sessionId will be a String in most cases because it is huge\n name: 'origin',\n reg: /^(\\S*) (\\d*) (\\d*) (\\S*) IP(\\d) (\\S*)/,\n names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],\n format: '%s %s %d %s IP%d %s'\n }],\n // default parsing of these only (though some of these feel outdated)\n s: [{ name: 'name' }],\n i: [{ name: 'description' }],\n u: [{ name: 'uri' }],\n e: [{ name: 'email' }],\n p: [{ name: 'phone' }],\n z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly...\n r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly\n // k: [{}], // outdated thing ignored\n t: [{\n // t=0 0\n name: 'timing',\n reg: /^(\\d*) (\\d*)/,\n names: ['start', 'stop'],\n format: '%d %d'\n }],\n c: [{\n // c=IN IP4 10.47.197.26\n name: 'connection',\n reg: /^IN IP(\\d) (\\S*)/,\n names: ['version', 'ip'],\n format: 'IN IP%d %s'\n }],\n b: [{\n // b=AS:4000\n push: 'bandwidth',\n reg: /^(TIAS|AS|CT|RR|RS):(\\d*)/,\n names: ['type', 'limit'],\n format: '%s:%s'\n }],\n m: [{\n // m=video 51744 RTP/AVP 126 97 98 34 31\n // NB: special - pushes to session\n // TODO: rtp/fmtp should be filtered by the payloads found here?\n reg: /^(\\w*) (\\d*) ([\\w/]*)(?: (.*))?/,\n names: ['type', 'port', 'protocol', 'payloads'],\n format: '%s %d %s %s'\n }],\n a: [\n {\n // a=rtpmap:110 opus/48000/2\n push: 'rtp',\n reg: /^rtpmap:(\\d*) ([\\w\\-.]*)(?:\\s*\\/(\\d*)(?:\\s*\\/(\\S*))?)?/,\n names: ['payload', 'codec', 'rate', 'encoding'],\n format: function (o) {\n return (o.encoding)\n ? 'rtpmap:%d %s/%s/%s'\n : o.rate\n ? 'rtpmap:%d %s/%s'\n : 'rtpmap:%d %s';\n }\n },\n {\n // a=fmtp:108 profile-level-id=24;object=23;bitrate=64000\n // a=fmtp:111 minptime=10; useinbandfec=1\n push: 'fmtp',\n reg: /^fmtp:(\\d*) ([\\S| ]*)/,\n names: ['payload', 'config'],\n format: 'fmtp:%d %s'\n },\n {\n // a=control:streamid=0\n name: 'control',\n reg: /^control:(.*)/,\n format: 'control:%s'\n },\n {\n // a=rtcp:65179 IN IP4 193.84.77.194\n name: 'rtcp',\n reg: /^rtcp:(\\d*)(?: (\\S*) IP(\\d) (\\S*))?/,\n names: ['port', 'netType', 'ipVer', 'address'],\n format: function (o) {\n return (o.address != null)\n ? 'rtcp:%d %s IP%d %s'\n : 'rtcp:%d';\n }\n },\n {\n // a=rtcp-fb:98 trr-int 100\n push: 'rtcpFbTrrInt',\n reg: /^rtcp-fb:(\\*|\\d*) trr-int (\\d*)/,\n names: ['payload', 'value'],\n format: 'rtcp-fb:%s trr-int %d'\n },\n {\n // a=rtcp-fb:98 nack rpsi\n push: 'rtcpFb',\n reg: /^rtcp-fb:(\\*|\\d*) ([\\w-_]*)(?: ([\\w-_]*))?/,\n names: ['payload', 'type', 'subtype'],\n format: function (o) {\n return (o.subtype != null)\n ? 'rtcp-fb:%s %s %s'\n : 'rtcp-fb:%s %s';\n }\n },\n {\n // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\n // a=extmap:1/recvonly URI-gps-string\n // a=extmap:3 urn:ietf:params:rtp-hdrext:encrypt urn:ietf:params:rtp-hdrext:smpte-tc 25@600/24\n push: 'ext',\n reg: /^extmap:(\\d+)(?:\\/(\\w+))?(?: (urn:ietf:params:rtp-hdrext:encrypt))? (\\S*)(?: (\\S*))?/,\n names: ['value', 'direction', 'encrypt-uri', 'uri', 'config'],\n format: function (o) {\n return (\n 'extmap:%d' +\n (o.direction ? '/%s' : '%v') +\n (o['encrypt-uri'] ? ' %s' : '%v') +\n ' %s' +\n (o.config ? ' %s' : '')\n );\n }\n },\n {\n // a=extmap-allow-mixed\n name: 'extmapAllowMixed',\n reg: /^(extmap-allow-mixed)/\n },\n {\n // a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32\n push: 'crypto',\n reg: /^crypto:(\\d*) ([\\w_]*) (\\S*)(?: (\\S*))?/,\n names: ['id', 'suite', 'config', 'sessionConfig'],\n format: function (o) {\n return (o.sessionConfig != null)\n ? 'crypto:%d %s %s %s'\n : 'crypto:%d %s %s';\n }\n },\n {\n // a=setup:actpass\n name: 'setup',\n reg: /^setup:(\\w*)/,\n format: 'setup:%s'\n },\n {\n // a=connection:new\n name: 'connectionType',\n reg: /^connection:(new|existing)/,\n format: 'connection:%s'\n },\n {\n // a=mid:1\n name: 'mid',\n reg: /^mid:([^\\s]*)/,\n format: 'mid:%s'\n },\n {\n // a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a\n name: 'msid',\n reg: /^msid:(.*)/,\n format: 'msid:%s'\n },\n {\n // a=ptime:20\n name: 'ptime',\n reg: /^ptime:(\\d*(?:\\.\\d*)*)/,\n format: 'ptime:%d'\n },\n {\n // a=maxptime:60\n name: 'maxptime',\n reg: /^maxptime:(\\d*(?:\\.\\d*)*)/,\n format: 'maxptime:%d'\n },\n {\n // a=sendrecv\n name: 'direction',\n reg: /^(sendrecv|recvonly|sendonly|inactive)/\n },\n {\n // a=ice-lite\n name: 'icelite',\n reg: /^(ice-lite)/\n },\n {\n // a=ice-ufrag:F7gI\n name: 'iceUfrag',\n reg: /^ice-ufrag:(\\S*)/,\n format: 'ice-ufrag:%s'\n },\n {\n // a=ice-pwd:x9cml/YzichV2+XlhiMu8g\n name: 'icePwd',\n reg: /^ice-pwd:(\\S*)/,\n format: 'ice-pwd:%s'\n },\n {\n // a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33\n name: 'fingerprint',\n reg: /^fingerprint:(\\S*) (\\S*)/,\n names: ['type', 'hash'],\n format: 'fingerprint:%s %s'\n },\n {\n // a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\n // a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10\n // a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 network-id 3 network-cost 10\n // a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10\n // a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 network-id 3 network-cost 10\n push:'candidates',\n reg: /^candidate:(\\S*) (\\d*) (\\S*) (\\d*) (\\S*) (\\d*) typ (\\S*)(?: raddr (\\S*) rport (\\d*))?(?: tcptype (\\S*))?(?: generation (\\d*))?(?: network-id (\\d*))?(?: network-cost (\\d*))?/,\n names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'],\n format: function (o) {\n var str = 'candidate:%s %d %s %d %s %d typ %s';\n\n str += (o.raddr != null) ? ' raddr %s rport %d' : '%v%v';\n\n // NB: candidate has three optional chunks, so %void middles one if it's missing\n str += (o.tcptype != null) ? ' tcptype %s' : '%v';\n\n if (o.generation != null) {\n str += ' generation %d';\n }\n\n str += (o['network-id'] != null) ? ' network-id %d' : '%v';\n str += (o['network-cost'] != null) ? ' network-cost %d' : '%v';\n return str;\n }\n },\n {\n // a=end-of-candidates (keep after the candidates line for readability)\n name: 'endOfCandidates',\n reg: /^(end-of-candidates)/\n },\n {\n // a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...\n name: 'remoteCandidates',\n reg: /^remote-candidates:(.*)/,\n format: 'remote-candidates:%s'\n },\n {\n // a=ice-options:google-ice\n name: 'iceOptions',\n reg: /^ice-options:(\\S*)/,\n format: 'ice-options:%s'\n },\n {\n // a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1\n push: 'ssrcs',\n reg: /^ssrc:(\\d*) ([\\w_-]*)(?::(.*))?/,\n names: ['id', 'attribute', 'value'],\n format: function (o) {\n var str = 'ssrc:%d';\n if (o.attribute != null) {\n str += ' %s';\n if (o.value != null) {\n str += ':%s';\n }\n }\n return str;\n }\n },\n {\n // a=ssrc-group:FEC 1 2\n // a=ssrc-group:FEC-FR 3004364195 1080772241\n push: 'ssrcGroups',\n // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E\n reg: /^ssrc-group:([\\x21\\x23\\x24\\x25\\x26\\x27\\x2A\\x2B\\x2D\\x2E\\w]*) (.*)/,\n names: ['semantics', 'ssrcs'],\n format: 'ssrc-group:%s %s'\n },\n {\n // a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV\n name: 'msidSemantic',\n reg: /^msid-semantic:\\s?(\\w*) (\\S*)/,\n names: ['semantic', 'token'],\n format: 'msid-semantic: %s %s' // space after ':' is not accidental\n },\n {\n // a=group:BUNDLE audio video\n push: 'groups',\n reg: /^group:(\\w*) (.*)/,\n names: ['type', 'mids'],\n format: 'group:%s %s'\n },\n {\n // a=rtcp-mux\n name: 'rtcpMux',\n reg: /^(rtcp-mux)/\n },\n {\n // a=rtcp-rsize\n name: 'rtcpRsize',\n reg: /^(rtcp-rsize)/\n },\n {\n // a=sctpmap:5000 webrtc-datachannel 1024\n name: 'sctpmap',\n reg: /^sctpmap:([\\w_/]*) (\\S*)(?: (\\S*))?/,\n names: ['sctpmapNumber', 'app', 'maxMessageSize'],\n format: function (o) {\n return (o.maxMessageSize != null)\n ? 'sctpmap:%s %s %s'\n : 'sctpmap:%s %s';\n }\n },\n {\n // a=x-google-flag:conference\n name: 'xGoogleFlag',\n reg: /^x-google-flag:([^\\s]*)/,\n format: 'x-google-flag:%s'\n },\n {\n // a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0\n push: 'rids',\n reg: /^rid:([\\d\\w]+) (\\w+)(?: ([\\S| ]*))?/,\n names: ['id', 'direction', 'params'],\n format: function (o) {\n return (o.params) ? 'rid:%s %s %s' : 'rid:%s %s';\n }\n },\n {\n // a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]\n // a=imageattr:* send [x=800,y=640] recv *\n // a=imageattr:100 recv [x=320,y=240]\n push: 'imageattrs',\n reg: new RegExp(\n // a=imageattr:97\n '^imageattr:(\\\\d+|\\\\*)' +\n // send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320]\n '[\\\\s\\\\t]+(send|recv)[\\\\s\\\\t]+(\\\\*|\\\\[\\\\S+\\\\](?:[\\\\s\\\\t]+\\\\[\\\\S+\\\\])*)' +\n // recv [x=330,y=250]\n '(?:[\\\\s\\\\t]+(recv|send)[\\\\s\\\\t]+(\\\\*|\\\\[\\\\S+\\\\](?:[\\\\s\\\\t]+\\\\[\\\\S+\\\\])*))?'\n ),\n names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'],\n format: function (o) {\n return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : '');\n }\n },\n {\n // a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8\n // a=simulcast:recv 1;4,5 send 6;7\n name: 'simulcast',\n reg: new RegExp(\n // a=simulcast:\n '^simulcast:' +\n // send 1,2,3;~4,~5\n '(send|recv) ([a-zA-Z0-9\\\\-_~;,]+)' +\n // space + recv 6;~7,~8\n '(?:\\\\s?(send|recv) ([a-zA-Z0-9\\\\-_~;,]+))?' +\n // end\n '$'\n ),\n names: ['dir1', 'list1', 'dir2', 'list2'],\n format: function (o) {\n return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : '');\n }\n },\n {\n // old simulcast draft 03 (implemented by Firefox)\n // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03\n // a=simulcast: recv pt=97;98 send pt=97\n // a=simulcast: send rid=5;6;7 paused=6,7\n name: 'simulcast_03',\n reg: /^simulcast:[\\s\\t]+([\\S+\\s\\t]+)$/,\n names: ['value'],\n format: 'simulcast: %s'\n },\n {\n // a=framerate:25\n // a=framerate:29.97\n name: 'framerate',\n reg: /^framerate:(\\d+(?:$|\\.\\d+))/,\n format: 'framerate:%s'\n },\n {\n // RFC4570\n // a=source-filter: incl IN IP4 239.5.2.31 10.1.15.5\n name: 'sourceFilter',\n reg: /^source-filter: *(excl|incl) (\\S*) (IP4|IP6|\\*) (\\S*) (.*)/,\n names: ['filterMode', 'netType', 'addressTypes', 'destAddress', 'srcList'],\n format: 'source-filter: %s %s %s %s %s'\n },\n {\n // a=bundle-only\n name: 'bundleOnly',\n reg: /^(bundle-only)/\n },\n {\n // a=label:1\n name: 'label',\n reg: /^label:(.+)/,\n format: 'label:%s'\n },\n {\n // RFC version 26 for SCTP over DTLS\n // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-5\n name: 'sctpPort',\n reg: /^sctp-port:(\\d+)$/,\n format: 'sctp-port:%s'\n },\n {\n // RFC version 26 for SCTP over DTLS\n // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-26#section-6\n name: 'maxMessageSize',\n reg: /^max-message-size:(\\d+)$/,\n format: 'max-message-size:%s'\n },\n {\n // RFC7273\n // a=ts-refclk:ptp=IEEE1588-2008:39-A7-94-FF-FE-07-CB-D0:37\n push:'tsRefClocks',\n reg: /^ts-refclk:([^\\s=]*)(?:=(\\S*))?/,\n names: ['clksrc', 'clksrcExt'],\n format: function (o) {\n return 'ts-refclk:%s' + (o.clksrcExt != null ? '=%s' : '');\n }\n },\n {\n // RFC7273\n // a=mediaclk:direct=963214424\n name:'mediaClk',\n reg: /^mediaclk:(?:id=(\\S*))? *([^\\s=]*)(?:=(\\S*))?(?: *rate=(\\d+)\\/(\\d+))?/,\n names: ['id', 'mediaClockName', 'mediaClockValue', 'rateNumerator', 'rateDenominator'],\n format: function (o) {\n var str = 'mediaclk:';\n str += (o.id != null ? 'id=%s %s' : '%v%s');\n str += (o.mediaClockValue != null ? '=%s' : '');\n str += (o.rateNumerator != null ? ' rate=%s' : '');\n str += (o.rateDenominator != null ? '/%s' : '');\n return str;\n }\n },\n {\n // a=keywds:keywords\n name: 'keywords',\n reg: /^keywds:(.+)$/,\n format: 'keywds:%s'\n },\n {\n // a=content:main\n name: 'content',\n reg: /^content:(.+)/,\n format: 'content:%s'\n },\n // BFCP https://tools.ietf.org/html/rfc4583\n {\n // a=floorctrl:c-s\n name: 'bfcpFloorCtrl',\n reg: /^floorctrl:(c-only|s-only|c-s)/,\n format: 'floorctrl:%s'\n },\n {\n // a=confid:1\n name: 'bfcpConfId',\n reg: /^confid:(\\d+)/,\n format: 'confid:%s'\n },\n {\n // a=userid:1\n name: 'bfcpUserId',\n reg: /^userid:(\\d+)/,\n format: 'userid:%s'\n },\n {\n // a=floorid:1\n name: 'bfcpFloorId',\n reg: /^floorid:(.+) (?:m-stream|mstrm):(.+)/,\n names: ['id', 'mStream'],\n format: 'floorid:%s mstrm:%s'\n },\n {\n // any a= that we don't understand is kept verbatim on media.invalid\n push: 'invalid',\n names: ['value']\n }\n ]\n};\n\n// set sensible defaults to avoid polluting the grammar with boring details\nObject.keys(grammar).forEach(function (key) {\n var objs = grammar[key];\n objs.forEach(function (obj) {\n if (!obj.reg) {\n obj.reg = /(.*)/;\n }\n if (!obj.format) {\n obj.format = '%s';\n }\n });\n});\n","var parser = require('./parser');\nvar writer = require('./writer');\n\nexports.write = writer;\nexports.parse = parser.parse;\nexports.parseParams = parser.parseParams;\nexports.parseFmtpConfig = parser.parseFmtpConfig; // Alias of parseParams().\nexports.parsePayloads = parser.parsePayloads;\nexports.parseRemoteCandidates = parser.parseRemoteCandidates;\nexports.parseImageAttributes = parser.parseImageAttributes;\nexports.parseSimulcastStreamList = parser.parseSimulcastStreamList;\n","var toIntIfInt = function (v) {\n return String(Number(v)) === v ? Number(v) : v;\n};\n\nvar attachProperties = function (match, location, names, rawName) {\n if (rawName && !names) {\n location[rawName] = toIntIfInt(match[1]);\n }\n else {\n for (var i = 0; i < names.length; i += 1) {\n if (match[i+1] != null) {\n location[names[i]] = toIntIfInt(match[i+1]);\n }\n }\n }\n};\n\nvar parseReg = function (obj, location, content) {\n var needsBlank = obj.name && obj.names;\n if (obj.push && !location[obj.push]) {\n location[obj.push] = [];\n }\n else if (needsBlank && !location[obj.name]) {\n location[obj.name] = {};\n }\n var keyLocation = obj.push ?\n {} : // blank object that will be pushed\n needsBlank ? location[obj.name] : location; // otherwise, named location or root\n\n attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);\n\n if (obj.push) {\n location[obj.push].push(keyLocation);\n }\n};\n\nvar grammar = require('./grammar');\nvar validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);\n\nexports.parse = function (sdp) {\n var session = {}\n , media = []\n , location = session; // points at where properties go under (one of the above)\n\n // parse lines we understand\n sdp.split(/(\\r\\n|\\r|\\n)/).filter(validLine).forEach(function (l) {\n var type = l[0];\n var content = l.slice(2);\n if (type === 'm') {\n media.push({rtp: [], fmtp: []});\n location = media[media.length-1]; // point at latest media line\n }\n\n for (var j = 0; j < (grammar[type] || []).length; j += 1) {\n var obj = grammar[type][j];\n if (obj.reg.test(content)) {\n return parseReg(obj, location, content);\n }\n }\n });\n\n session.media = media; // link it up\n return session;\n};\n\nvar paramReducer = function (acc, expr) {\n var s = expr.split(/=(.+)/, 2);\n if (s.length === 2) {\n acc[s[0]] = toIntIfInt(s[1]);\n } else if (s.length === 1 && expr.length > 1) {\n acc[s[0]] = undefined;\n }\n return acc;\n};\n\nexports.parseParams = function (str) {\n return str.split(/;\\s?/).reduce(paramReducer, {});\n};\n\n// For backward compatibility - alias will be removed in 3.0.0\nexports.parseFmtpConfig = exports.parseParams;\n\nexports.parsePayloads = function (str) {\n return str.toString().split(' ').map(Number);\n};\n\nexports.parseRemoteCandidates = function (str) {\n var candidates = [];\n var parts = str.split(' ').map(toIntIfInt);\n for (var i = 0; i < parts.length; i += 3) {\n candidates.push({\n component: parts[i],\n ip: parts[i + 1],\n port: parts[i + 2]\n });\n }\n return candidates;\n};\n\nexports.parseImageAttributes = function (str) {\n return str.split(' ').map(function (item) {\n return item.substring(1, item.length-1).split(',').reduce(paramReducer, {});\n });\n};\n\nexports.parseSimulcastStreamList = function (str) {\n return str.split(';').map(function (stream) {\n return stream.split(',').map(function (format) {\n var scid, paused = false;\n\n if (format[0] !== '~') {\n scid = toIntIfInt(format);\n } else {\n scid = toIntIfInt(format.substring(1, format.length));\n paused = true;\n }\n\n return {\n scid: scid,\n paused: paused\n };\n });\n });\n};\n","var grammar = require('./grammar');\n\n// customized util.format - discards excess arguments and can void middle ones\nvar formatRegExp = /%[sdv%]/g;\nvar format = function (formatStr) {\n var i = 1;\n var args = arguments;\n var len = args.length;\n return formatStr.replace(formatRegExp, function (x) {\n if (i >= len) {\n return x; // missing argument\n }\n var arg = args[i];\n i += 1;\n switch (x) {\n case '%%':\n return '%';\n case '%s':\n return String(arg);\n case '%d':\n return Number(arg);\n case '%v':\n return '';\n }\n });\n // NB: we discard excess arguments - they are typically undefined from makeLine\n};\n\nvar makeLine = function (type, obj, location) {\n var str = obj.format instanceof Function ?\n (obj.format(obj.push ? location : location[obj.name])) :\n obj.format;\n\n var args = [type + '=' + str];\n if (obj.names) {\n for (var i = 0; i < obj.names.length; i += 1) {\n var n = obj.names[i];\n if (obj.name) {\n args.push(location[obj.name][n]);\n }\n else { // for mLine and push attributes\n args.push(location[obj.names[i]]);\n }\n }\n }\n else {\n args.push(location[obj.name]);\n }\n return format.apply(null, args);\n};\n\n// RFC specified order\n// TODO: extend this with all the rest\nvar defaultOuterOrder = [\n 'v', 'o', 's', 'i',\n 'u', 'e', 'p', 'c',\n 'b', 't', 'r', 'z', 'a'\n];\nvar defaultInnerOrder = ['i', 'c', 'b', 'a'];\n\n\nmodule.exports = function (session, opts) {\n opts = opts || {};\n // ensure certain properties exist\n if (session.version == null) {\n session.version = 0; // 'v=0' must be there (only defined version atm)\n }\n if (session.name == null) {\n session.name = ' '; // 's= ' must be there if no meaningful name set\n }\n session.media.forEach(function (mLine) {\n if (mLine.payloads == null) {\n mLine.payloads = '';\n }\n });\n\n var outerOrder = opts.outerOrder || defaultOuterOrder;\n var innerOrder = opts.innerOrder || defaultInnerOrder;\n var sdp = [];\n\n // loop through outerOrder for matching properties on session\n outerOrder.forEach(function (type) {\n grammar[type].forEach(function (obj) {\n if (obj.name in session && session[obj.name] != null) {\n sdp.push(makeLine(type, obj, session));\n }\n else if (obj.push in session && session[obj.push] != null) {\n session[obj.push].forEach(function (el) {\n sdp.push(makeLine(type, obj, el));\n });\n }\n });\n });\n\n // then for each media line, follow the innerOrder\n session.media.forEach(function (mLine) {\n sdp.push(makeLine('m', grammar.m[0], mLine));\n\n innerOrder.forEach(function (type) {\n grammar[type].forEach(function (obj) {\n if (obj.name in mLine && mLine[obj.name] != null) {\n sdp.push(makeLine(type, obj, mLine));\n }\n else if (obj.push in mLine && mLine[obj.push] != null) {\n mLine[obj.push].forEach(function (el) {\n sdp.push(makeLine(type, obj, el));\n });\n }\n });\n });\n });\n\n return sdp.join('\\r\\n') + '\\r\\n';\n};\n","/* Copyright @ 2016 Atlassian Pty Ltd\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nvar transform = require('sdp-transform');\nvar transformUtils = require('./transform-utils');\nvar parseSsrcs = transformUtils.parseSsrcs;\nvar writeSsrcs = transformUtils.writeSsrcs;\n\n//region Constants\n\nvar DEFAULT_NUM_OF_LAYERS = 3;\n\n//endregion\n\nfunction getSsrcAttribute (mLine, ssrc, attributeName) {\n return mLine\n .ssrcs\n .filter(function(ssrcInfo) { return ssrcInfo.id === ssrc; })\n .filter(function(ssrcInfo) { return ssrcInfo.attribute === attributeName; })\n .map(function(ssrcInfo) { return ssrcInfo.value; })[0];\n}\n\n//region Ctor\n\nfunction Simulcast(options) {\n\n this.options = options ? options : {};\n\n if (!this.options.numOfLayers) {\n this.options.numOfLayers = DEFAULT_NUM_OF_LAYERS;\n }\n console.log(\"SdpSimulcast: using \" + this.options.numOfLayers + \" layers\");\n\n /**\n * An IN-ORDER list of the simulcast ssrcs\n * @type {list}\n */\n this.ssrcCache = [];\n}\n\n//endregion\n\n//region Stateless private utility functions\n\n/**\n * Returns a random integer between min (included) and max (excluded)\n * Using Math.round() gives a non-uniform distribution!\n * @returns {number}\n */\nfunction generateSSRC() {\n var min = 0, max = 0xffffffff;\n return Math.floor(Math.random() * (max - min)) + min;\n};\n\nfunction processVideo(session, action) {\n if (session == null || !Array.isArray(session.media)) {\n return;\n }\n\n session.media.forEach(function (mLine) {\n if (mLine.type === 'video') {\n action(mLine);\n }\n });\n};\n\nfunction validateDescription(desc)\n{\n return desc && desc != null\n && desc.type && desc.type != ''\n && desc.sdp && desc.sdp != '';\n}\n\nfunction explodeRemoteSimulcast(mLine) {\n\n if (!mLine || !Array.isArray(mLine.ssrcGroups)) {\n return;\n }\n\n var sources = parseSsrcs(mLine);\n var order = [];\n\n // Find the SIM group and explode its sources.\n var j = mLine.ssrcGroups.length;\n while (j--) {\n\n if (mLine.ssrcGroups[j].semantics !== 'SIM') {\n continue;\n }\n\n var simulcastSsrcs = mLine.ssrcGroups[j].ssrcs.split(' ');\n\n for (var i = 0; i < simulcastSsrcs.length; i++) {\n\n var ssrc = simulcastSsrcs[i];\n order.push(ssrc);\n\n var parts = sources[ssrc].msid.split(' ');\n sources[ssrc].msid = [parts[0], '/', i, ' ', parts[1], '/', i].join('');\n sources[ssrc].cname = [sources[ssrc].cname, '/', i].join('');\n\n // Remove all the groups that this SSRC participates in.\n mLine.ssrcGroups.forEach(function (relatedGroup) {\n if (relatedGroup.semantics === 'SIM') {\n return;\n }\n\n var relatedSsrcs = relatedGroup.ssrcs.split(' ');\n if (relatedSsrcs.indexOf(ssrc) === -1) {\n return;\n }\n\n // Nuke all the related SSRCs.\n relatedSsrcs.forEach(function (relatedSSRC) {\n sources[relatedSSRC].msid = sources[ssrc].msid;\n sources[relatedSSRC].cname = sources[ssrc].cname;\n if (relatedSSRC !== ssrc) {\n order.push(relatedSSRC);\n }\n });\n\n // Schedule the related group for nuking.\n })\n }\n\n mLine.ssrcs = writeSsrcs(sources, order);\n mLine.ssrcGroups.splice(j, 1);\n };\n}\n\nfunction implodeRemoteSimulcast(mLine) {\n\n if (!mLine || !Array.isArray(mLine.ssrcGroups)) {\n console.info('Halt: There are no SSRC groups in the remote ' +\n 'description.');\n return;\n }\n\n var sources = parseSsrcs(mLine);\n\n // Find the SIM group and nuke it.\n mLine.ssrcGroups.forEach(function (simulcastGroup) {\n if (simulcastGroup.semantics !== 'SIM') {\n return;\n }\n\n console.info(\"Imploding SIM group: \" + simulcastGroup.ssrcs);\n // Schedule the SIM group for nuking.\n simulcastGroup.nuke = true;\n\n var simulcastSsrcs = simulcastGroup.ssrcs.split(' ');\n\n // Nuke all the higher layer SSRCs.\n for (var i = 1; i < simulcastSsrcs.length; i++) {\n\n var ssrc = simulcastSsrcs[i];\n delete sources[ssrc];\n\n // Remove all the groups that this SSRC participates in.\n mLine.ssrcGroups.forEach(function (relatedGroup) {\n if (relatedGroup.semantics === 'SIM') {\n return;\n }\n\n var relatedSsrcs = relatedGroup.ssrcs.split(' ');\n if (relatedSsrcs.indexOf(ssrc) === -1) {\n return;\n }\n\n // Nuke all the related SSRCs.\n relatedSsrcs.forEach(function (relatedSSRC) {\n delete sources[relatedSSRC];\n });\n\n // Schedule the related group for nuking.\n relatedGroup.nuke = true;\n })\n }\n\n return;\n });\n\n mLine.ssrcs = writeSsrcs(sources);\n\n // Nuke all the scheduled groups.\n var i = mLine.ssrcGroups.length;\n while (i--) {\n if (mLine.ssrcGroups[i].nuke) {\n mLine.ssrcGroups.splice(i, 1);\n }\n }\n}\n\nfunction removeGoogConference(mLine) {\n if (!mLine || typeof mLine.xGoogleFlag === 'undefined') {\n return;\n }\n\n mLine.xGoogleFlag = undefined;\n}\n\nfunction assertGoogConference(mLine) {\n if (!mLine) {\n return;\n }\n\n if (!Array.isArray(mLine.invalid)) {\n mLine.invalid = [];\n }\n\n if (!mLine.invalid.some(\n function (i) { return i.value === 'x-google-flag:conference' })) {\n mLine.invalid.push({'value': 'x-google-flag:conference'});\n }\n}\n\nSimulcast.prototype.clearSsrcCache = function() {\n this.ssrcCache = [];\n}\n\n/**\n * When we start as video muted, all of the video\n * ssrcs get generated so we can include them as part\n * of the original session-accept. That means we\n * need this library to restore to those same ssrcs\n * the first time we unmute, so we need the ability to\n * force its cache\n */\nSimulcast.prototype.setSsrcCache = function(ssrcs) {\n this.ssrcCache = ssrcs;\n}\n\n//endregion\n\n//region \"Private\" functions\n\n/**\n * Given a video mLine, return a list of the video ssrcs\n * in simulcast layer order (returns a list of just\n * the primary ssrc if there are no simulcast layers)\n */\nSimulcast.prototype._parseSimLayers = function (mLine) {\n var simGroup = mLine.ssrcGroups &&\n mLine.ssrcGroups.find(function(group) { return group.semantics === \"SIM\"; });\n if (simGroup) {\n return simGroup.ssrcs\n .split(\" \")\n .map(function(ssrcStr) { return parseInt(ssrcStr) });\n } else {\n return [mLine.ssrcs[0].id];\n }\n}\n\nSimulcast.prototype._buildNewToOldSsrcMap = function (newSsrcList, oldSsrcList) {\n var ssrcMap = {};\n for (var i = 0; i < newSsrcList.length; ++i) {\n var newSsrc = newSsrcList[i];\n var oldSsrc = oldSsrcList[i] || null;\n ssrcMap[newSsrc] = oldSsrc;\n }\n return ssrcMap;\n}\n\nSimulcast.prototype._fillInSourceDataFromCache = function(mLine) {\n console.log(\"SdpSimulcast restoring from cache: \", this.ssrcCache);\n var newSimSsrcs = this._parseSimLayers(mLine);\n console.log(\"SdpSimulcast Parsed new sim ssrcs: \", newSimSsrcs);\n var newMsid = getSsrcAttribute(mLine, newSimSsrcs[0], \"msid\");\n var newCname = getSsrcAttribute(mLine, newSimSsrcs[0], \"cname\");\n var ssrcsToReplace = this._buildNewToOldSsrcMap(newSimSsrcs, this.ssrcCache);\n console.log(\"SdpSimulcast built replacement map: \", ssrcsToReplace);\n // New sdp might only have 1 layer, so not every cached ssrc will have a new one\n // to replace directly\n var ssrcsToAdd = this.ssrcCache\n .filter(function(ssrc) { return Object.values(ssrcsToReplace).indexOf(ssrc) === -1; });\n console.log(\"SdpSimulcast built ssrcs to add: \", ssrcsToAdd);\n\n // First do the replacements\n mLine.ssrcs.forEach(function(ssrc) {\n if (ssrcsToReplace[ssrc.id]) {\n ssrc.id = ssrcsToReplace[ssrc.id];\n }\n });\n // Now the adds\n ssrcsToAdd.forEach(function(ssrc) {\n mLine.ssrcs.push({\n id: ssrc,\n attribute: \"msid\",\n value: newMsid\n });\n mLine.ssrcs.push({\n id: ssrc,\n attribute: \"cname\",\n value: newCname\n });\n });\n mLine.ssrcGroups = mLine.ssrcGroups || [];\n mLine.ssrcGroups.push({\n semantics: \"SIM\",\n ssrcs: this.ssrcCache.join(\" \")\n });\n return mLine;\n}\n\nSimulcast.prototype._generateSourceData = function(mLine, primarySsrc) {\n var addAssociatedStream = function(mLine, ssrc) {\n mLine.ssrcs.push({\n id: ssrc,\n attribute: \"cname\",\n value: primarySsrcCname\n });\n mLine.ssrcs.push({\n id: ssrc,\n attribute: \"msid\",\n value: primarySsrcMsid\n });\n }\n var primarySsrcMsid = getSsrcAttribute(mLine, primarySsrc, \"msid\");\n var primarySsrcCname = getSsrcAttribute(mLine, primarySsrc, \"cname\");\n\n // In Unified-plan mode, the a=ssrc lines with the msid attribute are not present\n // in the answers that Chrome and Safari generate for an offer received from Jicofo.\n // Generate these a=ssrc lines using the msid values from the a=msid line.\n if (this.options.usesUnifiedPlan && !primarySsrcMsid) {\n primarySsrcMsid = mLine.msid;\n var primarySsrcs = mLine.ssrcs;\n primarySsrcs.forEach(ssrc => {\n mLine.ssrcs.push({\n id: ssrc.id,\n attribute: \"msid\",\n value: primarySsrcMsid\n });\n });\n }\n\n // Generate sim layers\n var simSsrcs = [];\n for (var i = 0; i < this.options.numOfLayers - 1; ++i) {\n var simSsrc = generateSSRC();\n addAssociatedStream(mLine, simSsrc);\n simSsrcs.push(simSsrc);\n }\n mLine.ssrcGroups = mLine.ssrcGroups || [];\n mLine.ssrcGroups.push({\n semantics: \"SIM\",\n ssrcs: primarySsrc + \" \" + simSsrcs.join(\" \")\n });\n return mLine;\n}\n\n\n\n// Assumptions:\n// 1) 'mLine' contains only a single primary video source\n// (i.e. it will not already have simulcast streams inserted)\n// 2) 'mLine' MAY already contain an RTX stream for its video source\n// 3) 'mLine' is in sendrecv or sendonly state\n// Guarantees:\n// 1) return mLine will contain 2 additional simulcast layers\n// generated\n// 2) if the base video ssrc in mLine has been seen before,\n// then the same generated simulcast streams from before will\n// be used again\n// 3) if rtx is enabled for the mLine, all generated simulcast\n// streams will have rtx streams generated as well\n// 4) if rtx has been generated for a src before, we will generate\n// the same rtx stream again\nSimulcast.prototype._restoreSimulcast = function(mLine) {\n // First, find the primary video source in the given\n // mLine and see if we've seen it before.\n var primarySsrc;\n var numSsrcs = mLine.ssrcs && mLine.ssrcs\n .map(function(ssrcInfo) { return ssrcInfo.id; })\n .filter(function(ssrc, index, array) {\n return array.indexOf(ssrc) === index;\n })\n .length || 0;\n var numGroups = (mLine.ssrcGroups && mLine.ssrcGroups.length) || 0;\n\n if (numSsrcs === 0 || numSsrcs > 2) {\n // Unsupported scenario\n return mLine;\n }\n if (numSsrcs == 2 && numGroups === 0) {\n // Unsupported scenario\n return mLine;\n }\n\n if (numSsrcs === 1) {\n primarySsrc = mLine.ssrcs[0].id;\n } else {\n // There must be an FID group, so parse\n // that and pull the primary ssrc from there\n var fidGroup = mLine.ssrcGroups.filter(function(group) { return group.semantics === \"FID\"; })[0];\n if (fidGroup) {\n primarySsrc = parseInt(fidGroup.ssrcs.split(\" \")[0]);\n } else {\n // Unsupported scenario\n return mLine;\n }\n }\n console.log(\"SdpSimulcast: current ssrc cache: \", this.ssrcCache);\n console.log(\"SdpSimulcast: parsed primary ssrc \" + primarySsrc);\n\n var seenPrimarySsrc = this.ssrcCache.indexOf(primarySsrc) !== -1;\n\n if (seenPrimarySsrc) {\n console.log(\"SdpSimulcast: Have seen primary ssrc before, \" +\n \"filling in data from cache\");\n mLine = this._fillInSourceDataFromCache(mLine);\n } else {\n console.log(\"SdpSimulcast: Have not seen primary ssrc before, \" +\n \"generating source data\");\n mLine = this._generateSourceData(mLine, primarySsrc);\n }\n // Now update the cache to match whatever we've just put into this sdp\n this.ssrcCache = this._parseSimLayers(mLine);\n return mLine;\n}\n\n//endregion\n\n//region \"Public\" functions\n\n/**\n *\n * @param desc\n * @param enableConferenceFlag\n * @returns {RTCSessionDescription}\n */\nSimulcast.prototype.mungeRemoteDescription = function (desc, enableConferenceFlag) {\n\n if (!validateDescription(desc)) {\n return desc;\n }\n\n var session = transform.parse(desc.sdp);\n\n var self = this;\n processVideo(session, function (mLine) {\n\n // Handle simulcast reception.\n if (self.options.explodeRemoteSimulcast) {\n explodeRemoteSimulcast(mLine);\n } else {\n implodeRemoteSimulcast(mLine);\n }\n\n // Add or remove \"x-google-conference\" from the remote description based on whether the client\n // has enabled simulcast for the local video source. For cases where we disable simulcast for desktop share,\n // it is necessary to remove the flag so that Chrome stops sending T1 temporal layers. It also fixes other\n // issues related to screensharing like https://bugs.chromium.org/p/chromium/issues/detail?id=1093819.\n if (!self.options.usesUnifiedPlan && enableConferenceFlag) {\n assertGoogConference(mLine);\n } else {\n removeGoogConference(mLine);\n }\n });\n\n return new RTCSessionDescription({\n type: desc.type,\n sdp: transform.write(session)\n });\n};\n\n/**\n *\n * NOTE this method should be called only if simulcast is supported by\n * the current browser, otherwise local SDP should not be munged.\n * @param desc\n * @returns {RTCSessionDescription}\n */\nSimulcast.prototype.mungeLocalDescription = function (desc) {\n\n if (!validateDescription(desc)) {\n return desc;\n }\n\n var session = transform.parse(desc.sdp);\n\n var self = this;\n processVideo(session, function (mLine) {\n if (mLine.direction == 'recvonly' || mLine.direction == 'inactive')\n {\n return;\n }\n self._restoreSimulcast(mLine);\n });\n\n return new RTCSessionDescription({\n type: desc.type,\n sdp: transform.write(session)\n });\n};\n\n//endregion\n\nmodule.exports = Simulcast;\n","/* Copyright @ 2015 Atlassian Pty Ltd\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * FIXME\n * @param sources FIXME\n * @param order An array of SSRCs which will be used to order the entries in\n * the returned array. Sources whose SSRC appears in 'order' will be added first,\n * in the specified order, and all other sources will be added afterwards (in\n * no specific order).\n * @returns {Array} FIXME\n */\nexports.writeSsrcs = function(sources, order) {\n var ssrcs = [];\n\n // expand sources to ssrcs\n if (typeof sources !== 'undefined' &&\n Object.keys(sources).length !== 0) {\n\n if (!Array.isArray(order)) {\n order = []\n }\n\n // Add the sources that appear in 'order' first.\n for (var i = 0; i < order.length; i++) {\n var ssrc = order[i];\n var source = sources[ssrc];\n Object.keys(source).forEach(function (attribute) {\n ssrcs.push({\n id: ssrc,\n attribute: attribute,\n value: source[attribute]\n });\n });\n }\n\n // Now add the rest of the sources.\n Object.keys(sources).forEach(function (ssrc) {\n ssrc = parseInt(ssrc); // Object.keys() returns string\n if (order.indexOf(ssrc) >= 0) {\n // Already added.\n return;\n }\n\n var source = sources[ssrc];\n Object.keys(source).forEach(function (attribute) {\n ssrcs.push({\n id: ssrc,\n attribute: attribute,\n value: source[attribute]\n });\n });\n });\n }\n\n return ssrcs;\n};\n\nexports.parseSsrcs = function (mLine) {\n var sources = {};\n // group sources attributes by ssrc.\n if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {\n mLine.ssrcs.forEach(function (ssrc) {\n if (!sources[ssrc.id])\n sources[ssrc.id] = {};\n sources[ssrc.id][ssrc.attribute] = ssrc.value;\n });\n }\n return sources;\n};\n\n","'use strict'\n\nexports.byteLength = byteLength\nexports.toByteArray = toByteArray\nexports.fromByteArray = fromByteArray\n\nvar lookup = []\nvar revLookup = []\nvar Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array\n\nvar code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nfor (var i = 0, len = code.length; i < len; ++i) {\n lookup[i] = code[i]\n revLookup[code.charCodeAt(i)] = i\n}\n\n// Support decoding URL-safe base64 strings, as Node.js does.\n// See: https://en.wikipedia.org/wiki/Base64#URL_applications\nrevLookup['-'.charCodeAt(0)] = 62\nrevLookup['_'.charCodeAt(0)] = 63\n\nfunction getLens (b64) {\n var len = b64.length\n\n if (len % 4 > 0) {\n throw new Error('Invalid string. Length must be a multiple of 4')\n }\n\n // Trim off extra bytes after placeholder bytes are found\n // See: https://github.com/beatgammit/base64-js/issues/42\n var validLen = b64.indexOf('=')\n if (validLen === -1) validLen = len\n\n var placeHoldersLen = validLen === len\n ? 0\n : 4 - (validLen % 4)\n\n return [validLen, placeHoldersLen]\n}\n\n// base64 is 4/3 + up to two characters of the original data\nfunction byteLength (b64) {\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction _byteLength (b64, validLen, placeHoldersLen) {\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction toByteArray (b64) {\n var tmp\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n\n var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))\n\n var curByte = 0\n\n // if there are placeholders, only get up to the last complete 4 chars\n var len = placeHoldersLen > 0\n ? validLen - 4\n : validLen\n\n var i\n for (i = 0; i < len; i += 4) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 18) |\n (revLookup[b64.charCodeAt(i + 1)] << 12) |\n (revLookup[b64.charCodeAt(i + 2)] << 6) |\n revLookup[b64.charCodeAt(i + 3)]\n arr[curByte++] = (tmp >> 16) & 0xFF\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 2) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 2) |\n (revLookup[b64.charCodeAt(i + 1)] >> 4)\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 1) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 10) |\n (revLookup[b64.charCodeAt(i + 1)] << 4) |\n (revLookup[b64.charCodeAt(i + 2)] >> 2)\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n return arr\n}\n\nfunction tripletToBase64 (num) {\n return lookup[num >> 18 & 0x3F] +\n lookup[num >> 12 & 0x3F] +\n lookup[num >> 6 & 0x3F] +\n lookup[num & 0x3F]\n}\n\nfunction encodeChunk (uint8, start, end) {\n var tmp\n var output = []\n for (var i = start; i < end; i += 3) {\n tmp =\n ((uint8[i] << 16) & 0xFF0000) +\n ((uint8[i + 1] << 8) & 0xFF00) +\n (uint8[i + 2] & 0xFF)\n output.push(tripletToBase64(tmp))\n }\n return output.join('')\n}\n\nfunction fromByteArray (uint8) {\n var tmp\n var len = uint8.length\n var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes\n var parts = []\n var maxChunkLength = 16383 // must be multiple of 3\n\n // go through the array every three bytes, we'll deal with trailing stuff later\n for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n parts.push(encodeChunk(\n uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)\n ))\n }\n\n // pad the end with zeros, but make sure to not forget the extra bytes\n if (extraBytes === 1) {\n tmp = uint8[len - 1]\n parts.push(\n lookup[tmp >> 2] +\n lookup[(tmp << 4) & 0x3F] +\n '=='\n )\n } else if (extraBytes === 2) {\n tmp = (uint8[len - 2] << 8) + uint8[len - 1]\n parts.push(\n lookup[tmp >> 10] +\n lookup[(tmp >> 4) & 0x3F] +\n lookup[(tmp << 2) & 0x3F] +\n '='\n )\n }\n\n return parts.join('')\n}\n","!function(e,t){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define([],t):\"object\"==typeof exports?exports.bowser=t():e.bowser=t()}(this,(function(){return function(e){var t={};function r(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(e,\"__esModule\",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&\"object\"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,\"default\",{enumerable:!0,value:e}),2&t&&\"string\"!=typeof e)for(var n in e)r.d(i,n,function(t){return e[t]}.bind(null,n));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,\"a\",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p=\"\",r(r.s=90)}({17:function(e,t,r){\"use strict\";t.__esModule=!0,t.default=void 0;var i=r(18),n=function(){function e(){}return e.getFirstMatch=function(e,t){var r=t.match(e);return r&&r.length>0&&r[1]||\"\"},e.getSecondMatch=function(e,t){var r=t.match(e);return r&&r.length>1&&r[2]||\"\"},e.matchAndReturnConst=function(e,t,r){if(e.test(t))return r},e.getWindowsVersionName=function(e){switch(e){case\"NT\":return\"NT\";case\"XP\":return\"XP\";case\"NT 5.0\":return\"2000\";case\"NT 5.1\":return\"XP\";case\"NT 5.2\":return\"2003\";case\"NT 6.0\":return\"Vista\";case\"NT 6.1\":return\"7\";case\"NT 6.2\":return\"8\";case\"NT 6.3\":return\"8.1\";case\"NT 10.0\":return\"10\";default:return}},e.getMacOSVersionName=function(e){var t=e.split(\".\").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),10===t[0])switch(t[1]){case 5:return\"Leopard\";case 6:return\"Snow Leopard\";case 7:return\"Lion\";case 8:return\"Mountain Lion\";case 9:return\"Mavericks\";case 10:return\"Yosemite\";case 11:return\"El Capitan\";case 12:return\"Sierra\";case 13:return\"High Sierra\";case 14:return\"Mojave\";case 15:return\"Catalina\";default:return}},e.getAndroidVersionName=function(e){var t=e.split(\".\").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),!(1===t[0]&&t[1]<5))return 1===t[0]&&t[1]<6?\"Cupcake\":1===t[0]&&t[1]>=6?\"Donut\":2===t[0]&&t[1]<2?\"Eclair\":2===t[0]&&2===t[1]?\"Froyo\":2===t[0]&&t[1]>2?\"Gingerbread\":3===t[0]?\"Honeycomb\":4===t[0]&&t[1]<1?\"Ice Cream Sandwich\":4===t[0]&&t[1]<4?\"Jelly Bean\":4===t[0]&&t[1]>=4?\"KitKat\":5===t[0]?\"Lollipop\":6===t[0]?\"Marshmallow\":7===t[0]?\"Nougat\":8===t[0]?\"Oreo\":9===t[0]?\"Pie\":void 0},e.getVersionPrecision=function(e){return e.split(\".\").length},e.compareVersions=function(t,r,i){void 0===i&&(i=!1);var n=e.getVersionPrecision(t),s=e.getVersionPrecision(r),o=Math.max(n,s),a=0,u=e.map([t,r],(function(t){var r=o-e.getVersionPrecision(t),i=t+new Array(r+1).join(\".0\");return e.map(i.split(\".\"),(function(e){return new Array(20-e.length).join(\"0\")+e})).reverse()}));for(i&&(a=o-Math.min(n,s)),o-=1;o>=a;){if(u[0][o]>u[1][o])return 1;if(u[0][o]===u[1][o]){if(o===a)return 0;o-=1}else if(u[0][o]0){var o=Object.keys(r),a=o.find((function(e){return t.isOS(e)}));if(a){var u=this.satisfies(r[a]);if(void 0!==u)return u}var d=o.find((function(e){return t.isPlatform(e)}));if(d){var c=this.satisfies(r[d]);if(void 0!==c)return c}}if(s>0){var f=Object.keys(n).find((function(e){return t.isBrowser(e,!0)}));if(void 0!==f)return this.compareVersion(n[f])}},t.isBrowser=function(e,t){void 0===t&&(t=!1);var r=this.getBrowserName().toLowerCase(),i=e.toLowerCase(),n=a.default.getBrowserTypeByAlias(i);return t&&n&&(i=n.toLowerCase()),i===r},t.compareVersion=function(e){var t=[0],r=e,i=!1,n=this.getBrowserVersion();if(\"string\"==typeof n)return\">\"===e[0]||\"<\"===e[0]?(r=e.substr(1),\"=\"===e[1]?(i=!0,r=e.substr(2)):t=[],\">\"===e[0]?t.push(1):t.push(-1)):\"=\"===e[0]?r=e.substr(1):\"~\"===e[0]&&(i=!0,r=e.substr(1)),t.indexOf(a.default.compareVersions(n,r,i))>-1},t.isOS=function(e){return this.getOSName(!0)===String(e).toLowerCase()},t.isPlatform=function(e){return this.getPlatformType(!0)===String(e).toLowerCase()},t.isEngine=function(e){return this.getEngineName(!0)===String(e).toLowerCase()},t.is=function(e){return this.isBrowser(e)||this.isOS(e)||this.isPlatform(e)},t.some=function(e){var t=this;return void 0===e&&(e=[]),e.some((function(e){return t.is(e)}))},e}();t.default=d,e.exports=t.default},92:function(e,t,r){\"use strict\";t.__esModule=!0,t.default=void 0;var i,n=(i=r(17))&&i.__esModule?i:{default:i};var s=/version\\/(\\d+(\\.?_?\\d+)+)/i,o=[{test:[/googlebot/i],describe:function(e){var t={name:\"Googlebot\"},r=n.default.getFirstMatch(/googlebot\\/(\\d+(\\.\\d+))/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/opera/i],describe:function(e){var t={name:\"Opera\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:opera)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/opr\\/|opios/i],describe:function(e){var t={name:\"Opera\"},r=n.default.getFirstMatch(/(?:opr|opios)[\\s/](\\S+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/SamsungBrowser/i],describe:function(e){var t={name:\"Samsung Internet for Android\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:SamsungBrowser)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/Whale/i],describe:function(e){var t={name:\"NAVER Whale Browser\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:whale)[\\s/](\\d+(?:\\.\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/MZBrowser/i],describe:function(e){var t={name:\"MZ Browser\"},r=n.default.getFirstMatch(/(?:MZBrowser)[\\s/](\\d+(?:\\.\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/focus/i],describe:function(e){var t={name:\"Focus\"},r=n.default.getFirstMatch(/(?:focus)[\\s/](\\d+(?:\\.\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/swing/i],describe:function(e){var t={name:\"Swing\"},r=n.default.getFirstMatch(/(?:swing)[\\s/](\\d+(?:\\.\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/coast/i],describe:function(e){var t={name:\"Opera Coast\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:coast)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/yabrowser/i],describe:function(e){var t={name:\"Yandex Browser\"},r=n.default.getFirstMatch(/(?:yabrowser)[\\s/](\\d+(\\.?_?\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/ucbrowser/i],describe:function(e){var t={name:\"UC Browser\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:ucbrowser)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/Maxthon|mxios/i],describe:function(e){var t={name:\"Maxthon\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:Maxthon|mxios)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/epiphany/i],describe:function(e){var t={name:\"Epiphany\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:epiphany)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/puffin/i],describe:function(e){var t={name:\"Puffin\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:puffin)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/sleipnir/i],describe:function(e){var t={name:\"Sleipnir\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:sleipnir)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/k-meleon/i],describe:function(e){var t={name:\"K-Meleon\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/(?:k-meleon)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/micromessenger/i],describe:function(e){var t={name:\"WeChat\"},r=n.default.getFirstMatch(/(?:micromessenger)[\\s/](\\d+(\\.?_?\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/qqbrowser/i],describe:function(e){var t={name:/qqbrowserlite/i.test(e)?\"QQ Browser Lite\":\"QQ Browser\"},r=n.default.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\\d+(\\.?_?\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/msie|trident/i],describe:function(e){var t={name:\"Internet Explorer\"},r=n.default.getFirstMatch(/(?:msie |rv:)(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/\\sedg\\//i],describe:function(e){var t={name:\"Microsoft Edge\"},r=n.default.getFirstMatch(/\\sedg\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/edg([ea]|ios)/i],describe:function(e){var t={name:\"Microsoft Edge\"},r=n.default.getSecondMatch(/edg([ea]|ios)\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/vivaldi/i],describe:function(e){var t={name:\"Vivaldi\"},r=n.default.getFirstMatch(/vivaldi\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/seamonkey/i],describe:function(e){var t={name:\"SeaMonkey\"},r=n.default.getFirstMatch(/seamonkey\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/sailfish/i],describe:function(e){var t={name:\"Sailfish\"},r=n.default.getFirstMatch(/sailfish\\s?browser\\/(\\d+(\\.\\d+)?)/i,e);return r&&(t.version=r),t}},{test:[/silk/i],describe:function(e){var t={name:\"Amazon Silk\"},r=n.default.getFirstMatch(/silk\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/phantom/i],describe:function(e){var t={name:\"PhantomJS\"},r=n.default.getFirstMatch(/phantomjs\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/slimerjs/i],describe:function(e){var t={name:\"SlimerJS\"},r=n.default.getFirstMatch(/slimerjs\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/blackberry|\\bbb\\d+/i,/rim\\stablet/i],describe:function(e){var t={name:\"BlackBerry\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/blackberry[\\d]+\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t={name:\"WebOS Browser\"},r=n.default.getFirstMatch(s,e)||n.default.getFirstMatch(/w(?:eb)?[o0]sbrowser\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/bada/i],describe:function(e){var t={name:\"Bada\"},r=n.default.getFirstMatch(/dolfin\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/tizen/i],describe:function(e){var t={name:\"Tizen\"},r=n.default.getFirstMatch(/(?:tizen\\s?)?browser\\/(\\d+(\\.?_?\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/qupzilla/i],describe:function(e){var t={name:\"QupZilla\"},r=n.default.getFirstMatch(/(?:qupzilla)[\\s/](\\d+(\\.?_?\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/firefox|iceweasel|fxios/i],describe:function(e){var t={name:\"Firefox\"},r=n.default.getFirstMatch(/(?:firefox|iceweasel|fxios)[\\s/](\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/chromium/i],describe:function(e){var t={name:\"Chromium\"},r=n.default.getFirstMatch(/(?:chromium)[\\s/](\\d+(\\.?_?\\d+)+)/i,e)||n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/chrome|crios|crmo/i],describe:function(e){var t={name:\"Chrome\"},r=n.default.getFirstMatch(/(?:chrome|crios|crmo)\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/GSA/i],describe:function(e){var t={name:\"Google Search\"},r=n.default.getFirstMatch(/(?:GSA)\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){var t=!e.test(/like android/i),r=e.test(/android/i);return t&&r},describe:function(e){var t={name:\"Android Browser\"},r=n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/playstation 4/i],describe:function(e){var t={name:\"PlayStation 4\"},r=n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/safari|applewebkit/i],describe:function(e){var t={name:\"Safari\"},r=n.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/.*/i],describe:function(e){var t=-1!==e.search(\"\\\\(\")?/^(.*)\\/(.*)[ \\t]\\((.*)/:/^(.*)\\/(.*) /;return{name:n.default.getFirstMatch(t,e),version:n.default.getSecondMatch(t,e)}}}];t.default=o,e.exports=t.default},93:function(e,t,r){\"use strict\";t.__esModule=!0,t.default=void 0;var i,n=(i=r(17))&&i.__esModule?i:{default:i},s=r(18);var o=[{test:[/Roku\\/DVP/],describe:function(e){var t=n.default.getFirstMatch(/Roku\\/DVP-(\\d+\\.\\d+)/i,e);return{name:s.OS_MAP.Roku,version:t}}},{test:[/windows phone/i],describe:function(e){var t=n.default.getFirstMatch(/windows phone (?:os)?\\s?(\\d+(\\.\\d+)*)/i,e);return{name:s.OS_MAP.WindowsPhone,version:t}}},{test:[/windows/i],describe:function(e){var t=n.default.getFirstMatch(/Windows ((NT|XP)( \\d\\d?.\\d)?)/i,e),r=n.default.getWindowsVersionName(t);return{name:s.OS_MAP.Windows,version:t,versionName:r}}},{test:[/macintosh/i],describe:function(e){var t=n.default.getFirstMatch(/mac os x (\\d+(\\.?_?\\d+)+)/i,e).replace(/[_\\s]/g,\".\"),r=n.default.getMacOSVersionName(t),i={name:s.OS_MAP.MacOS,version:t};return r&&(i.versionName=r),i}},{test:[/(ipod|iphone|ipad)/i],describe:function(e){var t=n.default.getFirstMatch(/os (\\d+([_\\s]\\d+)*) like mac os x/i,e).replace(/[_\\s]/g,\".\");return{name:s.OS_MAP.iOS,version:t}}},{test:function(e){var t=!e.test(/like android/i),r=e.test(/android/i);return t&&r},describe:function(e){var t=n.default.getFirstMatch(/android[\\s/-](\\d+(\\.\\d+)*)/i,e),r=n.default.getAndroidVersionName(t),i={name:s.OS_MAP.Android,version:t};return r&&(i.versionName=r),i}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t=n.default.getFirstMatch(/(?:web|hpw)[o0]s\\/(\\d+(\\.\\d+)*)/i,e),r={name:s.OS_MAP.WebOS};return t&&t.length&&(r.version=t),r}},{test:[/blackberry|\\bbb\\d+/i,/rim\\stablet/i],describe:function(e){var t=n.default.getFirstMatch(/rim\\stablet\\sos\\s(\\d+(\\.\\d+)*)/i,e)||n.default.getFirstMatch(/blackberry\\d+\\/(\\d+([_\\s]\\d+)*)/i,e)||n.default.getFirstMatch(/\\bbb(\\d+)/i,e);return{name:s.OS_MAP.BlackBerry,version:t}}},{test:[/bada/i],describe:function(e){var t=n.default.getFirstMatch(/bada\\/(\\d+(\\.\\d+)*)/i,e);return{name:s.OS_MAP.Bada,version:t}}},{test:[/tizen/i],describe:function(e){var t=n.default.getFirstMatch(/tizen[/\\s](\\d+(\\.\\d+)*)/i,e);return{name:s.OS_MAP.Tizen,version:t}}},{test:[/linux/i],describe:function(){return{name:s.OS_MAP.Linux}}},{test:[/CrOS/],describe:function(){return{name:s.OS_MAP.ChromeOS}}},{test:[/PlayStation 4/],describe:function(e){var t=n.default.getFirstMatch(/PlayStation 4[/\\s](\\d+(\\.\\d+)*)/i,e);return{name:s.OS_MAP.PlayStation4,version:t}}}];t.default=o,e.exports=t.default},94:function(e,t,r){\"use strict\";t.__esModule=!0,t.default=void 0;var i,n=(i=r(17))&&i.__esModule?i:{default:i},s=r(18);var o=[{test:[/googlebot/i],describe:function(){return{type:\"bot\",vendor:\"Google\"}}},{test:[/huawei/i],describe:function(e){var t=n.default.getFirstMatch(/(can-l01)/i,e)&&\"Nova\",r={type:s.PLATFORMS_MAP.mobile,vendor:\"Huawei\"};return t&&(r.model=t),r}},{test:[/nexus\\s*(?:7|8|9|10).*/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:\"Nexus\"}}},{test:[/ipad/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:\"Apple\",model:\"iPad\"}}},{test:[/kftt build/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:\"Amazon\",model:\"Kindle Fire HD 7\"}}},{test:[/silk/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:\"Amazon\"}}},{test:[/tablet(?! pc)/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet}}},{test:function(e){var t=e.test(/ipod|iphone/i),r=e.test(/like (ipod|iphone)/i);return t&&!r},describe:function(e){var t=n.default.getFirstMatch(/(ipod|iphone)/i,e);return{type:s.PLATFORMS_MAP.mobile,vendor:\"Apple\",model:t}}},{test:[/nexus\\s*[0-6].*/i,/galaxy nexus/i],describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:\"Nexus\"}}},{test:[/[^-]mobi/i],describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return\"blackberry\"===e.getBrowserName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:\"BlackBerry\"}}},{test:function(e){return\"bada\"===e.getBrowserName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return\"windows phone\"===e.getBrowserName()},describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:\"Microsoft\"}}},{test:function(e){var t=Number(String(e.getOSVersion()).split(\".\")[0]);return\"android\"===e.getOSName(!0)&&t>=3},describe:function(){return{type:s.PLATFORMS_MAP.tablet}}},{test:function(e){return\"android\"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return\"macos\"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop,vendor:\"Apple\"}}},{test:function(e){return\"windows\"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop}}},{test:function(e){return\"linux\"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop}}},{test:function(e){return\"playstation 4\"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.tv}}},{test:function(e){return\"roku\"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.tv}}}];t.default=o,e.exports=t.default},95:function(e,t,r){\"use strict\";t.__esModule=!0,t.default=void 0;var i,n=(i=r(17))&&i.__esModule?i:{default:i},s=r(18);var o=[{test:function(e){return\"microsoft edge\"===e.getBrowserName(!0)},describe:function(e){if(/\\sedg\\//i.test(e))return{name:s.ENGINE_MAP.Blink};var t=n.default.getFirstMatch(/edge\\/(\\d+(\\.?_?\\d+)+)/i,e);return{name:s.ENGINE_MAP.EdgeHTML,version:t}}},{test:[/trident/i],describe:function(e){var t={name:s.ENGINE_MAP.Trident},r=n.default.getFirstMatch(/trident\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){return e.test(/presto/i)},describe:function(e){var t={name:s.ENGINE_MAP.Presto},r=n.default.getFirstMatch(/presto\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){var t=e.test(/gecko/i),r=e.test(/like gecko/i);return t&&!r},describe:function(e){var t={name:s.ENGINE_MAP.Gecko},r=n.default.getFirstMatch(/gecko\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/(apple)?webkit\\/537\\.36/i],describe:function(){return{name:s.ENGINE_MAP.Blink}}},{test:[/(apple)?webkit/i],describe:function(e){var t={name:s.ENGINE_MAP.WebKit},r=n.default.getFirstMatch(/webkit\\/(\\d+(\\.?_?\\d+)+)/i,e);return r&&(t.version=r),t}}];t.default=o,e.exports=t.default}})}));","/*!\n * currentExecutingScript\n * Get the currently executing script, regardless of its source/trigger/synchronicity. Similar to HTML5's `document.currentScript` but arguably much more useful!\n * Copyright (c) 2015 James M. Greene\n * Licensed MIT\n * https://github.com/JamesMGreene/currentExecutingScript\n * v0.1.3\n */\n(function(root, factory) {\n if (typeof define === \"function\" && define.amd) {\n // AMD. Register as an anonymous module.\n define([], factory);\n } else if (typeof exports === \"object\") {\n // CommonJS-like environments that support `module.exports`,\n // like Node.js. Does not work with strict CommonJS!\n module.exports = factory();\n } else {\n // Browser globals (`root` is `window`)\n root.currentExecutingScript = factory();\n }\n}(\n // Current context/scope\n this || window,\n\n // Factory function to return the export\n function() {\n\nvar scriptReadyRegex = /^(interactive|loaded|complete)$/;\n\n// This page's URL (minus query string and fragment identifer hash, if any)\nvar fullPageUrl = !!window.location ? window.location.href : null;\nvar pageUrl = fullPageUrl ? fullPageUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\") || null : null;\n\n// Live NodeList collection\nvar scripts = document.getElementsByTagName(\"script\");\n\n// Check if the browser supports the `readyState` property on `script` elements\nvar supportsScriptReadyState = \"readyState\" in (scripts[0] || document.createElement(\"script\"));\n\n// Lousy browser detection for [not] Opera\nvar isNotOpera = !window.opera || window.opera.toString() !== \"[object Opera]\";\n\n// Detect if `document.currentScript` is supported\nvar hasNativeCurrentScriptAccessor = \"currentScript\" in document;\n\nvar originalStackDepthConfig;\n// Detect if the V8 Error Stack Trace API is supported\nif (\"stackTraceLimit\" in Error && Error.stackTraceLimit !== Infinity) {\n originalStackDepthConfig = Error.stackTraceLimit;\n Error.stackTraceLimit = Infinity;\n}\n\n\n// In some browsers (e.g. Chrome), you can get the current stack from an Error\n// object instance without needing to throw it. Avoiding an unnecessary\n// use of `throw` saves time and performance.\nvar hasStackBeforeThrowing = false,\n hasStackAfterThrowing = false;\n(function() {\n try {\n var err = new Error();\n hasStackBeforeThrowing = typeof err.stack === \"string\" && !!err.stack;\n throw err;\n }\n catch (thrownErr) {\n hasStackAfterThrowing = typeof thrownErr.stack === \"string\" && !!thrownErr.stack;\n }\n})();\n\n\n// Normalize whitespace within a string\nfunction normalizeWhitespace(str) {\n return str ? str.replace(/^\\s+$|\\s+$/g, \"\").replace(/\\s\\s+/g, \" \") : \"\";\n}\n\n// Get script object based on the `src` URL\nfunction getScriptFromUrl(url, eligibleScripts) {\n var i,\n script = null;\n\n eligibleScripts = eligibleScripts || scripts;\n\n if (typeof url === \"string\" && url) {\n for (i = eligibleScripts.length; i--; ) {\n if (eligibleScripts[i].src === url) {\n // NOTE: Could check if the same script URL is used by more than one `script` element\n // here... but let's not. That would yield less useful results in \"loose\" detection. ;)\n script = eligibleScripts[i];\n break;\n }\n }\n }\n return script;\n}\n\n// Get script object based on the caller function's source code body (text)\nfunction getInlineScriptFromCallerSource(callerFnSource, eligibleScripts) {\n var i, inlineScriptText,\n script = null,\n callerSourceText = normalizeWhitespace(callerFnSource);\n\n eligibleScripts = eligibleScripts || scripts;\n\n if (callerFnSource && callerSourceText) {\n for (i = eligibleScripts.length; i--; ) {\n // Only look at inline scripts\n if (!eligibleScripts[i].hasAttribute(\"src\")) {\n inlineScriptText = normalizeWhitespace(eligibleScripts[i].text);\n if (inlineScriptText.indexOf(callerSourceText) !== -1) {\n // If more than one match is found, don't return any\n if (script) {\n script = null;\n break;\n }\n script = eligibleScripts[i];\n }\n }\n }\n }\n\n return script;\n}\n\n// If there is only a single inline script on the page, return it; otherwise `null`\nfunction getSoleInlineScript(eligibleScripts) {\n var i, len,\n script = null;\n eligibleScripts = eligibleScripts || scripts;\n for (i = 0, len = eligibleScripts.length; i < len; i++) {\n if (!eligibleScripts[i].hasAttribute(\"src\")) {\n if (script) {\n script = null;\n break;\n }\n script = eligibleScripts[i];\n }\n }\n return script;\n}\n\n// Get the currently executing script URL from an Error stack trace\nfunction getScriptUrlFromStack(stack, skipStackDepth) {\n var matches, remainingStack,\n url = null,\n ignoreMessage = typeof skipStackDepth === \"number\";\n skipStackDepth = ignoreMessage ? Math.round(skipStackDepth) : 0;\n if (typeof stack === \"string\" && stack) {\n if (ignoreMessage) {\n matches = stack.match(/(data:text\\/javascript(?:;[^,]+)?,.+?|(?:|blob:)(?:http[s]?|file):\\/\\/[\\/]?.+?\\/[^:\\)]*?)(?::\\d+)(?::\\d+)?/);\n }\n else {\n matches = stack.match(/^(?:|[^:@]*@|.+\\)@(?=data:text\\/javascript|blob|http[s]?|file)|.+?\\s+(?: at |@)(?:[^:\\(]+ )*[\\(]?)(data:text\\/javascript(?:;[^,]+)?,.+?|(?:|blob:)(?:http[s]?|file):\\/\\/[\\/]?.+?\\/[^:\\)]*?)(?::\\d+)(?::\\d+)?/);\n\n if (!(matches && matches[1])) {\n matches = stack.match(/\\)@(data:text\\/javascript(?:;[^,]+)?,.+?|(?:|blob:)(?:http[s]?|file):\\/\\/[\\/]?.+?\\/[^:\\)]*?)(?::\\d+)(?::\\d+)?/);\n }\n }\n\n if (matches && matches[1]) {\n if (skipStackDepth > 0) {\n remainingStack = stack.slice(stack.indexOf(matches[0]) + matches[0].length);\n url = getScriptUrlFromStack(remainingStack, (skipStackDepth - 1));\n }\n else {\n url = matches[1];\n }\n }\n\n // TODO: Handle more edge cases!\n // Fixes #1\n // See https://github.com/JamesMGreene/currentExecutingScript/issues/1\n\n // ???\n\n }\n return url;\n}\n\n\n// Get the farthest currently executing (i.e. yes, EXECUTING) `script` DOM\n// element for the caller function, regardless of whether it is that `script`\n// DOM element is currently being evaluated for the first time. The farthest\n// currently executing `script` DOM element would typically be considered the\n// originator of the current execution stack.\nfunction _farthestExecutingScript() {\n /*jshint noarg:false */\n\n // TODO: Implement!\n // Fixes #3\n // See https://github.com/JamesMGreene/currentExecutingScript/issues/3\n return null;\n\n/*\n // Yes, this IS possible, i.e. if a script removes other scripts (or itself)\n if (scripts.length === 0) {\n return null;\n }\n\n // Guaranteed accurate in IE 6-10.\n // Not accurate/supported in any other browsers.\n if (isNotOpera && supportsScriptReadyState) {\n for (var i = scripts.length; i--; ) {\n if (scripts[i].readyState === \"interactive\") {\n return scripts[i];\n }\n }\n }\n\n var stack,\n e = new Error();\n if (hasStackBeforeThrowing) {\n stack = e.stack;\n }\n if (!stack && hasStackAfterThrowing) {\n try {\n throw e;\n }\n catch (err) {\n // NOTE: Cannot use `err.sourceURL` or `err.fileName` as they will always be THIS script\n stack = err.stack;\n }\n }\n if (stack) {\n var url = getScriptUrlFromStack(stack, skipStackDepth);\n var script = getScriptFromUrl(url, scripts );\n if (!script && pageUrl && url === pageUrl) {\n // Try to find the correct inline script by searching through\n // inline scripts' text content for the caller function's source\n // code to be present. If the caller function's source code is\n // not available, see if there is only one inline script element\n // in the DOM and return that (even though it may be wrong)\n\n // TODO: Implement!\n // Fixes #4 in part\n // See https://github.com/JamesMGreene/currentExecutingScript/issues/4\n\n var callerFn = _farthestExecutingScript.caller || null,\n callerFnStack = [],\n callerFnSource = null;\n\n while (callerFn) {\n callerFnStack.push(callerFn);\n callerFn = callerFn.caller || null;\n }\n callerFn = callerFnStack.slice(-1)[0];\n callerFnSource = callerFn ? (\"\" + callerFn) : null;\n\n\n if (callerFnSource) {\n script = getInlineScriptFromCallerSource(callerFnSource);\n }\n else {\n // NOTE: This is a loose assumption that could be inaccurate!\n //\n // Inaccuracies:\n // - If the inline script that initiated the call was also removed from the DOM.\n // - If the call was initiated by an element's inline event handler,\n // e.g. `click`\n script = getSoleInlineScript();\n }\n }\n return script;\n }\n\n // NOTE: This is a loose assumption that could be inaccurate!\n //\n // Inaccuracies:\n // - If a script is created dynamically and appended to some position\n // other than the very end of the document.\n // - If multiple scripts are created dynamically and all appended to the\n // same position within the document (and do not have their `async` attributes\n // set to `false`, at least in browsers that support async script evaluation.\n // other than the very end of the document.\n // - If any scripts are added with the `async` attribute set to `true` in a browser\n // that supports it.\n // - May get confused by `script` elements within `svg` elements\n return scripts[scripts.length - 1] || null;\n*/\n}\n\n\n// Get the originating currently executing (i.e. yes, EXECUTING) `script` DOM\n// element or attribute node (e.g. `onclick`) for the caller function,\n// regardless of whether it is that `script` DOM element is currently being\n// evaluated for the first time. The originating currently executing `script`\n// DOM element [or attribute node] is the originator of the current execution stack.\nfunction _originatingExecutingScript() {\n // TODO: Implement!\n // Fixes #2\n // See https://github.com/JamesMGreene/currentExecutingScript/issues/2\n return null;\n}\n\n// Get the nearest currently executing (i.e. yes, EXECUTING) `script` DOM\n// element for the caller function, regardless of whether it is that `script`\n// DOM element is currently being evaluated for the first time.\nfunction _nearestExecutingScript() {\n /*jshint noarg:false */\n\n // Yes, this IS possible, i.e. if a script removes other scripts (or itself)\n if (scripts.length === 0) {\n return null;\n }\n\n var i, e, stack, url, script,\n eligibleScripts = [],\n skipStackDepth = _nearestExecutingScript.skipStackDepth || 1,\n\n // TODO: Implement!\n // Fixes #4 in part\n // See https://github.com/JamesMGreene/currentExecutingScript/issues/4\n callerFnSource = null; //(\"\" + (_nearestExecutingScript.caller || \"\")) || null;\n\n // This part will only help in IE 6-10.\n for (i = 0; i < scripts.length; i++) {\n if (isNotOpera && supportsScriptReadyState) {\n if (scriptReadyRegex.test(scripts[i].readyState)) {\n eligibleScripts.push(scripts[i]);\n }\n }\n else {\n eligibleScripts.push(scripts[i]);\n }\n }\n\n e = new Error();\n if (hasStackBeforeThrowing) {\n stack = e.stack;\n }\n if (!stack && hasStackAfterThrowing) {\n try {\n throw e;\n }\n catch (err) {\n // NOTE: Cannot use `err.sourceURL` or `err.fileName` as they will always be THIS script\n stack = err.stack;\n }\n }\n\n if (stack) {\n url = getScriptUrlFromStack(stack, skipStackDepth);\n script = getScriptFromUrl(url, eligibleScripts);\n\n if (!script && pageUrl && url === pageUrl) {\n // Try to find the correct inline script by searching through\n // inline scripts' text content for the caller function's source\n // code to be present.\n if (callerFnSource) {\n script = getInlineScriptFromCallerSource(callerFnSource, eligibleScripts);\n }\n // If the caller function's source code is not available, see if\n // there is only one inline script element in the DOM and return\n // that (even though it may be wrong)...\n else {\n // NOTE: This is a loose assumption that could be inaccurate!\n //\n // Inaccuracies:\n // - If the inline script that initiated the call was also removed from the DOM.\n // - If the call was initiated by an element's inline event handler,\n // e.g. `click`\n script = getSoleInlineScript(eligibleScripts);\n }\n }\n }\n\n //\n // Welcome to the Island of Inaccurate Assumptions!\n // NOTE: ALL of the following are loose assumptions that could be inaccurate!\n //\n\n if (!script) {\n // Inaccuracies:\n // - If the inline script that initiated the call was also removed from the DOM.\n // - If the call was initiated by an element's inline event handler,\n // e.g. `click`\n if (eligibleScripts.length === 1) {\n script = eligibleScripts[0];\n }\n }\n\n if (!script) {\n // Inaccuracies:\n // - If script currently being synchronously evaluated by the parser is the\n // originator of this call stack but NOT the source script of the caller/invocation\n // e.g.\n // ```html\n // \n // \n if (hasNativeCurrentScriptAccessor) {\n script = document.currentScript;\n }\n }\n\n if (!script) {\n // Inaccuracies:\n // - If script currently being synchronously evaluated by the parser is the\n // originator of this call stack but NOT the source script of the caller/invocation\n // e.g.\n // ```html\n // \n // \n if (isNotOpera && supportsScriptReadyState) {\n for (i = eligibleScripts.length; i--; ) {\n if (eligibleScripts[i].readyState === \"interactive\") {\n script = eligibleScripts[i];\n break;\n }\n }\n }\n }\n\n if (!script) {\n // Inaccuracies:\n // - If a script is created dynamically and appended to some position\n // other than the very end of the document.\n // - If multiple scripts are created dynamically and all appended to the\n // same position within the document (and do not have their `async` attributes\n // set to `false`, at least in browsers that support async script evaluation.\n // other than the very end of the document.\n // - If any scripts are added with the `async` attribute set to `true` in a browser\n // that supports it.\n // - May get confused by `script` elements within `svg` elements\n // - If script currently being synchronously evaluated by the parser is the\n // originator of this call stack but NOT the source script of the caller/invocation\n // e.g.\n // ```html\n // \n // \n // ```\n script = eligibleScripts[eligibleScripts.length - 1] || null;\n }\n\n return script;\n}\n\n// Default stack depth to skip over when analyzing call stack frames\n_nearestExecutingScript.skipStackDepth = 1;\n\n\n\n //\n // Export the API\n //\n var currentExecutingScript = _nearestExecutingScript; // default\n currentExecutingScript.near = _nearestExecutingScript;\n currentExecutingScript.far = _farthestExecutingScript;\n currentExecutingScript.origin = _originatingExecutingScript;\n\n\n // Just return a value to define the module export.\n // This example returns an object, but the module\n // can return a function as the exported value.\n return currentExecutingScript;\n })\n);\n","// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nvar R = typeof Reflect === 'object' ? Reflect : null\nvar ReflectApply = R && typeof R.apply === 'function'\n ? R.apply\n : function ReflectApply(target, receiver, args) {\n return Function.prototype.apply.call(target, receiver, args);\n }\n\nvar ReflectOwnKeys\nif (R && typeof R.ownKeys === 'function') {\n ReflectOwnKeys = R.ownKeys\n} else if (Object.getOwnPropertySymbols) {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target)\n .concat(Object.getOwnPropertySymbols(target));\n };\n} else {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target);\n };\n}\n\nfunction ProcessEmitWarning(warning) {\n if (console && console.warn) console.warn(warning);\n}\n\nvar NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {\n return value !== value;\n}\n\nfunction EventEmitter() {\n EventEmitter.init.call(this);\n}\nmodule.exports = EventEmitter;\nmodule.exports.once = once;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._eventsCount = 0;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nvar defaultMaxListeners = 10;\n\nfunction checkListener(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('The \"listener\" argument must be of type Function. Received type ' + typeof listener);\n }\n}\n\nObject.defineProperty(EventEmitter, 'defaultMaxListeners', {\n enumerable: true,\n get: function() {\n return defaultMaxListeners;\n },\n set: function(arg) {\n if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {\n throw new RangeError('The value of \"defaultMaxListeners\" is out of range. It must be a non-negative number. Received ' + arg + '.');\n }\n defaultMaxListeners = arg;\n }\n});\n\nEventEmitter.init = function() {\n\n if (this._events === undefined ||\n this._events === Object.getPrototypeOf(this)._events) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n }\n\n this._maxListeners = this._maxListeners || undefined;\n};\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {\n if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {\n throw new RangeError('The value of \"n\" is out of range. It must be a non-negative number. Received ' + n + '.');\n }\n this._maxListeners = n;\n return this;\n};\n\nfunction _getMaxListeners(that) {\n if (that._maxListeners === undefined)\n return EventEmitter.defaultMaxListeners;\n return that._maxListeners;\n}\n\nEventEmitter.prototype.getMaxListeners = function getMaxListeners() {\n return _getMaxListeners(this);\n};\n\nEventEmitter.prototype.emit = function emit(type) {\n var args = [];\n for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);\n var doError = (type === 'error');\n\n var events = this._events;\n if (events !== undefined)\n doError = (doError && events.error === undefined);\n else if (!doError)\n return false;\n\n // If there is no 'error' event listener then throw.\n if (doError) {\n var er;\n if (args.length > 0)\n er = args[0];\n if (er instanceof Error) {\n // Note: The comments on the `throw` lines are intentional, they show\n // up in Node's output if this results in an unhandled exception.\n throw er; // Unhandled 'error' event\n }\n // At least give some kind of context to the user\n var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));\n err.context = er;\n throw err; // Unhandled 'error' event\n }\n\n var handler = events[type];\n\n if (handler === undefined)\n return false;\n\n if (typeof handler === 'function') {\n ReflectApply(handler, this, args);\n } else {\n var len = handler.length;\n var listeners = arrayClone(handler, len);\n for (var i = 0; i < len; ++i)\n ReflectApply(listeners[i], this, args);\n }\n\n return true;\n};\n\nfunction _addListener(target, type, listener, prepend) {\n var m;\n var events;\n var existing;\n\n checkListener(listener);\n\n events = target._events;\n if (events === undefined) {\n events = target._events = Object.create(null);\n target._eventsCount = 0;\n } else {\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (events.newListener !== undefined) {\n target.emit('newListener', type,\n listener.listener ? listener.listener : listener);\n\n // Re-assign `events` because a newListener handler could have caused the\n // this._events to be assigned to a new object\n events = target._events;\n }\n existing = events[type];\n }\n\n if (existing === undefined) {\n // Optimize the case of one listener. Don't need the extra array object.\n existing = events[type] = listener;\n ++target._eventsCount;\n } else {\n if (typeof existing === 'function') {\n // Adding the second element, need to change to array.\n existing = events[type] =\n prepend ? [listener, existing] : [existing, listener];\n // If we've already got an array, just append.\n } else if (prepend) {\n existing.unshift(listener);\n } else {\n existing.push(listener);\n }\n\n // Check for listener leak\n m = _getMaxListeners(target);\n if (m > 0 && existing.length > m && !existing.warned) {\n existing.warned = true;\n // No error code for this since it is a Warning\n // eslint-disable-next-line no-restricted-syntax\n var w = new Error('Possible EventEmitter memory leak detected. ' +\n existing.length + ' ' + String(type) + ' listeners ' +\n 'added. Use emitter.setMaxListeners() to ' +\n 'increase limit');\n w.name = 'MaxListenersExceededWarning';\n w.emitter = target;\n w.type = type;\n w.count = existing.length;\n ProcessEmitWarning(w);\n }\n }\n\n return target;\n}\n\nEventEmitter.prototype.addListener = function addListener(type, listener) {\n return _addListener(this, type, listener, false);\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.prependListener =\n function prependListener(type, listener) {\n return _addListener(this, type, listener, true);\n };\n\nfunction onceWrapper() {\n if (!this.fired) {\n this.target.removeListener(this.type, this.wrapFn);\n this.fired = true;\n if (arguments.length === 0)\n return this.listener.call(this.target);\n return this.listener.apply(this.target, arguments);\n }\n}\n\nfunction _onceWrap(target, type, listener) {\n var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };\n var wrapped = onceWrapper.bind(state);\n wrapped.listener = listener;\n state.wrapFn = wrapped;\n return wrapped;\n}\n\nEventEmitter.prototype.once = function once(type, listener) {\n checkListener(listener);\n this.on(type, _onceWrap(this, type, listener));\n return this;\n};\n\nEventEmitter.prototype.prependOnceListener =\n function prependOnceListener(type, listener) {\n checkListener(listener);\n this.prependListener(type, _onceWrap(this, type, listener));\n return this;\n };\n\n// Emits a 'removeListener' event if and only if the listener was removed.\nEventEmitter.prototype.removeListener =\n function removeListener(type, listener) {\n var list, events, position, i, originalListener;\n\n checkListener(listener);\n\n events = this._events;\n if (events === undefined)\n return this;\n\n list = events[type];\n if (list === undefined)\n return this;\n\n if (list === listener || list.listener === listener) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else {\n delete events[type];\n if (events.removeListener)\n this.emit('removeListener', type, list.listener || listener);\n }\n } else if (typeof list !== 'function') {\n position = -1;\n\n for (i = list.length - 1; i >= 0; i--) {\n if (list[i] === listener || list[i].listener === listener) {\n originalListener = list[i].listener;\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (position === 0)\n list.shift();\n else {\n spliceOne(list, position);\n }\n\n if (list.length === 1)\n events[type] = list[0];\n\n if (events.removeListener !== undefined)\n this.emit('removeListener', type, originalListener || listener);\n }\n\n return this;\n };\n\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\n\nEventEmitter.prototype.removeAllListeners =\n function removeAllListeners(type) {\n var listeners, events, i;\n\n events = this._events;\n if (events === undefined)\n return this;\n\n // not listening for removeListener, no need to emit\n if (events.removeListener === undefined) {\n if (arguments.length === 0) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n } else if (events[type] !== undefined) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else\n delete events[type];\n }\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n var keys = Object.keys(events);\n var key;\n for (i = 0; i < keys.length; ++i) {\n key = keys[i];\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = Object.create(null);\n this._eventsCount = 0;\n return this;\n }\n\n listeners = events[type];\n\n if (typeof listeners === 'function') {\n this.removeListener(type, listeners);\n } else if (listeners !== undefined) {\n // LIFO order\n for (i = listeners.length - 1; i >= 0; i--) {\n this.removeListener(type, listeners[i]);\n }\n }\n\n return this;\n };\n\nfunction _listeners(target, type, unwrap) {\n var events = target._events;\n\n if (events === undefined)\n return [];\n\n var evlistener = events[type];\n if (evlistener === undefined)\n return [];\n\n if (typeof evlistener === 'function')\n return unwrap ? [evlistener.listener || evlistener] : [evlistener];\n\n return unwrap ?\n unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);\n}\n\nEventEmitter.prototype.listeners = function listeners(type) {\n return _listeners(this, type, true);\n};\n\nEventEmitter.prototype.rawListeners = function rawListeners(type) {\n return _listeners(this, type, false);\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n if (typeof emitter.listenerCount === 'function') {\n return emitter.listenerCount(type);\n } else {\n return listenerCount.call(emitter, type);\n }\n};\n\nEventEmitter.prototype.listenerCount = listenerCount;\nfunction listenerCount(type) {\n var events = this._events;\n\n if (events !== undefined) {\n var evlistener = events[type];\n\n if (typeof evlistener === 'function') {\n return 1;\n } else if (evlistener !== undefined) {\n return evlistener.length;\n }\n }\n\n return 0;\n}\n\nEventEmitter.prototype.eventNames = function eventNames() {\n return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];\n};\n\nfunction arrayClone(arr, n) {\n var copy = new Array(n);\n for (var i = 0; i < n; ++i)\n copy[i] = arr[i];\n return copy;\n}\n\nfunction spliceOne(list, index) {\n for (; index + 1 < list.length; index++)\n list[index] = list[index + 1];\n list.pop();\n}\n\nfunction unwrapListeners(arr) {\n var ret = new Array(arr.length);\n for (var i = 0; i < ret.length; ++i) {\n ret[i] = arr[i].listener || arr[i];\n }\n return ret;\n}\n\nfunction once(emitter, name) {\n return new Promise(function (resolve, reject) {\n function errorListener(err) {\n emitter.removeListener(name, resolver);\n reject(err);\n }\n\n function resolver() {\n if (typeof emitter.removeListener === 'function') {\n emitter.removeListener('error', errorListener);\n }\n resolve([].slice.call(arguments));\n };\n\n eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });\n if (name !== 'error') {\n addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });\n }\n });\n}\n\nfunction addErrorHandlerIfEventEmitter(emitter, handler, flags) {\n if (typeof emitter.on === 'function') {\n eventTargetAgnosticAddListener(emitter, 'error', handler, flags);\n }\n}\n\nfunction eventTargetAgnosticAddListener(emitter, name, listener, flags) {\n if (typeof emitter.on === 'function') {\n if (flags.once) {\n emitter.once(name, listener);\n } else {\n emitter.on(name, listener);\n }\n } else if (typeof emitter.addEventListener === 'function') {\n // EventTarget does not have `error` event semantics like Node\n // EventEmitters, we do not listen for `error` events here.\n emitter.addEventListener(name, function wrapListener(arg) {\n // IE does not have builtin `{ once: true }` support so we\n // have to do it manually.\n if (flags.once) {\n emitter.removeEventListener(name, wrapListener);\n }\n listener(arg);\n });\n } else {\n throw new TypeError('The \"emitter\" argument must be of type EventEmitter. Received type ' + typeof emitter);\n }\n}\n","/**\n * [js-md5]{@link https://github.com/emn178/js-md5}\n *\n * @namespace md5\n * @version 0.7.3\n * @author Chen, Yi-Cyuan [emn178@gmail.com]\n * @copyright Chen, Yi-Cyuan 2014-2017\n * @license MIT\n */\n(function () {\n 'use strict';\n\n var ERROR = 'input is invalid type';\n var WINDOW = typeof window === 'object';\n var root = WINDOW ? window : {};\n if (root.JS_MD5_NO_WINDOW) {\n WINDOW = false;\n }\n var WEB_WORKER = !WINDOW && typeof self === 'object';\n var NODE_JS = !root.JS_MD5_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;\n if (NODE_JS) {\n root = global;\n } else if (WEB_WORKER) {\n root = self;\n }\n var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && typeof module === 'object' && module.exports;\n var AMD = typeof define === 'function' && define.amd;\n var ARRAY_BUFFER = !root.JS_MD5_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';\n var HEX_CHARS = '0123456789abcdef'.split('');\n var EXTRA = [128, 32768, 8388608, -2147483648];\n var SHIFT = [0, 8, 16, 24];\n var OUTPUT_TYPES = ['hex', 'array', 'digest', 'buffer', 'arrayBuffer', 'base64'];\n var BASE64_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');\n\n var blocks = [], buffer8;\n if (ARRAY_BUFFER) {\n var buffer = new ArrayBuffer(68);\n buffer8 = new Uint8Array(buffer);\n blocks = new Uint32Array(buffer);\n }\n\n if (root.JS_MD5_NO_NODE_JS || !Array.isArray) {\n Array.isArray = function (obj) {\n return Object.prototype.toString.call(obj) === '[object Array]';\n };\n }\n\n if (ARRAY_BUFFER && (root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) {\n ArrayBuffer.isView = function (obj) {\n return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;\n };\n }\n\n /**\n * @method hex\n * @memberof md5\n * @description Output hash as hex string\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {String} Hex string\n * @example\n * md5.hex('The quick brown fox jumps over the lazy dog');\n * // equal to\n * md5('The quick brown fox jumps over the lazy dog');\n */\n /**\n * @method digest\n * @memberof md5\n * @description Output hash as bytes array\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {Array} Bytes array\n * @example\n * md5.digest('The quick brown fox jumps over the lazy dog');\n */\n /**\n * @method array\n * @memberof md5\n * @description Output hash as bytes array\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {Array} Bytes array\n * @example\n * md5.array('The quick brown fox jumps over the lazy dog');\n */\n /**\n * @method arrayBuffer\n * @memberof md5\n * @description Output hash as ArrayBuffer\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {ArrayBuffer} ArrayBuffer\n * @example\n * md5.arrayBuffer('The quick brown fox jumps over the lazy dog');\n */\n /**\n * @method buffer\n * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.\n * @memberof md5\n * @description Output hash as ArrayBuffer\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {ArrayBuffer} ArrayBuffer\n * @example\n * md5.buffer('The quick brown fox jumps over the lazy dog');\n */\n /**\n * @method base64\n * @memberof md5\n * @description Output hash as base64 string\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {String} base64 string\n * @example\n * md5.base64('The quick brown fox jumps over the lazy dog');\n */\n var createOutputMethod = function (outputType) {\n return function (message) {\n return new Md5(true).update(message)[outputType]();\n };\n };\n\n /**\n * @method create\n * @memberof md5\n * @description Create Md5 object\n * @returns {Md5} Md5 object.\n * @example\n * var hash = md5.create();\n */\n /**\n * @method update\n * @memberof md5\n * @description Create and update Md5 object\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {Md5} Md5 object.\n * @example\n * var hash = md5.update('The quick brown fox jumps over the lazy dog');\n * // equal to\n * var hash = md5.create();\n * hash.update('The quick brown fox jumps over the lazy dog');\n */\n var createMethod = function () {\n var method = createOutputMethod('hex');\n if (NODE_JS) {\n method = nodeWrap(method);\n }\n method.create = function () {\n return new Md5();\n };\n method.update = function (message) {\n return method.create().update(message);\n };\n for (var i = 0; i < OUTPUT_TYPES.length; ++i) {\n var type = OUTPUT_TYPES[i];\n method[type] = createOutputMethod(type);\n }\n return method;\n };\n\n var nodeWrap = function (method) {\n var crypto = eval(\"require('crypto')\");\n var Buffer = eval(\"require('buffer').Buffer\");\n var nodeMethod = function (message) {\n if (typeof message === 'string') {\n return crypto.createHash('md5').update(message, 'utf8').digest('hex');\n } else {\n if (message === null || message === undefined) {\n throw ERROR;\n } else if (message.constructor === ArrayBuffer) {\n message = new Uint8Array(message);\n }\n }\n if (Array.isArray(message) || ArrayBuffer.isView(message) ||\n message.constructor === Buffer) {\n return crypto.createHash('md5').update(new Buffer(message)).digest('hex');\n } else {\n return method(message);\n }\n };\n return nodeMethod;\n };\n\n /**\n * Md5 class\n * @class Md5\n * @description This is internal class.\n * @see {@link md5.create}\n */\n function Md5(sharedMemory) {\n if (sharedMemory) {\n blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =\n blocks[4] = blocks[5] = blocks[6] = blocks[7] =\n blocks[8] = blocks[9] = blocks[10] = blocks[11] =\n blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;\n this.blocks = blocks;\n this.buffer8 = buffer8;\n } else {\n if (ARRAY_BUFFER) {\n var buffer = new ArrayBuffer(68);\n this.buffer8 = new Uint8Array(buffer);\n this.blocks = new Uint32Array(buffer);\n } else {\n this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n }\n }\n this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0;\n this.finalized = this.hashed = false;\n this.first = true;\n }\n\n /**\n * @method update\n * @memberof Md5\n * @instance\n * @description Update hash\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {Md5} Md5 object.\n * @see {@link md5.update}\n */\n Md5.prototype.update = function (message) {\n if (this.finalized) {\n return;\n }\n\n var notString, type = typeof message;\n if (type !== 'string') {\n if (type === 'object') {\n if (message === null) {\n throw ERROR;\n } else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {\n message = new Uint8Array(message);\n } else if (!Array.isArray(message)) {\n if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) {\n throw ERROR;\n }\n }\n } else {\n throw ERROR;\n }\n notString = true;\n }\n var code, index = 0, i, length = message.length, blocks = this.blocks;\n var buffer8 = this.buffer8;\n\n while (index < length) {\n if (this.hashed) {\n this.hashed = false;\n blocks[0] = blocks[16];\n blocks[16] = blocks[1] = blocks[2] = blocks[3] =\n blocks[4] = blocks[5] = blocks[6] = blocks[7] =\n blocks[8] = blocks[9] = blocks[10] = blocks[11] =\n blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;\n }\n\n if (notString) {\n if (ARRAY_BUFFER) {\n for (i = this.start; index < length && i < 64; ++index) {\n buffer8[i++] = message[index];\n }\n } else {\n for (i = this.start; index < length && i < 64; ++index) {\n blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];\n }\n }\n } else {\n if (ARRAY_BUFFER) {\n for (i = this.start; index < length && i < 64; ++index) {\n code = message.charCodeAt(index);\n if (code < 0x80) {\n buffer8[i++] = code;\n } else if (code < 0x800) {\n buffer8[i++] = 0xc0 | (code >> 6);\n buffer8[i++] = 0x80 | (code & 0x3f);\n } else if (code < 0xd800 || code >= 0xe000) {\n buffer8[i++] = 0xe0 | (code >> 12);\n buffer8[i++] = 0x80 | ((code >> 6) & 0x3f);\n buffer8[i++] = 0x80 | (code & 0x3f);\n } else {\n code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));\n buffer8[i++] = 0xf0 | (code >> 18);\n buffer8[i++] = 0x80 | ((code >> 12) & 0x3f);\n buffer8[i++] = 0x80 | ((code >> 6) & 0x3f);\n buffer8[i++] = 0x80 | (code & 0x3f);\n }\n }\n } else {\n for (i = this.start; index < length && i < 64; ++index) {\n code = message.charCodeAt(index);\n if (code < 0x80) {\n blocks[i >> 2] |= code << SHIFT[i++ & 3];\n } else if (code < 0x800) {\n blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];\n blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];\n } else if (code < 0xd800 || code >= 0xe000) {\n blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];\n blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];\n blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];\n } else {\n code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));\n blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];\n blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];\n blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];\n blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];\n }\n }\n }\n }\n this.lastByteIndex = i;\n this.bytes += i - this.start;\n if (i >= 64) {\n this.start = i - 64;\n this.hash();\n this.hashed = true;\n } else {\n this.start = i;\n }\n }\n if (this.bytes > 4294967295) {\n this.hBytes += this.bytes / 4294967296 << 0;\n this.bytes = this.bytes % 4294967296;\n }\n return this;\n };\n\n Md5.prototype.finalize = function () {\n if (this.finalized) {\n return;\n }\n this.finalized = true;\n var blocks = this.blocks, i = this.lastByteIndex;\n blocks[i >> 2] |= EXTRA[i & 3];\n if (i >= 56) {\n if (!this.hashed) {\n this.hash();\n }\n blocks[0] = blocks[16];\n blocks[16] = blocks[1] = blocks[2] = blocks[3] =\n blocks[4] = blocks[5] = blocks[6] = blocks[7] =\n blocks[8] = blocks[9] = blocks[10] = blocks[11] =\n blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;\n }\n blocks[14] = this.bytes << 3;\n blocks[15] = this.hBytes << 3 | this.bytes >>> 29;\n this.hash();\n };\n\n Md5.prototype.hash = function () {\n var a, b, c, d, bc, da, blocks = this.blocks;\n\n if (this.first) {\n a = blocks[0] - 680876937;\n a = (a << 7 | a >>> 25) - 271733879 << 0;\n d = (-1732584194 ^ a & 2004318071) + blocks[1] - 117830708;\n d = (d << 12 | d >>> 20) + a << 0;\n c = (-271733879 ^ (d & (a ^ -271733879))) + blocks[2] - 1126478375;\n c = (c << 17 | c >>> 15) + d << 0;\n b = (a ^ (c & (d ^ a))) + blocks[3] - 1316259209;\n b = (b << 22 | b >>> 10) + c << 0;\n } else {\n a = this.h0;\n b = this.h1;\n c = this.h2;\n d = this.h3;\n a += (d ^ (b & (c ^ d))) + blocks[0] - 680876936;\n a = (a << 7 | a >>> 25) + b << 0;\n d += (c ^ (a & (b ^ c))) + blocks[1] - 389564586;\n d = (d << 12 | d >>> 20) + a << 0;\n c += (b ^ (d & (a ^ b))) + blocks[2] + 606105819;\n c = (c << 17 | c >>> 15) + d << 0;\n b += (a ^ (c & (d ^ a))) + blocks[3] - 1044525330;\n b = (b << 22 | b >>> 10) + c << 0;\n }\n\n a += (d ^ (b & (c ^ d))) + blocks[4] - 176418897;\n a = (a << 7 | a >>> 25) + b << 0;\n d += (c ^ (a & (b ^ c))) + blocks[5] + 1200080426;\n d = (d << 12 | d >>> 20) + a << 0;\n c += (b ^ (d & (a ^ b))) + blocks[6] - 1473231341;\n c = (c << 17 | c >>> 15) + d << 0;\n b += (a ^ (c & (d ^ a))) + blocks[7] - 45705983;\n b = (b << 22 | b >>> 10) + c << 0;\n a += (d ^ (b & (c ^ d))) + blocks[8] + 1770035416;\n a = (a << 7 | a >>> 25) + b << 0;\n d += (c ^ (a & (b ^ c))) + blocks[9] - 1958414417;\n d = (d << 12 | d >>> 20) + a << 0;\n c += (b ^ (d & (a ^ b))) + blocks[10] - 42063;\n c = (c << 17 | c >>> 15) + d << 0;\n b += (a ^ (c & (d ^ a))) + blocks[11] - 1990404162;\n b = (b << 22 | b >>> 10) + c << 0;\n a += (d ^ (b & (c ^ d))) + blocks[12] + 1804603682;\n a = (a << 7 | a >>> 25) + b << 0;\n d += (c ^ (a & (b ^ c))) + blocks[13] - 40341101;\n d = (d << 12 | d >>> 20) + a << 0;\n c += (b ^ (d & (a ^ b))) + blocks[14] - 1502002290;\n c = (c << 17 | c >>> 15) + d << 0;\n b += (a ^ (c & (d ^ a))) + blocks[15] + 1236535329;\n b = (b << 22 | b >>> 10) + c << 0;\n a += (c ^ (d & (b ^ c))) + blocks[1] - 165796510;\n a = (a << 5 | a >>> 27) + b << 0;\n d += (b ^ (c & (a ^ b))) + blocks[6] - 1069501632;\n d = (d << 9 | d >>> 23) + a << 0;\n c += (a ^ (b & (d ^ a))) + blocks[11] + 643717713;\n c = (c << 14 | c >>> 18) + d << 0;\n b += (d ^ (a & (c ^ d))) + blocks[0] - 373897302;\n b = (b << 20 | b >>> 12) + c << 0;\n a += (c ^ (d & (b ^ c))) + blocks[5] - 701558691;\n a = (a << 5 | a >>> 27) + b << 0;\n d += (b ^ (c & (a ^ b))) + blocks[10] + 38016083;\n d = (d << 9 | d >>> 23) + a << 0;\n c += (a ^ (b & (d ^ a))) + blocks[15] - 660478335;\n c = (c << 14 | c >>> 18) + d << 0;\n b += (d ^ (a & (c ^ d))) + blocks[4] - 405537848;\n b = (b << 20 | b >>> 12) + c << 0;\n a += (c ^ (d & (b ^ c))) + blocks[9] + 568446438;\n a = (a << 5 | a >>> 27) + b << 0;\n d += (b ^ (c & (a ^ b))) + blocks[14] - 1019803690;\n d = (d << 9 | d >>> 23) + a << 0;\n c += (a ^ (b & (d ^ a))) + blocks[3] - 187363961;\n c = (c << 14 | c >>> 18) + d << 0;\n b += (d ^ (a & (c ^ d))) + blocks[8] + 1163531501;\n b = (b << 20 | b >>> 12) + c << 0;\n a += (c ^ (d & (b ^ c))) + blocks[13] - 1444681467;\n a = (a << 5 | a >>> 27) + b << 0;\n d += (b ^ (c & (a ^ b))) + blocks[2] - 51403784;\n d = (d << 9 | d >>> 23) + a << 0;\n c += (a ^ (b & (d ^ a))) + blocks[7] + 1735328473;\n c = (c << 14 | c >>> 18) + d << 0;\n b += (d ^ (a & (c ^ d))) + blocks[12] - 1926607734;\n b = (b << 20 | b >>> 12) + c << 0;\n bc = b ^ c;\n a += (bc ^ d) + blocks[5] - 378558;\n a = (a << 4 | a >>> 28) + b << 0;\n d += (bc ^ a) + blocks[8] - 2022574463;\n d = (d << 11 | d >>> 21) + a << 0;\n da = d ^ a;\n c += (da ^ b) + blocks[11] + 1839030562;\n c = (c << 16 | c >>> 16) + d << 0;\n b += (da ^ c) + blocks[14] - 35309556;\n b = (b << 23 | b >>> 9) + c << 0;\n bc = b ^ c;\n a += (bc ^ d) + blocks[1] - 1530992060;\n a = (a << 4 | a >>> 28) + b << 0;\n d += (bc ^ a) + blocks[4] + 1272893353;\n d = (d << 11 | d >>> 21) + a << 0;\n da = d ^ a;\n c += (da ^ b) + blocks[7] - 155497632;\n c = (c << 16 | c >>> 16) + d << 0;\n b += (da ^ c) + blocks[10] - 1094730640;\n b = (b << 23 | b >>> 9) + c << 0;\n bc = b ^ c;\n a += (bc ^ d) + blocks[13] + 681279174;\n a = (a << 4 | a >>> 28) + b << 0;\n d += (bc ^ a) + blocks[0] - 358537222;\n d = (d << 11 | d >>> 21) + a << 0;\n da = d ^ a;\n c += (da ^ b) + blocks[3] - 722521979;\n c = (c << 16 | c >>> 16) + d << 0;\n b += (da ^ c) + blocks[6] + 76029189;\n b = (b << 23 | b >>> 9) + c << 0;\n bc = b ^ c;\n a += (bc ^ d) + blocks[9] - 640364487;\n a = (a << 4 | a >>> 28) + b << 0;\n d += (bc ^ a) + blocks[12] - 421815835;\n d = (d << 11 | d >>> 21) + a << 0;\n da = d ^ a;\n c += (da ^ b) + blocks[15] + 530742520;\n c = (c << 16 | c >>> 16) + d << 0;\n b += (da ^ c) + blocks[2] - 995338651;\n b = (b << 23 | b >>> 9) + c << 0;\n a += (c ^ (b | ~d)) + blocks[0] - 198630844;\n a = (a << 6 | a >>> 26) + b << 0;\n d += (b ^ (a | ~c)) + blocks[7] + 1126891415;\n d = (d << 10 | d >>> 22) + a << 0;\n c += (a ^ (d | ~b)) + blocks[14] - 1416354905;\n c = (c << 15 | c >>> 17) + d << 0;\n b += (d ^ (c | ~a)) + blocks[5] - 57434055;\n b = (b << 21 | b >>> 11) + c << 0;\n a += (c ^ (b | ~d)) + blocks[12] + 1700485571;\n a = (a << 6 | a >>> 26) + b << 0;\n d += (b ^ (a | ~c)) + blocks[3] - 1894986606;\n d = (d << 10 | d >>> 22) + a << 0;\n c += (a ^ (d | ~b)) + blocks[10] - 1051523;\n c = (c << 15 | c >>> 17) + d << 0;\n b += (d ^ (c | ~a)) + blocks[1] - 2054922799;\n b = (b << 21 | b >>> 11) + c << 0;\n a += (c ^ (b | ~d)) + blocks[8] + 1873313359;\n a = (a << 6 | a >>> 26) + b << 0;\n d += (b ^ (a | ~c)) + blocks[15] - 30611744;\n d = (d << 10 | d >>> 22) + a << 0;\n c += (a ^ (d | ~b)) + blocks[6] - 1560198380;\n c = (c << 15 | c >>> 17) + d << 0;\n b += (d ^ (c | ~a)) + blocks[13] + 1309151649;\n b = (b << 21 | b >>> 11) + c << 0;\n a += (c ^ (b | ~d)) + blocks[4] - 145523070;\n a = (a << 6 | a >>> 26) + b << 0;\n d += (b ^ (a | ~c)) + blocks[11] - 1120210379;\n d = (d << 10 | d >>> 22) + a << 0;\n c += (a ^ (d | ~b)) + blocks[2] + 718787259;\n c = (c << 15 | c >>> 17) + d << 0;\n b += (d ^ (c | ~a)) + blocks[9] - 343485551;\n b = (b << 21 | b >>> 11) + c << 0;\n\n if (this.first) {\n this.h0 = a + 1732584193 << 0;\n this.h1 = b - 271733879 << 0;\n this.h2 = c - 1732584194 << 0;\n this.h3 = d + 271733878 << 0;\n this.first = false;\n } else {\n this.h0 = this.h0 + a << 0;\n this.h1 = this.h1 + b << 0;\n this.h2 = this.h2 + c << 0;\n this.h3 = this.h3 + d << 0;\n }\n };\n\n /**\n * @method hex\n * @memberof Md5\n * @instance\n * @description Output hash as hex string\n * @returns {String} Hex string\n * @see {@link md5.hex}\n * @example\n * hash.hex();\n */\n Md5.prototype.hex = function () {\n this.finalize();\n\n var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3;\n\n return HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +\n HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] +\n HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] +\n HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] +\n HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +\n HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] +\n HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] +\n HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] +\n HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +\n HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] +\n HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] +\n HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] +\n HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +\n HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] +\n HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] +\n HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F];\n };\n\n /**\n * @method toString\n * @memberof Md5\n * @instance\n * @description Output hash as hex string\n * @returns {String} Hex string\n * @see {@link md5.hex}\n * @example\n * hash.toString();\n */\n Md5.prototype.toString = Md5.prototype.hex;\n\n /**\n * @method digest\n * @memberof Md5\n * @instance\n * @description Output hash as bytes array\n * @returns {Array} Bytes array\n * @see {@link md5.digest}\n * @example\n * hash.digest();\n */\n Md5.prototype.digest = function () {\n this.finalize();\n\n var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3;\n return [\n h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 24) & 0xFF,\n h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 24) & 0xFF,\n h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 24) & 0xFF,\n h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 24) & 0xFF\n ];\n };\n\n /**\n * @method array\n * @memberof Md5\n * @instance\n * @description Output hash as bytes array\n * @returns {Array} Bytes array\n * @see {@link md5.array}\n * @example\n * hash.array();\n */\n Md5.prototype.array = Md5.prototype.digest;\n\n /**\n * @method arrayBuffer\n * @memberof Md5\n * @instance\n * @description Output hash as ArrayBuffer\n * @returns {ArrayBuffer} ArrayBuffer\n * @see {@link md5.arrayBuffer}\n * @example\n * hash.arrayBuffer();\n */\n Md5.prototype.arrayBuffer = function () {\n this.finalize();\n\n var buffer = new ArrayBuffer(16);\n var blocks = new Uint32Array(buffer);\n blocks[0] = this.h0;\n blocks[1] = this.h1;\n blocks[2] = this.h2;\n blocks[3] = this.h3;\n return buffer;\n };\n\n /**\n * @method buffer\n * @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.\n * @memberof Md5\n * @instance\n * @description Output hash as ArrayBuffer\n * @returns {ArrayBuffer} ArrayBuffer\n * @see {@link md5.buffer}\n * @example\n * hash.buffer();\n */\n Md5.prototype.buffer = Md5.prototype.arrayBuffer;\n\n /**\n * @method base64\n * @memberof Md5\n * @instance\n * @description Output hash as base64 string\n * @returns {String} base64 string\n * @see {@link md5.base64}\n * @example\n * hash.base64();\n */\n Md5.prototype.base64 = function () {\n var v1, v2, v3, base64Str = '', bytes = this.array();\n for (var i = 0; i < 15;) {\n v1 = bytes[i++];\n v2 = bytes[i++];\n v3 = bytes[i++];\n base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] +\n BASE64_ENCODE_CHAR[(v1 << 4 | v2 >>> 4) & 63] +\n BASE64_ENCODE_CHAR[(v2 << 2 | v3 >>> 6) & 63] +\n BASE64_ENCODE_CHAR[v3 & 63];\n }\n v1 = bytes[i];\n base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] +\n BASE64_ENCODE_CHAR[(v1 << 4) & 63] +\n '==';\n return base64Str;\n };\n\n var exports = createMethod();\n\n if (COMMON_JS) {\n module.exports = exports;\n } else {\n /**\n * @method md5\b\n * @description Md5 hash function, export to global in browsers.\n * @param {String|Array|Uint8Array|ArrayBuffer} message message to hash\n * @returns {String} md5 hashes\n * @example\n * md5(''); // d41d8cd98f00b204e9800998ecf8427e\n * md5('The quick brown fox jumps over the lazy dog'); // 9e107d9d372bb6826bd81d3542a419d6\n * md5('The quick brown fox jumps over the lazy dog.'); // e4d909c290d0fb1ca068ffaddf22cbd0\n *\n * // It also supports UTF-8 encoding\n * md5('中文'); // a7bac2239fcdcb3a067903d8077c4a07\n *\n * // It also supports byte `Array`, `Uint8Array`, `ArrayBuffer`\n * md5([]); // d41d8cd98f00b204e9800998ecf8427e\n * md5(new Uint8Array([])); // d41d8cd98f00b204e9800998ecf8427e\n */\n root.md5 = exports;\n if (AMD) {\n define(function () {\n return exports;\n });\n }\n }\n})();\n","/**\n * lodash (Custom Build) \n * Build: `lodash modularize exports=\"npm\" -o ./`\n * Copyright jQuery Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n */\n\n/** Used as the size to enable large array optimizations. */\nvar LARGE_ARRAY_SIZE = 200;\n\n/** Used to stand-in for `undefined` hash values. */\nvar HASH_UNDEFINED = '__lodash_hash_undefined__';\n\n/** Used as references for various `Number` constants. */\nvar MAX_SAFE_INTEGER = 9007199254740991;\n\n/** `Object#toString` result references. */\nvar argsTag = '[object Arguments]',\n arrayTag = '[object Array]',\n boolTag = '[object Boolean]',\n dateTag = '[object Date]',\n errorTag = '[object Error]',\n funcTag = '[object Function]',\n genTag = '[object GeneratorFunction]',\n mapTag = '[object Map]',\n numberTag = '[object Number]',\n objectTag = '[object Object]',\n promiseTag = '[object Promise]',\n regexpTag = '[object RegExp]',\n setTag = '[object Set]',\n stringTag = '[object String]',\n symbolTag = '[object Symbol]',\n weakMapTag = '[object WeakMap]';\n\nvar arrayBufferTag = '[object ArrayBuffer]',\n dataViewTag = '[object DataView]',\n float32Tag = '[object Float32Array]',\n float64Tag = '[object Float64Array]',\n int8Tag = '[object Int8Array]',\n int16Tag = '[object Int16Array]',\n int32Tag = '[object Int32Array]',\n uint8Tag = '[object Uint8Array]',\n uint8ClampedTag = '[object Uint8ClampedArray]',\n uint16Tag = '[object Uint16Array]',\n uint32Tag = '[object Uint32Array]';\n\n/**\n * Used to match `RegExp`\n * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).\n */\nvar reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** Used to match `RegExp` flags from their coerced string values. */\nvar reFlags = /\\w*$/;\n\n/** Used to detect host constructors (Safari). */\nvar reIsHostCtor = /^\\[object .+?Constructor\\]$/;\n\n/** Used to detect unsigned integer values. */\nvar reIsUint = /^(?:0|[1-9]\\d*)$/;\n\n/** Used to identify `toStringTag` values supported by `_.clone`. */\nvar cloneableTags = {};\ncloneableTags[argsTag] = cloneableTags[arrayTag] =\ncloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =\ncloneableTags[boolTag] = cloneableTags[dateTag] =\ncloneableTags[float32Tag] = cloneableTags[float64Tag] =\ncloneableTags[int8Tag] = cloneableTags[int16Tag] =\ncloneableTags[int32Tag] = cloneableTags[mapTag] =\ncloneableTags[numberTag] = cloneableTags[objectTag] =\ncloneableTags[regexpTag] = cloneableTags[setTag] =\ncloneableTags[stringTag] = cloneableTags[symbolTag] =\ncloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =\ncloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;\ncloneableTags[errorTag] = cloneableTags[funcTag] =\ncloneableTags[weakMapTag] = false;\n\n/** Detect free variable `global` from Node.js. */\nvar freeGlobal = typeof global == 'object' && global && global.Object === Object && global;\n\n/** Detect free variable `self`. */\nvar freeSelf = typeof self == 'object' && self && self.Object === Object && self;\n\n/** Used as a reference to the global object. */\nvar root = freeGlobal || freeSelf || Function('return this')();\n\n/** Detect free variable `exports`. */\nvar freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;\n\n/** Detect free variable `module`. */\nvar freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;\n\n/** Detect the popular CommonJS extension `module.exports`. */\nvar moduleExports = freeModule && freeModule.exports === freeExports;\n\n/**\n * Adds the key-value `pair` to `map`.\n *\n * @private\n * @param {Object} map The map to modify.\n * @param {Array} pair The key-value pair to add.\n * @returns {Object} Returns `map`.\n */\nfunction addMapEntry(map, pair) {\n // Don't return `map.set` because it's not chainable in IE 11.\n map.set(pair[0], pair[1]);\n return map;\n}\n\n/**\n * Adds `value` to `set`.\n *\n * @private\n * @param {Object} set The set to modify.\n * @param {*} value The value to add.\n * @returns {Object} Returns `set`.\n */\nfunction addSetEntry(set, value) {\n // Don't return `set.add` because it's not chainable in IE 11.\n set.add(value);\n return set;\n}\n\n/**\n * A specialized version of `_.forEach` for arrays without support for\n * iteratee shorthands.\n *\n * @private\n * @param {Array} [array] The array to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array} Returns `array`.\n */\nfunction arrayEach(array, iteratee) {\n var index = -1,\n length = array ? array.length : 0;\n\n while (++index < length) {\n if (iteratee(array[index], index, array) === false) {\n break;\n }\n }\n return array;\n}\n\n/**\n * Appends the elements of `values` to `array`.\n *\n * @private\n * @param {Array} array The array to modify.\n * @param {Array} values The values to append.\n * @returns {Array} Returns `array`.\n */\nfunction arrayPush(array, values) {\n var index = -1,\n length = values.length,\n offset = array.length;\n\n while (++index < length) {\n array[offset + index] = values[index];\n }\n return array;\n}\n\n/**\n * A specialized version of `_.reduce` for arrays without support for\n * iteratee shorthands.\n *\n * @private\n * @param {Array} [array] The array to iterate over.\n * @param {Function} iteratee The function invoked per iteration.\n * @param {*} [accumulator] The initial value.\n * @param {boolean} [initAccum] Specify using the first element of `array` as\n * the initial value.\n * @returns {*} Returns the accumulated value.\n */\nfunction arrayReduce(array, iteratee, accumulator, initAccum) {\n var index = -1,\n length = array ? array.length : 0;\n\n if (initAccum && length) {\n accumulator = array[++index];\n }\n while (++index < length) {\n accumulator = iteratee(accumulator, array[index], index, array);\n }\n return accumulator;\n}\n\n/**\n * The base implementation of `_.times` without support for iteratee shorthands\n * or max array length checks.\n *\n * @private\n * @param {number} n The number of times to invoke `iteratee`.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array} Returns the array of results.\n */\nfunction baseTimes(n, iteratee) {\n var index = -1,\n result = Array(n);\n\n while (++index < n) {\n result[index] = iteratee(index);\n }\n return result;\n}\n\n/**\n * Gets the value at `key` of `object`.\n *\n * @private\n * @param {Object} [object] The object to query.\n * @param {string} key The key of the property to get.\n * @returns {*} Returns the property value.\n */\nfunction getValue(object, key) {\n return object == null ? undefined : object[key];\n}\n\n/**\n * Checks if `value` is a host object in IE < 9.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a host object, else `false`.\n */\nfunction isHostObject(value) {\n // Many host objects are `Object` objects that can coerce to strings\n // despite having improperly defined `toString` methods.\n var result = false;\n if (value != null && typeof value.toString != 'function') {\n try {\n result = !!(value + '');\n } catch (e) {}\n }\n return result;\n}\n\n/**\n * Converts `map` to its key-value pairs.\n *\n * @private\n * @param {Object} map The map to convert.\n * @returns {Array} Returns the key-value pairs.\n */\nfunction mapToArray(map) {\n var index = -1,\n result = Array(map.size);\n\n map.forEach(function(value, key) {\n result[++index] = [key, value];\n });\n return result;\n}\n\n/**\n * Creates a unary function that invokes `func` with its argument transformed.\n *\n * @private\n * @param {Function} func The function to wrap.\n * @param {Function} transform The argument transform.\n * @returns {Function} Returns the new function.\n */\nfunction overArg(func, transform) {\n return function(arg) {\n return func(transform(arg));\n };\n}\n\n/**\n * Converts `set` to an array of its values.\n *\n * @private\n * @param {Object} set The set to convert.\n * @returns {Array} Returns the values.\n */\nfunction setToArray(set) {\n var index = -1,\n result = Array(set.size);\n\n set.forEach(function(value) {\n result[++index] = value;\n });\n return result;\n}\n\n/** Used for built-in method references. */\nvar arrayProto = Array.prototype,\n funcProto = Function.prototype,\n objectProto = Object.prototype;\n\n/** Used to detect overreaching core-js shims. */\nvar coreJsData = root['__core-js_shared__'];\n\n/** Used to detect methods masquerading as native. */\nvar maskSrcKey = (function() {\n var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');\n return uid ? ('Symbol(src)_1.' + uid) : '';\n}());\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objectToString = objectProto.toString;\n\n/** Used to detect if a method is native. */\nvar reIsNative = RegExp('^' +\n funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\\\$&')\n .replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, '$1.*?') + '$'\n);\n\n/** Built-in value references. */\nvar Buffer = moduleExports ? root.Buffer : undefined,\n Symbol = root.Symbol,\n Uint8Array = root.Uint8Array,\n getPrototype = overArg(Object.getPrototypeOf, Object),\n objectCreate = Object.create,\n propertyIsEnumerable = objectProto.propertyIsEnumerable,\n splice = arrayProto.splice;\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeGetSymbols = Object.getOwnPropertySymbols,\n nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,\n nativeKeys = overArg(Object.keys, Object);\n\n/* Built-in method references that are verified to be native. */\nvar DataView = getNative(root, 'DataView'),\n Map = getNative(root, 'Map'),\n Promise = getNative(root, 'Promise'),\n Set = getNative(root, 'Set'),\n WeakMap = getNative(root, 'WeakMap'),\n nativeCreate = getNative(Object, 'create');\n\n/** Used to detect maps, sets, and weakmaps. */\nvar dataViewCtorString = toSource(DataView),\n mapCtorString = toSource(Map),\n promiseCtorString = toSource(Promise),\n setCtorString = toSource(Set),\n weakMapCtorString = toSource(WeakMap);\n\n/** Used to convert symbols to primitives and strings. */\nvar symbolProto = Symbol ? Symbol.prototype : undefined,\n symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;\n\n/**\n * Creates a hash object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction Hash(entries) {\n var index = -1,\n length = entries ? entries.length : 0;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the hash.\n *\n * @private\n * @name clear\n * @memberOf Hash\n */\nfunction hashClear() {\n this.__data__ = nativeCreate ? nativeCreate(null) : {};\n}\n\n/**\n * Removes `key` and its value from the hash.\n *\n * @private\n * @name delete\n * @memberOf Hash\n * @param {Object} hash The hash to modify.\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction hashDelete(key) {\n return this.has(key) && delete this.__data__[key];\n}\n\n/**\n * Gets the hash value for `key`.\n *\n * @private\n * @name get\n * @memberOf Hash\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction hashGet(key) {\n var data = this.__data__;\n if (nativeCreate) {\n var result = data[key];\n return result === HASH_UNDEFINED ? undefined : result;\n }\n return hasOwnProperty.call(data, key) ? data[key] : undefined;\n}\n\n/**\n * Checks if a hash value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf Hash\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction hashHas(key) {\n var data = this.__data__;\n return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);\n}\n\n/**\n * Sets the hash `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf Hash\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the hash instance.\n */\nfunction hashSet(key, value) {\n var data = this.__data__;\n data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;\n return this;\n}\n\n// Add methods to `Hash`.\nHash.prototype.clear = hashClear;\nHash.prototype['delete'] = hashDelete;\nHash.prototype.get = hashGet;\nHash.prototype.has = hashHas;\nHash.prototype.set = hashSet;\n\n/**\n * Creates an list cache object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction ListCache(entries) {\n var index = -1,\n length = entries ? entries.length : 0;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the list cache.\n *\n * @private\n * @name clear\n * @memberOf ListCache\n */\nfunction listCacheClear() {\n this.__data__ = [];\n}\n\n/**\n * Removes `key` and its value from the list cache.\n *\n * @private\n * @name delete\n * @memberOf ListCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction listCacheDelete(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n return false;\n }\n var lastIndex = data.length - 1;\n if (index == lastIndex) {\n data.pop();\n } else {\n splice.call(data, index, 1);\n }\n return true;\n}\n\n/**\n * Gets the list cache value for `key`.\n *\n * @private\n * @name get\n * @memberOf ListCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction listCacheGet(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n return index < 0 ? undefined : data[index][1];\n}\n\n/**\n * Checks if a list cache value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf ListCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction listCacheHas(key) {\n return assocIndexOf(this.__data__, key) > -1;\n}\n\n/**\n * Sets the list cache `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf ListCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the list cache instance.\n */\nfunction listCacheSet(key, value) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n data.push([key, value]);\n } else {\n data[index][1] = value;\n }\n return this;\n}\n\n// Add methods to `ListCache`.\nListCache.prototype.clear = listCacheClear;\nListCache.prototype['delete'] = listCacheDelete;\nListCache.prototype.get = listCacheGet;\nListCache.prototype.has = listCacheHas;\nListCache.prototype.set = listCacheSet;\n\n/**\n * Creates a map cache object to store key-value pairs.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction MapCache(entries) {\n var index = -1,\n length = entries ? entries.length : 0;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the map.\n *\n * @private\n * @name clear\n * @memberOf MapCache\n */\nfunction mapCacheClear() {\n this.__data__ = {\n 'hash': new Hash,\n 'map': new (Map || ListCache),\n 'string': new Hash\n };\n}\n\n/**\n * Removes `key` and its value from the map.\n *\n * @private\n * @name delete\n * @memberOf MapCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction mapCacheDelete(key) {\n return getMapData(this, key)['delete'](key);\n}\n\n/**\n * Gets the map value for `key`.\n *\n * @private\n * @name get\n * @memberOf MapCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction mapCacheGet(key) {\n return getMapData(this, key).get(key);\n}\n\n/**\n * Checks if a map value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf MapCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction mapCacheHas(key) {\n return getMapData(this, key).has(key);\n}\n\n/**\n * Sets the map `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf MapCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the map cache instance.\n */\nfunction mapCacheSet(key, value) {\n getMapData(this, key).set(key, value);\n return this;\n}\n\n// Add methods to `MapCache`.\nMapCache.prototype.clear = mapCacheClear;\nMapCache.prototype['delete'] = mapCacheDelete;\nMapCache.prototype.get = mapCacheGet;\nMapCache.prototype.has = mapCacheHas;\nMapCache.prototype.set = mapCacheSet;\n\n/**\n * Creates a stack cache object to store key-value pairs.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction Stack(entries) {\n this.__data__ = new ListCache(entries);\n}\n\n/**\n * Removes all key-value entries from the stack.\n *\n * @private\n * @name clear\n * @memberOf Stack\n */\nfunction stackClear() {\n this.__data__ = new ListCache;\n}\n\n/**\n * Removes `key` and its value from the stack.\n *\n * @private\n * @name delete\n * @memberOf Stack\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction stackDelete(key) {\n return this.__data__['delete'](key);\n}\n\n/**\n * Gets the stack value for `key`.\n *\n * @private\n * @name get\n * @memberOf Stack\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction stackGet(key) {\n return this.__data__.get(key);\n}\n\n/**\n * Checks if a stack value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf Stack\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction stackHas(key) {\n return this.__data__.has(key);\n}\n\n/**\n * Sets the stack `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf Stack\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the stack cache instance.\n */\nfunction stackSet(key, value) {\n var cache = this.__data__;\n if (cache instanceof ListCache) {\n var pairs = cache.__data__;\n if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {\n pairs.push([key, value]);\n return this;\n }\n cache = this.__data__ = new MapCache(pairs);\n }\n cache.set(key, value);\n return this;\n}\n\n// Add methods to `Stack`.\nStack.prototype.clear = stackClear;\nStack.prototype['delete'] = stackDelete;\nStack.prototype.get = stackGet;\nStack.prototype.has = stackHas;\nStack.prototype.set = stackSet;\n\n/**\n * Creates an array of the enumerable property names of the array-like `value`.\n *\n * @private\n * @param {*} value The value to query.\n * @param {boolean} inherited Specify returning inherited property names.\n * @returns {Array} Returns the array of property names.\n */\nfunction arrayLikeKeys(value, inherited) {\n // Safari 8.1 makes `arguments.callee` enumerable in strict mode.\n // Safari 9 makes `arguments.length` enumerable in strict mode.\n var result = (isArray(value) || isArguments(value))\n ? baseTimes(value.length, String)\n : [];\n\n var length = result.length,\n skipIndexes = !!length;\n\n for (var key in value) {\n if ((inherited || hasOwnProperty.call(value, key)) &&\n !(skipIndexes && (key == 'length' || isIndex(key, length)))) {\n result.push(key);\n }\n }\n return result;\n}\n\n/**\n * Assigns `value` to `key` of `object` if the existing value is not equivalent\n * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)\n * for equality comparisons.\n *\n * @private\n * @param {Object} object The object to modify.\n * @param {string} key The key of the property to assign.\n * @param {*} value The value to assign.\n */\nfunction assignValue(object, key, value) {\n var objValue = object[key];\n if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||\n (value === undefined && !(key in object))) {\n object[key] = value;\n }\n}\n\n/**\n * Gets the index at which the `key` is found in `array` of key-value pairs.\n *\n * @private\n * @param {Array} array The array to inspect.\n * @param {*} key The key to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n */\nfunction assocIndexOf(array, key) {\n var length = array.length;\n while (length--) {\n if (eq(array[length][0], key)) {\n return length;\n }\n }\n return -1;\n}\n\n/**\n * The base implementation of `_.assign` without support for multiple sources\n * or `customizer` functions.\n *\n * @private\n * @param {Object} object The destination object.\n * @param {Object} source The source object.\n * @returns {Object} Returns `object`.\n */\nfunction baseAssign(object, source) {\n return object && copyObject(source, keys(source), object);\n}\n\n/**\n * The base implementation of `_.clone` and `_.cloneDeep` which tracks\n * traversed objects.\n *\n * @private\n * @param {*} value The value to clone.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @param {boolean} [isFull] Specify a clone including symbols.\n * @param {Function} [customizer] The function to customize cloning.\n * @param {string} [key] The key of `value`.\n * @param {Object} [object] The parent object of `value`.\n * @param {Object} [stack] Tracks traversed objects and their clone counterparts.\n * @returns {*} Returns the cloned value.\n */\nfunction baseClone(value, isDeep, isFull, customizer, key, object, stack) {\n var result;\n if (customizer) {\n result = object ? customizer(value, key, object, stack) : customizer(value);\n }\n if (result !== undefined) {\n return result;\n }\n if (!isObject(value)) {\n return value;\n }\n var isArr = isArray(value);\n if (isArr) {\n result = initCloneArray(value);\n if (!isDeep) {\n return copyArray(value, result);\n }\n } else {\n var tag = getTag(value),\n isFunc = tag == funcTag || tag == genTag;\n\n if (isBuffer(value)) {\n return cloneBuffer(value, isDeep);\n }\n if (tag == objectTag || tag == argsTag || (isFunc && !object)) {\n if (isHostObject(value)) {\n return object ? value : {};\n }\n result = initCloneObject(isFunc ? {} : value);\n if (!isDeep) {\n return copySymbols(value, baseAssign(result, value));\n }\n } else {\n if (!cloneableTags[tag]) {\n return object ? value : {};\n }\n result = initCloneByTag(value, tag, baseClone, isDeep);\n }\n }\n // Check for circular references and return its corresponding clone.\n stack || (stack = new Stack);\n var stacked = stack.get(value);\n if (stacked) {\n return stacked;\n }\n stack.set(value, result);\n\n if (!isArr) {\n var props = isFull ? getAllKeys(value) : keys(value);\n }\n arrayEach(props || value, function(subValue, key) {\n if (props) {\n key = subValue;\n subValue = value[key];\n }\n // Recursively populate clone (susceptible to call stack limits).\n assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack));\n });\n return result;\n}\n\n/**\n * The base implementation of `_.create` without support for assigning\n * properties to the created object.\n *\n * @private\n * @param {Object} prototype The object to inherit from.\n * @returns {Object} Returns the new object.\n */\nfunction baseCreate(proto) {\n return isObject(proto) ? objectCreate(proto) : {};\n}\n\n/**\n * The base implementation of `getAllKeys` and `getAllKeysIn` which uses\n * `keysFunc` and `symbolsFunc` to get the enumerable property names and\n * symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {Function} keysFunc The function to get the keys of `object`.\n * @param {Function} symbolsFunc The function to get the symbols of `object`.\n * @returns {Array} Returns the array of property names and symbols.\n */\nfunction baseGetAllKeys(object, keysFunc, symbolsFunc) {\n var result = keysFunc(object);\n return isArray(object) ? result : arrayPush(result, symbolsFunc(object));\n}\n\n/**\n * The base implementation of `getTag`.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nfunction baseGetTag(value) {\n return objectToString.call(value);\n}\n\n/**\n * The base implementation of `_.isNative` without bad shim checks.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a native function,\n * else `false`.\n */\nfunction baseIsNative(value) {\n if (!isObject(value) || isMasked(value)) {\n return false;\n }\n var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;\n return pattern.test(toSource(value));\n}\n\n/**\n * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n */\nfunction baseKeys(object) {\n if (!isPrototype(object)) {\n return nativeKeys(object);\n }\n var result = [];\n for (var key in Object(object)) {\n if (hasOwnProperty.call(object, key) && key != 'constructor') {\n result.push(key);\n }\n }\n return result;\n}\n\n/**\n * Creates a clone of `buffer`.\n *\n * @private\n * @param {Buffer} buffer The buffer to clone.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @returns {Buffer} Returns the cloned buffer.\n */\nfunction cloneBuffer(buffer, isDeep) {\n if (isDeep) {\n return buffer.slice();\n }\n var result = new buffer.constructor(buffer.length);\n buffer.copy(result);\n return result;\n}\n\n/**\n * Creates a clone of `arrayBuffer`.\n *\n * @private\n * @param {ArrayBuffer} arrayBuffer The array buffer to clone.\n * @returns {ArrayBuffer} Returns the cloned array buffer.\n */\nfunction cloneArrayBuffer(arrayBuffer) {\n var result = new arrayBuffer.constructor(arrayBuffer.byteLength);\n new Uint8Array(result).set(new Uint8Array(arrayBuffer));\n return result;\n}\n\n/**\n * Creates a clone of `dataView`.\n *\n * @private\n * @param {Object} dataView The data view to clone.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @returns {Object} Returns the cloned data view.\n */\nfunction cloneDataView(dataView, isDeep) {\n var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;\n return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);\n}\n\n/**\n * Creates a clone of `map`.\n *\n * @private\n * @param {Object} map The map to clone.\n * @param {Function} cloneFunc The function to clone values.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @returns {Object} Returns the cloned map.\n */\nfunction cloneMap(map, isDeep, cloneFunc) {\n var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map);\n return arrayReduce(array, addMapEntry, new map.constructor);\n}\n\n/**\n * Creates a clone of `regexp`.\n *\n * @private\n * @param {Object} regexp The regexp to clone.\n * @returns {Object} Returns the cloned regexp.\n */\nfunction cloneRegExp(regexp) {\n var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));\n result.lastIndex = regexp.lastIndex;\n return result;\n}\n\n/**\n * Creates a clone of `set`.\n *\n * @private\n * @param {Object} set The set to clone.\n * @param {Function} cloneFunc The function to clone values.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @returns {Object} Returns the cloned set.\n */\nfunction cloneSet(set, isDeep, cloneFunc) {\n var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set);\n return arrayReduce(array, addSetEntry, new set.constructor);\n}\n\n/**\n * Creates a clone of the `symbol` object.\n *\n * @private\n * @param {Object} symbol The symbol object to clone.\n * @returns {Object} Returns the cloned symbol object.\n */\nfunction cloneSymbol(symbol) {\n return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};\n}\n\n/**\n * Creates a clone of `typedArray`.\n *\n * @private\n * @param {Object} typedArray The typed array to clone.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @returns {Object} Returns the cloned typed array.\n */\nfunction cloneTypedArray(typedArray, isDeep) {\n var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;\n return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);\n}\n\n/**\n * Copies the values of `source` to `array`.\n *\n * @private\n * @param {Array} source The array to copy values from.\n * @param {Array} [array=[]] The array to copy values to.\n * @returns {Array} Returns `array`.\n */\nfunction copyArray(source, array) {\n var index = -1,\n length = source.length;\n\n array || (array = Array(length));\n while (++index < length) {\n array[index] = source[index];\n }\n return array;\n}\n\n/**\n * Copies properties of `source` to `object`.\n *\n * @private\n * @param {Object} source The object to copy properties from.\n * @param {Array} props The property identifiers to copy.\n * @param {Object} [object={}] The object to copy properties to.\n * @param {Function} [customizer] The function to customize copied values.\n * @returns {Object} Returns `object`.\n */\nfunction copyObject(source, props, object, customizer) {\n object || (object = {});\n\n var index = -1,\n length = props.length;\n\n while (++index < length) {\n var key = props[index];\n\n var newValue = customizer\n ? customizer(object[key], source[key], key, object, source)\n : undefined;\n\n assignValue(object, key, newValue === undefined ? source[key] : newValue);\n }\n return object;\n}\n\n/**\n * Copies own symbol properties of `source` to `object`.\n *\n * @private\n * @param {Object} source The object to copy symbols from.\n * @param {Object} [object={}] The object to copy symbols to.\n * @returns {Object} Returns `object`.\n */\nfunction copySymbols(source, object) {\n return copyObject(source, getSymbols(source), object);\n}\n\n/**\n * Creates an array of own enumerable property names and symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names and symbols.\n */\nfunction getAllKeys(object) {\n return baseGetAllKeys(object, keys, getSymbols);\n}\n\n/**\n * Gets the data for `map`.\n *\n * @private\n * @param {Object} map The map to query.\n * @param {string} key The reference key.\n * @returns {*} Returns the map data.\n */\nfunction getMapData(map, key) {\n var data = map.__data__;\n return isKeyable(key)\n ? data[typeof key == 'string' ? 'string' : 'hash']\n : data.map;\n}\n\n/**\n * Gets the native function at `key` of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {string} key The key of the method to get.\n * @returns {*} Returns the function if it's native, else `undefined`.\n */\nfunction getNative(object, key) {\n var value = getValue(object, key);\n return baseIsNative(value) ? value : undefined;\n}\n\n/**\n * Creates an array of the own enumerable symbol properties of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of symbols.\n */\nvar getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray;\n\n/**\n * Gets the `toStringTag` of `value`.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nvar getTag = baseGetTag;\n\n// Fallback for data views, maps, sets, and weak maps in IE 11,\n// for data views in Edge < 14, and promises in Node.js.\nif ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||\n (Map && getTag(new Map) != mapTag) ||\n (Promise && getTag(Promise.resolve()) != promiseTag) ||\n (Set && getTag(new Set) != setTag) ||\n (WeakMap && getTag(new WeakMap) != weakMapTag)) {\n getTag = function(value) {\n var result = objectToString.call(value),\n Ctor = result == objectTag ? value.constructor : undefined,\n ctorString = Ctor ? toSource(Ctor) : undefined;\n\n if (ctorString) {\n switch (ctorString) {\n case dataViewCtorString: return dataViewTag;\n case mapCtorString: return mapTag;\n case promiseCtorString: return promiseTag;\n case setCtorString: return setTag;\n case weakMapCtorString: return weakMapTag;\n }\n }\n return result;\n };\n}\n\n/**\n * Initializes an array clone.\n *\n * @private\n * @param {Array} array The array to clone.\n * @returns {Array} Returns the initialized clone.\n */\nfunction initCloneArray(array) {\n var length = array.length,\n result = array.constructor(length);\n\n // Add properties assigned by `RegExp#exec`.\n if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {\n result.index = array.index;\n result.input = array.input;\n }\n return result;\n}\n\n/**\n * Initializes an object clone.\n *\n * @private\n * @param {Object} object The object to clone.\n * @returns {Object} Returns the initialized clone.\n */\nfunction initCloneObject(object) {\n return (typeof object.constructor == 'function' && !isPrototype(object))\n ? baseCreate(getPrototype(object))\n : {};\n}\n\n/**\n * Initializes an object clone based on its `toStringTag`.\n *\n * **Note:** This function only supports cloning values with tags of\n * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.\n *\n * @private\n * @param {Object} object The object to clone.\n * @param {string} tag The `toStringTag` of the object to clone.\n * @param {Function} cloneFunc The function to clone values.\n * @param {boolean} [isDeep] Specify a deep clone.\n * @returns {Object} Returns the initialized clone.\n */\nfunction initCloneByTag(object, tag, cloneFunc, isDeep) {\n var Ctor = object.constructor;\n switch (tag) {\n case arrayBufferTag:\n return cloneArrayBuffer(object);\n\n case boolTag:\n case dateTag:\n return new Ctor(+object);\n\n case dataViewTag:\n return cloneDataView(object, isDeep);\n\n case float32Tag: case float64Tag:\n case int8Tag: case int16Tag: case int32Tag:\n case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:\n return cloneTypedArray(object, isDeep);\n\n case mapTag:\n return cloneMap(object, isDeep, cloneFunc);\n\n case numberTag:\n case stringTag:\n return new Ctor(object);\n\n case regexpTag:\n return cloneRegExp(object);\n\n case setTag:\n return cloneSet(object, isDeep, cloneFunc);\n\n case symbolTag:\n return cloneSymbol(object);\n }\n}\n\n/**\n * Checks if `value` is a valid array-like index.\n *\n * @private\n * @param {*} value The value to check.\n * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.\n * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.\n */\nfunction isIndex(value, length) {\n length = length == null ? MAX_SAFE_INTEGER : length;\n return !!length &&\n (typeof value == 'number' || reIsUint.test(value)) &&\n (value > -1 && value % 1 == 0 && value < length);\n}\n\n/**\n * Checks if `value` is suitable for use as unique object key.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is suitable, else `false`.\n */\nfunction isKeyable(value) {\n var type = typeof value;\n return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')\n ? (value !== '__proto__')\n : (value === null);\n}\n\n/**\n * Checks if `func` has its source masked.\n *\n * @private\n * @param {Function} func The function to check.\n * @returns {boolean} Returns `true` if `func` is masked, else `false`.\n */\nfunction isMasked(func) {\n return !!maskSrcKey && (maskSrcKey in func);\n}\n\n/**\n * Checks if `value` is likely a prototype object.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.\n */\nfunction isPrototype(value) {\n var Ctor = value && value.constructor,\n proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;\n\n return value === proto;\n}\n\n/**\n * Converts `func` to its source code.\n *\n * @private\n * @param {Function} func The function to process.\n * @returns {string} Returns the source code.\n */\nfunction toSource(func) {\n if (func != null) {\n try {\n return funcToString.call(func);\n } catch (e) {}\n try {\n return (func + '');\n } catch (e) {}\n }\n return '';\n}\n\n/**\n * This method is like `_.clone` except that it recursively clones `value`.\n *\n * @static\n * @memberOf _\n * @since 1.0.0\n * @category Lang\n * @param {*} value The value to recursively clone.\n * @returns {*} Returns the deep cloned value.\n * @see _.clone\n * @example\n *\n * var objects = [{ 'a': 1 }, { 'b': 2 }];\n *\n * var deep = _.cloneDeep(objects);\n * console.log(deep[0] === objects[0]);\n * // => false\n */\nfunction cloneDeep(value) {\n return baseClone(value, true, true);\n}\n\n/**\n * Performs a\n * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)\n * comparison between two values to determine if they are equivalent.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'a': 1 };\n * var other = { 'a': 1 };\n *\n * _.eq(object, object);\n * // => true\n *\n * _.eq(object, other);\n * // => false\n *\n * _.eq('a', 'a');\n * // => true\n *\n * _.eq('a', Object('a'));\n * // => false\n *\n * _.eq(NaN, NaN);\n * // => true\n */\nfunction eq(value, other) {\n return value === other || (value !== value && other !== other);\n}\n\n/**\n * Checks if `value` is likely an `arguments` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an `arguments` object,\n * else `false`.\n * @example\n *\n * _.isArguments(function() { return arguments; }());\n * // => true\n *\n * _.isArguments([1, 2, 3]);\n * // => false\n */\nfunction isArguments(value) {\n // Safari 8.1 makes `arguments.callee` enumerable in strict mode.\n return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&\n (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);\n}\n\n/**\n * Checks if `value` is classified as an `Array` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an array, else `false`.\n * @example\n *\n * _.isArray([1, 2, 3]);\n * // => true\n *\n * _.isArray(document.body.children);\n * // => false\n *\n * _.isArray('abc');\n * // => false\n *\n * _.isArray(_.noop);\n * // => false\n */\nvar isArray = Array.isArray;\n\n/**\n * Checks if `value` is array-like. A value is considered array-like if it's\n * not a function and has a `value.length` that's an integer greater than or\n * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is array-like, else `false`.\n * @example\n *\n * _.isArrayLike([1, 2, 3]);\n * // => true\n *\n * _.isArrayLike(document.body.children);\n * // => true\n *\n * _.isArrayLike('abc');\n * // => true\n *\n * _.isArrayLike(_.noop);\n * // => false\n */\nfunction isArrayLike(value) {\n return value != null && isLength(value.length) && !isFunction(value);\n}\n\n/**\n * This method is like `_.isArrayLike` except that it also checks if `value`\n * is an object.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an array-like object,\n * else `false`.\n * @example\n *\n * _.isArrayLikeObject([1, 2, 3]);\n * // => true\n *\n * _.isArrayLikeObject(document.body.children);\n * // => true\n *\n * _.isArrayLikeObject('abc');\n * // => false\n *\n * _.isArrayLikeObject(_.noop);\n * // => false\n */\nfunction isArrayLikeObject(value) {\n return isObjectLike(value) && isArrayLike(value);\n}\n\n/**\n * Checks if `value` is a buffer.\n *\n * @static\n * @memberOf _\n * @since 4.3.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.\n * @example\n *\n * _.isBuffer(new Buffer(2));\n * // => true\n *\n * _.isBuffer(new Uint8Array(2));\n * // => false\n */\nvar isBuffer = nativeIsBuffer || stubFalse;\n\n/**\n * Checks if `value` is classified as a `Function` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a function, else `false`.\n * @example\n *\n * _.isFunction(_);\n * // => true\n *\n * _.isFunction(/abc/);\n * // => false\n */\nfunction isFunction(value) {\n // The use of `Object#toString` avoids issues with the `typeof` operator\n // in Safari 8-9 which returns 'object' for typed array and other constructors.\n var tag = isObject(value) ? objectToString.call(value) : '';\n return tag == funcTag || tag == genTag;\n}\n\n/**\n * Checks if `value` is a valid array-like length.\n *\n * **Note:** This method is loosely based on\n * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.\n * @example\n *\n * _.isLength(3);\n * // => true\n *\n * _.isLength(Number.MIN_VALUE);\n * // => false\n *\n * _.isLength(Infinity);\n * // => false\n *\n * _.isLength('3');\n * // => false\n */\nfunction isLength(value) {\n return typeof value == 'number' &&\n value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;\n}\n\n/**\n * Checks if `value` is the\n * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)\n * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(_.noop);\n * // => true\n *\n * _.isObject(null);\n * // => false\n */\nfunction isObject(value) {\n var type = typeof value;\n return !!value && (type == 'object' || type == 'function');\n}\n\n/**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\nfunction isObjectLike(value) {\n return !!value && typeof value == 'object';\n}\n\n/**\n * Creates an array of the own enumerable property names of `object`.\n *\n * **Note:** Non-object values are coerced to objects. See the\n * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)\n * for more details.\n *\n * @static\n * @since 0.1.0\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n * @example\n *\n * function Foo() {\n * this.a = 1;\n * this.b = 2;\n * }\n *\n * Foo.prototype.c = 3;\n *\n * _.keys(new Foo);\n * // => ['a', 'b'] (iteration order is not guaranteed)\n *\n * _.keys('hi');\n * // => ['0', '1']\n */\nfunction keys(object) {\n return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);\n}\n\n/**\n * This method returns a new empty array.\n *\n * @static\n * @memberOf _\n * @since 4.13.0\n * @category Util\n * @returns {Array} Returns the new empty array.\n * @example\n *\n * var arrays = _.times(2, _.stubArray);\n *\n * console.log(arrays);\n * // => [[], []]\n *\n * console.log(arrays[0] === arrays[1]);\n * // => false\n */\nfunction stubArray() {\n return [];\n}\n\n/**\n * This method returns `false`.\n *\n * @static\n * @memberOf _\n * @since 4.13.0\n * @category Util\n * @returns {boolean} Returns `false`.\n * @example\n *\n * _.times(2, _.stubFalse);\n * // => [false, false]\n */\nfunction stubFalse() {\n return false;\n}\n\nmodule.exports = cloneDeep;\n","/**\n * lodash (Custom Build) \n * Build: `lodash modularize exports=\"npm\" -o ./`\n * Copyright jQuery Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n */\n\n/** Used as the `TypeError` message for \"Functions\" methods. */\nvar FUNC_ERROR_TEXT = 'Expected a function';\n\n/** Used as references for various `Number` constants. */\nvar NAN = 0 / 0;\n\n/** `Object#toString` result references. */\nvar symbolTag = '[object Symbol]';\n\n/** Used to match leading and trailing whitespace. */\nvar reTrim = /^\\s+|\\s+$/g;\n\n/** Used to detect bad signed hexadecimal string values. */\nvar reIsBadHex = /^[-+]0x[0-9a-f]+$/i;\n\n/** Used to detect binary string values. */\nvar reIsBinary = /^0b[01]+$/i;\n\n/** Used to detect octal string values. */\nvar reIsOctal = /^0o[0-7]+$/i;\n\n/** Built-in method references without a dependency on `root`. */\nvar freeParseInt = parseInt;\n\n/** Detect free variable `global` from Node.js. */\nvar freeGlobal = typeof global == 'object' && global && global.Object === Object && global;\n\n/** Detect free variable `self`. */\nvar freeSelf = typeof self == 'object' && self && self.Object === Object && self;\n\n/** Used as a reference to the global object. */\nvar root = freeGlobal || freeSelf || Function('return this')();\n\n/** Used for built-in method references. */\nvar objectProto = Object.prototype;\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar objectToString = objectProto.toString;\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeMax = Math.max,\n nativeMin = Math.min;\n\n/**\n * Gets the timestamp of the number of milliseconds that have elapsed since\n * the Unix epoch (1 January 1970 00:00:00 UTC).\n *\n * @static\n * @memberOf _\n * @since 2.4.0\n * @category Date\n * @returns {number} Returns the timestamp.\n * @example\n *\n * _.defer(function(stamp) {\n * console.log(_.now() - stamp);\n * }, _.now());\n * // => Logs the number of milliseconds it took for the deferred invocation.\n */\nvar now = function() {\n return root.Date.now();\n};\n\n/**\n * Creates a debounced function that delays invoking `func` until after `wait`\n * milliseconds have elapsed since the last time the debounced function was\n * invoked. The debounced function comes with a `cancel` method to cancel\n * delayed `func` invocations and a `flush` method to immediately invoke them.\n * Provide `options` to indicate whether `func` should be invoked on the\n * leading and/or trailing edge of the `wait` timeout. The `func` is invoked\n * with the last arguments provided to the debounced function. Subsequent\n * calls to the debounced function return the result of the last `func`\n * invocation.\n *\n * **Note:** If `leading` and `trailing` options are `true`, `func` is\n * invoked on the trailing edge of the timeout only if the debounced function\n * is invoked more than once during the `wait` timeout.\n *\n * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred\n * until to the next tick, similar to `setTimeout` with a timeout of `0`.\n *\n * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)\n * for details over the differences between `_.debounce` and `_.throttle`.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Function\n * @param {Function} func The function to debounce.\n * @param {number} [wait=0] The number of milliseconds to delay.\n * @param {Object} [options={}] The options object.\n * @param {boolean} [options.leading=false]\n * Specify invoking on the leading edge of the timeout.\n * @param {number} [options.maxWait]\n * The maximum time `func` is allowed to be delayed before it's invoked.\n * @param {boolean} [options.trailing=true]\n * Specify invoking on the trailing edge of the timeout.\n * @returns {Function} Returns the new debounced function.\n * @example\n *\n * // Avoid costly calculations while the window size is in flux.\n * jQuery(window).on('resize', _.debounce(calculateLayout, 150));\n *\n * // Invoke `sendMail` when clicked, debouncing subsequent calls.\n * jQuery(element).on('click', _.debounce(sendMail, 300, {\n * 'leading': true,\n * 'trailing': false\n * }));\n *\n * // Ensure `batchLog` is invoked once after 1 second of debounced calls.\n * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });\n * var source = new EventSource('/stream');\n * jQuery(source).on('message', debounced);\n *\n * // Cancel the trailing debounced invocation.\n * jQuery(window).on('popstate', debounced.cancel);\n */\nfunction debounce(func, wait, options) {\n var lastArgs,\n lastThis,\n maxWait,\n result,\n timerId,\n lastCallTime,\n lastInvokeTime = 0,\n leading = false,\n maxing = false,\n trailing = true;\n\n if (typeof func != 'function') {\n throw new TypeError(FUNC_ERROR_TEXT);\n }\n wait = toNumber(wait) || 0;\n if (isObject(options)) {\n leading = !!options.leading;\n maxing = 'maxWait' in options;\n maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;\n trailing = 'trailing' in options ? !!options.trailing : trailing;\n }\n\n function invokeFunc(time) {\n var args = lastArgs,\n thisArg = lastThis;\n\n lastArgs = lastThis = undefined;\n lastInvokeTime = time;\n result = func.apply(thisArg, args);\n return result;\n }\n\n function leadingEdge(time) {\n // Reset any `maxWait` timer.\n lastInvokeTime = time;\n // Start the timer for the trailing edge.\n timerId = setTimeout(timerExpired, wait);\n // Invoke the leading edge.\n return leading ? invokeFunc(time) : result;\n }\n\n function remainingWait(time) {\n var timeSinceLastCall = time - lastCallTime,\n timeSinceLastInvoke = time - lastInvokeTime,\n result = wait - timeSinceLastCall;\n\n return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;\n }\n\n function shouldInvoke(time) {\n var timeSinceLastCall = time - lastCallTime,\n timeSinceLastInvoke = time - lastInvokeTime;\n\n // Either this is the first call, activity has stopped and we're at the\n // trailing edge, the system time has gone backwards and we're treating\n // it as the trailing edge, or we've hit the `maxWait` limit.\n return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||\n (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));\n }\n\n function timerExpired() {\n var time = now();\n if (shouldInvoke(time)) {\n return trailingEdge(time);\n }\n // Restart the timer.\n timerId = setTimeout(timerExpired, remainingWait(time));\n }\n\n function trailingEdge(time) {\n timerId = undefined;\n\n // Only invoke if we have `lastArgs` which means `func` has been\n // debounced at least once.\n if (trailing && lastArgs) {\n return invokeFunc(time);\n }\n lastArgs = lastThis = undefined;\n return result;\n }\n\n function cancel() {\n if (timerId !== undefined) {\n clearTimeout(timerId);\n }\n lastInvokeTime = 0;\n lastArgs = lastCallTime = lastThis = timerId = undefined;\n }\n\n function flush() {\n return timerId === undefined ? result : trailingEdge(now());\n }\n\n function debounced() {\n var time = now(),\n isInvoking = shouldInvoke(time);\n\n lastArgs = arguments;\n lastThis = this;\n lastCallTime = time;\n\n if (isInvoking) {\n if (timerId === undefined) {\n return leadingEdge(lastCallTime);\n }\n if (maxing) {\n // Handle invocations in a tight loop.\n timerId = setTimeout(timerExpired, wait);\n return invokeFunc(lastCallTime);\n }\n }\n if (timerId === undefined) {\n timerId = setTimeout(timerExpired, wait);\n }\n return result;\n }\n debounced.cancel = cancel;\n debounced.flush = flush;\n return debounced;\n}\n\n/**\n * Checks if `value` is the\n * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)\n * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(_.noop);\n * // => true\n *\n * _.isObject(null);\n * // => false\n */\nfunction isObject(value) {\n var type = typeof value;\n return !!value && (type == 'object' || type == 'function');\n}\n\n/**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\nfunction isObjectLike(value) {\n return !!value && typeof value == 'object';\n}\n\n/**\n * Checks if `value` is classified as a `Symbol` primitive or object.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.\n * @example\n *\n * _.isSymbol(Symbol.iterator);\n * // => true\n *\n * _.isSymbol('abc');\n * // => false\n */\nfunction isSymbol(value) {\n return typeof value == 'symbol' ||\n (isObjectLike(value) && objectToString.call(value) == symbolTag);\n}\n\n/**\n * Converts `value` to a number.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to process.\n * @returns {number} Returns the number.\n * @example\n *\n * _.toNumber(3.2);\n * // => 3.2\n *\n * _.toNumber(Number.MIN_VALUE);\n * // => 5e-324\n *\n * _.toNumber(Infinity);\n * // => Infinity\n *\n * _.toNumber('3.2');\n * // => 3.2\n */\nfunction toNumber(value) {\n if (typeof value == 'number') {\n return value;\n }\n if (isSymbol(value)) {\n return NAN;\n }\n if (isObject(value)) {\n var other = typeof value.valueOf == 'function' ? value.valueOf() : value;\n value = isObject(other) ? (other + '') : other;\n }\n if (typeof value != 'string') {\n return value === 0 ? value : +value;\n }\n value = value.replace(reTrim, '');\n var isBinary = reIsBinary.test(value);\n return (isBinary || reIsOctal.test(value))\n ? freeParseInt(value.slice(2), isBinary ? 2 : 8)\n : (reIsBadHex.test(value) ? NAN : +value);\n}\n\nmodule.exports = debounce;\n","/**\n * Lodash (Custom Build) \n * Build: `lodash modularize exports=\"npm\" -o ./`\n * Copyright JS Foundation and other contributors \n * Released under MIT license \n * Based on Underscore.js 1.8.3 \n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n */\n\n/** Used as the size to enable large array optimizations. */\nvar LARGE_ARRAY_SIZE = 200;\n\n/** Used to stand-in for `undefined` hash values. */\nvar HASH_UNDEFINED = '__lodash_hash_undefined__';\n\n/** Used to compose bitmasks for value comparisons. */\nvar COMPARE_PARTIAL_FLAG = 1,\n COMPARE_UNORDERED_FLAG = 2;\n\n/** Used as references for various `Number` constants. */\nvar MAX_SAFE_INTEGER = 9007199254740991;\n\n/** `Object#toString` result references. */\nvar argsTag = '[object Arguments]',\n arrayTag = '[object Array]',\n asyncTag = '[object AsyncFunction]',\n boolTag = '[object Boolean]',\n dateTag = '[object Date]',\n errorTag = '[object Error]',\n funcTag = '[object Function]',\n genTag = '[object GeneratorFunction]',\n mapTag = '[object Map]',\n numberTag = '[object Number]',\n nullTag = '[object Null]',\n objectTag = '[object Object]',\n promiseTag = '[object Promise]',\n proxyTag = '[object Proxy]',\n regexpTag = '[object RegExp]',\n setTag = '[object Set]',\n stringTag = '[object String]',\n symbolTag = '[object Symbol]',\n undefinedTag = '[object Undefined]',\n weakMapTag = '[object WeakMap]';\n\nvar arrayBufferTag = '[object ArrayBuffer]',\n dataViewTag = '[object DataView]',\n float32Tag = '[object Float32Array]',\n float64Tag = '[object Float64Array]',\n int8Tag = '[object Int8Array]',\n int16Tag = '[object Int16Array]',\n int32Tag = '[object Int32Array]',\n uint8Tag = '[object Uint8Array]',\n uint8ClampedTag = '[object Uint8ClampedArray]',\n uint16Tag = '[object Uint16Array]',\n uint32Tag = '[object Uint32Array]';\n\n/**\n * Used to match `RegExp`\n * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).\n */\nvar reRegExpChar = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** Used to detect host constructors (Safari). */\nvar reIsHostCtor = /^\\[object .+?Constructor\\]$/;\n\n/** Used to detect unsigned integer values. */\nvar reIsUint = /^(?:0|[1-9]\\d*)$/;\n\n/** Used to identify `toStringTag` values of typed arrays. */\nvar typedArrayTags = {};\ntypedArrayTags[float32Tag] = typedArrayTags[float64Tag] =\ntypedArrayTags[int8Tag] = typedArrayTags[int16Tag] =\ntypedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =\ntypedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =\ntypedArrayTags[uint32Tag] = true;\ntypedArrayTags[argsTag] = typedArrayTags[arrayTag] =\ntypedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =\ntypedArrayTags[dataViewTag] = typedArrayTags[dateTag] =\ntypedArrayTags[errorTag] = typedArrayTags[funcTag] =\ntypedArrayTags[mapTag] = typedArrayTags[numberTag] =\ntypedArrayTags[objectTag] = typedArrayTags[regexpTag] =\ntypedArrayTags[setTag] = typedArrayTags[stringTag] =\ntypedArrayTags[weakMapTag] = false;\n\n/** Detect free variable `global` from Node.js. */\nvar freeGlobal = typeof global == 'object' && global && global.Object === Object && global;\n\n/** Detect free variable `self`. */\nvar freeSelf = typeof self == 'object' && self && self.Object === Object && self;\n\n/** Used as a reference to the global object. */\nvar root = freeGlobal || freeSelf || Function('return this')();\n\n/** Detect free variable `exports`. */\nvar freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;\n\n/** Detect free variable `module`. */\nvar freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;\n\n/** Detect the popular CommonJS extension `module.exports`. */\nvar moduleExports = freeModule && freeModule.exports === freeExports;\n\n/** Detect free variable `process` from Node.js. */\nvar freeProcess = moduleExports && freeGlobal.process;\n\n/** Used to access faster Node.js helpers. */\nvar nodeUtil = (function() {\n try {\n return freeProcess && freeProcess.binding && freeProcess.binding('util');\n } catch (e) {}\n}());\n\n/* Node.js helper references. */\nvar nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;\n\n/**\n * A specialized version of `_.filter` for arrays without support for\n * iteratee shorthands.\n *\n * @private\n * @param {Array} [array] The array to iterate over.\n * @param {Function} predicate The function invoked per iteration.\n * @returns {Array} Returns the new filtered array.\n */\nfunction arrayFilter(array, predicate) {\n var index = -1,\n length = array == null ? 0 : array.length,\n resIndex = 0,\n result = [];\n\n while (++index < length) {\n var value = array[index];\n if (predicate(value, index, array)) {\n result[resIndex++] = value;\n }\n }\n return result;\n}\n\n/**\n * Appends the elements of `values` to `array`.\n *\n * @private\n * @param {Array} array The array to modify.\n * @param {Array} values The values to append.\n * @returns {Array} Returns `array`.\n */\nfunction arrayPush(array, values) {\n var index = -1,\n length = values.length,\n offset = array.length;\n\n while (++index < length) {\n array[offset + index] = values[index];\n }\n return array;\n}\n\n/**\n * A specialized version of `_.some` for arrays without support for iteratee\n * shorthands.\n *\n * @private\n * @param {Array} [array] The array to iterate over.\n * @param {Function} predicate The function invoked per iteration.\n * @returns {boolean} Returns `true` if any element passes the predicate check,\n * else `false`.\n */\nfunction arraySome(array, predicate) {\n var index = -1,\n length = array == null ? 0 : array.length;\n\n while (++index < length) {\n if (predicate(array[index], index, array)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * The base implementation of `_.times` without support for iteratee shorthands\n * or max array length checks.\n *\n * @private\n * @param {number} n The number of times to invoke `iteratee`.\n * @param {Function} iteratee The function invoked per iteration.\n * @returns {Array} Returns the array of results.\n */\nfunction baseTimes(n, iteratee) {\n var index = -1,\n result = Array(n);\n\n while (++index < n) {\n result[index] = iteratee(index);\n }\n return result;\n}\n\n/**\n * The base implementation of `_.unary` without support for storing metadata.\n *\n * @private\n * @param {Function} func The function to cap arguments for.\n * @returns {Function} Returns the new capped function.\n */\nfunction baseUnary(func) {\n return function(value) {\n return func(value);\n };\n}\n\n/**\n * Checks if a `cache` value for `key` exists.\n *\n * @private\n * @param {Object} cache The cache to query.\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction cacheHas(cache, key) {\n return cache.has(key);\n}\n\n/**\n * Gets the value at `key` of `object`.\n *\n * @private\n * @param {Object} [object] The object to query.\n * @param {string} key The key of the property to get.\n * @returns {*} Returns the property value.\n */\nfunction getValue(object, key) {\n return object == null ? undefined : object[key];\n}\n\n/**\n * Converts `map` to its key-value pairs.\n *\n * @private\n * @param {Object} map The map to convert.\n * @returns {Array} Returns the key-value pairs.\n */\nfunction mapToArray(map) {\n var index = -1,\n result = Array(map.size);\n\n map.forEach(function(value, key) {\n result[++index] = [key, value];\n });\n return result;\n}\n\n/**\n * Creates a unary function that invokes `func` with its argument transformed.\n *\n * @private\n * @param {Function} func The function to wrap.\n * @param {Function} transform The argument transform.\n * @returns {Function} Returns the new function.\n */\nfunction overArg(func, transform) {\n return function(arg) {\n return func(transform(arg));\n };\n}\n\n/**\n * Converts `set` to an array of its values.\n *\n * @private\n * @param {Object} set The set to convert.\n * @returns {Array} Returns the values.\n */\nfunction setToArray(set) {\n var index = -1,\n result = Array(set.size);\n\n set.forEach(function(value) {\n result[++index] = value;\n });\n return result;\n}\n\n/** Used for built-in method references. */\nvar arrayProto = Array.prototype,\n funcProto = Function.prototype,\n objectProto = Object.prototype;\n\n/** Used to detect overreaching core-js shims. */\nvar coreJsData = root['__core-js_shared__'];\n\n/** Used to resolve the decompiled source of functions. */\nvar funcToString = funcProto.toString;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/** Used to detect methods masquerading as native. */\nvar maskSrcKey = (function() {\n var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');\n return uid ? ('Symbol(src)_1.' + uid) : '';\n}());\n\n/**\n * Used to resolve the\n * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)\n * of values.\n */\nvar nativeObjectToString = objectProto.toString;\n\n/** Used to detect if a method is native. */\nvar reIsNative = RegExp('^' +\n funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\\\$&')\n .replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g, '$1.*?') + '$'\n);\n\n/** Built-in value references. */\nvar Buffer = moduleExports ? root.Buffer : undefined,\n Symbol = root.Symbol,\n Uint8Array = root.Uint8Array,\n propertyIsEnumerable = objectProto.propertyIsEnumerable,\n splice = arrayProto.splice,\n symToStringTag = Symbol ? Symbol.toStringTag : undefined;\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeGetSymbols = Object.getOwnPropertySymbols,\n nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,\n nativeKeys = overArg(Object.keys, Object);\n\n/* Built-in method references that are verified to be native. */\nvar DataView = getNative(root, 'DataView'),\n Map = getNative(root, 'Map'),\n Promise = getNative(root, 'Promise'),\n Set = getNative(root, 'Set'),\n WeakMap = getNative(root, 'WeakMap'),\n nativeCreate = getNative(Object, 'create');\n\n/** Used to detect maps, sets, and weakmaps. */\nvar dataViewCtorString = toSource(DataView),\n mapCtorString = toSource(Map),\n promiseCtorString = toSource(Promise),\n setCtorString = toSource(Set),\n weakMapCtorString = toSource(WeakMap);\n\n/** Used to convert symbols to primitives and strings. */\nvar symbolProto = Symbol ? Symbol.prototype : undefined,\n symbolValueOf = symbolProto ? symbolProto.valueOf : undefined;\n\n/**\n * Creates a hash object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction Hash(entries) {\n var index = -1,\n length = entries == null ? 0 : entries.length;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the hash.\n *\n * @private\n * @name clear\n * @memberOf Hash\n */\nfunction hashClear() {\n this.__data__ = nativeCreate ? nativeCreate(null) : {};\n this.size = 0;\n}\n\n/**\n * Removes `key` and its value from the hash.\n *\n * @private\n * @name delete\n * @memberOf Hash\n * @param {Object} hash The hash to modify.\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction hashDelete(key) {\n var result = this.has(key) && delete this.__data__[key];\n this.size -= result ? 1 : 0;\n return result;\n}\n\n/**\n * Gets the hash value for `key`.\n *\n * @private\n * @name get\n * @memberOf Hash\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction hashGet(key) {\n var data = this.__data__;\n if (nativeCreate) {\n var result = data[key];\n return result === HASH_UNDEFINED ? undefined : result;\n }\n return hasOwnProperty.call(data, key) ? data[key] : undefined;\n}\n\n/**\n * Checks if a hash value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf Hash\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction hashHas(key) {\n var data = this.__data__;\n return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);\n}\n\n/**\n * Sets the hash `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf Hash\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the hash instance.\n */\nfunction hashSet(key, value) {\n var data = this.__data__;\n this.size += this.has(key) ? 0 : 1;\n data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;\n return this;\n}\n\n// Add methods to `Hash`.\nHash.prototype.clear = hashClear;\nHash.prototype['delete'] = hashDelete;\nHash.prototype.get = hashGet;\nHash.prototype.has = hashHas;\nHash.prototype.set = hashSet;\n\n/**\n * Creates an list cache object.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction ListCache(entries) {\n var index = -1,\n length = entries == null ? 0 : entries.length;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the list cache.\n *\n * @private\n * @name clear\n * @memberOf ListCache\n */\nfunction listCacheClear() {\n this.__data__ = [];\n this.size = 0;\n}\n\n/**\n * Removes `key` and its value from the list cache.\n *\n * @private\n * @name delete\n * @memberOf ListCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction listCacheDelete(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n return false;\n }\n var lastIndex = data.length - 1;\n if (index == lastIndex) {\n data.pop();\n } else {\n splice.call(data, index, 1);\n }\n --this.size;\n return true;\n}\n\n/**\n * Gets the list cache value for `key`.\n *\n * @private\n * @name get\n * @memberOf ListCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction listCacheGet(key) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n return index < 0 ? undefined : data[index][1];\n}\n\n/**\n * Checks if a list cache value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf ListCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction listCacheHas(key) {\n return assocIndexOf(this.__data__, key) > -1;\n}\n\n/**\n * Sets the list cache `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf ListCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the list cache instance.\n */\nfunction listCacheSet(key, value) {\n var data = this.__data__,\n index = assocIndexOf(data, key);\n\n if (index < 0) {\n ++this.size;\n data.push([key, value]);\n } else {\n data[index][1] = value;\n }\n return this;\n}\n\n// Add methods to `ListCache`.\nListCache.prototype.clear = listCacheClear;\nListCache.prototype['delete'] = listCacheDelete;\nListCache.prototype.get = listCacheGet;\nListCache.prototype.has = listCacheHas;\nListCache.prototype.set = listCacheSet;\n\n/**\n * Creates a map cache object to store key-value pairs.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction MapCache(entries) {\n var index = -1,\n length = entries == null ? 0 : entries.length;\n\n this.clear();\n while (++index < length) {\n var entry = entries[index];\n this.set(entry[0], entry[1]);\n }\n}\n\n/**\n * Removes all key-value entries from the map.\n *\n * @private\n * @name clear\n * @memberOf MapCache\n */\nfunction mapCacheClear() {\n this.size = 0;\n this.__data__ = {\n 'hash': new Hash,\n 'map': new (Map || ListCache),\n 'string': new Hash\n };\n}\n\n/**\n * Removes `key` and its value from the map.\n *\n * @private\n * @name delete\n * @memberOf MapCache\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction mapCacheDelete(key) {\n var result = getMapData(this, key)['delete'](key);\n this.size -= result ? 1 : 0;\n return result;\n}\n\n/**\n * Gets the map value for `key`.\n *\n * @private\n * @name get\n * @memberOf MapCache\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction mapCacheGet(key) {\n return getMapData(this, key).get(key);\n}\n\n/**\n * Checks if a map value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf MapCache\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction mapCacheHas(key) {\n return getMapData(this, key).has(key);\n}\n\n/**\n * Sets the map `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf MapCache\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the map cache instance.\n */\nfunction mapCacheSet(key, value) {\n var data = getMapData(this, key),\n size = data.size;\n\n data.set(key, value);\n this.size += data.size == size ? 0 : 1;\n return this;\n}\n\n// Add methods to `MapCache`.\nMapCache.prototype.clear = mapCacheClear;\nMapCache.prototype['delete'] = mapCacheDelete;\nMapCache.prototype.get = mapCacheGet;\nMapCache.prototype.has = mapCacheHas;\nMapCache.prototype.set = mapCacheSet;\n\n/**\n *\n * Creates an array cache object to store unique values.\n *\n * @private\n * @constructor\n * @param {Array} [values] The values to cache.\n */\nfunction SetCache(values) {\n var index = -1,\n length = values == null ? 0 : values.length;\n\n this.__data__ = new MapCache;\n while (++index < length) {\n this.add(values[index]);\n }\n}\n\n/**\n * Adds `value` to the array cache.\n *\n * @private\n * @name add\n * @memberOf SetCache\n * @alias push\n * @param {*} value The value to cache.\n * @returns {Object} Returns the cache instance.\n */\nfunction setCacheAdd(value) {\n this.__data__.set(value, HASH_UNDEFINED);\n return this;\n}\n\n/**\n * Checks if `value` is in the array cache.\n *\n * @private\n * @name has\n * @memberOf SetCache\n * @param {*} value The value to search for.\n * @returns {number} Returns `true` if `value` is found, else `false`.\n */\nfunction setCacheHas(value) {\n return this.__data__.has(value);\n}\n\n// Add methods to `SetCache`.\nSetCache.prototype.add = SetCache.prototype.push = setCacheAdd;\nSetCache.prototype.has = setCacheHas;\n\n/**\n * Creates a stack cache object to store key-value pairs.\n *\n * @private\n * @constructor\n * @param {Array} [entries] The key-value pairs to cache.\n */\nfunction Stack(entries) {\n var data = this.__data__ = new ListCache(entries);\n this.size = data.size;\n}\n\n/**\n * Removes all key-value entries from the stack.\n *\n * @private\n * @name clear\n * @memberOf Stack\n */\nfunction stackClear() {\n this.__data__ = new ListCache;\n this.size = 0;\n}\n\n/**\n * Removes `key` and its value from the stack.\n *\n * @private\n * @name delete\n * @memberOf Stack\n * @param {string} key The key of the value to remove.\n * @returns {boolean} Returns `true` if the entry was removed, else `false`.\n */\nfunction stackDelete(key) {\n var data = this.__data__,\n result = data['delete'](key);\n\n this.size = data.size;\n return result;\n}\n\n/**\n * Gets the stack value for `key`.\n *\n * @private\n * @name get\n * @memberOf Stack\n * @param {string} key The key of the value to get.\n * @returns {*} Returns the entry value.\n */\nfunction stackGet(key) {\n return this.__data__.get(key);\n}\n\n/**\n * Checks if a stack value for `key` exists.\n *\n * @private\n * @name has\n * @memberOf Stack\n * @param {string} key The key of the entry to check.\n * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.\n */\nfunction stackHas(key) {\n return this.__data__.has(key);\n}\n\n/**\n * Sets the stack `key` to `value`.\n *\n * @private\n * @name set\n * @memberOf Stack\n * @param {string} key The key of the value to set.\n * @param {*} value The value to set.\n * @returns {Object} Returns the stack cache instance.\n */\nfunction stackSet(key, value) {\n var data = this.__data__;\n if (data instanceof ListCache) {\n var pairs = data.__data__;\n if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {\n pairs.push([key, value]);\n this.size = ++data.size;\n return this;\n }\n data = this.__data__ = new MapCache(pairs);\n }\n data.set(key, value);\n this.size = data.size;\n return this;\n}\n\n// Add methods to `Stack`.\nStack.prototype.clear = stackClear;\nStack.prototype['delete'] = stackDelete;\nStack.prototype.get = stackGet;\nStack.prototype.has = stackHas;\nStack.prototype.set = stackSet;\n\n/**\n * Creates an array of the enumerable property names of the array-like `value`.\n *\n * @private\n * @param {*} value The value to query.\n * @param {boolean} inherited Specify returning inherited property names.\n * @returns {Array} Returns the array of property names.\n */\nfunction arrayLikeKeys(value, inherited) {\n var isArr = isArray(value),\n isArg = !isArr && isArguments(value),\n isBuff = !isArr && !isArg && isBuffer(value),\n isType = !isArr && !isArg && !isBuff && isTypedArray(value),\n skipIndexes = isArr || isArg || isBuff || isType,\n result = skipIndexes ? baseTimes(value.length, String) : [],\n length = result.length;\n\n for (var key in value) {\n if ((inherited || hasOwnProperty.call(value, key)) &&\n !(skipIndexes && (\n // Safari 9 has enumerable `arguments.length` in strict mode.\n key == 'length' ||\n // Node.js 0.10 has enumerable non-index properties on buffers.\n (isBuff && (key == 'offset' || key == 'parent')) ||\n // PhantomJS 2 has enumerable non-index properties on typed arrays.\n (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||\n // Skip index properties.\n isIndex(key, length)\n ))) {\n result.push(key);\n }\n }\n return result;\n}\n\n/**\n * Gets the index at which the `key` is found in `array` of key-value pairs.\n *\n * @private\n * @param {Array} array The array to inspect.\n * @param {*} key The key to search for.\n * @returns {number} Returns the index of the matched value, else `-1`.\n */\nfunction assocIndexOf(array, key) {\n var length = array.length;\n while (length--) {\n if (eq(array[length][0], key)) {\n return length;\n }\n }\n return -1;\n}\n\n/**\n * The base implementation of `getAllKeys` and `getAllKeysIn` which uses\n * `keysFunc` and `symbolsFunc` to get the enumerable property names and\n * symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {Function} keysFunc The function to get the keys of `object`.\n * @param {Function} symbolsFunc The function to get the symbols of `object`.\n * @returns {Array} Returns the array of property names and symbols.\n */\nfunction baseGetAllKeys(object, keysFunc, symbolsFunc) {\n var result = keysFunc(object);\n return isArray(object) ? result : arrayPush(result, symbolsFunc(object));\n}\n\n/**\n * The base implementation of `getTag` without fallbacks for buggy environments.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nfunction baseGetTag(value) {\n if (value == null) {\n return value === undefined ? undefinedTag : nullTag;\n }\n return (symToStringTag && symToStringTag in Object(value))\n ? getRawTag(value)\n : objectToString(value);\n}\n\n/**\n * The base implementation of `_.isArguments`.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an `arguments` object,\n */\nfunction baseIsArguments(value) {\n return isObjectLike(value) && baseGetTag(value) == argsTag;\n}\n\n/**\n * The base implementation of `_.isEqual` which supports partial comparisons\n * and tracks traversed objects.\n *\n * @private\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @param {boolean} bitmask The bitmask flags.\n * 1 - Unordered comparison\n * 2 - Partial comparison\n * @param {Function} [customizer] The function to customize comparisons.\n * @param {Object} [stack] Tracks traversed `value` and `other` objects.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n */\nfunction baseIsEqual(value, other, bitmask, customizer, stack) {\n if (value === other) {\n return true;\n }\n if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {\n return value !== value && other !== other;\n }\n return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);\n}\n\n/**\n * A specialized version of `baseIsEqual` for arrays and objects which performs\n * deep comparisons and tracks traversed objects enabling objects with circular\n * references to be compared.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} [stack] Tracks traversed `object` and `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {\n var objIsArr = isArray(object),\n othIsArr = isArray(other),\n objTag = objIsArr ? arrayTag : getTag(object),\n othTag = othIsArr ? arrayTag : getTag(other);\n\n objTag = objTag == argsTag ? objectTag : objTag;\n othTag = othTag == argsTag ? objectTag : othTag;\n\n var objIsObj = objTag == objectTag,\n othIsObj = othTag == objectTag,\n isSameTag = objTag == othTag;\n\n if (isSameTag && isBuffer(object)) {\n if (!isBuffer(other)) {\n return false;\n }\n objIsArr = true;\n objIsObj = false;\n }\n if (isSameTag && !objIsObj) {\n stack || (stack = new Stack);\n return (objIsArr || isTypedArray(object))\n ? equalArrays(object, other, bitmask, customizer, equalFunc, stack)\n : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);\n }\n if (!(bitmask & COMPARE_PARTIAL_FLAG)) {\n var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),\n othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');\n\n if (objIsWrapped || othIsWrapped) {\n var objUnwrapped = objIsWrapped ? object.value() : object,\n othUnwrapped = othIsWrapped ? other.value() : other;\n\n stack || (stack = new Stack);\n return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);\n }\n }\n if (!isSameTag) {\n return false;\n }\n stack || (stack = new Stack);\n return equalObjects(object, other, bitmask, customizer, equalFunc, stack);\n}\n\n/**\n * The base implementation of `_.isNative` without bad shim checks.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a native function,\n * else `false`.\n */\nfunction baseIsNative(value) {\n if (!isObject(value) || isMasked(value)) {\n return false;\n }\n var pattern = isFunction(value) ? reIsNative : reIsHostCtor;\n return pattern.test(toSource(value));\n}\n\n/**\n * The base implementation of `_.isTypedArray` without Node.js optimizations.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.\n */\nfunction baseIsTypedArray(value) {\n return isObjectLike(value) &&\n isLength(value.length) && !!typedArrayTags[baseGetTag(value)];\n}\n\n/**\n * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n */\nfunction baseKeys(object) {\n if (!isPrototype(object)) {\n return nativeKeys(object);\n }\n var result = [];\n for (var key in Object(object)) {\n if (hasOwnProperty.call(object, key) && key != 'constructor') {\n result.push(key);\n }\n }\n return result;\n}\n\n/**\n * A specialized version of `baseIsEqualDeep` for arrays with support for\n * partial deep comparisons.\n *\n * @private\n * @param {Array} array The array to compare.\n * @param {Array} other The other array to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} stack Tracks traversed `array` and `other` objects.\n * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.\n */\nfunction equalArrays(array, other, bitmask, customizer, equalFunc, stack) {\n var isPartial = bitmask & COMPARE_PARTIAL_FLAG,\n arrLength = array.length,\n othLength = other.length;\n\n if (arrLength != othLength && !(isPartial && othLength > arrLength)) {\n return false;\n }\n // Assume cyclic values are equal.\n var stacked = stack.get(array);\n if (stacked && stack.get(other)) {\n return stacked == other;\n }\n var index = -1,\n result = true,\n seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;\n\n stack.set(array, other);\n stack.set(other, array);\n\n // Ignore non-index properties.\n while (++index < arrLength) {\n var arrValue = array[index],\n othValue = other[index];\n\n if (customizer) {\n var compared = isPartial\n ? customizer(othValue, arrValue, index, other, array, stack)\n : customizer(arrValue, othValue, index, array, other, stack);\n }\n if (compared !== undefined) {\n if (compared) {\n continue;\n }\n result = false;\n break;\n }\n // Recursively compare arrays (susceptible to call stack limits).\n if (seen) {\n if (!arraySome(other, function(othValue, othIndex) {\n if (!cacheHas(seen, othIndex) &&\n (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {\n return seen.push(othIndex);\n }\n })) {\n result = false;\n break;\n }\n } else if (!(\n arrValue === othValue ||\n equalFunc(arrValue, othValue, bitmask, customizer, stack)\n )) {\n result = false;\n break;\n }\n }\n stack['delete'](array);\n stack['delete'](other);\n return result;\n}\n\n/**\n * A specialized version of `baseIsEqualDeep` for comparing objects of\n * the same `toStringTag`.\n *\n * **Note:** This function only supports comparing values with tags of\n * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {string} tag The `toStringTag` of the objects to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} stack Tracks traversed `object` and `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {\n switch (tag) {\n case dataViewTag:\n if ((object.byteLength != other.byteLength) ||\n (object.byteOffset != other.byteOffset)) {\n return false;\n }\n object = object.buffer;\n other = other.buffer;\n\n case arrayBufferTag:\n if ((object.byteLength != other.byteLength) ||\n !equalFunc(new Uint8Array(object), new Uint8Array(other))) {\n return false;\n }\n return true;\n\n case boolTag:\n case dateTag:\n case numberTag:\n // Coerce booleans to `1` or `0` and dates to milliseconds.\n // Invalid dates are coerced to `NaN`.\n return eq(+object, +other);\n\n case errorTag:\n return object.name == other.name && object.message == other.message;\n\n case regexpTag:\n case stringTag:\n // Coerce regexes to strings and treat strings, primitives and objects,\n // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring\n // for more details.\n return object == (other + '');\n\n case mapTag:\n var convert = mapToArray;\n\n case setTag:\n var isPartial = bitmask & COMPARE_PARTIAL_FLAG;\n convert || (convert = setToArray);\n\n if (object.size != other.size && !isPartial) {\n return false;\n }\n // Assume cyclic values are equal.\n var stacked = stack.get(object);\n if (stacked) {\n return stacked == other;\n }\n bitmask |= COMPARE_UNORDERED_FLAG;\n\n // Recursively compare objects (susceptible to call stack limits).\n stack.set(object, other);\n var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);\n stack['delete'](object);\n return result;\n\n case symbolTag:\n if (symbolValueOf) {\n return symbolValueOf.call(object) == symbolValueOf.call(other);\n }\n }\n return false;\n}\n\n/**\n * A specialized version of `baseIsEqualDeep` for objects with support for\n * partial deep comparisons.\n *\n * @private\n * @param {Object} object The object to compare.\n * @param {Object} other The other object to compare.\n * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.\n * @param {Function} customizer The function to customize comparisons.\n * @param {Function} equalFunc The function to determine equivalents of values.\n * @param {Object} stack Tracks traversed `object` and `other` objects.\n * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.\n */\nfunction equalObjects(object, other, bitmask, customizer, equalFunc, stack) {\n var isPartial = bitmask & COMPARE_PARTIAL_FLAG,\n objProps = getAllKeys(object),\n objLength = objProps.length,\n othProps = getAllKeys(other),\n othLength = othProps.length;\n\n if (objLength != othLength && !isPartial) {\n return false;\n }\n var index = objLength;\n while (index--) {\n var key = objProps[index];\n if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {\n return false;\n }\n }\n // Assume cyclic values are equal.\n var stacked = stack.get(object);\n if (stacked && stack.get(other)) {\n return stacked == other;\n }\n var result = true;\n stack.set(object, other);\n stack.set(other, object);\n\n var skipCtor = isPartial;\n while (++index < objLength) {\n key = objProps[index];\n var objValue = object[key],\n othValue = other[key];\n\n if (customizer) {\n var compared = isPartial\n ? customizer(othValue, objValue, key, other, object, stack)\n : customizer(objValue, othValue, key, object, other, stack);\n }\n // Recursively compare objects (susceptible to call stack limits).\n if (!(compared === undefined\n ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))\n : compared\n )) {\n result = false;\n break;\n }\n skipCtor || (skipCtor = key == 'constructor');\n }\n if (result && !skipCtor) {\n var objCtor = object.constructor,\n othCtor = other.constructor;\n\n // Non `Object` object instances with different constructors are not equal.\n if (objCtor != othCtor &&\n ('constructor' in object && 'constructor' in other) &&\n !(typeof objCtor == 'function' && objCtor instanceof objCtor &&\n typeof othCtor == 'function' && othCtor instanceof othCtor)) {\n result = false;\n }\n }\n stack['delete'](object);\n stack['delete'](other);\n return result;\n}\n\n/**\n * Creates an array of own enumerable property names and symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names and symbols.\n */\nfunction getAllKeys(object) {\n return baseGetAllKeys(object, keys, getSymbols);\n}\n\n/**\n * Gets the data for `map`.\n *\n * @private\n * @param {Object} map The map to query.\n * @param {string} key The reference key.\n * @returns {*} Returns the map data.\n */\nfunction getMapData(map, key) {\n var data = map.__data__;\n return isKeyable(key)\n ? data[typeof key == 'string' ? 'string' : 'hash']\n : data.map;\n}\n\n/**\n * Gets the native function at `key` of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @param {string} key The key of the method to get.\n * @returns {*} Returns the function if it's native, else `undefined`.\n */\nfunction getNative(object, key) {\n var value = getValue(object, key);\n return baseIsNative(value) ? value : undefined;\n}\n\n/**\n * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the raw `toStringTag`.\n */\nfunction getRawTag(value) {\n var isOwn = hasOwnProperty.call(value, symToStringTag),\n tag = value[symToStringTag];\n\n try {\n value[symToStringTag] = undefined;\n var unmasked = true;\n } catch (e) {}\n\n var result = nativeObjectToString.call(value);\n if (unmasked) {\n if (isOwn) {\n value[symToStringTag] = tag;\n } else {\n delete value[symToStringTag];\n }\n }\n return result;\n}\n\n/**\n * Creates an array of the own enumerable symbols of `object`.\n *\n * @private\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of symbols.\n */\nvar getSymbols = !nativeGetSymbols ? stubArray : function(object) {\n if (object == null) {\n return [];\n }\n object = Object(object);\n return arrayFilter(nativeGetSymbols(object), function(symbol) {\n return propertyIsEnumerable.call(object, symbol);\n });\n};\n\n/**\n * Gets the `toStringTag` of `value`.\n *\n * @private\n * @param {*} value The value to query.\n * @returns {string} Returns the `toStringTag`.\n */\nvar getTag = baseGetTag;\n\n// Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.\nif ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||\n (Map && getTag(new Map) != mapTag) ||\n (Promise && getTag(Promise.resolve()) != promiseTag) ||\n (Set && getTag(new Set) != setTag) ||\n (WeakMap && getTag(new WeakMap) != weakMapTag)) {\n getTag = function(value) {\n var result = baseGetTag(value),\n Ctor = result == objectTag ? value.constructor : undefined,\n ctorString = Ctor ? toSource(Ctor) : '';\n\n if (ctorString) {\n switch (ctorString) {\n case dataViewCtorString: return dataViewTag;\n case mapCtorString: return mapTag;\n case promiseCtorString: return promiseTag;\n case setCtorString: return setTag;\n case weakMapCtorString: return weakMapTag;\n }\n }\n return result;\n };\n}\n\n/**\n * Checks if `value` is a valid array-like index.\n *\n * @private\n * @param {*} value The value to check.\n * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.\n * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.\n */\nfunction isIndex(value, length) {\n length = length == null ? MAX_SAFE_INTEGER : length;\n return !!length &&\n (typeof value == 'number' || reIsUint.test(value)) &&\n (value > -1 && value % 1 == 0 && value < length);\n}\n\n/**\n * Checks if `value` is suitable for use as unique object key.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is suitable, else `false`.\n */\nfunction isKeyable(value) {\n var type = typeof value;\n return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')\n ? (value !== '__proto__')\n : (value === null);\n}\n\n/**\n * Checks if `func` has its source masked.\n *\n * @private\n * @param {Function} func The function to check.\n * @returns {boolean} Returns `true` if `func` is masked, else `false`.\n */\nfunction isMasked(func) {\n return !!maskSrcKey && (maskSrcKey in func);\n}\n\n/**\n * Checks if `value` is likely a prototype object.\n *\n * @private\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.\n */\nfunction isPrototype(value) {\n var Ctor = value && value.constructor,\n proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;\n\n return value === proto;\n}\n\n/**\n * Converts `value` to a string using `Object.prototype.toString`.\n *\n * @private\n * @param {*} value The value to convert.\n * @returns {string} Returns the converted string.\n */\nfunction objectToString(value) {\n return nativeObjectToString.call(value);\n}\n\n/**\n * Converts `func` to its source code.\n *\n * @private\n * @param {Function} func The function to convert.\n * @returns {string} Returns the source code.\n */\nfunction toSource(func) {\n if (func != null) {\n try {\n return funcToString.call(func);\n } catch (e) {}\n try {\n return (func + '');\n } catch (e) {}\n }\n return '';\n}\n\n/**\n * Performs a\n * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)\n * comparison between two values to determine if they are equivalent.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'a': 1 };\n * var other = { 'a': 1 };\n *\n * _.eq(object, object);\n * // => true\n *\n * _.eq(object, other);\n * // => false\n *\n * _.eq('a', 'a');\n * // => true\n *\n * _.eq('a', Object('a'));\n * // => false\n *\n * _.eq(NaN, NaN);\n * // => true\n */\nfunction eq(value, other) {\n return value === other || (value !== value && other !== other);\n}\n\n/**\n * Checks if `value` is likely an `arguments` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an `arguments` object,\n * else `false`.\n * @example\n *\n * _.isArguments(function() { return arguments; }());\n * // => true\n *\n * _.isArguments([1, 2, 3]);\n * // => false\n */\nvar isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {\n return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&\n !propertyIsEnumerable.call(value, 'callee');\n};\n\n/**\n * Checks if `value` is classified as an `Array` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an array, else `false`.\n * @example\n *\n * _.isArray([1, 2, 3]);\n * // => true\n *\n * _.isArray(document.body.children);\n * // => false\n *\n * _.isArray('abc');\n * // => false\n *\n * _.isArray(_.noop);\n * // => false\n */\nvar isArray = Array.isArray;\n\n/**\n * Checks if `value` is array-like. A value is considered array-like if it's\n * not a function and has a `value.length` that's an integer greater than or\n * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is array-like, else `false`.\n * @example\n *\n * _.isArrayLike([1, 2, 3]);\n * // => true\n *\n * _.isArrayLike(document.body.children);\n * // => true\n *\n * _.isArrayLike('abc');\n * // => true\n *\n * _.isArrayLike(_.noop);\n * // => false\n */\nfunction isArrayLike(value) {\n return value != null && isLength(value.length) && !isFunction(value);\n}\n\n/**\n * Checks if `value` is a buffer.\n *\n * @static\n * @memberOf _\n * @since 4.3.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.\n * @example\n *\n * _.isBuffer(new Buffer(2));\n * // => true\n *\n * _.isBuffer(new Uint8Array(2));\n * // => false\n */\nvar isBuffer = nativeIsBuffer || stubFalse;\n\n/**\n * Performs a deep comparison between two values to determine if they are\n * equivalent.\n *\n * **Note:** This method supports comparing arrays, array buffers, booleans,\n * date objects, error objects, maps, numbers, `Object` objects, regexes,\n * sets, strings, symbols, and typed arrays. `Object` objects are compared\n * by their own, not inherited, enumerable properties. Functions and DOM\n * nodes are compared by strict equality, i.e. `===`.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to compare.\n * @param {*} other The other value to compare.\n * @returns {boolean} Returns `true` if the values are equivalent, else `false`.\n * @example\n *\n * var object = { 'a': 1 };\n * var other = { 'a': 1 };\n *\n * _.isEqual(object, other);\n * // => true\n *\n * object === other;\n * // => false\n */\nfunction isEqual(value, other) {\n return baseIsEqual(value, other);\n}\n\n/**\n * Checks if `value` is classified as a `Function` object.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a function, else `false`.\n * @example\n *\n * _.isFunction(_);\n * // => true\n *\n * _.isFunction(/abc/);\n * // => false\n */\nfunction isFunction(value) {\n if (!isObject(value)) {\n return false;\n }\n // The use of `Object#toString` avoids issues with the `typeof` operator\n // in Safari 9 which returns 'object' for typed arrays and other constructors.\n var tag = baseGetTag(value);\n return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;\n}\n\n/**\n * Checks if `value` is a valid array-like length.\n *\n * **Note:** This method is loosely based on\n * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.\n * @example\n *\n * _.isLength(3);\n * // => true\n *\n * _.isLength(Number.MIN_VALUE);\n * // => false\n *\n * _.isLength(Infinity);\n * // => false\n *\n * _.isLength('3');\n * // => false\n */\nfunction isLength(value) {\n return typeof value == 'number' &&\n value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;\n}\n\n/**\n * Checks if `value` is the\n * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)\n * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is an object, else `false`.\n * @example\n *\n * _.isObject({});\n * // => true\n *\n * _.isObject([1, 2, 3]);\n * // => true\n *\n * _.isObject(_.noop);\n * // => true\n *\n * _.isObject(null);\n * // => false\n */\nfunction isObject(value) {\n var type = typeof value;\n return value != null && (type == 'object' || type == 'function');\n}\n\n/**\n * Checks if `value` is object-like. A value is object-like if it's not `null`\n * and has a `typeof` result of \"object\".\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is object-like, else `false`.\n * @example\n *\n * _.isObjectLike({});\n * // => true\n *\n * _.isObjectLike([1, 2, 3]);\n * // => true\n *\n * _.isObjectLike(_.noop);\n * // => false\n *\n * _.isObjectLike(null);\n * // => false\n */\nfunction isObjectLike(value) {\n return value != null && typeof value == 'object';\n}\n\n/**\n * Checks if `value` is classified as a typed array.\n *\n * @static\n * @memberOf _\n * @since 3.0.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.\n * @example\n *\n * _.isTypedArray(new Uint8Array);\n * // => true\n *\n * _.isTypedArray([]);\n * // => false\n */\nvar isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;\n\n/**\n * Creates an array of the own enumerable property names of `object`.\n *\n * **Note:** Non-object values are coerced to objects. See the\n * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)\n * for more details.\n *\n * @static\n * @since 0.1.0\n * @memberOf _\n * @category Object\n * @param {Object} object The object to query.\n * @returns {Array} Returns the array of property names.\n * @example\n *\n * function Foo() {\n * this.a = 1;\n * this.b = 2;\n * }\n *\n * Foo.prototype.c = 3;\n *\n * _.keys(new Foo);\n * // => ['a', 'b'] (iteration order is not guaranteed)\n *\n * _.keys('hi');\n * // => ['0', '1']\n */\nfunction keys(object) {\n return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);\n}\n\n/**\n * This method returns a new empty array.\n *\n * @static\n * @memberOf _\n * @since 4.13.0\n * @category Util\n * @returns {Array} Returns the new empty array.\n * @example\n *\n * var arrays = _.times(2, _.stubArray);\n *\n * console.log(arrays);\n * // => [[], []]\n *\n * console.log(arrays[0] === arrays[1]);\n * // => false\n */\nfunction stubArray() {\n return [];\n}\n\n/**\n * This method returns `false`.\n *\n * @static\n * @memberOf _\n * @since 4.13.0\n * @category Util\n * @returns {boolean} Returns `false`.\n * @example\n *\n * _.times(2, _.stubFalse);\n * // => [false, false]\n */\nfunction stubFalse() {\n return false;\n}\n\nmodule.exports = isEqual;\n","var grammar = module.exports = {\n v: [{\n name: 'version',\n reg: /^(\\d*)$/\n }],\n o: [{ //o=- 20518 0 IN IP4 203.0.113.1\n // NB: sessionId will be a String in most cases because it is huge\n name: 'origin',\n reg: /^(\\S*) (\\d*) (\\d*) (\\S*) IP(\\d) (\\S*)/,\n names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],\n format: '%s %s %d %s IP%d %s'\n }],\n // default parsing of these only (though some of these feel outdated)\n s: [{ name: 'name' }],\n i: [{ name: 'description' }],\n u: [{ name: 'uri' }],\n e: [{ name: 'email' }],\n p: [{ name: 'phone' }],\n z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..\n r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly\n //k: [{}], // outdated thing ignored\n t: [{ //t=0 0\n name: 'timing',\n reg: /^(\\d*) (\\d*)/,\n names: ['start', 'stop'],\n format: '%d %d'\n }],\n c: [{ //c=IN IP4 10.47.197.26\n name: 'connection',\n reg: /^IN IP(\\d) (\\S*)/,\n names: ['version', 'ip'],\n format: 'IN IP%d %s'\n }],\n b: [{ //b=AS:4000\n push: 'bandwidth',\n reg: /^(TIAS|AS|CT|RR|RS):(\\d*)/,\n names: ['type', 'limit'],\n format: '%s:%s'\n }],\n m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31\n // NB: special - pushes to session\n // TODO: rtp/fmtp should be filtered by the payloads found here?\n reg: /^(\\w*) (\\d*) ([\\w\\/]*)(?: (.*))?/,\n names: ['type', 'port', 'protocol', 'payloads'],\n format: '%s %d %s %s'\n }],\n a: [\n { //a=rtpmap:110 opus/48000/2\n push: 'rtp',\n reg: /^rtpmap:(\\d*) ([\\w\\-\\.]*)(?:\\s*\\/(\\d*)(?:\\s*\\/(\\S*))?)?/,\n names: ['payload', 'codec', 'rate', 'encoding'],\n format: function (o) {\n return (o.encoding) ?\n 'rtpmap:%d %s/%s/%s':\n o.rate ?\n 'rtpmap:%d %s/%s':\n 'rtpmap:%d %s';\n }\n },\n { //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000\n //a=fmtp:111 minptime=10; useinbandfec=1\n push: 'fmtp',\n reg: /^fmtp:(\\d*) ([\\S| ]*)/,\n names: ['payload', 'config'],\n format: 'fmtp:%d %s'\n },\n { //a=control:streamid=0\n name: 'control',\n reg: /^control:(.*)/,\n format: 'control:%s'\n },\n { //a=rtcp:65179 IN IP4 193.84.77.194\n name: 'rtcp',\n reg: /^rtcp:(\\d*)(?: (\\S*) IP(\\d) (\\S*))?/,\n names: ['port', 'netType', 'ipVer', 'address'],\n format: function (o) {\n return (o.address != null) ?\n 'rtcp:%d %s IP%d %s':\n 'rtcp:%d';\n }\n },\n { //a=rtcp-fb:98 trr-int 100\n push: 'rtcpFbTrrInt',\n reg: /^rtcp-fb:(\\*|\\d*) trr-int (\\d*)/,\n names: ['payload', 'value'],\n format: 'rtcp-fb:%d trr-int %d'\n },\n { //a=rtcp-fb:98 nack rpsi\n push: 'rtcpFb',\n reg: /^rtcp-fb:(\\*|\\d*) ([\\w-_]*)(?: ([\\w-_]*))?/,\n names: ['payload', 'type', 'subtype'],\n format: function (o) {\n return (o.subtype != null) ?\n 'rtcp-fb:%s %s %s':\n 'rtcp-fb:%s %s';\n }\n },\n { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\n //a=extmap:1/recvonly URI-gps-string\n push: 'ext',\n reg: /^extmap:(\\d+)(?:\\/(\\w+))? (\\S*)(?: (\\S*))?/,\n names: ['value', 'direction', 'uri', 'config'],\n format: function (o) {\n return 'extmap:%d' + (o.direction ? '/%s' : '%v') + ' %s' + (o.config ? ' %s' : '');\n }\n },\n { //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32\n push: 'crypto',\n reg: /^crypto:(\\d*) ([\\w_]*) (\\S*)(?: (\\S*))?/,\n names: ['id', 'suite', 'config', 'sessionConfig'],\n format: function (o) {\n return (o.sessionConfig != null) ?\n 'crypto:%d %s %s %s':\n 'crypto:%d %s %s';\n }\n },\n { //a=setup:actpass\n name: 'setup',\n reg: /^setup:(\\w*)/,\n format: 'setup:%s'\n },\n { //a=mid:1\n name: 'mid',\n reg: /^mid:([^\\s]*)/,\n format: 'mid:%s'\n },\n { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a\n name: 'msid',\n reg: /^msid:(.*)/,\n format: 'msid:%s'\n },\n { //a=ptime:20\n name: 'ptime',\n reg: /^ptime:(\\d*)/,\n format: 'ptime:%d'\n },\n { //a=maxptime:60\n name: 'maxptime',\n reg: /^maxptime:(\\d*)/,\n format: 'maxptime:%d'\n },\n { //a=sendrecv\n name: 'direction',\n reg: /^(sendrecv|recvonly|sendonly|inactive)/\n },\n { //a=ice-lite\n name: 'icelite',\n reg: /^(ice-lite)/\n },\n { //a=ice-ufrag:F7gI\n name: 'iceUfrag',\n reg: /^ice-ufrag:(\\S*)/,\n format: 'ice-ufrag:%s'\n },\n { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g\n name: 'icePwd',\n reg: /^ice-pwd:(\\S*)/,\n format: 'ice-pwd:%s'\n },\n { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33\n name: 'fingerprint',\n reg: /^fingerprint:(\\S*) (\\S*)/,\n names: ['type', 'hash'],\n format: 'fingerprint:%s %s'\n },\n { //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host\n //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 network-id 3 network-cost 10\n //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 network-id 3 network-cost 10\n //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 network-id 3 network-cost 10\n //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 network-id 3 network-cost 10\n push:'candidates',\n reg: /^candidate:(\\S*) (\\d*) (\\S*) (\\d*) (\\S*) (\\d*) typ (\\S*)(?: raddr (\\S*) rport (\\d*))?(?: tcptype (\\S*))?(?: generation (\\d*))?(?: network-id (\\d*))?(?: network-cost (\\d*))?/,\n names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation', 'network-id', 'network-cost'],\n format: function (o) {\n var str = 'candidate:%s %d %s %d %s %d typ %s';\n\n str += (o.raddr != null) ? ' raddr %s rport %d' : '%v%v';\n\n // NB: candidate has three optional chunks, so %void middles one if it's missing\n str += (o.tcptype != null) ? ' tcptype %s' : '%v';\n\n if (o.generation != null) {\n str += ' generation %d';\n }\n\n str += (o['network-id'] != null) ? ' network-id %d' : '%v';\n str += (o['network-cost'] != null) ? ' network-cost %d' : '%v';\n return str;\n }\n },\n { //a=end-of-candidates (keep after the candidates line for readability)\n name: 'endOfCandidates',\n reg: /^(end-of-candidates)/\n },\n { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...\n name: 'remoteCandidates',\n reg: /^remote-candidates:(.*)/,\n format: 'remote-candidates:%s'\n },\n { //a=ice-options:google-ice\n name: 'iceOptions',\n reg: /^ice-options:(\\S*)/,\n format: 'ice-options:%s'\n },\n { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1\n push: 'ssrcs',\n reg: /^ssrc:(\\d*) ([\\w_]*)(?::(.*))?/,\n names: ['id', 'attribute', 'value'],\n format: function (o) {\n var str = 'ssrc:%d';\n if (o.attribute != null) {\n str += ' %s';\n if (o.value != null) {\n str += ':%s';\n }\n }\n return str;\n }\n },\n { //a=ssrc-group:FEC 1 2\n //a=ssrc-group:FEC-FR 3004364195 1080772241\n push: 'ssrcGroups',\n // token-char = %x21 / %x23-27 / %x2A-2B / %x2D-2E / %x30-39 / %x41-5A / %x5E-7E\n reg: /^ssrc-group:([\\x21\\x23\\x24\\x25\\x26\\x27\\x2A\\x2B\\x2D\\x2E\\w]*) (.*)/,\n names: ['semantics', 'ssrcs'],\n format: 'ssrc-group:%s %s'\n },\n { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV\n name: 'msidSemantic',\n reg: /^msid-semantic:\\s?(\\w*) (\\S*)/,\n names: ['semantic', 'token'],\n format: 'msid-semantic: %s %s' // space after ':' is not accidental\n },\n { //a=group:BUNDLE audio video\n push: 'groups',\n reg: /^group:(\\w*) (.*)/,\n names: ['type', 'mids'],\n format: 'group:%s %s'\n },\n { //a=rtcp-mux\n name: 'rtcpMux',\n reg: /^(rtcp-mux)/\n },\n { //a=rtcp-rsize\n name: 'rtcpRsize',\n reg: /^(rtcp-rsize)/\n },\n { //a=sctpmap:5000 webrtc-datachannel 1024\n name: 'sctpmap',\n reg: /^sctpmap:([\\w_\\/]*) (\\S*)(?: (\\S*))?/,\n names: ['sctpmapNumber', 'app', 'maxMessageSize'],\n format: function (o) {\n return (o.maxMessageSize != null) ?\n 'sctpmap:%s %s %s' :\n 'sctpmap:%s %s';\n }\n },\n { //a=x-google-flag:conference\n name: 'xGoogleFlag',\n reg: /^x-google-flag:([^\\s]*)/,\n format: 'x-google-flag:%s'\n },\n { //a=rid:1 send max-width=1280;max-height=720;max-fps=30;depend=0\n push: 'rids',\n reg: /^rid:([\\d\\w]+) (\\w+)(?: ([\\S| ]*))?/,\n names: ['id', 'direction', 'params'],\n format: function (o) {\n return (o.params) ? 'rid:%s %s %s' : 'rid:%s %s';\n }\n },\n { //a=imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]\n //a=imageattr:* send [x=800,y=640] recv *\n //a=imageattr:100 recv [x=320,y=240]\n push: 'imageattrs',\n reg: new RegExp(\n //a=imageattr:97\n '^imageattr:(\\\\d+|\\\\*)' +\n //send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320]\n '[\\\\s\\\\t]+(send|recv)[\\\\s\\\\t]+(\\\\*|\\\\[\\\\S+\\\\](?:[\\\\s\\\\t]+\\\\[\\\\S+\\\\])*)' +\n //recv [x=330,y=250]\n '(?:[\\\\s\\\\t]+(recv|send)[\\\\s\\\\t]+(\\\\*|\\\\[\\\\S+\\\\](?:[\\\\s\\\\t]+\\\\[\\\\S+\\\\])*))?'\n ),\n names: ['pt', 'dir1', 'attrs1', 'dir2', 'attrs2'],\n format: function (o) {\n return 'imageattr:%s %s %s' + (o.dir2 ? ' %s %s' : '');\n }\n },\n { //a=simulcast:send 1,2,3;~4,~5 recv 6;~7,~8\n //a=simulcast:recv 1;4,5 send 6;7\n name: 'simulcast',\n reg: new RegExp(\n //a=simulcast:\n '^simulcast:' +\n //send 1,2,3;~4,~5\n '(send|recv) ([a-zA-Z0-9\\\\-_~;,]+)' +\n //space + recv 6;~7,~8\n '(?:\\\\s?(send|recv) ([a-zA-Z0-9\\\\-_~;,]+))?' +\n //end\n '$'\n ),\n names: ['dir1', 'list1', 'dir2', 'list2'],\n format: function (o) {\n return 'simulcast:%s %s' + (o.dir2 ? ' %s %s' : '');\n }\n },\n { //Old simulcast draft 03 (implemented by Firefox)\n // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-03\n //a=simulcast: recv pt=97;98 send pt=97\n //a=simulcast: send rid=5;6;7 paused=6,7\n name: 'simulcast_03',\n reg: /^simulcast:[\\s\\t]+([\\S+\\s\\t]+)$/,\n names: ['value'],\n format: 'simulcast: %s'\n },\n {\n //a=framerate:25\n //a=framerate:29.97\n name: 'framerate',\n reg: /^framerate:(\\d+(?:$|\\.\\d+))/,\n format: 'framerate:%s'\n },\n { // any a= that we don't understand is kepts verbatim on media.invalid\n push: 'invalid',\n names: ['value']\n }\n ]\n};\n\n// set sensible defaults to avoid polluting the grammar with boring details\nObject.keys(grammar).forEach(function (key) {\n var objs = grammar[key];\n objs.forEach(function (obj) {\n if (!obj.reg) {\n obj.reg = /(.*)/;\n }\n if (!obj.format) {\n obj.format = '%s';\n }\n });\n});\n","var parser = require('./parser');\nvar writer = require('./writer');\n\nexports.write = writer;\nexports.parse = parser.parse;\nexports.parseFmtpConfig = parser.parseFmtpConfig;\nexports.parseParams = parser.parseParams;\nexports.parsePayloads = parser.parsePayloads;\nexports.parseRemoteCandidates = parser.parseRemoteCandidates;\nexports.parseImageAttributes = parser.parseImageAttributes;\nexports.parseSimulcastStreamList = parser.parseSimulcastStreamList;\n","var toIntIfInt = function (v) {\n return String(Number(v)) === v ? Number(v) : v;\n};\n\nvar attachProperties = function (match, location, names, rawName) {\n if (rawName && !names) {\n location[rawName] = toIntIfInt(match[1]);\n }\n else {\n for (var i = 0; i < names.length; i += 1) {\n if (match[i+1] != null) {\n location[names[i]] = toIntIfInt(match[i+1]);\n }\n }\n }\n};\n\nvar parseReg = function (obj, location, content) {\n var needsBlank = obj.name && obj.names;\n if (obj.push && !location[obj.push]) {\n location[obj.push] = [];\n }\n else if (needsBlank && !location[obj.name]) {\n location[obj.name] = {};\n }\n var keyLocation = obj.push ?\n {} : // blank object that will be pushed\n needsBlank ? location[obj.name] : location; // otherwise, named location or root\n\n attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);\n\n if (obj.push) {\n location[obj.push].push(keyLocation);\n }\n};\n\nvar grammar = require('./grammar');\nvar validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);\n\nexports.parse = function (sdp) {\n var session = {}\n , media = []\n , location = session; // points at where properties go under (one of the above)\n\n // parse lines we understand\n sdp.split(/(\\r\\n|\\r|\\n)/).filter(validLine).forEach(function (l) {\n var type = l[0];\n var content = l.slice(2);\n if (type === 'm') {\n media.push({rtp: [], fmtp: []});\n location = media[media.length-1]; // point at latest media line\n }\n\n for (var j = 0; j < (grammar[type] || []).length; j += 1) {\n var obj = grammar[type][j];\n if (obj.reg.test(content)) {\n return parseReg(obj, location, content);\n }\n }\n });\n\n session.media = media; // link it up\n return session;\n};\n\nvar paramReducer = function (acc, expr) {\n var s = expr.split(/=(.+)/, 2);\n if (s.length === 2) {\n acc[s[0]] = toIntIfInt(s[1]);\n }\n return acc;\n};\n\nexports.parseParams = function (str) {\n return str.split(/\\;\\s?/).reduce(paramReducer, {});\n};\n\n// For backward compatibility - alias will be removed in 3.0.0\nexports.parseFmtpConfig = exports.parseParams;\n\nexports.parsePayloads = function (str) {\n return str.split(' ').map(Number);\n};\n\nexports.parseRemoteCandidates = function (str) {\n var candidates = [];\n var parts = str.split(' ').map(toIntIfInt);\n for (var i = 0; i < parts.length; i += 3) {\n candidates.push({\n component: parts[i],\n ip: parts[i + 1],\n port: parts[i + 2]\n });\n }\n return candidates;\n};\n\nexports.parseImageAttributes = function (str) {\n return str.split(' ').map(function (item) {\n return item.substring(1, item.length-1).split(',').reduce(paramReducer, {});\n });\n};\n\nexports.parseSimulcastStreamList = function (str) {\n return str.split(';').map(function (stream) {\n return stream.split(',').map(function (format) {\n var scid, paused = false;\n\n if (format[0] !== '~') {\n scid = toIntIfInt(format);\n } else {\n scid = toIntIfInt(format.substring(1, format.length));\n paused = true;\n }\n\n return {\n scid: scid,\n paused: paused\n };\n });\n });\n};\n","var grammar = require('./grammar');\n\n// customized util.format - discards excess arguments and can void middle ones\nvar formatRegExp = /%[sdv%]/g;\nvar format = function (formatStr) {\n var i = 1;\n var args = arguments;\n var len = args.length;\n return formatStr.replace(formatRegExp, function (x) {\n if (i >= len) {\n return x; // missing argument\n }\n var arg = args[i];\n i += 1;\n switch (x) {\n case '%%':\n return '%';\n case '%s':\n return String(arg);\n case '%d':\n return Number(arg);\n case '%v':\n return '';\n }\n });\n // NB: we discard excess arguments - they are typically undefined from makeLine\n};\n\nvar makeLine = function (type, obj, location) {\n var str = obj.format instanceof Function ?\n (obj.format(obj.push ? location : location[obj.name])) :\n obj.format;\n\n var args = [type + '=' + str];\n if (obj.names) {\n for (var i = 0; i < obj.names.length; i += 1) {\n var n = obj.names[i];\n if (obj.name) {\n args.push(location[obj.name][n]);\n }\n else { // for mLine and push attributes\n args.push(location[obj.names[i]]);\n }\n }\n }\n else {\n args.push(location[obj.name]);\n }\n return format.apply(null, args);\n};\n\n// RFC specified order\n// TODO: extend this with all the rest\nvar defaultOuterOrder = [\n 'v', 'o', 's', 'i',\n 'u', 'e', 'p', 'c',\n 'b', 't', 'r', 'z', 'a'\n];\nvar defaultInnerOrder = ['i', 'c', 'b', 'a'];\n\n\nmodule.exports = function (session, opts) {\n opts = opts || {};\n // ensure certain properties exist\n if (session.version == null) {\n session.version = 0; // 'v=0' must be there (only defined version atm)\n }\n if (session.name == null) {\n session.name = ' '; // 's= ' must be there if no meaningful name set\n }\n session.media.forEach(function (mLine) {\n if (mLine.payloads == null) {\n mLine.payloads = '';\n }\n });\n\n var outerOrder = opts.outerOrder || defaultOuterOrder;\n var innerOrder = opts.innerOrder || defaultInnerOrder;\n var sdp = [];\n\n // loop through outerOrder for matching properties on session\n outerOrder.forEach(function (type) {\n grammar[type].forEach(function (obj) {\n if (obj.name in session && session[obj.name] != null) {\n sdp.push(makeLine(type, obj, session));\n }\n else if (obj.push in session && session[obj.push] != null) {\n session[obj.push].forEach(function (el) {\n sdp.push(makeLine(type, obj, el));\n });\n }\n });\n });\n\n // then for each media line, follow the innerOrder\n session.media.forEach(function (mLine) {\n sdp.push(makeLine('m', grammar.m[0], mLine));\n\n innerOrder.forEach(function (type) {\n grammar[type].forEach(function (obj) {\n if (obj.name in mLine && mLine[obj.name] != null) {\n sdp.push(makeLine(type, obj, mLine));\n }\n else if (obj.push in mLine && mLine[obj.push] != null) {\n mLine[obj.push].forEach(function (el) {\n sdp.push(makeLine(type, obj, el));\n });\n }\n });\n });\n });\n\n return sdp.join('\\r\\n') + '\\r\\n';\n};\n","/* eslint-env node */\n'use strict';\n\n// SDP helpers.\nconst SDPUtils = {};\n\n// Generate an alphanumeric identifier for cname or mids.\n// TODO: use UUIDs instead? https://gist.github.com/jed/982883\nSDPUtils.generateIdentifier = function() {\n return Math.random().toString(36).substr(2, 10);\n};\n\n// The RTCP CNAME used by all peerconnections from the same JS.\nSDPUtils.localCName = SDPUtils.generateIdentifier();\n\n// Splits SDP into lines, dealing with both CRLF and LF.\nSDPUtils.splitLines = function(blob) {\n return blob.trim().split('\\n').map(line => line.trim());\n};\n// Splits SDP into sessionpart and mediasections. Ensures CRLF.\nSDPUtils.splitSections = function(blob) {\n const parts = blob.split('\\nm=');\n return parts.map((part, index) => (index > 0 ?\n 'm=' + part : part).trim() + '\\r\\n');\n};\n\n// Returns the session description.\nSDPUtils.getDescription = function(blob) {\n const sections = SDPUtils.splitSections(blob);\n return sections && sections[0];\n};\n\n// Returns the individual media sections.\nSDPUtils.getMediaSections = function(blob) {\n const sections = SDPUtils.splitSections(blob);\n sections.shift();\n return sections;\n};\n\n// Returns lines that start with a certain prefix.\nSDPUtils.matchPrefix = function(blob, prefix) {\n return SDPUtils.splitLines(blob).filter(line => line.indexOf(prefix) === 0);\n};\n\n// Parses an ICE candidate line. Sample input:\n// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8\n// rport 55996\"\n// Input can be prefixed with a=.\nSDPUtils.parseCandidate = function(line) {\n let parts;\n // Parse both variants.\n if (line.indexOf('a=candidate:') === 0) {\n parts = line.substring(12).split(' ');\n } else {\n parts = line.substring(10).split(' ');\n }\n\n const candidate = {\n foundation: parts[0],\n component: {1: 'rtp', 2: 'rtcp'}[parts[1]] || parts[1],\n protocol: parts[2].toLowerCase(),\n priority: parseInt(parts[3], 10),\n ip: parts[4],\n address: parts[4], // address is an alias for ip.\n port: parseInt(parts[5], 10),\n // skip parts[6] == 'typ'\n type: parts[7],\n };\n\n for (let i = 8; i < parts.length; i += 2) {\n switch (parts[i]) {\n case 'raddr':\n candidate.relatedAddress = parts[i + 1];\n break;\n case 'rport':\n candidate.relatedPort = parseInt(parts[i + 1], 10);\n break;\n case 'tcptype':\n candidate.tcpType = parts[i + 1];\n break;\n case 'ufrag':\n candidate.ufrag = parts[i + 1]; // for backward compatibility.\n candidate.usernameFragment = parts[i + 1];\n break;\n default: // extension handling, in particular ufrag. Don't overwrite.\n if (candidate[parts[i]] === undefined) {\n candidate[parts[i]] = parts[i + 1];\n }\n break;\n }\n }\n return candidate;\n};\n\n// Translates a candidate object into SDP candidate attribute.\n// This does not include the a= prefix!\nSDPUtils.writeCandidate = function(candidate) {\n const sdp = [];\n sdp.push(candidate.foundation);\n\n const component = candidate.component;\n if (component === 'rtp') {\n sdp.push(1);\n } else if (component === 'rtcp') {\n sdp.push(2);\n } else {\n sdp.push(component);\n }\n sdp.push(candidate.protocol.toUpperCase());\n sdp.push(candidate.priority);\n sdp.push(candidate.address || candidate.ip);\n sdp.push(candidate.port);\n\n const type = candidate.type;\n sdp.push('typ');\n sdp.push(type);\n if (type !== 'host' && candidate.relatedAddress &&\n candidate.relatedPort) {\n sdp.push('raddr');\n sdp.push(candidate.relatedAddress);\n sdp.push('rport');\n sdp.push(candidate.relatedPort);\n }\n if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {\n sdp.push('tcptype');\n sdp.push(candidate.tcpType);\n }\n if (candidate.usernameFragment || candidate.ufrag) {\n sdp.push('ufrag');\n sdp.push(candidate.usernameFragment || candidate.ufrag);\n }\n return 'candidate:' + sdp.join(' ');\n};\n\n// Parses an ice-options line, returns an array of option tags.\n// Sample input:\n// a=ice-options:foo bar\nSDPUtils.parseIceOptions = function(line) {\n return line.substr(14).split(' ');\n};\n\n// Parses a rtpmap line, returns RTCRtpCoddecParameters. Sample input:\n// a=rtpmap:111 opus/48000/2\nSDPUtils.parseRtpMap = function(line) {\n let parts = line.substr(9).split(' ');\n const parsed = {\n payloadType: parseInt(parts.shift(), 10), // was: id\n };\n\n parts = parts[0].split('/');\n\n parsed.name = parts[0];\n parsed.clockRate = parseInt(parts[1], 10); // was: clockrate\n parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;\n // legacy alias, got renamed back to channels in ORTC.\n parsed.numChannels = parsed.channels;\n return parsed;\n};\n\n// Generates a rtpmap line from RTCRtpCodecCapability or\n// RTCRtpCodecParameters.\nSDPUtils.writeRtpMap = function(codec) {\n let pt = codec.payloadType;\n if (codec.preferredPayloadType !== undefined) {\n pt = codec.preferredPayloadType;\n }\n const channels = codec.channels || codec.numChannels || 1;\n return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +\n (channels !== 1 ? '/' + channels : '') + '\\r\\n';\n};\n\n// Parses a extmap line (headerextension from RFC 5285). Sample input:\n// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\n// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset\nSDPUtils.parseExtmap = function(line) {\n const parts = line.substr(9).split(' ');\n return {\n id: parseInt(parts[0], 10),\n direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',\n uri: parts[1],\n };\n};\n\n// Generates an extmap line from RTCRtpHeaderExtensionParameters or\n// RTCRtpHeaderExtension.\nSDPUtils.writeExtmap = function(headerExtension) {\n return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +\n (headerExtension.direction && headerExtension.direction !== 'sendrecv'\n ? '/' + headerExtension.direction\n : '') +\n ' ' + headerExtension.uri + '\\r\\n';\n};\n\n// Parses a fmtp line, returns dictionary. Sample input:\n// a=fmtp:96 vbr=on;cng=on\n// Also deals with vbr=on; cng=on\nSDPUtils.parseFmtp = function(line) {\n const parsed = {};\n let kv;\n const parts = line.substr(line.indexOf(' ') + 1).split(';');\n for (let j = 0; j < parts.length; j++) {\n kv = parts[j].trim().split('=');\n parsed[kv[0].trim()] = kv[1];\n }\n return parsed;\n};\n\n// Generates a fmtp line from RTCRtpCodecCapability or RTCRtpCodecParameters.\nSDPUtils.writeFmtp = function(codec) {\n let line = '';\n let pt = codec.payloadType;\n if (codec.preferredPayloadType !== undefined) {\n pt = codec.preferredPayloadType;\n }\n if (codec.parameters && Object.keys(codec.parameters).length) {\n const params = [];\n Object.keys(codec.parameters).forEach(param => {\n if (codec.parameters[param] !== undefined) {\n params.push(param + '=' + codec.parameters[param]);\n } else {\n params.push(param);\n }\n });\n line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\\r\\n';\n }\n return line;\n};\n\n// Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:\n// a=rtcp-fb:98 nack rpsi\nSDPUtils.parseRtcpFb = function(line) {\n const parts = line.substr(line.indexOf(' ') + 1).split(' ');\n return {\n type: parts.shift(),\n parameter: parts.join(' '),\n };\n};\n\n// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.\nSDPUtils.writeRtcpFb = function(codec) {\n let lines = '';\n let pt = codec.payloadType;\n if (codec.preferredPayloadType !== undefined) {\n pt = codec.preferredPayloadType;\n }\n if (codec.rtcpFeedback && codec.rtcpFeedback.length) {\n // FIXME: special handling for trr-int?\n codec.rtcpFeedback.forEach(fb => {\n lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +\n (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +\n '\\r\\n';\n });\n }\n return lines;\n};\n\n// Parses a RFC 5576 ssrc media attribute. Sample input:\n// a=ssrc:3735928559 cname:something\nSDPUtils.parseSsrcMedia = function(line) {\n const sp = line.indexOf(' ');\n const parts = {\n ssrc: parseInt(line.substr(7, sp - 7), 10),\n };\n const colon = line.indexOf(':', sp);\n if (colon > -1) {\n parts.attribute = line.substr(sp + 1, colon - sp - 1);\n parts.value = line.substr(colon + 1);\n } else {\n parts.attribute = line.substr(sp + 1);\n }\n return parts;\n};\n\n// Parse a ssrc-group line (see RFC 5576). Sample input:\n// a=ssrc-group:semantics 12 34\nSDPUtils.parseSsrcGroup = function(line) {\n const parts = line.substr(13).split(' ');\n return {\n semantics: parts.shift(),\n ssrcs: parts.map(ssrc => parseInt(ssrc, 10)),\n };\n};\n\n// Extracts the MID (RFC 5888) from a media section.\n// Returns the MID or undefined if no mid line was found.\nSDPUtils.getMid = function(mediaSection) {\n const mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];\n if (mid) {\n return mid.substr(6);\n }\n};\n\n// Parses a fingerprint line for DTLS-SRTP.\nSDPUtils.parseFingerprint = function(line) {\n const parts = line.substr(14).split(' ');\n return {\n algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.\n value: parts[1].toUpperCase(), // the definition is upper-case in RFC 4572.\n };\n};\n\n// Extracts DTLS parameters from SDP media section or sessionpart.\n// FIXME: for consistency with other functions this should only\n// get the fingerprint line as input. See also getIceParameters.\nSDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {\n const lines = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=fingerprint:');\n // Note: a=setup line is ignored since we use the 'auto' role in Edge.\n return {\n role: 'auto',\n fingerprints: lines.map(SDPUtils.parseFingerprint),\n };\n};\n\n// Serializes DTLS parameters to SDP.\nSDPUtils.writeDtlsParameters = function(params, setupType) {\n let sdp = 'a=setup:' + setupType + '\\r\\n';\n params.fingerprints.forEach(fp => {\n sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\\r\\n';\n });\n return sdp;\n};\n\n// Parses a=crypto lines into\n// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members\nSDPUtils.parseCryptoLine = function(line) {\n const parts = line.substr(9).split(' ');\n return {\n tag: parseInt(parts[0], 10),\n cryptoSuite: parts[1],\n keyParams: parts[2],\n sessionParams: parts.slice(3),\n };\n};\n\nSDPUtils.writeCryptoLine = function(parameters) {\n return 'a=crypto:' + parameters.tag + ' ' +\n parameters.cryptoSuite + ' ' +\n (typeof parameters.keyParams === 'object'\n ? SDPUtils.writeCryptoKeyParams(parameters.keyParams)\n : parameters.keyParams) +\n (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') +\n '\\r\\n';\n};\n\n// Parses the crypto key parameters into\n// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam*\nSDPUtils.parseCryptoKeyParams = function(keyParams) {\n if (keyParams.indexOf('inline:') !== 0) {\n return null;\n }\n const parts = keyParams.substr(7).split('|');\n return {\n keyMethod: 'inline',\n keySalt: parts[0],\n lifeTime: parts[1],\n mkiValue: parts[2] ? parts[2].split(':')[0] : undefined,\n mkiLength: parts[2] ? parts[2].split(':')[1] : undefined,\n };\n};\n\nSDPUtils.writeCryptoKeyParams = function(keyParams) {\n return keyParams.keyMethod + ':'\n + keyParams.keySalt +\n (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') +\n (keyParams.mkiValue && keyParams.mkiLength\n ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength\n : '');\n};\n\n// Extracts all SDES parameters.\nSDPUtils.getCryptoParameters = function(mediaSection, sessionpart) {\n const lines = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=crypto:');\n return lines.map(SDPUtils.parseCryptoLine);\n};\n\n// Parses ICE information from SDP media section or sessionpart.\n// FIXME: for consistency with other functions this should only\n// get the ice-ufrag and ice-pwd lines as input.\nSDPUtils.getIceParameters = function(mediaSection, sessionpart) {\n const ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=ice-ufrag:')[0];\n const pwd = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=ice-pwd:')[0];\n if (!(ufrag && pwd)) {\n return null;\n }\n return {\n usernameFragment: ufrag.substr(12),\n password: pwd.substr(10),\n };\n};\n\n// Serializes ICE parameters to SDP.\nSDPUtils.writeIceParameters = function(params) {\n let sdp = 'a=ice-ufrag:' + params.usernameFragment + '\\r\\n' +\n 'a=ice-pwd:' + params.password + '\\r\\n';\n if (params.iceLite) {\n sdp += 'a=ice-lite\\r\\n';\n }\n return sdp;\n};\n\n// Parses the SDP media section and returns RTCRtpParameters.\nSDPUtils.parseRtpParameters = function(mediaSection) {\n const description = {\n codecs: [],\n headerExtensions: [],\n fecMechanisms: [],\n rtcp: [],\n };\n const lines = SDPUtils.splitLines(mediaSection);\n const mline = lines[0].split(' ');\n for (let i = 3; i < mline.length; i++) { // find all codecs from mline[3..]\n const pt = mline[i];\n const rtpmapline = SDPUtils.matchPrefix(\n mediaSection, 'a=rtpmap:' + pt + ' ')[0];\n if (rtpmapline) {\n const codec = SDPUtils.parseRtpMap(rtpmapline);\n const fmtps = SDPUtils.matchPrefix(\n mediaSection, 'a=fmtp:' + pt + ' ');\n // Only the first a=fmtp: is considered.\n codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};\n codec.rtcpFeedback = SDPUtils.matchPrefix(\n mediaSection, 'a=rtcp-fb:' + pt + ' ')\n .map(SDPUtils.parseRtcpFb);\n description.codecs.push(codec);\n // parse FEC mechanisms from rtpmap lines.\n switch (codec.name.toUpperCase()) {\n case 'RED':\n case 'ULPFEC':\n description.fecMechanisms.push(codec.name.toUpperCase());\n break;\n default: // only RED and ULPFEC are recognized as FEC mechanisms.\n break;\n }\n }\n }\n SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(line => {\n description.headerExtensions.push(SDPUtils.parseExtmap(line));\n });\n // FIXME: parse rtcp.\n return description;\n};\n\n// Generates parts of the SDP media section describing the capabilities /\n// parameters.\nSDPUtils.writeRtpDescription = function(kind, caps) {\n let sdp = '';\n\n // Build the mline.\n sdp += 'm=' + kind + ' ';\n sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.\n sdp += ' UDP/TLS/RTP/SAVPF ';\n sdp += caps.codecs.map(codec => {\n if (codec.preferredPayloadType !== undefined) {\n return codec.preferredPayloadType;\n }\n return codec.payloadType;\n }).join(' ') + '\\r\\n';\n\n sdp += 'c=IN IP4 0.0.0.0\\r\\n';\n sdp += 'a=rtcp:9 IN IP4 0.0.0.0\\r\\n';\n\n // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.\n caps.codecs.forEach(codec => {\n sdp += SDPUtils.writeRtpMap(codec);\n sdp += SDPUtils.writeFmtp(codec);\n sdp += SDPUtils.writeRtcpFb(codec);\n });\n let maxptime = 0;\n caps.codecs.forEach(codec => {\n if (codec.maxptime > maxptime) {\n maxptime = codec.maxptime;\n }\n });\n if (maxptime > 0) {\n sdp += 'a=maxptime:' + maxptime + '\\r\\n';\n }\n\n if (caps.headerExtensions) {\n caps.headerExtensions.forEach(extension => {\n sdp += SDPUtils.writeExtmap(extension);\n });\n }\n // FIXME: write fecMechanisms.\n return sdp;\n};\n\n// Parses the SDP media section and returns an array of\n// RTCRtpEncodingParameters.\nSDPUtils.parseRtpEncodingParameters = function(mediaSection) {\n const encodingParameters = [];\n const description = SDPUtils.parseRtpParameters(mediaSection);\n const hasRed = description.fecMechanisms.indexOf('RED') !== -1;\n const hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;\n\n // filter a=ssrc:... cname:, ignore PlanB-msid\n const ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')\n .map(line => SDPUtils.parseSsrcMedia(line))\n .filter(parts => parts.attribute === 'cname');\n const primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;\n let secondarySsrc;\n\n const flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')\n .map(line => {\n const parts = line.substr(17).split(' ');\n return parts.map(part => parseInt(part, 10));\n });\n if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {\n secondarySsrc = flows[0][1];\n }\n\n description.codecs.forEach(codec => {\n if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {\n let encParam = {\n ssrc: primarySsrc,\n codecPayloadType: parseInt(codec.parameters.apt, 10),\n };\n if (primarySsrc && secondarySsrc) {\n encParam.rtx = {ssrc: secondarySsrc};\n }\n encodingParameters.push(encParam);\n if (hasRed) {\n encParam = JSON.parse(JSON.stringify(encParam));\n encParam.fec = {\n ssrc: primarySsrc,\n mechanism: hasUlpfec ? 'red+ulpfec' : 'red',\n };\n encodingParameters.push(encParam);\n }\n }\n });\n if (encodingParameters.length === 0 && primarySsrc) {\n encodingParameters.push({\n ssrc: primarySsrc,\n });\n }\n\n // we support both b=AS and b=TIAS but interpret AS as TIAS.\n let bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');\n if (bandwidth.length) {\n if (bandwidth[0].indexOf('b=TIAS:') === 0) {\n bandwidth = parseInt(bandwidth[0].substr(7), 10);\n } else if (bandwidth[0].indexOf('b=AS:') === 0) {\n // use formula from JSEP to convert b=AS to TIAS value.\n bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95\n - (50 * 40 * 8);\n } else {\n bandwidth = undefined;\n }\n encodingParameters.forEach(params => {\n params.maxBitrate = bandwidth;\n });\n }\n return encodingParameters;\n};\n\n// parses http://draft.ortc.org/#rtcrtcpparameters*\nSDPUtils.parseRtcpParameters = function(mediaSection) {\n const rtcpParameters = {};\n\n // Gets the first SSRC. Note that with RTX there might be multiple\n // SSRCs.\n const remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')\n .map(line => SDPUtils.parseSsrcMedia(line))\n .filter(obj => obj.attribute === 'cname')[0];\n if (remoteSsrc) {\n rtcpParameters.cname = remoteSsrc.value;\n rtcpParameters.ssrc = remoteSsrc.ssrc;\n }\n\n // Edge uses the compound attribute instead of reducedSize\n // compound is !reducedSize\n const rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');\n rtcpParameters.reducedSize = rsize.length > 0;\n rtcpParameters.compound = rsize.length === 0;\n\n // parses the rtcp-mux attrÑ–bute.\n // Note that Edge does not support unmuxed RTCP.\n const mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');\n rtcpParameters.mux = mux.length > 0;\n\n return rtcpParameters;\n};\n\nSDPUtils.writeRtcpParameters = function(rtcpParameters) {\n let sdp = '';\n if (rtcpParameters.reducedSize) {\n sdp += 'a=rtcp-rsize\\r\\n';\n }\n if (rtcpParameters.mux) {\n sdp += 'a=rtcp-mux\\r\\n';\n }\n if (rtcpParameters.ssrc !== undefined && rtcpParameters.cname) {\n sdp += 'a=ssrc:' + rtcpParameters.ssrc +\n ' cname:' + rtcpParameters.cname + '\\r\\n';\n }\n return sdp;\n};\n\n\n// parses either a=msid: or a=ssrc:... msid lines and returns\n// the id of the MediaStream and MediaStreamTrack.\nSDPUtils.parseMsid = function(mediaSection) {\n let parts;\n const spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');\n if (spec.length === 1) {\n parts = spec[0].substr(7).split(' ');\n return {stream: parts[0], track: parts[1]};\n }\n const planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')\n .map(line => SDPUtils.parseSsrcMedia(line))\n .filter(msidParts => msidParts.attribute === 'msid');\n if (planB.length > 0) {\n parts = planB[0].value.split(' ');\n return {stream: parts[0], track: parts[1]};\n }\n};\n\n// SCTP\n// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back\n// to draft-ietf-mmusic-sctp-sdp-05\nSDPUtils.parseSctpDescription = function(mediaSection) {\n const mline = SDPUtils.parseMLine(mediaSection);\n const maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:');\n let maxMessageSize;\n if (maxSizeLine.length > 0) {\n maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10);\n }\n if (isNaN(maxMessageSize)) {\n maxMessageSize = 65536;\n }\n const sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:');\n if (sctpPort.length > 0) {\n return {\n port: parseInt(sctpPort[0].substr(12), 10),\n protocol: mline.fmt,\n maxMessageSize,\n };\n }\n const sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:');\n if (sctpMapLines.length > 0) {\n const parts = sctpMapLines[0]\n .substr(10)\n .split(' ');\n return {\n port: parseInt(parts[0], 10),\n protocol: parts[1],\n maxMessageSize,\n };\n }\n};\n\n// SCTP\n// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers\n// support by now receiving in this format, unless we originally parsed\n// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line\n// protocol of DTLS/SCTP -- without UDP/ or TCP/)\nSDPUtils.writeSctpDescription = function(media, sctp) {\n let output = [];\n if (media.protocol !== 'DTLS/SCTP') {\n output = [\n 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\\r\\n',\n 'c=IN IP4 0.0.0.0\\r\\n',\n 'a=sctp-port:' + sctp.port + '\\r\\n',\n ];\n } else {\n output = [\n 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\\r\\n',\n 'c=IN IP4 0.0.0.0\\r\\n',\n 'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\\r\\n',\n ];\n }\n if (sctp.maxMessageSize !== undefined) {\n output.push('a=max-message-size:' + sctp.maxMessageSize + '\\r\\n');\n }\n return output.join('');\n};\n\n// Generate a session ID for SDP.\n// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1\n// recommends using a cryptographically random +ve 64-bit value\n// but right now this should be acceptable and within the right range\nSDPUtils.generateSessionId = function() {\n return Math.random().toString().substr(2, 21);\n};\n\n// Write boiler plate for start of SDP\n// sessId argument is optional - if not supplied it will\n// be generated randomly\n// sessVersion is optional and defaults to 2\n// sessUser is optional and defaults to 'thisisadapterortc'\nSDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) {\n let sessionId;\n const version = sessVer !== undefined ? sessVer : 2;\n if (sessId) {\n sessionId = sessId;\n } else {\n sessionId = SDPUtils.generateSessionId();\n }\n const user = sessUser || 'thisisadapterortc';\n // FIXME: sess-id should be an NTP timestamp.\n return 'v=0\\r\\n' +\n 'o=' + user + ' ' + sessionId + ' ' + version +\n ' IN IP4 127.0.0.1\\r\\n' +\n 's=-\\r\\n' +\n 't=0 0\\r\\n';\n};\n\n// Gets the direction from the mediaSection or the sessionpart.\nSDPUtils.getDirection = function(mediaSection, sessionpart) {\n // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.\n const lines = SDPUtils.splitLines(mediaSection);\n for (let i = 0; i < lines.length; i++) {\n switch (lines[i]) {\n case 'a=sendrecv':\n case 'a=sendonly':\n case 'a=recvonly':\n case 'a=inactive':\n return lines[i].substr(2);\n default:\n // FIXME: What should happen here?\n }\n }\n if (sessionpart) {\n return SDPUtils.getDirection(sessionpart);\n }\n return 'sendrecv';\n};\n\nSDPUtils.getKind = function(mediaSection) {\n const lines = SDPUtils.splitLines(mediaSection);\n const mline = lines[0].split(' ');\n return mline[0].substr(2);\n};\n\nSDPUtils.isRejected = function(mediaSection) {\n return mediaSection.split(' ', 2)[1] === '0';\n};\n\nSDPUtils.parseMLine = function(mediaSection) {\n const lines = SDPUtils.splitLines(mediaSection);\n const parts = lines[0].substr(2).split(' ');\n return {\n kind: parts[0],\n port: parseInt(parts[1], 10),\n protocol: parts[2],\n fmt: parts.slice(3).join(' '),\n };\n};\n\nSDPUtils.parseOLine = function(mediaSection) {\n const line = SDPUtils.matchPrefix(mediaSection, 'o=')[0];\n const parts = line.substr(2).split(' ');\n return {\n username: parts[0],\n sessionId: parts[1],\n sessionVersion: parseInt(parts[2], 10),\n netType: parts[3],\n addressType: parts[4],\n address: parts[5],\n };\n};\n\n// a very naive interpretation of a valid SDP.\nSDPUtils.isValidSDP = function(blob) {\n if (typeof blob !== 'string' || blob.length === 0) {\n return false;\n }\n const lines = SDPUtils.splitLines(blob);\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].length < 2 || lines[i].charAt(1) !== '=') {\n return false;\n }\n // TODO: check the modifier a bit more.\n }\n return true;\n};\n\n// Expose public methods.\nif (typeof module === 'object') {\n module.exports = SDPUtils;\n}\n","(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (global = global || self, global.strophe = factory());\n}(this, function () { 'use strict';\n\n var global$1 = (typeof global !== \"undefined\" ? global :\n typeof self !== \"undefined\" ? self :\n typeof window !== \"undefined\" ? window : {});\n\n function _typeof(obj) {\n if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") {\n _typeof = function (obj) {\n return typeof obj;\n };\n } else {\n _typeof = function (obj) {\n return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n };\n }\n\n return _typeof(obj);\n }\n\n function _toConsumableArray(arr) {\n return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();\n }\n\n function _arrayWithoutHoles(arr) {\n if (Array.isArray(arr)) {\n for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];\n\n return arr2;\n }\n }\n\n function _iterableToArray(iter) {\n if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === \"[object Arguments]\") return Array.from(iter);\n }\n\n function _nonIterableSpread() {\n throw new TypeError(\"Invalid attempt to spread non-iterable instance\");\n }\n\n /*\n * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message\n * Digest Algorithm, as defined in RFC 1321.\n * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n * Distributed under the BSD License\n * See http://pajhome.org.uk/crypt/md5 for more info.\n */\n\n /*\n * Everything that isn't used by Strophe has been stripped here!\n */\n\n /*\n * Add integers, wrapping at 2^32. This uses 16-bit operations internally\n * to work around bugs in some JS interpreters.\n */\n var safe_add = function safe_add(x, y) {\n var lsw = (x & 0xFFFF) + (y & 0xFFFF);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return msw << 16 | lsw & 0xFFFF;\n };\n /*\n * Bitwise rotate a 32-bit number to the left.\n */\n\n\n var bit_rol = function bit_rol(num, cnt) {\n return num << cnt | num >>> 32 - cnt;\n };\n /*\n * Convert a string to an array of little-endian words\n */\n\n\n var str2binl = function str2binl(str) {\n if (typeof str !== \"string\") {\n throw new Error(\"str2binl was passed a non-string\");\n }\n\n var bin = [];\n\n for (var i = 0; i < str.length * 8; i += 8) {\n bin[i >> 5] |= (str.charCodeAt(i / 8) & 255) << i % 32;\n }\n\n return bin;\n };\n /*\n * Convert an array of little-endian words to a string\n */\n\n\n var binl2str = function binl2str(bin) {\n var str = \"\";\n\n for (var i = 0; i < bin.length * 32; i += 8) {\n str += String.fromCharCode(bin[i >> 5] >>> i % 32 & 255);\n }\n\n return str;\n };\n /*\n * Convert an array of little-endian words to a hex string.\n */\n\n\n var binl2hex = function binl2hex(binarray) {\n var hex_tab = \"0123456789abcdef\";\n var str = \"\";\n\n for (var i = 0; i < binarray.length * 4; i++) {\n str += hex_tab.charAt(binarray[i >> 2] >> i % 4 * 8 + 4 & 0xF) + hex_tab.charAt(binarray[i >> 2] >> i % 4 * 8 & 0xF);\n }\n\n return str;\n };\n /*\n * These functions implement the four basic operations the algorithm uses.\n */\n\n\n var md5_cmn = function md5_cmn(q, a, b, x, s, t) {\n return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);\n };\n\n var md5_ff = function md5_ff(a, b, c, d, x, s, t) {\n return md5_cmn(b & c | ~b & d, a, b, x, s, t);\n };\n\n var md5_gg = function md5_gg(a, b, c, d, x, s, t) {\n return md5_cmn(b & d | c & ~d, a, b, x, s, t);\n };\n\n var md5_hh = function md5_hh(a, b, c, d, x, s, t) {\n return md5_cmn(b ^ c ^ d, a, b, x, s, t);\n };\n\n var md5_ii = function md5_ii(a, b, c, d, x, s, t) {\n return md5_cmn(c ^ (b | ~d), a, b, x, s, t);\n };\n /*\n * Calculate the MD5 of an array of little-endian words, and a bit length\n */\n\n\n var core_md5 = function core_md5(x, len) {\n /* append padding */\n x[len >> 5] |= 0x80 << len % 32;\n x[(len + 64 >>> 9 << 4) + 14] = len;\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var olda, oldb, oldc, oldd;\n\n for (var i = 0; i < x.length; i += 16) {\n olda = a;\n oldb = b;\n oldc = c;\n oldd = d;\n a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);\n d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);\n b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);\n a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);\n d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);\n c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);\n d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = safe_add(a, olda);\n b = safe_add(b, oldb);\n c = safe_add(c, oldc);\n d = safe_add(d, oldd);\n }\n\n return [a, b, c, d];\n };\n /*\n * These are the functions you'll usually want to call.\n * They take string arguments and return either hex or base-64 encoded\n * strings.\n */\n\n\n var MD5 = {\n hexdigest: function hexdigest(s) {\n return binl2hex(core_md5(str2binl(s), s.length * 8));\n },\n hash: function hash(s) {\n return binl2str(core_md5(str2binl(s), s.length * 8));\n }\n };\n\n /*\n * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined\n * in FIPS PUB 180-1\n * Version 2.1a Copyright Paul Johnston 2000 - 2002.\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n * Distributed under the BSD License\n * See http://pajhome.org.uk/crypt/md5 for details.\n */\n\n /* global define */\n\n /* Some functions and variables have been stripped for use with Strophe */\n\n /*\n * Calculate the SHA-1 of an array of big-endian words, and a bit length\n */\n function core_sha1(x, len) {\n /* append padding */\n x[len >> 5] |= 0x80 << 24 - len % 32;\n x[(len + 64 >> 9 << 4) + 15] = len;\n var w = new Array(80);\n var a = 1732584193;\n var b = -271733879;\n var c = -1732584194;\n var d = 271733878;\n var e = -1009589776;\n var i, j, t, olda, oldb, oldc, oldd, olde;\n\n for (i = 0; i < x.length; i += 16) {\n olda = a;\n oldb = b;\n oldc = c;\n oldd = d;\n olde = e;\n\n for (j = 0; j < 80; j++) {\n if (j < 16) {\n w[j] = x[i + j];\n } else {\n w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);\n }\n\n t = safe_add$1(safe_add$1(rol(a, 5), sha1_ft(j, b, c, d)), safe_add$1(safe_add$1(e, w[j]), sha1_kt(j)));\n e = d;\n d = c;\n c = rol(b, 30);\n b = a;\n a = t;\n }\n\n a = safe_add$1(a, olda);\n b = safe_add$1(b, oldb);\n c = safe_add$1(c, oldc);\n d = safe_add$1(d, oldd);\n e = safe_add$1(e, olde);\n }\n\n return [a, b, c, d, e];\n }\n /*\n * Perform the appropriate triplet combination function for the current\n * iteration\n */\n\n\n function sha1_ft(t, b, c, d) {\n if (t < 20) {\n return b & c | ~b & d;\n }\n\n if (t < 40) {\n return b ^ c ^ d;\n }\n\n if (t < 60) {\n return b & c | b & d | c & d;\n }\n\n return b ^ c ^ d;\n }\n /*\n * Determine the appropriate additive constant for the current iteration\n */\n\n\n function sha1_kt(t) {\n return t < 20 ? 1518500249 : t < 40 ? 1859775393 : t < 60 ? -1894007588 : -899497514;\n }\n /*\n * Calculate the HMAC-SHA1 of a key and some data\n */\n\n\n function core_hmac_sha1(key, data) {\n var bkey = str2binb(key);\n\n if (bkey.length > 16) {\n bkey = core_sha1(bkey, key.length * 8);\n }\n\n var ipad = new Array(16),\n opad = new Array(16);\n\n for (var i = 0; i < 16; i++) {\n ipad[i] = bkey[i] ^ 0x36363636;\n opad[i] = bkey[i] ^ 0x5C5C5C5C;\n }\n\n var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * 8);\n return core_sha1(opad.concat(hash), 512 + 160);\n }\n /*\n * Add integers, wrapping at 2^32. This uses 16-bit operations internally\n * to work around bugs in some JS interpreters.\n */\n\n\n function safe_add$1(x, y) {\n var lsw = (x & 0xFFFF) + (y & 0xFFFF);\n var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return msw << 16 | lsw & 0xFFFF;\n }\n /*\n * Bitwise rotate a 32-bit number to the left.\n */\n\n\n function rol(num, cnt) {\n return num << cnt | num >>> 32 - cnt;\n }\n /*\n * Convert an 8-bit or 16-bit string to an array of big-endian words\n * In 8-bit function, characters >255 have their hi-byte silently ignored.\n */\n\n\n function str2binb(str) {\n var bin = [];\n var mask = 255;\n\n for (var i = 0; i < str.length * 8; i += 8) {\n bin[i >> 5] |= (str.charCodeAt(i / 8) & mask) << 24 - i % 32;\n }\n\n return bin;\n }\n /*\n * Convert an array of big-endian words to a base-64 string\n */\n\n\n function binb2b64(binarray) {\n var tab = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n var str = \"\";\n var triplet, j;\n\n for (var i = 0; i < binarray.length * 4; i += 3) {\n triplet = (binarray[i >> 2] >> 8 * (3 - i % 4) & 0xFF) << 16 | (binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4) & 0xFF) << 8 | binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4) & 0xFF;\n\n for (j = 0; j < 4; j++) {\n if (i * 8 + j * 6 > binarray.length * 32) {\n str += \"=\";\n } else {\n str += tab.charAt(triplet >> 6 * (3 - j) & 0x3F);\n }\n }\n }\n\n return str;\n }\n /*\n * Convert an array of big-endian words to a string\n */\n\n\n function binb2str(bin) {\n var str = \"\";\n var mask = 255;\n\n for (var i = 0; i < bin.length * 32; i += 8) {\n str += String.fromCharCode(bin[i >> 5] >>> 24 - i % 32 & mask);\n }\n\n return str;\n }\n /*\n * These are the functions you'll usually want to call\n * They take string arguments and return either hex or base-64 encoded strings\n */\n\n\n var SHA1 = {\n b64_hmac_sha1: function b64_hmac_sha1(key, data) {\n return binb2b64(core_hmac_sha1(key, data));\n },\n b64_sha1: function b64_sha1(s) {\n return binb2b64(core_sha1(str2binb(s), s.length * 8));\n },\n binb2str: binb2str,\n core_hmac_sha1: core_hmac_sha1,\n str_hmac_sha1: function str_hmac_sha1(key, data) {\n return binb2str(core_hmac_sha1(key, data));\n },\n str_sha1: function str_sha1(s) {\n return binb2str(core_sha1(str2binb(s), s.length * 8));\n }\n };\n\n var utils = {\n utf16to8: function utf16to8(str) {\n var i, c;\n var out = \"\";\n var len = str.length;\n\n for (i = 0; i < len; i++) {\n c = str.charCodeAt(i);\n\n if (c >= 0x0000 && c <= 0x007F) {\n out += str.charAt(i);\n } else if (c > 0x07FF) {\n out += String.fromCharCode(0xE0 | c >> 12 & 0x0F);\n out += String.fromCharCode(0x80 | c >> 6 & 0x3F);\n out += String.fromCharCode(0x80 | c >> 0 & 0x3F);\n } else {\n out += String.fromCharCode(0xC0 | c >> 6 & 0x1F);\n out += String.fromCharCode(0x80 | c >> 0 & 0x3F);\n }\n }\n\n return out;\n },\n addCookies: function addCookies(cookies) {\n /* Parameters:\n * (Object) cookies - either a map of cookie names\n * to string values or to maps of cookie values.\n *\n * For example:\n * { \"myCookie\": \"1234\" }\n *\n * or:\n * { \"myCookie\": {\n * \"value\": \"1234\",\n * \"domain\": \".example.org\",\n * \"path\": \"/\",\n * \"expires\": expirationDate\n * }\n * }\n *\n * These values get passed to Strophe.Connection via\n * options.cookies\n */\n cookies = cookies || {};\n\n for (var cookieName in cookies) {\n if (Object.prototype.hasOwnProperty.call(cookies, cookieName)) {\n var expires = '';\n var domain = '';\n var path = '';\n var cookieObj = cookies[cookieName];\n var isObj = _typeof(cookieObj) === \"object\";\n var cookieValue = escape(unescape(isObj ? cookieObj.value : cookieObj));\n\n if (isObj) {\n expires = cookieObj.expires ? \";expires=\" + cookieObj.expires : '';\n domain = cookieObj.domain ? \";domain=\" + cookieObj.domain : '';\n path = cookieObj.path ? \";path=\" + cookieObj.path : '';\n }\n\n document.cookie = cookieName + '=' + cookieValue + expires + domain + path;\n }\n }\n }\n };\n\n /** Function: $build\n * Create a Strophe.Builder.\n * This is an alias for 'new Strophe.Builder(name, attrs)'.\n *\n * Parameters:\n * (String) name - The root element name.\n * (Object) attrs - The attributes for the root element in object notation.\n *\n * Returns:\n * A new Strophe.Builder object.\n */\n\n function $build(name, attrs) {\n return new Strophe.Builder(name, attrs);\n }\n /** Function: $msg\n * Create a Strophe.Builder with a element as the root.\n *\n * Parameters:\n * (Object) attrs - The element attributes in object notation.\n *\n * Returns:\n * A new Strophe.Builder object.\n */\n\n\n function $msg(attrs) {\n return new Strophe.Builder(\"message\", attrs);\n }\n /** Function: $iq\n * Create a Strophe.Builder with an element as the root.\n *\n * Parameters:\n * (Object) attrs - The element attributes in object notation.\n *\n * Returns:\n * A new Strophe.Builder object.\n */\n\n\n function $iq(attrs) {\n return new Strophe.Builder(\"iq\", attrs);\n }\n /** Function: $pres\n * Create a Strophe.Builder with a element as the root.\n *\n * Parameters:\n * (Object) attrs - The element attributes in object notation.\n *\n * Returns:\n * A new Strophe.Builder object.\n */\n\n\n function $pres(attrs) {\n return new Strophe.Builder(\"presence\", attrs);\n }\n /** Class: Strophe\n * An object container for all Strophe library functions.\n *\n * This class is just a container for all the objects and constants\n * used in the library. It is not meant to be instantiated, but to\n * provide a namespace for library objects, constants, and functions.\n */\n\n\n var Strophe = {\n /** Constant: VERSION */\n VERSION: \"@VERSION@\",\n\n /** Constants: XMPP Namespace Constants\n * Common namespace constants from the XMPP RFCs and XEPs.\n *\n * NS.HTTPBIND - HTTP BIND namespace from XEP 124.\n * NS.BOSH - BOSH namespace from XEP 206.\n * NS.CLIENT - Main XMPP client namespace.\n * NS.AUTH - Legacy authentication namespace.\n * NS.ROSTER - Roster operations namespace.\n * NS.PROFILE - Profile namespace.\n * NS.DISCO_INFO - Service discovery info namespace from XEP 30.\n * NS.DISCO_ITEMS - Service discovery items namespace from XEP 30.\n * NS.MUC - Multi-User Chat namespace from XEP 45.\n * NS.SASL - XMPP SASL namespace from RFC 3920.\n * NS.STREAM - XMPP Streams namespace from RFC 3920.\n * NS.BIND - XMPP Binding namespace from RFC 3920 and RFC 6120.\n * NS.SESSION - XMPP Session namespace from RFC 3920.\n * NS.XHTML_IM - XHTML-IM namespace from XEP 71.\n * NS.XHTML - XHTML body namespace from XEP 71.\n */\n NS: {\n HTTPBIND: \"http://jabber.org/protocol/httpbind\",\n BOSH: \"urn:xmpp:xbosh\",\n CLIENT: \"jabber:client\",\n AUTH: \"jabber:iq:auth\",\n ROSTER: \"jabber:iq:roster\",\n PROFILE: \"jabber:iq:profile\",\n DISCO_INFO: \"http://jabber.org/protocol/disco#info\",\n DISCO_ITEMS: \"http://jabber.org/protocol/disco#items\",\n MUC: \"http://jabber.org/protocol/muc\",\n SASL: \"urn:ietf:params:xml:ns:xmpp-sasl\",\n STREAM: \"http://etherx.jabber.org/streams\",\n FRAMING: \"urn:ietf:params:xml:ns:xmpp-framing\",\n BIND: \"urn:ietf:params:xml:ns:xmpp-bind\",\n SESSION: \"urn:ietf:params:xml:ns:xmpp-session\",\n VERSION: \"jabber:iq:version\",\n STANZAS: \"urn:ietf:params:xml:ns:xmpp-stanzas\",\n XHTML_IM: \"http://jabber.org/protocol/xhtml-im\",\n XHTML: \"http://www.w3.org/1999/xhtml\"\n },\n\n /** Constants: XHTML_IM Namespace\n * contains allowed tags, tag attributes, and css properties.\n * Used in the createHtml function to filter incoming html into the allowed XHTML-IM subset.\n * See http://xmpp.org/extensions/xep-0071.html#profile-summary for the list of recommended\n * allowed tags and their attributes.\n */\n XHTML: {\n tags: ['a', 'blockquote', 'br', 'cite', 'em', 'img', 'li', 'ol', 'p', 'span', 'strong', 'ul', 'body'],\n attributes: {\n 'a': ['href'],\n 'blockquote': ['style'],\n 'br': [],\n 'cite': ['style'],\n 'em': [],\n 'img': ['src', 'alt', 'style', 'height', 'width'],\n 'li': ['style'],\n 'ol': ['style'],\n 'p': ['style'],\n 'span': ['style'],\n 'strong': [],\n 'ul': ['style'],\n 'body': []\n },\n css: ['background-color', 'color', 'font-family', 'font-size', 'font-style', 'font-weight', 'margin-left', 'margin-right', 'text-align', 'text-decoration'],\n\n /** Function: XHTML.validTag\n *\n * Utility method to determine whether a tag is allowed\n * in the XHTML_IM namespace.\n *\n * XHTML tag names are case sensitive and must be lower case.\n */\n validTag: function validTag(tag) {\n for (var i = 0; i < Strophe.XHTML.tags.length; i++) {\n if (tag === Strophe.XHTML.tags[i]) {\n return true;\n }\n }\n\n return false;\n },\n\n /** Function: XHTML.validAttribute\n *\n * Utility method to determine whether an attribute is allowed\n * as recommended per XEP-0071\n *\n * XHTML attribute names are case sensitive and must be lower case.\n */\n validAttribute: function validAttribute(tag, attribute) {\n if (typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) {\n for (var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {\n if (attribute === Strophe.XHTML.attributes[tag][i]) {\n return true;\n }\n }\n }\n\n return false;\n },\n validCSS: function validCSS(style) {\n for (var i = 0; i < Strophe.XHTML.css.length; i++) {\n if (style === Strophe.XHTML.css[i]) {\n return true;\n }\n }\n\n return false;\n }\n },\n\n /** Constants: Connection Status Constants\n * Connection status constants for use by the connection handler\n * callback.\n *\n * Status.ERROR - An error has occurred\n * Status.CONNECTING - The connection is currently being made\n * Status.CONNFAIL - The connection attempt failed\n * Status.AUTHENTICATING - The connection is authenticating\n * Status.AUTHFAIL - The authentication attempt failed\n * Status.CONNECTED - The connection has succeeded\n * Status.DISCONNECTED - The connection has been terminated\n * Status.DISCONNECTING - The connection is currently being terminated\n * Status.ATTACHED - The connection has been attached\n * Status.REDIRECT - The connection has been redirected\n * Status.CONNTIMEOUT - The connection has timed out\n */\n Status: {\n ERROR: 0,\n CONNECTING: 1,\n CONNFAIL: 2,\n AUTHENTICATING: 3,\n AUTHFAIL: 4,\n CONNECTED: 5,\n DISCONNECTED: 6,\n DISCONNECTING: 7,\n ATTACHED: 8,\n REDIRECT: 9,\n CONNTIMEOUT: 10,\n BINDREQUIRED: 11\n },\n ErrorCondition: {\n BAD_FORMAT: \"bad-format\",\n CONFLICT: \"conflict\",\n MISSING_JID_NODE: \"x-strophe-bad-non-anon-jid\",\n NO_AUTH_MECH: \"no-auth-mech\",\n UNKNOWN_REASON: \"unknown\"\n },\n\n /** Constants: Log Level Constants\n * Logging level indicators.\n *\n * LogLevel.DEBUG - Debug output\n * LogLevel.INFO - Informational output\n * LogLevel.WARN - Warnings\n * LogLevel.ERROR - Errors\n * LogLevel.FATAL - Fatal errors\n */\n LogLevel: {\n DEBUG: 0,\n INFO: 1,\n WARN: 2,\n ERROR: 3,\n FATAL: 4\n },\n\n /** PrivateConstants: DOM Element Type Constants\n * DOM element types.\n *\n * ElementType.NORMAL - Normal element.\n * ElementType.TEXT - Text data element.\n * ElementType.FRAGMENT - XHTML fragment element.\n */\n ElementType: {\n NORMAL: 1,\n TEXT: 3,\n CDATA: 4,\n FRAGMENT: 11\n },\n\n /** PrivateConstants: Timeout Values\n * Timeout values for error states. These values are in seconds.\n * These should not be changed unless you know exactly what you are\n * doing.\n *\n * TIMEOUT - Timeout multiplier. A waiting request will be considered\n * failed after Math.floor(TIMEOUT * wait) seconds have elapsed.\n * This defaults to 1.1, and with default wait, 66 seconds.\n * SECONDARY_TIMEOUT - Secondary timeout multiplier. In cases where\n * Strophe can detect early failure, it will consider the request\n * failed if it doesn't return after\n * Math.floor(SECONDARY_TIMEOUT * wait) seconds have elapsed.\n * This defaults to 0.1, and with default wait, 6 seconds.\n */\n TIMEOUT: 1.1,\n SECONDARY_TIMEOUT: 0.1,\n\n /** Function: addNamespace\n * This function is used to extend the current namespaces in\n * Strophe.NS. It takes a key and a value with the key being the\n * name of the new namespace, with its actual value.\n * For example:\n * Strophe.addNamespace('PUBSUB', \"http://jabber.org/protocol/pubsub\");\n *\n * Parameters:\n * (String) name - The name under which the namespace will be\n * referenced under Strophe.NS\n * (String) value - The actual namespace.\n */\n addNamespace: function addNamespace(name, value) {\n Strophe.NS[name] = value;\n },\n\n /** Function: forEachChild\n * Map a function over some or all child elements of a given element.\n *\n * This is a small convenience function for mapping a function over\n * some or all of the children of an element. If elemName is null, all\n * children will be passed to the function, otherwise only children\n * whose tag names match elemName will be passed.\n *\n * Parameters:\n * (XMLElement) elem - The element to operate on.\n * (String) elemName - The child element tag name filter.\n * (Function) func - The function to apply to each child. This\n * function should take a single argument, a DOM element.\n */\n forEachChild: function forEachChild(elem, elemName, func) {\n for (var i = 0; i < elem.childNodes.length; i++) {\n var childNode = elem.childNodes[i];\n\n if (childNode.nodeType === Strophe.ElementType.NORMAL && (!elemName || this.isTagEqual(childNode, elemName))) {\n func(childNode);\n }\n }\n },\n\n /** Function: isTagEqual\n * Compare an element's tag name with a string.\n *\n * This function is case sensitive.\n *\n * Parameters:\n * (XMLElement) el - A DOM element.\n * (String) name - The element name.\n *\n * Returns:\n * true if the element's tag name matches _el_, and false\n * otherwise.\n */\n isTagEqual: function isTagEqual(el, name) {\n return el.tagName === name;\n },\n\n /** PrivateVariable: _xmlGenerator\n * _Private_ variable that caches a DOM document to\n * generate elements.\n */\n _xmlGenerator: null,\n\n /** PrivateFunction: _makeGenerator\n * _Private_ function that creates a dummy XML DOM document to serve as\n * an element and text node generator.\n */\n _makeGenerator: function _makeGenerator() {\n var doc; // IE9 does implement createDocument(); however, using it will cause the browser to leak memory on page unload.\n // Here, we test for presence of createDocument() plus IE's proprietary documentMode attribute, which would be\n // less than 10 in the case of IE9 and below.\n\n if (document.implementation.createDocument === undefined || document.implementation.createDocument && document.documentMode && document.documentMode < 10) {\n doc = this._getIEXmlDom();\n doc.appendChild(doc.createElement('strophe'));\n } else {\n doc = document.implementation.createDocument('jabber:client', 'strophe', null);\n }\n\n return doc;\n },\n\n /** Function: xmlGenerator\n * Get the DOM document to generate elements.\n *\n * Returns:\n * The currently used DOM document.\n */\n xmlGenerator: function xmlGenerator() {\n if (!Strophe._xmlGenerator) {\n Strophe._xmlGenerator = Strophe._makeGenerator();\n }\n\n return Strophe._xmlGenerator;\n },\n\n /** PrivateFunction: _getIEXmlDom\n * Gets IE xml doc object\n *\n * Returns:\n * A Microsoft XML DOM Object\n * See Also:\n * http://msdn.microsoft.com/en-us/library/ms757837%28VS.85%29.aspx\n */\n _getIEXmlDom: function _getIEXmlDom() {\n var doc = null;\n var docStrings = [\"Msxml2.DOMDocument.6.0\", \"Msxml2.DOMDocument.5.0\", \"Msxml2.DOMDocument.4.0\", \"MSXML2.DOMDocument.3.0\", \"MSXML2.DOMDocument\", \"MSXML.DOMDocument\", \"Microsoft.XMLDOM\"];\n\n for (var d = 0; d < docStrings.length; d++) {\n if (doc === null) {\n try {\n doc = new ActiveXObject(docStrings[d]);\n } catch (e) {\n doc = null;\n }\n } else {\n break;\n }\n }\n\n return doc;\n },\n\n /** Function: xmlElement\n * Create an XML DOM element.\n *\n * This function creates an XML DOM element correctly across all\n * implementations. Note that these are not HTML DOM elements, which\n * aren't appropriate for XMPP stanzas.\n *\n * Parameters:\n * (String) name - The name for the element.\n * (Array|Object) attrs - An optional array or object containing\n * key/value pairs to use as element attributes. The object should\n * be in the format {'key': 'value'} or {key: 'value'}. The array\n * should have the format [['key1', 'value1'], ['key2', 'value2']].\n * (String) text - The text child data for the element.\n *\n * Returns:\n * A new XML DOM element.\n */\n xmlElement: function xmlElement(name) {\n if (!name) {\n return null;\n }\n\n var node = Strophe.xmlGenerator().createElement(name); // FIXME: this should throw errors if args are the wrong type or\n // there are more than two optional args\n\n for (var a = 1; a < arguments.length; a++) {\n var arg = arguments[a];\n\n if (!arg) {\n continue;\n }\n\n if (typeof arg === \"string\" || typeof arg === \"number\") {\n node.appendChild(Strophe.xmlTextNode(arg));\n } else if (_typeof(arg) === \"object\" && typeof arg.sort === \"function\") {\n for (var i = 0; i < arg.length; i++) {\n var attr = arg[i];\n\n if (_typeof(attr) === \"object\" && typeof attr.sort === \"function\" && attr[1] !== undefined && attr[1] !== null) {\n node.setAttribute(attr[0], attr[1]);\n }\n }\n } else if (_typeof(arg) === \"object\") {\n for (var k in arg) {\n if (Object.prototype.hasOwnProperty.call(arg, k) && arg[k] !== undefined && arg[k] !== null) {\n node.setAttribute(k, arg[k]);\n }\n }\n }\n }\n\n return node;\n },\n\n /* Function: xmlescape\n * Excapes invalid xml characters.\n *\n * Parameters:\n * (String) text - text to escape.\n *\n * Returns:\n * Escaped text.\n */\n xmlescape: function xmlescape(text) {\n text = text.replace(/\\&/g, \"&\");\n text = text.replace(//g, \">\");\n text = text.replace(/'/g, \"'\");\n text = text.replace(/\"/g, \""\");\n return text;\n },\n\n /* Function: xmlunescape\n * Unexcapes invalid xml characters.\n *\n * Parameters:\n * (String) text - text to unescape.\n *\n * Returns:\n * Unescaped text.\n */\n xmlunescape: function xmlunescape(text) {\n text = text.replace(/\\&/g, \"&\");\n text = text.replace(/</g, \"<\");\n text = text.replace(/>/g, \">\");\n text = text.replace(/'/g, \"'\");\n text = text.replace(/"/g, \"\\\"\");\n return text;\n },\n\n /** Function: xmlTextNode\n * Creates an XML DOM text node.\n *\n * Provides a cross implementation version of document.createTextNode.\n *\n * Parameters:\n * (String) text - The content of the text node.\n *\n * Returns:\n * A new XML DOM text node.\n */\n xmlTextNode: function xmlTextNode(text) {\n return Strophe.xmlGenerator().createTextNode(text);\n },\n\n /** Function: xmlHtmlNode\n * Creates an XML DOM html node.\n *\n * Parameters:\n * (String) html - The content of the html node.\n *\n * Returns:\n * A new XML DOM text node.\n */\n xmlHtmlNode: function xmlHtmlNode(html) {\n var node; //ensure text is escaped\n\n if (DOMParser) {\n var parser = new DOMParser();\n node = parser.parseFromString(html, \"text/xml\");\n } else {\n node = new ActiveXObject(\"Microsoft.XMLDOM\");\n node.async = \"false\";\n node.loadXML(html);\n }\n\n return node;\n },\n\n /** Function: getText\n * Get the concatenation of all text children of an element.\n *\n * Parameters:\n * (XMLElement) elem - A DOM element.\n *\n * Returns:\n * A String with the concatenated text of all text element children.\n */\n getText: function getText(elem) {\n if (!elem) {\n return null;\n }\n\n var str = \"\";\n\n if (elem.childNodes.length === 0 && elem.nodeType === Strophe.ElementType.TEXT) {\n str += elem.nodeValue;\n }\n\n for (var i = 0; i < elem.childNodes.length; i++) {\n if (elem.childNodes[i].nodeType === Strophe.ElementType.TEXT) {\n str += elem.childNodes[i].nodeValue;\n }\n }\n\n return Strophe.xmlescape(str);\n },\n\n /** Function: copyElement\n * Copy an XML DOM element.\n *\n * This function copies a DOM element and all its descendants and returns\n * the new copy.\n *\n * Parameters:\n * (XMLElement) elem - A DOM element.\n *\n * Returns:\n * A new, copied DOM element tree.\n */\n copyElement: function copyElement(elem) {\n var el;\n\n if (elem.nodeType === Strophe.ElementType.NORMAL) {\n el = Strophe.xmlElement(elem.tagName);\n\n for (var i = 0; i < elem.attributes.length; i++) {\n el.setAttribute(elem.attributes[i].nodeName, elem.attributes[i].value);\n }\n\n for (var _i = 0; _i < elem.childNodes.length; _i++) {\n el.appendChild(Strophe.copyElement(elem.childNodes[_i]));\n }\n } else if (elem.nodeType === Strophe.ElementType.TEXT) {\n el = Strophe.xmlGenerator().createTextNode(elem.nodeValue);\n }\n\n return el;\n },\n\n /** Function: createHtml\n * Copy an HTML DOM element into an XML DOM.\n *\n * This function copies a DOM element and all its descendants and returns\n * the new copy.\n *\n * Parameters:\n * (HTMLElement) elem - A DOM element.\n *\n * Returns:\n * A new, copied DOM element tree.\n */\n createHtml: function createHtml(elem) {\n var el;\n\n if (elem.nodeType === Strophe.ElementType.NORMAL) {\n var tag = elem.nodeName.toLowerCase(); // XHTML tags must be lower case.\n\n if (Strophe.XHTML.validTag(tag)) {\n try {\n el = Strophe.xmlElement(tag);\n\n for (var i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {\n var attribute = Strophe.XHTML.attributes[tag][i];\n var value = elem.getAttribute(attribute);\n\n if (typeof value === 'undefined' || value === null || value === '' || value === false || value === 0) {\n continue;\n }\n\n if (attribute === 'style' && _typeof(value) === 'object' && typeof value.cssText !== 'undefined') {\n value = value.cssText; // we're dealing with IE, need to get CSS out\n } // filter out invalid css styles\n\n\n if (attribute === 'style') {\n var css = [];\n var cssAttrs = value.split(';');\n\n for (var j = 0; j < cssAttrs.length; j++) {\n var attr = cssAttrs[j].split(':');\n var cssName = attr[0].replace(/^\\s*/, \"\").replace(/\\s*$/, \"\").toLowerCase();\n\n if (Strophe.XHTML.validCSS(cssName)) {\n var cssValue = attr[1].replace(/^\\s*/, \"\").replace(/\\s*$/, \"\");\n css.push(cssName + ': ' + cssValue);\n }\n }\n\n if (css.length > 0) {\n value = css.join('; ');\n el.setAttribute(attribute, value);\n }\n } else {\n el.setAttribute(attribute, value);\n }\n }\n\n for (var _i2 = 0; _i2 < elem.childNodes.length; _i2++) {\n el.appendChild(Strophe.createHtml(elem.childNodes[_i2]));\n }\n } catch (e) {\n // invalid elements\n el = Strophe.xmlTextNode('');\n }\n } else {\n el = Strophe.xmlGenerator().createDocumentFragment();\n\n for (var _i3 = 0; _i3 < elem.childNodes.length; _i3++) {\n el.appendChild(Strophe.createHtml(elem.childNodes[_i3]));\n }\n }\n } else if (elem.nodeType === Strophe.ElementType.FRAGMENT) {\n el = Strophe.xmlGenerator().createDocumentFragment();\n\n for (var _i4 = 0; _i4 < elem.childNodes.length; _i4++) {\n el.appendChild(Strophe.createHtml(elem.childNodes[_i4]));\n }\n } else if (elem.nodeType === Strophe.ElementType.TEXT) {\n el = Strophe.xmlTextNode(elem.nodeValue);\n }\n\n return el;\n },\n\n /** Function: escapeNode\n * Escape the node part (also called local part) of a JID.\n *\n * Parameters:\n * (String) node - A node (or local part).\n *\n * Returns:\n * An escaped node (or local part).\n */\n escapeNode: function escapeNode(node) {\n if (typeof node !== \"string\") {\n return node;\n }\n\n return node.replace(/^\\s+|\\s+$/g, '').replace(/\\\\/g, \"\\\\5c\").replace(/ /g, \"\\\\20\").replace(/\\\"/g, \"\\\\22\").replace(/\\&/g, \"\\\\26\").replace(/\\'/g, \"\\\\27\").replace(/\\//g, \"\\\\2f\").replace(/:/g, \"\\\\3a\").replace(//g, \"\\\\3e\").replace(/@/g, \"\\\\40\");\n },\n\n /** Function: unescapeNode\n * Unescape a node part (also called local part) of a JID.\n *\n * Parameters:\n * (String) node - A node (or local part).\n *\n * Returns:\n * An unescaped node (or local part).\n */\n unescapeNode: function unescapeNode(node) {\n if (typeof node !== \"string\") {\n return node;\n }\n\n return node.replace(/\\\\20/g, \" \").replace(/\\\\22/g, '\"').replace(/\\\\26/g, \"&\").replace(/\\\\27/g, \"'\").replace(/\\\\2f/g, \"/\").replace(/\\\\3a/g, \":\").replace(/\\\\3c/g, \"<\").replace(/\\\\3e/g, \">\").replace(/\\\\40/g, \"@\").replace(/\\\\5c/g, \"\\\\\");\n },\n\n /** Function: getNodeFromJid\n * Get the node portion of a JID String.\n *\n * Parameters:\n * (String) jid - A JID.\n *\n * Returns:\n * A String containing the node.\n */\n getNodeFromJid: function getNodeFromJid(jid) {\n if (jid.indexOf(\"@\") < 0) {\n return null;\n }\n\n return jid.split(\"@\")[0];\n },\n\n /** Function: getDomainFromJid\n * Get the domain portion of a JID String.\n *\n * Parameters:\n * (String) jid - A JID.\n *\n * Returns:\n * A String containing the domain.\n */\n getDomainFromJid: function getDomainFromJid(jid) {\n var bare = Strophe.getBareJidFromJid(jid);\n\n if (bare.indexOf(\"@\") < 0) {\n return bare;\n } else {\n var parts = bare.split(\"@\");\n parts.splice(0, 1);\n return parts.join('@');\n }\n },\n\n /** Function: getResourceFromJid\n * Get the resource portion of a JID String.\n *\n * Parameters:\n * (String) jid - A JID.\n *\n * Returns:\n * A String containing the resource.\n */\n getResourceFromJid: function getResourceFromJid(jid) {\n if (!jid) {\n return null;\n }\n\n var s = jid.split(\"/\");\n\n if (s.length < 2) {\n return null;\n }\n\n s.splice(0, 1);\n return s.join('/');\n },\n\n /** Function: getBareJidFromJid\n * Get the bare JID from a JID String.\n *\n * Parameters:\n * (String) jid - A JID.\n *\n * Returns:\n * A String containing the bare JID.\n */\n getBareJidFromJid: function getBareJidFromJid(jid) {\n return jid ? jid.split(\"/\")[0] : null;\n },\n\n /** PrivateFunction: _handleError\n * _Private_ function that properly logs an error to the console\n */\n _handleError: function _handleError(e) {\n if (typeof e.stack !== \"undefined\") {\n Strophe.fatal(e.stack);\n }\n\n if (e.sourceURL) {\n Strophe.fatal(\"error: \" + this.handler + \" \" + e.sourceURL + \":\" + e.line + \" - \" + e.name + \": \" + e.message);\n } else if (e.fileName) {\n Strophe.fatal(\"error: \" + this.handler + \" \" + e.fileName + \":\" + e.lineNumber + \" - \" + e.name + \": \" + e.message);\n } else {\n Strophe.fatal(\"error: \" + e.message);\n }\n },\n\n /** Function: log\n * User overrideable logging function.\n *\n * This function is called whenever the Strophe library calls any\n * of the logging functions. The default implementation of this\n * function logs only fatal errors. If client code wishes to handle the logging\n * messages, it should override this with\n * > Strophe.log = function (level, msg) {\n * > (user code here)\n * > };\n *\n * Please note that data sent and received over the wire is logged\n * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput().\n *\n * The different levels and their meanings are\n *\n * DEBUG - Messages useful for debugging purposes.\n * INFO - Informational messages. This is mostly information like\n * 'disconnect was called' or 'SASL auth succeeded'.\n * WARN - Warnings about potential problems. This is mostly used\n * to report transient connection errors like request timeouts.\n * ERROR - Some error occurred.\n * FATAL - A non-recoverable fatal error occurred.\n *\n * Parameters:\n * (Integer) level - The log level of the log message. This will\n * be one of the values in Strophe.LogLevel.\n * (String) msg - The log message.\n */\n log: function log(level, msg) {\n if (level === this.LogLevel.FATAL && _typeof(window.console) === 'object' && typeof window.console.error === 'function') {\n window.console.error(msg);\n }\n },\n\n /** Function: debug\n * Log a message at the Strophe.LogLevel.DEBUG level.\n *\n * Parameters:\n * (String) msg - The log message.\n */\n debug: function debug(msg) {\n this.log(this.LogLevel.DEBUG, msg);\n },\n\n /** Function: info\n * Log a message at the Strophe.LogLevel.INFO level.\n *\n * Parameters:\n * (String) msg - The log message.\n */\n info: function info(msg) {\n this.log(this.LogLevel.INFO, msg);\n },\n\n /** Function: warn\n * Log a message at the Strophe.LogLevel.WARN level.\n *\n * Parameters:\n * (String) msg - The log message.\n */\n warn: function warn(msg) {\n this.log(this.LogLevel.WARN, msg);\n },\n\n /** Function: error\n * Log a message at the Strophe.LogLevel.ERROR level.\n *\n * Parameters:\n * (String) msg - The log message.\n */\n error: function error(msg) {\n this.log(this.LogLevel.ERROR, msg);\n },\n\n /** Function: fatal\n * Log a message at the Strophe.LogLevel.FATAL level.\n *\n * Parameters:\n * (String) msg - The log message.\n */\n fatal: function fatal(msg) {\n this.log(this.LogLevel.FATAL, msg);\n },\n\n /** Function: serialize\n * Render a DOM element and all descendants to a String.\n *\n * Parameters:\n * (XMLElement) elem - A DOM element.\n *\n * Returns:\n * The serialized element tree as a String.\n */\n serialize: function serialize(elem) {\n if (!elem) {\n return null;\n }\n\n if (typeof elem.tree === \"function\") {\n elem = elem.tree();\n }\n\n var names = _toConsumableArray(Array(elem.attributes.length).keys()).map(function (i) {\n return elem.attributes[i].nodeName;\n });\n\n names.sort();\n var result = names.reduce(function (a, n) {\n return \"\".concat(a, \" \").concat(n, \"=\\\"\").concat(Strophe.xmlescape(elem.attributes.getNamedItem(n).value), \"\\\"\");\n }, \"<\".concat(elem.nodeName));\n\n if (elem.childNodes.length > 0) {\n result += \">\";\n\n for (var i = 0; i < elem.childNodes.length; i++) {\n var child = elem.childNodes[i];\n\n switch (child.nodeType) {\n case Strophe.ElementType.NORMAL:\n // normal element, so recurse\n result += Strophe.serialize(child);\n break;\n\n case Strophe.ElementType.TEXT:\n // text element to escape values\n result += Strophe.xmlescape(child.nodeValue);\n break;\n\n case Strophe.ElementType.CDATA:\n // cdata section so don't escape values\n result += \"\";\n }\n }\n\n result += \"\";\n } else {\n result += \"/>\";\n }\n\n return result;\n },\n\n /** PrivateVariable: _requestId\n * _Private_ variable that keeps track of the request ids for\n * connections.\n */\n _requestId: 0,\n\n /** PrivateVariable: Strophe.connectionPlugins\n * _Private_ variable Used to store plugin names that need\n * initialization on Strophe.Connection construction.\n */\n _connectionPlugins: {},\n\n /** Function: addConnectionPlugin\n * Extends the Strophe.Connection object with the given plugin.\n *\n * Parameters:\n * (String) name - The name of the extension.\n * (Object) ptype - The plugin's prototype.\n */\n addConnectionPlugin: function addConnectionPlugin(name, ptype) {\n Strophe._connectionPlugins[name] = ptype;\n }\n };\n /** Class: Strophe.Builder\n * XML DOM builder.\n *\n * This object provides an interface similar to JQuery but for building\n * DOM elements easily and rapidly. All the functions except for toString()\n * and tree() return the object, so calls can be chained. Here's an\n * example using the $iq() builder helper.\n * > $iq({to: 'you', from: 'me', type: 'get', id: '1'})\n * > .c('query', {xmlns: 'strophe:example'})\n * > .c('example')\n * > .toString()\n *\n * The above generates this XML fragment\n * > \n * > \n * > \n * > \n * > \n * The corresponding DOM manipulations to get a similar fragment would be\n * a lot more tedious and probably involve several helper variables.\n *\n * Since adding children makes new operations operate on the child, up()\n * is provided to traverse up the tree. To add two children, do\n * > builder.c('child1', ...).up().c('child2', ...)\n * The next operation on the Builder will be relative to the second child.\n */\n\n /** Constructor: Strophe.Builder\n * Create a Strophe.Builder object.\n *\n * The attributes should be passed in object notation. For example\n * > let b = new Builder('message', {to: 'you', from: 'me'});\n * or\n * > let b = new Builder('messsage', {'xml:lang': 'en'});\n *\n * Parameters:\n * (String) name - The name of the root element.\n * (Object) attrs - The attributes for the root element in object notation.\n *\n * Returns:\n * A new Strophe.Builder.\n */\n\n Strophe.Builder = function (name, attrs) {\n // Set correct namespace for jabber:client elements\n if (name === \"presence\" || name === \"message\" || name === \"iq\") {\n if (attrs && !attrs.xmlns) {\n attrs.xmlns = Strophe.NS.CLIENT;\n } else if (!attrs) {\n attrs = {\n xmlns: Strophe.NS.CLIENT\n };\n }\n } // Holds the tree being built.\n\n\n this.nodeTree = Strophe.xmlElement(name, attrs); // Points to the current operation node.\n\n this.node = this.nodeTree;\n };\n\n Strophe.Builder.prototype = {\n /** Function: tree\n * Return the DOM tree.\n *\n * This function returns the current DOM tree as an element object. This\n * is suitable for passing to functions like Strophe.Connection.send().\n *\n * Returns:\n * The DOM tree as a element object.\n */\n tree: function tree() {\n return this.nodeTree;\n },\n\n /** Function: toString\n * Serialize the DOM tree to a String.\n *\n * This function returns a string serialization of the current DOM\n * tree. It is often used internally to pass data to a\n * Strophe.Request object.\n *\n * Returns:\n * The serialized DOM tree in a String.\n */\n toString: function toString() {\n return Strophe.serialize(this.nodeTree);\n },\n\n /** Function: up\n * Make the current parent element the new current element.\n *\n * This function is often used after c() to traverse back up the tree.\n * For example, to add two children to the same element\n * > builder.c('child1', {}).up().c('child2', {});\n *\n * Returns:\n * The Stophe.Builder object.\n */\n up: function up() {\n this.node = this.node.parentNode;\n return this;\n },\n\n /** Function: root\n * Make the root element the new current element.\n *\n * When at a deeply nested element in the tree, this function can be used\n * to jump back to the root of the tree, instead of having to repeatedly\n * call up().\n *\n * Returns:\n * The Stophe.Builder object.\n */\n root: function root() {\n this.node = this.nodeTree;\n return this;\n },\n\n /** Function: attrs\n * Add or modify attributes of the current element.\n *\n * The attributes should be passed in object notation. This function\n * does not move the current element pointer.\n *\n * Parameters:\n * (Object) moreattrs - The attributes to add/modify in object notation.\n *\n * Returns:\n * The Strophe.Builder object.\n */\n attrs: function attrs(moreattrs) {\n for (var k in moreattrs) {\n if (Object.prototype.hasOwnProperty.call(moreattrs, k)) {\n if (moreattrs[k] === undefined) {\n this.node.removeAttribute(k);\n } else {\n this.node.setAttribute(k, moreattrs[k]);\n }\n }\n }\n\n return this;\n },\n\n /** Function: c\n * Add a child to the current element and make it the new current\n * element.\n *\n * This function moves the current element pointer to the child,\n * unless text is provided. If you need to add another child, it\n * is necessary to use up() to go back to the parent in the tree.\n *\n * Parameters:\n * (String) name - The name of the child.\n * (Object) attrs - The attributes of the child in object notation.\n * (String) text - The text to add to the child.\n *\n * Returns:\n * The Strophe.Builder object.\n */\n c: function c(name, attrs, text) {\n var child = Strophe.xmlElement(name, attrs, text);\n this.node.appendChild(child);\n\n if (typeof text !== \"string\" && typeof text !== \"number\") {\n this.node = child;\n }\n\n return this;\n },\n\n /** Function: cnode\n * Add a child to the current element and make it the new current\n * element.\n *\n * This function is the same as c() except that instead of using a\n * name and an attributes object to create the child it uses an\n * existing DOM element object.\n *\n * Parameters:\n * (XMLElement) elem - A DOM element.\n *\n * Returns:\n * The Strophe.Builder object.\n */\n cnode: function cnode(elem) {\n var impNode;\n var xmlGen = Strophe.xmlGenerator();\n\n try {\n impNode = xmlGen.importNode !== undefined;\n } catch (e) {\n impNode = false;\n }\n\n var newElem = impNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem);\n this.node.appendChild(newElem);\n this.node = newElem;\n return this;\n },\n\n /** Function: t\n * Add a child text element.\n *\n * This *does not* make the child the new current element since there\n * are no children of text elements.\n *\n * Parameters:\n * (String) text - The text data to append to the current element.\n *\n * Returns:\n * The Strophe.Builder object.\n */\n t: function t(text) {\n var child = Strophe.xmlTextNode(text);\n this.node.appendChild(child);\n return this;\n },\n\n /** Function: h\n * Replace current element contents with the HTML passed in.\n *\n * This *does not* make the child the new current element\n *\n * Parameters:\n * (String) html - The html to insert as contents of current element.\n *\n * Returns:\n * The Strophe.Builder object.\n */\n h: function h(html) {\n var fragment = document.createElement('body'); // force the browser to try and fix any invalid HTML tags\n\n fragment.innerHTML = html; // copy cleaned html into an xml dom\n\n var xhtml = Strophe.createHtml(fragment);\n\n while (xhtml.childNodes.length > 0) {\n this.node.appendChild(xhtml.childNodes[0]);\n }\n\n return this;\n }\n };\n /** PrivateClass: Strophe.Handler\n * _Private_ helper class for managing stanza handlers.\n *\n * A Strophe.Handler encapsulates a user provided callback function to be\n * executed when matching stanzas are received by the connection.\n * Handlers can be either one-off or persistant depending on their\n * return value. Returning true will cause a Handler to remain active, and\n * returning false will remove the Handler.\n *\n * Users will not use Strophe.Handler objects directly, but instead they\n * will use Strophe.Connection.addHandler() and\n * Strophe.Connection.deleteHandler().\n */\n\n /** PrivateConstructor: Strophe.Handler\n * Create and initialize a new Strophe.Handler.\n *\n * Parameters:\n * (Function) handler - A function to be executed when the handler is run.\n * (String) ns - The namespace to match.\n * (String) name - The element name to match.\n * (String) type - The element type to match.\n * (String) id - The element id attribute to match.\n * (String) from - The element from attribute to match.\n * (Object) options - Handler options\n *\n * Returns:\n * A new Strophe.Handler object.\n */\n\n Strophe.Handler = function (handler, ns, name, type, id, from, options) {\n this.handler = handler;\n this.ns = ns;\n this.name = name;\n this.type = type;\n this.id = id;\n this.options = options || {\n 'matchBareFromJid': false,\n 'ignoreNamespaceFragment': false\n }; // BBB: Maintain backward compatibility with old `matchBare` option\n\n if (this.options.matchBare) {\n Strophe.warn('The \"matchBare\" option is deprecated, use \"matchBareFromJid\" instead.');\n this.options.matchBareFromJid = this.options.matchBare;\n delete this.options.matchBare;\n }\n\n if (this.options.matchBareFromJid) {\n this.from = from ? Strophe.getBareJidFromJid(from) : null;\n } else {\n this.from = from;\n } // whether the handler is a user handler or a system handler\n\n\n this.user = true;\n };\n\n Strophe.Handler.prototype = {\n /** PrivateFunction: getNamespace\n * Returns the XML namespace attribute on an element.\n * If `ignoreNamespaceFragment` was passed in for this handler, then the\n * URL fragment will be stripped.\n *\n * Parameters:\n * (XMLElement) elem - The XML element with the namespace.\n *\n * Returns:\n * The namespace, with optionally the fragment stripped.\n */\n getNamespace: function getNamespace(elem) {\n var elNamespace = elem.getAttribute(\"xmlns\");\n\n if (elNamespace && this.options.ignoreNamespaceFragment) {\n elNamespace = elNamespace.split('#')[0];\n }\n\n return elNamespace;\n },\n\n /** PrivateFunction: namespaceMatch\n * Tests if a stanza matches the namespace set for this Strophe.Handler.\n *\n * Parameters:\n * (XMLElement) elem - The XML element to test.\n *\n * Returns:\n * true if the stanza matches and false otherwise.\n */\n namespaceMatch: function namespaceMatch(elem) {\n var _this = this;\n\n var nsMatch = false;\n\n if (!this.ns) {\n return true;\n } else {\n Strophe.forEachChild(elem, null, function (elem) {\n if (_this.getNamespace(elem) === _this.ns) {\n nsMatch = true;\n }\n });\n return nsMatch || this.getNamespace(elem) === this.ns;\n }\n },\n\n /** PrivateFunction: isMatch\n * Tests if a stanza matches the Strophe.Handler.\n *\n * Parameters:\n * (XMLElement) elem - The XML element to test.\n *\n * Returns:\n * true if the stanza matches and false otherwise.\n */\n isMatch: function isMatch(elem) {\n var from = elem.getAttribute('from');\n\n if (this.options.matchBareFromJid) {\n from = Strophe.getBareJidFromJid(from);\n }\n\n var elem_type = elem.getAttribute(\"type\");\n\n if (this.namespaceMatch(elem) && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || (Array.isArray(this.type) ? this.type.indexOf(elem_type) !== -1 : elem_type === this.type)) && (!this.id || elem.getAttribute(\"id\") === this.id) && (!this.from || from === this.from)) {\n return true;\n }\n\n return false;\n },\n\n /** PrivateFunction: run\n * Run the callback on a matching stanza.\n *\n * Parameters:\n * (XMLElement) elem - The DOM element that triggered the\n * Strophe.Handler.\n *\n * Returns:\n * A boolean indicating if the handler should remain active.\n */\n run: function run(elem) {\n var result = null;\n\n try {\n result = this.handler(elem);\n } catch (e) {\n Strophe._handleError(e);\n\n throw e;\n }\n\n return result;\n },\n\n /** PrivateFunction: toString\n * Get a String representation of the Strophe.Handler object.\n *\n * Returns:\n * A String.\n */\n toString: function toString() {\n return \"{Handler: \" + this.handler + \"(\" + this.name + \",\" + this.id + \",\" + this.ns + \")}\";\n }\n };\n /** PrivateClass: Strophe.TimedHandler\n * _Private_ helper class for managing timed handlers.\n *\n * A Strophe.TimedHandler encapsulates a user provided callback that\n * should be called after a certain period of time or at regular\n * intervals. The return value of the callback determines whether the\n * Strophe.TimedHandler will continue to fire.\n *\n * Users will not use Strophe.TimedHandler objects directly, but instead\n * they will use Strophe.Connection.addTimedHandler() and\n * Strophe.Connection.deleteTimedHandler().\n */\n\n /** PrivateConstructor: Strophe.TimedHandler\n * Create and initialize a new Strophe.TimedHandler object.\n *\n * Parameters:\n * (Integer) period - The number of milliseconds to wait before the\n * handler is called.\n * (Function) handler - The callback to run when the handler fires. This\n * function should take no arguments.\n *\n * Returns:\n * A new Strophe.TimedHandler object.\n */\n\n Strophe.TimedHandler = function (period, handler) {\n this.period = period;\n this.handler = handler;\n this.lastCalled = new Date().getTime();\n this.user = true;\n };\n\n Strophe.TimedHandler.prototype = {\n /** PrivateFunction: run\n * Run the callback for the Strophe.TimedHandler.\n *\n * Returns:\n * true if the Strophe.TimedHandler should be called again, and false\n * otherwise.\n */\n run: function run() {\n this.lastCalled = new Date().getTime();\n return this.handler();\n },\n\n /** PrivateFunction: reset\n * Reset the last called time for the Strophe.TimedHandler.\n */\n reset: function reset() {\n this.lastCalled = new Date().getTime();\n },\n\n /** PrivateFunction: toString\n * Get a string representation of the Strophe.TimedHandler object.\n *\n * Returns:\n * The string representation.\n */\n toString: function toString() {\n return \"{TimedHandler: \" + this.handler + \"(\" + this.period + \")}\";\n }\n };\n /** Class: Strophe.Connection\n * XMPP Connection manager.\n *\n * This class is the main part of Strophe. It manages a BOSH or websocket\n * connection to an XMPP server and dispatches events to the user callbacks\n * as data arrives. It supports SASL PLAIN, SASL DIGEST-MD5, SASL SCRAM-SHA1\n * and legacy authentication.\n *\n * After creating a Strophe.Connection object, the user will typically\n * call connect() with a user supplied callback to handle connection level\n * events like authentication failure, disconnection, or connection\n * complete.\n *\n * The user will also have several event handlers defined by using\n * addHandler() and addTimedHandler(). These will allow the user code to\n * respond to interesting stanzas or do something periodically with the\n * connection. These handlers will be active once authentication is\n * finished.\n *\n * To send data to the connection, use send().\n */\n\n /** Constructor: Strophe.Connection\n * Create and initialize a Strophe.Connection object.\n *\n * The transport-protocol for this connection will be chosen automatically\n * based on the given service parameter. URLs starting with \"ws://\" or\n * \"wss://\" will use WebSockets, URLs starting with \"http://\", \"https://\"\n * or without a protocol will use BOSH.\n *\n * To make Strophe connect to the current host you can leave out the protocol\n * and host part and just pass the path, e.g.\n *\n * > let conn = new Strophe.Connection(\"/http-bind/\");\n *\n * Options common to both Websocket and BOSH:\n * ------------------------------------------\n *\n * cookies:\n *\n * The *cookies* option allows you to pass in cookies to be added to the\n * document. These cookies will then be included in the BOSH XMLHttpRequest\n * or in the websocket connection.\n *\n * The passed in value must be a map of cookie names and string values.\n *\n * > { \"myCookie\": {\n * > \"value\": \"1234\",\n * > \"domain\": \".example.org\",\n * > \"path\": \"/\",\n * > \"expires\": expirationDate\n * > }\n * > }\n *\n * Note that cookies can't be set in this way for other domains (i.e. cross-domain).\n * Those cookies need to be set under those domains, for example they can be\n * set server-side by making a XHR call to that domain to ask it to set any\n * necessary cookies.\n *\n * mechanisms:\n *\n * The *mechanisms* option allows you to specify the SASL mechanisms that this\n * instance of Strophe.Connection (and therefore your XMPP client) will\n * support.\n *\n * The value must be an array of objects with Strophe.SASLMechanism\n * prototypes.\n *\n * If nothing is specified, then the following mechanisms (and their\n * priorities) are registered:\n *\n * SCRAM-SHA1 - 70\n * DIGEST-MD5 - 60\n * PLAIN - 50\n * OAUTH-BEARER - 40\n * OAUTH-2 - 30\n * ANONYMOUS - 20\n * EXTERNAL - 10\n *\n * explicitResourceBinding:\n *\n * If `explicitResourceBinding` is set to a truthy value, then the XMPP client\n * needs to explicitly call `Strophe.Connection.prototype.bind` once the XMPP\n * server has advertised the \"urn:ietf:params:xml:ns:xmpp-bind\" feature.\n *\n * Making this step explicit allows client authors to first finish other\n * stream related tasks, such as setting up an XEP-0198 Stream Management\n * session, before binding the JID resource for this session.\n *\n * WebSocket options:\n * ------------------\n *\n * If you want to connect to the current host with a WebSocket connection you\n * can tell Strophe to use WebSockets through a \"protocol\" attribute in the\n * optional options parameter. Valid values are \"ws\" for WebSocket and \"wss\"\n * for Secure WebSocket.\n * So to connect to \"wss://CURRENT_HOSTNAME/xmpp-websocket\" you would call\n *\n * > let conn = new Strophe.Connection(\"/xmpp-websocket/\", {protocol: \"wss\"});\n *\n * Note that relative URLs _NOT_ starting with a \"/\" will also include the path\n * of the current site.\n *\n * Also because downgrading security is not permitted by browsers, when using\n * relative URLs both BOSH and WebSocket connections will use their secure\n * variants if the current connection to the site is also secure (https).\n *\n * BOSH options:\n * -------------\n *\n * By adding \"sync\" to the options, you can control if requests will\n * be made synchronously or not. The default behaviour is asynchronous.\n * If you want to make requests synchronous, make \"sync\" evaluate to true.\n * > let conn = new Strophe.Connection(\"/http-bind/\", {sync: true});\n *\n * You can also toggle this on an already established connection.\n * > conn.options.sync = true;\n *\n * The *customHeaders* option can be used to provide custom HTTP headers to be\n * included in the XMLHttpRequests made.\n *\n * The *keepalive* option can be used to instruct Strophe to maintain the\n * current BOSH session across interruptions such as webpage reloads.\n *\n * It will do this by caching the sessions tokens in sessionStorage, and when\n * \"restore\" is called it will check whether there are cached tokens with\n * which it can resume an existing session.\n *\n * The *withCredentials* option should receive a Boolean value and is used to\n * indicate wether cookies should be included in ajax requests (by default\n * they're not).\n * Set this value to true if you are connecting to a BOSH service\n * and for some reason need to send cookies to it.\n * In order for this to work cross-domain, the server must also enable\n * credentials by setting the Access-Control-Allow-Credentials response header\n * to \"true\". For most usecases however this setting should be false (which\n * is the default).\n * Additionally, when using Access-Control-Allow-Credentials, the\n * Access-Control-Allow-Origin header can't be set to the wildcard \"*\", but\n * instead must be restricted to actual domains.\n *\n * The *contentType* option can be set to change the default Content-Type\n * of \"text/xml; charset=utf-8\", which can be useful to reduce the amount of\n * CORS preflight requests that are sent to the server.\n *\n * Parameters:\n * (String) service - The BOSH or WebSocket service URL.\n * (Object) options - A hash of configuration options\n *\n * Returns:\n * A new Strophe.Connection object.\n */\n\n Strophe.Connection = function (service, options) {\n var _this2 = this;\n\n // The service URL\n this.service = service; // Configuration options\n\n this.options = options || {};\n var proto = this.options.protocol || \"\"; // Select protocal based on service or options\n\n if (service.indexOf(\"ws:\") === 0 || service.indexOf(\"wss:\") === 0 || proto.indexOf(\"ws\") === 0) {\n this._proto = new Strophe.Websocket(this);\n } else {\n this._proto = new Strophe.Bosh(this);\n }\n /* The connected JID. */\n\n\n this.jid = \"\";\n /* the JIDs domain */\n\n this.domain = null;\n /* stream:features */\n\n this.features = null; // SASL\n\n this._sasl_data = {};\n this.do_session = false;\n this.do_bind = false; // handler lists\n\n this.timedHandlers = [];\n this.handlers = [];\n this.removeTimeds = [];\n this.removeHandlers = [];\n this.addTimeds = [];\n this.addHandlers = [];\n this.protocolErrorHandlers = {\n 'HTTP': {},\n 'websocket': {}\n };\n this._idleTimeout = null;\n this._disconnectTimeout = null;\n this.authenticated = false;\n this.connected = false;\n this.disconnecting = false;\n this.do_authentication = true;\n this.paused = false;\n this.restored = false;\n this._data = [];\n this._uniqueId = 0;\n this._sasl_success_handler = null;\n this._sasl_failure_handler = null;\n this._sasl_challenge_handler = null; // Max retries before disconnecting\n\n this.maxRetries = 5; // Call onIdle callback every 1/10th of a second\n\n this._idleTimeout = setTimeout(function () {\n return _this2._onIdle();\n }, 100);\n utils.addCookies(this.options.cookies);\n this.registerSASLMechanisms(this.options.mechanisms); // initialize plugins\n\n for (var k in Strophe._connectionPlugins) {\n if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) {\n var F = function F() {};\n\n F.prototype = Strophe._connectionPlugins[k];\n this[k] = new F();\n this[k].init(this);\n }\n }\n };\n\n Strophe.Connection.prototype = {\n /** Function: reset\n * Reset the connection.\n *\n * This function should be called after a connection is disconnected\n * before that connection is reused.\n */\n reset: function reset() {\n this._proto._reset(); // SASL\n\n\n this.do_session = false;\n this.do_bind = false; // handler lists\n\n this.timedHandlers = [];\n this.handlers = [];\n this.removeTimeds = [];\n this.removeHandlers = [];\n this.addTimeds = [];\n this.addHandlers = [];\n this.authenticated = false;\n this.connected = false;\n this.disconnecting = false;\n this.restored = false;\n this._data = [];\n this._requests = [];\n this._uniqueId = 0;\n },\n\n /** Function: pause\n * Pause the request manager.\n *\n * This will prevent Strophe from sending any more requests to the\n * server. This is very useful for temporarily pausing\n * BOSH-Connections while a lot of send() calls are happening quickly.\n * This causes Strophe to send the data in a single request, saving\n * many request trips.\n */\n pause: function pause() {\n this.paused = true;\n },\n\n /** Function: resume\n * Resume the request manager.\n *\n * This resumes after pause() has been called.\n */\n resume: function resume() {\n this.paused = false;\n },\n\n /** Function: getUniqueId\n * Generate a unique ID for use in elements.\n *\n * All stanzas are required to have unique id attributes. This\n * function makes creating these easy. Each connection instance has\n * a counter which starts from zero, and the value of this counter\n * plus a colon followed by the suffix becomes the unique id. If no\n * suffix is supplied, the counter is used as the unique id.\n *\n * Suffixes are used to make debugging easier when reading the stream\n * data, and their use is recommended. The counter resets to 0 for\n * every new connection for the same reason. For connections to the\n * same server that authenticate the same way, all the ids should be\n * the same, which makes it easy to see changes. This is useful for\n * automated testing as well.\n *\n * Parameters:\n * (String) suffix - A optional suffix to append to the id.\n *\n * Returns:\n * A unique string to be used for the id attribute.\n */\n getUniqueId: function getUniqueId(suffix) {\n var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n var r = Math.random() * 16 | 0,\n v = c === 'x' ? r : r & 0x3 | 0x8;\n return v.toString(16);\n });\n\n if (typeof suffix === \"string\" || typeof suffix === \"number\") {\n return uuid + \":\" + suffix;\n } else {\n return uuid + \"\";\n }\n },\n\n /** Function: addProtocolErrorHandler\n * Register a handler function for when a protocol (websocker or HTTP)\n * error occurs.\n *\n * NOTE: Currently only HTTP errors for BOSH requests are handled.\n * Patches that handle websocket errors would be very welcome.\n *\n * Parameters:\n * (String) protocol - 'HTTP' or 'websocket'\n * (Integer) status_code - Error status code (e.g 500, 400 or 404)\n * (Function) callback - Function that will fire on Http error\n *\n * Example:\n * function onError(err_code){\n * //do stuff\n * }\n *\n * let conn = Strophe.connect('http://example.com/http-bind');\n * conn.addProtocolErrorHandler('HTTP', 500, onError);\n * // Triggers HTTP 500 error and onError handler will be called\n * conn.connect('user_jid@incorrect_jabber_host', 'secret', onConnect);\n */\n addProtocolErrorHandler: function addProtocolErrorHandler(protocol, status_code, callback) {\n this.protocolErrorHandlers[protocol][status_code] = callback;\n },\n\n /** Function: connect\n * Starts the connection process.\n *\n * As the connection process proceeds, the user supplied callback will\n * be triggered multiple times with status updates. The callback\n * should take two arguments - the status code and the error condition.\n *\n * The status code will be one of the values in the Strophe.Status\n * constants. The error condition will be one of the conditions\n * defined in RFC 3920 or the condition 'strophe-parsererror'.\n *\n * The Parameters _wait_, _hold_ and _route_ are optional and only relevant\n * for BOSH connections. Please see XEP 124 for a more detailed explanation\n * of the optional parameters.\n *\n * Parameters:\n * (String) jid - The user's JID. This may be a bare JID,\n * or a full JID. If a node is not supplied, SASL OAUTHBEARER or\n * SASL ANONYMOUS authentication will be attempted (OAUTHBEARER will\n * process the provided password value as an access token).\n * (String) pass - The user's password.\n * (Function) callback - The connect callback function.\n * (Integer) wait - The optional HTTPBIND wait value. This is the\n * time the server will wait before returning an empty result for\n * a request. The default setting of 60 seconds is recommended.\n * (Integer) hold - The optional HTTPBIND hold value. This is the\n * number of connections the server will hold at one time. This\n * should almost always be set to 1 (the default).\n * (String) route - The optional route value.\n * (String) authcid - The optional alternative authentication identity\n * (username) if intending to impersonate another user.\n * When using the SASL-EXTERNAL authentication mechanism, for example\n * with client certificates, then the authcid value is used to\n * determine whether an authorization JID (authzid) should be sent to\n * the server. The authzid should not be sent to the server if the\n * authzid and authcid are the same. So to prevent it from being sent\n * (for example when the JID is already contained in the client\n * certificate), set authcid to that same JID. See XEP-178 for more\n * details.\n */\n connect: function connect(jid, pass, callback, wait, hold, route, authcid) {\n this.jid = jid;\n /** Variable: authzid\n * Authorization identity.\n */\n\n this.authzid = Strophe.getBareJidFromJid(this.jid);\n /** Variable: authcid\n * Authentication identity (User name).\n */\n\n this.authcid = authcid || Strophe.getNodeFromJid(this.jid);\n /** Variable: pass\n * Authentication identity (User password).\n */\n\n this.pass = pass;\n /** Variable: servtype\n * Digest MD5 compatibility.\n */\n\n this.servtype = \"xmpp\";\n this.connect_callback = callback;\n this.disconnecting = false;\n this.connected = false;\n this.authenticated = false;\n this.restored = false; // parse jid for domain\n\n this.domain = Strophe.getDomainFromJid(this.jid);\n\n this._changeConnectStatus(Strophe.Status.CONNECTING, null);\n\n this._proto._connect(wait, hold, route);\n },\n\n /** Function: attach\n * Attach to an already created and authenticated BOSH session.\n *\n * This function is provided to allow Strophe to attach to BOSH\n * sessions which have been created externally, perhaps by a Web\n * application. This is often used to support auto-login type features\n * without putting user credentials into the page.\n *\n * Parameters:\n * (String) jid - The full JID that is bound by the session.\n * (String) sid - The SID of the BOSH session.\n * (String) rid - The current RID of the BOSH session. This RID\n * will be used by the next request.\n * (Function) callback The connect callback function.\n * (Integer) wait - The optional HTTPBIND wait value. This is the\n * time the server will wait before returning an empty result for\n * a request. The default setting of 60 seconds is recommended.\n * Other settings will require tweaks to the Strophe.TIMEOUT value.\n * (Integer) hold - The optional HTTPBIND hold value. This is the\n * number of connections the server will hold at one time. This\n * should almost always be set to 1 (the default).\n * (Integer) wind - The optional HTTBIND window value. This is the\n * allowed range of request ids that are valid. The default is 5.\n */\n attach: function attach(jid, sid, rid, callback, wait, hold, wind) {\n if (this._proto instanceof Strophe.Bosh) {\n this._proto._attach(jid, sid, rid, callback, wait, hold, wind);\n } else {\n var error = new Error('The \"attach\" method can only be used with a BOSH connection.');\n error.name = 'StropheSessionError';\n throw error;\n }\n },\n\n /** Function: restore\n * Attempt to restore a cached BOSH session.\n *\n * This function is only useful in conjunction with providing the\n * \"keepalive\":true option when instantiating a new Strophe.Connection.\n *\n * When \"keepalive\" is set to true, Strophe will cache the BOSH tokens\n * RID (Request ID) and SID (Session ID) and then when this function is\n * called, it will attempt to restore the session from those cached\n * tokens.\n *\n * This function must therefore be called instead of connect or attach.\n *\n * For an example on how to use it, please see examples/restore.js\n *\n * Parameters:\n * (String) jid - The user's JID. This may be a bare JID or a full JID.\n * (Function) callback - The connect callback function.\n * (Integer) wait - The optional HTTPBIND wait value. This is the\n * time the server will wait before returning an empty result for\n * a request. The default setting of 60 seconds is recommended.\n * (Integer) hold - The optional HTTPBIND hold value. This is the\n * number of connections the server will hold at one time. This\n * should almost always be set to 1 (the default).\n * (Integer) wind - The optional HTTBIND window value. This is the\n * allowed range of request ids that are valid. The default is 5.\n */\n restore: function restore(jid, callback, wait, hold, wind) {\n if (this._sessionCachingSupported()) {\n this._proto._restore(jid, callback, wait, hold, wind);\n } else {\n var error = new Error('The \"restore\" method can only be used with a BOSH connection.');\n error.name = 'StropheSessionError';\n throw error;\n }\n },\n\n /** PrivateFunction: _sessionCachingSupported\n * Checks whether sessionStorage and JSON are supported and whether we're\n * using BOSH.\n */\n _sessionCachingSupported: function _sessionCachingSupported() {\n if (this._proto instanceof Strophe.Bosh) {\n if (!JSON) {\n return false;\n }\n\n try {\n sessionStorage.setItem('_strophe_', '_strophe_');\n sessionStorage.removeItem('_strophe_');\n } catch (e) {\n return false;\n }\n\n return true;\n }\n\n return false;\n },\n\n /** Function: xmlInput\n * User overrideable function that receives XML data coming into the\n * connection.\n *\n * The default function does nothing. User code can override this with\n * > Strophe.Connection.xmlInput = function (elem) {\n * > (user code)\n * > };\n *\n * Due to limitations of current Browsers' XML-Parsers the opening and closing\n * tag for WebSocket-Connoctions will be passed as selfclosing here.\n *\n * BOSH-Connections will have all stanzas wrapped in a tag. See\n * if you want to strip this tag.\n *\n * Parameters:\n * (XMLElement) elem - The XML data received by the connection.\n */\n xmlInput: function xmlInput(elem) {\n return;\n },\n\n /** Function: xmlOutput\n * User overrideable function that receives XML data sent to the\n * connection.\n *\n * The default function does nothing. User code can override this with\n * > Strophe.Connection.xmlOutput = function (elem) {\n * > (user code)\n * > };\n *\n * Due to limitations of current Browsers' XML-Parsers the opening and closing\n * tag for WebSocket-Connoctions will be passed as selfclosing here.\n *\n * BOSH-Connections will have all stanzas wrapped in a tag. See\n * if you want to strip this tag.\n *\n * Parameters:\n * (XMLElement) elem - The XMLdata sent by the connection.\n */\n xmlOutput: function xmlOutput(elem) {\n return;\n },\n\n /** Function: rawInput\n * User overrideable function that receives raw data coming into the\n * connection.\n *\n * The default function does nothing. User code can override this with\n * > Strophe.Connection.rawInput = function (data) {\n * > (user code)\n * > };\n *\n * Parameters:\n * (String) data - The data received by the connection.\n */\n rawInput: function rawInput(data) {\n return;\n },\n\n /** Function: rawOutput\n * User overrideable function that receives raw data sent to the\n * connection.\n *\n * The default function does nothing. User code can override this with\n * > Strophe.Connection.rawOutput = function (data) {\n * > (user code)\n * > };\n *\n * Parameters:\n * (String) data - The data sent by the connection.\n */\n rawOutput: function rawOutput(data) {\n return;\n },\n\n /** Function: nextValidRid\n * User overrideable function that receives the new valid rid.\n *\n * The default function does nothing. User code can override this with\n * > Strophe.Connection.nextValidRid = function (rid) {\n * > (user code)\n * > };\n *\n * Parameters:\n * (Number) rid - The next valid rid\n */\n nextValidRid: function nextValidRid(rid) {\n return;\n },\n\n /** Function: send\n * Send a stanza.\n *\n * This function is called to push data onto the send queue to\n * go out over the wire. Whenever a request is sent to the BOSH\n * server, all pending data is sent and the queue is flushed.\n *\n * Parameters:\n * (XMLElement |\n * [XMLElement] |\n * Strophe.Builder) elem - The stanza to send.\n */\n send: function send(elem) {\n if (elem === null) {\n return;\n }\n\n if (typeof elem.sort === \"function\") {\n for (var i = 0; i < elem.length; i++) {\n this._queueData(elem[i]);\n }\n } else if (typeof elem.tree === \"function\") {\n this._queueData(elem.tree());\n } else {\n this._queueData(elem);\n }\n\n this._proto._send();\n },\n\n /** Function: flush\n * Immediately send any pending outgoing data.\n *\n * Normally send() queues outgoing data until the next idle period\n * (100ms), which optimizes network use in the common cases when\n * several send()s are called in succession. flush() can be used to\n * immediately send all pending data.\n */\n flush: function flush() {\n // cancel the pending idle period and run the idle function\n // immediately\n clearTimeout(this._idleTimeout);\n\n this._onIdle();\n },\n\n /** Function: sendPresence\n * Helper function to send presence stanzas. The main benefit is for\n * sending presence stanzas for which you expect a responding presence\n * stanza with the same id (for example when leaving a chat room).\n *\n * Parameters:\n * (XMLElement) elem - The stanza to send.\n * (Function) callback - The callback function for a successful request.\n * (Function) errback - The callback function for a failed or timed\n * out request. On timeout, the stanza will be null.\n * (Integer) timeout - The time specified in milliseconds for a\n * timeout to occur.\n *\n * Returns:\n * The id used to send the presence.\n */\n sendPresence: function sendPresence(elem, callback, errback, timeout) {\n var _this3 = this;\n\n var timeoutHandler = null;\n\n if (typeof elem.tree === \"function\") {\n elem = elem.tree();\n }\n\n var id = elem.getAttribute('id');\n\n if (!id) {\n // inject id if not found\n id = this.getUniqueId(\"sendPresence\");\n elem.setAttribute(\"id\", id);\n }\n\n if (typeof callback === \"function\" || typeof errback === \"function\") {\n var handler = this.addHandler(function (stanza) {\n // remove timeout handler if there is one\n if (timeoutHandler) {\n _this3.deleteTimedHandler(timeoutHandler);\n }\n\n if (stanza.getAttribute('type') === 'error') {\n if (errback) {\n errback(stanza);\n }\n } else if (callback) {\n callback(stanza);\n }\n }, null, 'presence', null, id); // if timeout specified, set up a timeout handler.\n\n if (timeout) {\n timeoutHandler = this.addTimedHandler(timeout, function () {\n // get rid of normal handler\n _this3.deleteHandler(handler); // call errback on timeout with null stanza\n\n\n if (errback) {\n errback(null);\n }\n\n return false;\n });\n }\n }\n\n this.send(elem);\n return id;\n },\n\n /** Function: sendIQ\n * Helper function to send IQ stanzas.\n *\n * Parameters:\n * (XMLElement) elem - The stanza to send.\n * (Function) callback - The callback function for a successful request.\n * (Function) errback - The callback function for a failed or timed\n * out request. On timeout, the stanza will be null.\n * (Integer) timeout - The time specified in milliseconds for a\n * timeout to occur.\n *\n * Returns:\n * The id used to send the IQ.\n */\n sendIQ: function sendIQ(elem, callback, errback, timeout) {\n var _this4 = this;\n\n var timeoutHandler = null;\n\n if (typeof elem.tree === \"function\") {\n elem = elem.tree();\n }\n\n var id = elem.getAttribute('id');\n\n if (!id) {\n // inject id if not found\n id = this.getUniqueId(\"sendIQ\");\n elem.setAttribute(\"id\", id);\n }\n\n if (typeof callback === \"function\" || typeof errback === \"function\") {\n var handler = this.addHandler(function (stanza) {\n // remove timeout handler if there is one\n if (timeoutHandler) {\n _this4.deleteTimedHandler(timeoutHandler);\n }\n\n var iqtype = stanza.getAttribute('type');\n\n if (iqtype === 'result') {\n if (callback) {\n callback(stanza);\n }\n } else if (iqtype === 'error') {\n if (errback) {\n errback(stanza);\n }\n } else {\n var error = new Error(\"Got bad IQ type of \".concat(iqtype));\n error.name = \"StropheError\";\n throw error;\n }\n }, null, 'iq', ['error', 'result'], id); // if timeout specified, set up a timeout handler.\n\n if (timeout) {\n timeoutHandler = this.addTimedHandler(timeout, function () {\n // get rid of normal handler\n _this4.deleteHandler(handler); // call errback on timeout with null stanza\n\n\n if (errback) {\n errback(null);\n }\n\n return false;\n });\n }\n }\n\n this.send(elem);\n return id;\n },\n\n /** PrivateFunction: _queueData\n * Queue outgoing data for later sending. Also ensures that the data\n * is a DOMElement.\n */\n _queueData: function _queueData(element) {\n if (element === null || !element.tagName || !element.childNodes) {\n var error = new Error(\"Cannot queue non-DOMElement.\");\n error.name = \"StropheError\";\n throw error;\n }\n\n this._data.push(element);\n },\n\n /** PrivateFunction: _sendRestart\n * Send an xmpp:restart stanza.\n */\n _sendRestart: function _sendRestart() {\n var _this5 = this;\n\n this._data.push(\"restart\");\n\n this._proto._sendRestart();\n\n this._idleTimeout = setTimeout(function () {\n return _this5._onIdle();\n }, 100);\n },\n\n /** Function: addTimedHandler\n * Add a timed handler to the connection.\n *\n * This function adds a timed handler. The provided handler will\n * be called every period milliseconds until it returns false,\n * the connection is terminated, or the handler is removed. Handlers\n * that wish to continue being invoked should return true.\n *\n * Because of method binding it is necessary to save the result of\n * this function if you wish to remove a handler with\n * deleteTimedHandler().\n *\n * Note that user handlers are not active until authentication is\n * successful.\n *\n * Parameters:\n * (Integer) period - The period of the handler.\n * (Function) handler - The callback function.\n *\n * Returns:\n * A reference to the handler that can be used to remove it.\n */\n addTimedHandler: function addTimedHandler(period, handler) {\n var thand = new Strophe.TimedHandler(period, handler);\n this.addTimeds.push(thand);\n return thand;\n },\n\n /** Function: deleteTimedHandler\n * Delete a timed handler for a connection.\n *\n * This function removes a timed handler from the connection. The\n * handRef parameter is *not* the function passed to addTimedHandler(),\n * but is the reference returned from addTimedHandler().\n *\n * Parameters:\n * (Strophe.TimedHandler) handRef - The handler reference.\n */\n deleteTimedHandler: function deleteTimedHandler(handRef) {\n // this must be done in the Idle loop so that we don't change\n // the handlers during iteration\n this.removeTimeds.push(handRef);\n },\n\n /** Function: addHandler\n * Add a stanza handler for the connection.\n *\n * This function adds a stanza handler to the connection. The\n * handler callback will be called for any stanza that matches\n * the parameters. Note that if multiple parameters are supplied,\n * they must all match for the handler to be invoked.\n *\n * The handler will receive the stanza that triggered it as its argument.\n * *The handler should return true if it is to be invoked again;\n * returning false will remove the handler after it returns.*\n *\n * As a convenience, the ns parameters applies to the top level element\n * and also any of its immediate children. This is primarily to make\n * matching /iq/query elements easy.\n *\n * Options\n * ~~~~~~~\n * With the options argument, you can specify boolean flags that affect how\n * matches are being done.\n *\n * Currently two flags exist:\n *\n * - matchBareFromJid:\n * When set to true, the from parameter and the\n * from attribute on the stanza will be matched as bare JIDs instead\n * of full JIDs. To use this, pass {matchBareFromJid: true} as the\n * value of options. The default value for matchBareFromJid is false.\n *\n * - ignoreNamespaceFragment:\n * When set to true, a fragment specified on the stanza's namespace\n * URL will be ignored when it's matched with the one configured for\n * the handler.\n *\n * This means that if you register like this:\n * > connection.addHandler(\n * > handler,\n * > 'http://jabber.org/protocol/muc',\n * > null, null, null, null,\n * > {'ignoreNamespaceFragment': true}\n * > );\n *\n * Then a stanza with XML namespace of\n * 'http://jabber.org/protocol/muc#user' will also be matched. If\n * 'ignoreNamespaceFragment' is false, then only stanzas with\n * 'http://jabber.org/protocol/muc' will be matched.\n *\n * Deleting the handler\n * ~~~~~~~~~~~~~~~~~~~~\n * The return value should be saved if you wish to remove the handler\n * with deleteHandler().\n *\n * Parameters:\n * (Function) handler - The user callback.\n * (String) ns - The namespace to match.\n * (String) name - The stanza name to match.\n * (String|Array) type - The stanza type (or types if an array) to match.\n * (String) id - The stanza id attribute to match.\n * (String) from - The stanza from attribute to match.\n * (String) options - The handler options\n *\n * Returns:\n * A reference to the handler that can be used to remove it.\n */\n addHandler: function addHandler(handler, ns, name, type, id, from, options) {\n var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);\n this.addHandlers.push(hand);\n return hand;\n },\n\n /** Function: deleteHandler\n * Delete a stanza handler for a connection.\n *\n * This function removes a stanza handler from the connection. The\n * handRef parameter is *not* the function passed to addHandler(),\n * but is the reference returned from addHandler().\n *\n * Parameters:\n * (Strophe.Handler) handRef - The handler reference.\n */\n deleteHandler: function deleteHandler(handRef) {\n // this must be done in the Idle loop so that we don't change\n // the handlers during iteration\n this.removeHandlers.push(handRef); // If a handler is being deleted while it is being added,\n // prevent it from getting added\n\n var i = this.addHandlers.indexOf(handRef);\n\n if (i >= 0) {\n this.addHandlers.splice(i, 1);\n }\n },\n\n /** Function: registerSASLMechanisms\n *\n * Register the SASL mechanisms which will be supported by this instance of\n * Strophe.Connection (i.e. which this XMPP client will support).\n *\n * Parameters:\n * (Array) mechanisms - Array of objects with Strophe.SASLMechanism prototypes\n *\n */\n registerSASLMechanisms: function registerSASLMechanisms(mechanisms) {\n this.mechanisms = {};\n mechanisms = mechanisms || [Strophe.SASLAnonymous, Strophe.SASLExternal, Strophe.SASLMD5, Strophe.SASLOAuthBearer, Strophe.SASLXOAuth2, Strophe.SASLPlain, Strophe.SASLSHA1];\n mechanisms.forEach(this.registerSASLMechanism.bind(this));\n },\n\n /** Function: registerSASLMechanism\n *\n * Register a single SASL mechanism, to be supported by this client.\n *\n * Parameters:\n * (Object) mechanism - Object with a Strophe.SASLMechanism prototype\n *\n */\n registerSASLMechanism: function registerSASLMechanism(mechanism) {\n this.mechanisms[mechanism.prototype.name] = mechanism;\n },\n\n /** Function: disconnect\n * Start the graceful disconnection process.\n *\n * This function starts the disconnection process. This process starts\n * by sending unavailable presence and sending BOSH body of type\n * terminate. A timeout handler makes sure that disconnection happens\n * even if the BOSH server does not respond.\n * If the Connection object isn't connected, at least tries to abort all pending requests\n * so the connection object won't generate successful requests (which were already opened).\n *\n * The user supplied connection callback will be notified of the\n * progress as this process happens.\n *\n * Parameters:\n * (String) reason - The reason the disconnect is occuring.\n */\n disconnect: function disconnect(reason) {\n this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);\n\n Strophe.warn(\"Disconnect was called because: \" + reason);\n\n if (this.connected) {\n var pres = false;\n this.disconnecting = true;\n\n if (this.authenticated) {\n pres = $pres({\n 'xmlns': Strophe.NS.CLIENT,\n 'type': 'unavailable'\n });\n } // setup timeout handler\n\n\n this._disconnectTimeout = this._addSysTimedHandler(3000, this._onDisconnectTimeout.bind(this));\n\n this._proto._disconnect(pres);\n } else {\n Strophe.warn(\"Disconnect was called before Strophe connected to the server\");\n\n this._proto._abortAllRequests();\n\n this._doDisconnect();\n }\n },\n\n /** PrivateFunction: _changeConnectStatus\n * _Private_ helper function that makes sure plugins and the user's\n * callback are notified of connection status changes.\n *\n * Parameters:\n * (Integer) status - the new connection status, one of the values\n * in Strophe.Status\n * (String) condition - the error condition or null\n * (XMLElement) elem - The triggering stanza.\n */\n _changeConnectStatus: function _changeConnectStatus(status, condition, elem) {\n // notify all plugins listening for status changes\n for (var k in Strophe._connectionPlugins) {\n if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) {\n var plugin = this[k];\n\n if (plugin.statusChanged) {\n try {\n plugin.statusChanged(status, condition);\n } catch (err) {\n Strophe.error(\"\".concat(k, \" plugin caused an exception changing status: \").concat(err));\n }\n }\n }\n } // notify the user's callback\n\n\n if (this.connect_callback) {\n try {\n this.connect_callback(status, condition, elem);\n } catch (e) {\n Strophe._handleError(e);\n\n Strophe.error(\"User connection callback caused an exception: \".concat(e));\n }\n }\n },\n\n /** PrivateFunction: _doDisconnect\n * _Private_ function to disconnect.\n *\n * This is the last piece of the disconnection logic. This resets the\n * connection and alerts the user's connection callback.\n */\n _doDisconnect: function _doDisconnect(condition) {\n if (typeof this._idleTimeout === \"number\") {\n clearTimeout(this._idleTimeout);\n } // Cancel Disconnect Timeout\n\n\n if (this._disconnectTimeout !== null) {\n this.deleteTimedHandler(this._disconnectTimeout);\n this._disconnectTimeout = null;\n }\n\n Strophe.debug(\"_doDisconnect was called\");\n\n this._proto._doDisconnect();\n\n this.authenticated = false;\n this.disconnecting = false;\n this.restored = false; // delete handlers\n\n this.handlers = [];\n this.timedHandlers = [];\n this.removeTimeds = [];\n this.removeHandlers = [];\n this.addTimeds = [];\n this.addHandlers = []; // tell the parent we disconnected\n\n this._changeConnectStatus(Strophe.Status.DISCONNECTED, condition);\n\n this.connected = false;\n },\n\n /** PrivateFunction: _dataRecv\n * _Private_ handler to processes incoming data from the the connection.\n *\n * Except for _connect_cb handling the initial connection request,\n * this function handles the incoming data for all requests. This\n * function also fires stanza handlers that match each incoming\n * stanza.\n *\n * Parameters:\n * (Strophe.Request) req - The request that has data ready.\n * (string) req - The stanza a raw string (optiona).\n */\n _dataRecv: function _dataRecv(req, raw) {\n var _this6 = this;\n\n Strophe.debug(\"_dataRecv called\");\n\n var elem = this._proto._reqToData(req);\n\n if (elem === null) {\n return;\n }\n\n if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {\n if (elem.nodeName === this._proto.strip && elem.childNodes.length) {\n this.xmlInput(elem.childNodes[0]);\n } else {\n this.xmlInput(elem);\n }\n }\n\n if (this.rawInput !== Strophe.Connection.prototype.rawInput) {\n if (raw) {\n this.rawInput(raw);\n } else {\n this.rawInput(Strophe.serialize(elem));\n }\n } // remove handlers scheduled for deletion\n\n\n while (this.removeHandlers.length > 0) {\n var hand = this.removeHandlers.pop();\n var i = this.handlers.indexOf(hand);\n\n if (i >= 0) {\n this.handlers.splice(i, 1);\n }\n } // add handlers scheduled for addition\n\n\n while (this.addHandlers.length > 0) {\n this.handlers.push(this.addHandlers.pop());\n } // handle graceful disconnect\n\n\n if (this.disconnecting && this._proto._emptyQueue()) {\n this._doDisconnect();\n\n return;\n }\n\n var type = elem.getAttribute(\"type\");\n\n if (type !== null && type === \"terminate\") {\n // Don't process stanzas that come in after disconnect\n if (this.disconnecting) {\n return;\n } // an error occurred\n\n\n var cond = elem.getAttribute(\"condition\");\n var conflict = elem.getElementsByTagName(\"conflict\");\n\n if (cond !== null) {\n if (cond === \"remote-stream-error\" && conflict.length > 0) {\n cond = \"conflict\";\n }\n\n this._changeConnectStatus(Strophe.Status.CONNFAIL, cond);\n } else {\n this._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.UNKOWN_REASON);\n }\n\n this._doDisconnect(cond);\n\n return;\n } // send each incoming stanza through the handler chain\n\n\n Strophe.forEachChild(elem, null, function (child) {\n // process handlers\n var newList = _this6.handlers;\n _this6.handlers = [];\n\n for (var _i5 = 0; _i5 < newList.length; _i5++) {\n var _hand = newList[_i5]; // encapsulate 'handler.run' not to lose the whole handler list if\n // one of the handlers throws an exception\n\n try {\n if (_hand.isMatch(child) && (_this6.authenticated || !_hand.user)) {\n if (_hand.run(child)) {\n _this6.handlers.push(_hand);\n }\n } else {\n _this6.handlers.push(_hand);\n }\n } catch (e) {\n // if the handler throws an exception, we consider it as false\n Strophe.warn('Removing Strophe handlers due to uncaught exception: ' + e.message);\n }\n }\n });\n },\n\n /** Attribute: mechanisms\n * SASL Mechanisms available for Connection.\n */\n mechanisms: {},\n\n /** PrivateFunction: _connect_cb\n * _Private_ handler for initial connection request.\n *\n * This handler is used to process the initial connection request\n * response from the BOSH server. It is used to set up authentication\n * handlers and start the authentication process.\n *\n * SASL authentication will be attempted if available, otherwise\n * the code will fall back to legacy authentication.\n *\n * Parameters:\n * (Strophe.Request) req - The current request.\n * (Function) _callback - low level (xmpp) connect callback function.\n * Useful for plugins with their own xmpp connect callback (when they\n * want to do something special).\n */\n _connect_cb: function _connect_cb(req, _callback, raw) {\n Strophe.debug(\"_connect_cb was called\");\n this.connected = true;\n var bodyWrap;\n\n try {\n bodyWrap = this._proto._reqToData(req);\n } catch (e) {\n if (e.name !== Strophe.ErrorCondition.BAD_FORMAT) {\n throw e;\n }\n\n this._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.BAD_FORMAT);\n\n this._doDisconnect(Strophe.ErrorCondition.BAD_FORMAT);\n }\n\n if (!bodyWrap) {\n return;\n }\n\n if (this.xmlInput !== Strophe.Connection.prototype.xmlInput) {\n if (bodyWrap.nodeName === this._proto.strip && bodyWrap.childNodes.length) {\n this.xmlInput(bodyWrap.childNodes[0]);\n } else {\n this.xmlInput(bodyWrap);\n }\n }\n\n if (this.rawInput !== Strophe.Connection.prototype.rawInput) {\n if (raw) {\n this.rawInput(raw);\n } else {\n this.rawInput(Strophe.serialize(bodyWrap));\n }\n }\n\n var conncheck = this._proto._connect_cb(bodyWrap);\n\n if (conncheck === Strophe.Status.CONNFAIL) {\n return;\n } // Check for the stream:features tag\n\n\n var hasFeatures;\n\n if (bodyWrap.getElementsByTagNameNS) {\n hasFeatures = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, \"features\").length > 0;\n } else {\n hasFeatures = bodyWrap.getElementsByTagName(\"stream:features\").length > 0 || bodyWrap.getElementsByTagName(\"features\").length > 0;\n }\n\n if (!hasFeatures) {\n this._proto._no_auth_received(_callback);\n\n return;\n }\n\n var matched = [];\n var mechanisms = bodyWrap.getElementsByTagName(\"mechanism\");\n\n if (mechanisms.length > 0) {\n for (var i = 0; i < mechanisms.length; i++) {\n var mech = Strophe.getText(mechanisms[i]);\n if (this.mechanisms[mech]) matched.push(this.mechanisms[mech]);\n }\n }\n\n if (matched.length === 0) {\n if (bodyWrap.getElementsByTagName(\"auth\").length === 0) {\n // There are no matching SASL mechanisms and also no legacy\n // auth available.\n this._proto._no_auth_received(_callback);\n\n return;\n }\n }\n\n if (this.do_authentication !== false) {\n this.authenticate(matched);\n }\n },\n\n /** Function: sortMechanismsByPriority\n *\n * Sorts an array of objects with prototype SASLMechanism according to\n * their priorities.\n *\n * Parameters:\n * (Array) mechanisms - Array of SASL mechanisms.\n *\n */\n sortMechanismsByPriority: function sortMechanismsByPriority(mechanisms) {\n // Sorting mechanisms according to priority.\n for (var i = 0; i < mechanisms.length - 1; ++i) {\n var higher = i;\n\n for (var j = i + 1; j < mechanisms.length; ++j) {\n if (mechanisms[j].prototype.priority > mechanisms[higher].prototype.priority) {\n higher = j;\n }\n }\n\n if (higher !== i) {\n var swap = mechanisms[i];\n mechanisms[i] = mechanisms[higher];\n mechanisms[higher] = swap;\n }\n }\n\n return mechanisms;\n },\n\n /** Function: authenticate\n * Set up authentication\n *\n * Continues the initial connection request by setting up authentication\n * handlers and starting the authentication process.\n *\n * SASL authentication will be attempted if available, otherwise\n * the code will fall back to legacy authentication.\n *\n * Parameters:\n * (Array) matched - Array of SASL mechanisms supported.\n *\n */\n authenticate: function authenticate(matched) {\n if (!this._attemptSASLAuth(matched)) {\n this._attemptLegacyAuth();\n }\n },\n\n /** PrivateFunction: _attemptSASLAuth\n *\n * Iterate through an array of SASL mechanisms and attempt authentication\n * with the highest priority (enabled) mechanism.\n *\n * Parameters:\n * (Array) mechanisms - Array of SASL mechanisms.\n *\n * Returns:\n * (Boolean) mechanism_found - true or false, depending on whether a\n * valid SASL mechanism was found with which authentication could be\n * started.\n */\n _attemptSASLAuth: function _attemptSASLAuth(mechanisms) {\n mechanisms = this.sortMechanismsByPriority(mechanisms || []);\n var mechanism_found = false;\n\n for (var i = 0; i < mechanisms.length; ++i) {\n if (!mechanisms[i].prototype.test(this)) {\n continue;\n }\n\n this._sasl_success_handler = this._addSysHandler(this._sasl_success_cb.bind(this), null, \"success\", null, null);\n this._sasl_failure_handler = this._addSysHandler(this._sasl_failure_cb.bind(this), null, \"failure\", null, null);\n this._sasl_challenge_handler = this._addSysHandler(this._sasl_challenge_cb.bind(this), null, \"challenge\", null, null);\n this._sasl_mechanism = new mechanisms[i]();\n\n this._sasl_mechanism.onStart(this);\n\n var request_auth_exchange = $build(\"auth\", {\n 'xmlns': Strophe.NS.SASL,\n 'mechanism': this._sasl_mechanism.name\n });\n\n if (this._sasl_mechanism.isClientFirst) {\n var response = this._sasl_mechanism.onChallenge(this, null);\n\n request_auth_exchange.t(btoa(response));\n }\n\n this.send(request_auth_exchange.tree());\n mechanism_found = true;\n break;\n }\n\n return mechanism_found;\n },\n\n /** PrivateFunction: _sasl_challenge_cb\n * _Private_ handler for the SASL challenge\n *\n */\n _sasl_challenge_cb: function _sasl_challenge_cb(elem) {\n var challenge = atob(Strophe.getText(elem));\n\n var response = this._sasl_mechanism.onChallenge(this, challenge);\n\n var stanza = $build('response', {\n 'xmlns': Strophe.NS.SASL\n });\n\n if (response !== \"\") {\n stanza.t(btoa(response));\n }\n\n this.send(stanza.tree());\n return true;\n },\n\n /** PrivateFunction: _attemptLegacyAuth\n *\n * Attempt legacy (i.e. non-SASL) authentication.\n */\n _attemptLegacyAuth: function _attemptLegacyAuth() {\n if (Strophe.getNodeFromJid(this.jid) === null) {\n // we don't have a node, which is required for non-anonymous\n // client connections\n this._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.MISSING_JID_NODE);\n\n this.disconnect(Strophe.ErrorCondition.MISSING_JID_NODE);\n } else {\n // Fall back to legacy authentication\n this._changeConnectStatus(Strophe.Status.AUTHENTICATING, null);\n\n this._addSysHandler(this._onLegacyAuthIQResult.bind(this), null, null, null, \"_auth_1\");\n\n this.send($iq({\n 'type': \"get\",\n 'to': this.domain,\n 'id': \"_auth_1\"\n }).c(\"query\", {\n xmlns: Strophe.NS.AUTH\n }).c(\"username\", {}).t(Strophe.getNodeFromJid(this.jid)).tree());\n }\n },\n\n /** PrivateFunction: _onLegacyAuthIQResult\n * _Private_ handler for legacy authentication.\n *\n * This handler is called in response to the initial \n * for legacy authentication. It builds an authentication and\n * sends it, creating a handler (calling back to _auth2_cb()) to\n * handle the result\n *\n * Parameters:\n * (XMLElement) elem - The stanza that triggered the callback.\n *\n * Returns:\n * false to remove the handler.\n */\n _onLegacyAuthIQResult: function _onLegacyAuthIQResult(elem) {\n // build plaintext auth iq\n var iq = $iq({\n type: \"set\",\n id: \"_auth_2\"\n }).c('query', {\n xmlns: Strophe.NS.AUTH\n }).c('username', {}).t(Strophe.getNodeFromJid(this.jid)).up().c('password').t(this.pass);\n\n if (!Strophe.getResourceFromJid(this.jid)) {\n // since the user has not supplied a resource, we pick\n // a default one here. unlike other auth methods, the server\n // cannot do this for us.\n this.jid = Strophe.getBareJidFromJid(this.jid) + '/strophe';\n }\n\n iq.up().c('resource', {}).t(Strophe.getResourceFromJid(this.jid));\n\n this._addSysHandler(this._auth2_cb.bind(this), null, null, null, \"_auth_2\");\n\n this.send(iq.tree());\n return false;\n },\n\n /** PrivateFunction: _sasl_success_cb\n * _Private_ handler for succesful SASL authentication.\n *\n * Parameters:\n * (XMLElement) elem - The matching stanza.\n *\n * Returns:\n * false to remove the handler.\n */\n _sasl_success_cb: function _sasl_success_cb(elem) {\n var _this7 = this;\n\n if (this._sasl_data[\"server-signature\"]) {\n var serverSignature;\n var success = atob(Strophe.getText(elem));\n var attribMatch = /([a-z]+)=([^,]+)(,|$)/;\n var matches = success.match(attribMatch);\n\n if (matches[1] === \"v\") {\n serverSignature = matches[2];\n }\n\n if (serverSignature !== this._sasl_data[\"server-signature\"]) {\n // remove old handlers\n this.deleteHandler(this._sasl_failure_handler);\n this._sasl_failure_handler = null;\n\n if (this._sasl_challenge_handler) {\n this.deleteHandler(this._sasl_challenge_handler);\n this._sasl_challenge_handler = null;\n }\n\n this._sasl_data = {};\n return this._sasl_failure_cb(null);\n }\n }\n\n Strophe.info(\"SASL authentication succeeded.\");\n\n if (this._sasl_mechanism) {\n this._sasl_mechanism.onSuccess();\n } // remove old handlers\n\n\n this.deleteHandler(this._sasl_failure_handler);\n this._sasl_failure_handler = null;\n\n if (this._sasl_challenge_handler) {\n this.deleteHandler(this._sasl_challenge_handler);\n this._sasl_challenge_handler = null;\n }\n\n var streamfeature_handlers = [];\n\n var wrapper = function wrapper(handlers, elem) {\n while (handlers.length) {\n _this7.deleteHandler(handlers.pop());\n }\n\n _this7._onStreamFeaturesAfterSASL(elem);\n\n return false;\n };\n\n streamfeature_handlers.push(this._addSysHandler(function (elem) {\n return wrapper(streamfeature_handlers, elem);\n }, null, \"stream:features\", null, null));\n streamfeature_handlers.push(this._addSysHandler(function (elem) {\n return wrapper(streamfeature_handlers, elem);\n }, Strophe.NS.STREAM, \"features\", null, null)); // we must send an xmpp:restart now\n\n this._sendRestart();\n\n return false;\n },\n\n /** PrivateFunction: _onStreamFeaturesAfterSASL\n * Parameters:\n * (XMLElement) elem - The matching stanza.\n *\n * Returns:\n * false to remove the handler.\n */\n _onStreamFeaturesAfterSASL: function _onStreamFeaturesAfterSASL(elem) {\n // save stream:features for future usage\n this.features = elem;\n\n for (var i = 0; i < elem.childNodes.length; i++) {\n var child = elem.childNodes[i];\n\n if (child.nodeName === 'bind') {\n this.do_bind = true;\n }\n\n if (child.nodeName === 'session') {\n this.do_session = true;\n }\n }\n\n if (!this.do_bind) {\n this._changeConnectStatus(Strophe.Status.AUTHFAIL, null);\n\n return false;\n } else if (!this.options.explicitResourceBinding) {\n this.bind();\n } else {\n this._changeConnectStatus(Strophe.Status.BINDREQUIRED, null);\n }\n\n return false;\n },\n\n /** Function: bind\n *\n * Sends an IQ to the XMPP server to bind a JID resource for this session.\n *\n * https://tools.ietf.org/html/rfc6120#section-7.5\n *\n * If `explicitResourceBinding` was set to a truthy value in the options\n * passed to the Strophe.Connection constructor, then this function needs\n * to be called explicitly by the client author.\n *\n * Otherwise it'll be called automatically as soon as the XMPP server\n * advertises the \"urn:ietf:params:xml:ns:xmpp-bind\" stream feature.\n */\n bind: function bind() {\n if (!this.do_bind) {\n Strophe.log(Strophe.LogLevel.INFO, \"Strophe.Connection.prototype.bind called but \\\"do_bind\\\" is false\");\n return;\n }\n\n this._addSysHandler(this._onResourceBindResultIQ.bind(this), null, null, null, \"_bind_auth_2\");\n\n var resource = Strophe.getResourceFromJid(this.jid);\n\n if (resource) {\n this.send($iq({\n type: \"set\",\n id: \"_bind_auth_2\"\n }).c('bind', {\n xmlns: Strophe.NS.BIND\n }).c('resource', {}).t(resource).tree());\n } else {\n this.send($iq({\n type: \"set\",\n id: \"_bind_auth_2\"\n }).c('bind', {\n xmlns: Strophe.NS.BIND\n }).tree());\n }\n },\n\n /** PrivateFunction: _onResourceBindIQ\n * _Private_ handler for binding result and session start.\n *\n * Parameters:\n * (XMLElement) elem - The matching stanza.\n *\n * Returns:\n * false to remove the handler.\n */\n _onResourceBindResultIQ: function _onResourceBindResultIQ(elem) {\n if (elem.getAttribute(\"type\") === \"error\") {\n Strophe.warn(\"Resource binding failed.\");\n var conflict = elem.getElementsByTagName(\"conflict\");\n var condition;\n\n if (conflict.length > 0) {\n condition = Strophe.ErrorCondition.CONFLICT;\n }\n\n this._changeConnectStatus(Strophe.Status.AUTHFAIL, condition, elem);\n\n return false;\n } // TODO - need to grab errors\n\n\n var bind = elem.getElementsByTagName(\"bind\");\n\n if (bind.length > 0) {\n var jidNode = bind[0].getElementsByTagName(\"jid\");\n\n if (jidNode.length > 0) {\n this.jid = Strophe.getText(jidNode[0]);\n\n if (this.do_session) {\n this._establishSession();\n } else {\n this.authenticated = true;\n\n this._changeConnectStatus(Strophe.Status.CONNECTED, null);\n }\n }\n } else {\n Strophe.warn(\"Resource binding failed.\");\n\n this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);\n\n return false;\n }\n },\n\n /** PrivateFunction: _establishSession\n * Send IQ request to establish a session with the XMPP server.\n *\n * See https://xmpp.org/rfcs/rfc3921.html#session\n *\n * Note: The protocol for session establishment has been determined as\n * unnecessary and removed in RFC-6121.\n */\n _establishSession: function _establishSession() {\n if (!this.do_session) {\n throw new Error(\"Strophe.Connection.prototype._establishSession \" + \"called but apparently \".concat(Strophe.NS.SESSION, \" wasn't advertised by the server\"));\n }\n\n this._addSysHandler(this._onSessionResultIQ.bind(this), null, null, null, \"_session_auth_2\");\n\n this.send($iq({\n type: \"set\",\n id: \"_session_auth_2\"\n }).c('session', {\n xmlns: Strophe.NS.SESSION\n }).tree());\n },\n\n /** PrivateFunction: _onSessionResultIQ\n * _Private_ handler for the server's IQ response to a client's session\n * request.\n *\n * This sets Connection.authenticated to true on success, which\n * starts the processing of user handlers.\n *\n * See https://xmpp.org/rfcs/rfc3921.html#session\n *\n * Note: The protocol for session establishment has been determined as\n * unnecessary and removed in RFC-6121.\n *\n * Parameters:\n * (XMLElement) elem - The matching stanza.\n *\n * Returns:\n * false to remove the handler.\n */\n _onSessionResultIQ: function _onSessionResultIQ(elem) {\n if (elem.getAttribute(\"type\") === \"result\") {\n this.authenticated = true;\n\n this._changeConnectStatus(Strophe.Status.CONNECTED, null);\n } else if (elem.getAttribute(\"type\") === \"error\") {\n Strophe.warn(\"Session creation failed.\");\n\n this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);\n\n return false;\n }\n\n return false;\n },\n\n /** PrivateFunction: _sasl_failure_cb\n * _Private_ handler for SASL authentication failure.\n *\n * Parameters:\n * (XMLElement) elem - The matching stanza.\n *\n * Returns:\n * false to remove the handler.\n */\n _sasl_failure_cb: function _sasl_failure_cb(elem) {\n // delete unneeded handlers\n if (this._sasl_success_handler) {\n this.deleteHandler(this._sasl_success_handler);\n this._sasl_success_handler = null;\n }\n\n if (this._sasl_challenge_handler) {\n this.deleteHandler(this._sasl_challenge_handler);\n this._sasl_challenge_handler = null;\n }\n\n if (this._sasl_mechanism) this._sasl_mechanism.onFailure();\n\n this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);\n\n return false;\n },\n\n /** PrivateFunction: _auth2_cb\n * _Private_ handler to finish legacy authentication.\n *\n * This handler is called when the result from the jabber:iq:auth\n * stanza is returned.\n *\n * Parameters:\n * (XMLElement) elem - The stanza that triggered the callback.\n *\n * Returns:\n * false to remove the handler.\n */\n _auth2_cb: function _auth2_cb(elem) {\n if (elem.getAttribute(\"type\") === \"result\") {\n this.authenticated = true;\n\n this._changeConnectStatus(Strophe.Status.CONNECTED, null);\n } else if (elem.getAttribute(\"type\") === \"error\") {\n this._changeConnectStatus(Strophe.Status.AUTHFAIL, null, elem);\n\n this.disconnect('authentication failed');\n }\n\n return false;\n },\n\n /** PrivateFunction: _addSysTimedHandler\n * _Private_ function to add a system level timed handler.\n *\n * This function is used to add a Strophe.TimedHandler for the\n * library code. System timed handlers are allowed to run before\n * authentication is complete.\n *\n * Parameters:\n * (Integer) period - The period of the handler.\n * (Function) handler - The callback function.\n */\n _addSysTimedHandler: function _addSysTimedHandler(period, handler) {\n var thand = new Strophe.TimedHandler(period, handler);\n thand.user = false;\n this.addTimeds.push(thand);\n return thand;\n },\n\n /** PrivateFunction: _addSysHandler\n * _Private_ function to add a system level stanza handler.\n *\n * This function is used to add a Strophe.Handler for the\n * library code. System stanza handlers are allowed to run before\n * authentication is complete.\n *\n * Parameters:\n * (Function) handler - The callback function.\n * (String) ns - The namespace to match.\n * (String) name - The stanza name to match.\n * (String) type - The stanza type attribute to match.\n * (String) id - The stanza id attribute to match.\n */\n _addSysHandler: function _addSysHandler(handler, ns, name, type, id) {\n var hand = new Strophe.Handler(handler, ns, name, type, id);\n hand.user = false;\n this.addHandlers.push(hand);\n return hand;\n },\n\n /** PrivateFunction: _onDisconnectTimeout\n * _Private_ timeout handler for handling non-graceful disconnection.\n *\n * If the graceful disconnect process does not complete within the\n * time allotted, this handler finishes the disconnect anyway.\n *\n * Returns:\n * false to remove the handler.\n */\n _onDisconnectTimeout: function _onDisconnectTimeout() {\n Strophe.debug(\"_onDisconnectTimeout was called\");\n\n this._changeConnectStatus(Strophe.Status.CONNTIMEOUT, null);\n\n this._proto._onDisconnectTimeout(); // actually disconnect\n\n\n this._doDisconnect();\n\n return false;\n },\n\n /** PrivateFunction: _onIdle\n * _Private_ handler to process events during idle cycle.\n *\n * This handler is called every 100ms to fire timed handlers that\n * are ready and keep poll requests going.\n */\n _onIdle: function _onIdle() {\n var _this8 = this;\n\n // add timed handlers scheduled for addition\n // NOTE: we add before remove in the case a timed handler is\n // added and then deleted before the next _onIdle() call.\n while (this.addTimeds.length > 0) {\n this.timedHandlers.push(this.addTimeds.pop());\n } // remove timed handlers that have been scheduled for deletion\n\n\n while (this.removeTimeds.length > 0) {\n var thand = this.removeTimeds.pop();\n var i = this.timedHandlers.indexOf(thand);\n\n if (i >= 0) {\n this.timedHandlers.splice(i, 1);\n }\n } // call ready timed handlers\n\n\n var now = new Date().getTime();\n var newList = [];\n\n for (var _i6 = 0; _i6 < this.timedHandlers.length; _i6++) {\n var _thand = this.timedHandlers[_i6];\n\n if (this.authenticated || !_thand.user) {\n var since = _thand.lastCalled + _thand.period;\n\n if (since - now <= 0) {\n if (_thand.run()) {\n newList.push(_thand);\n }\n } else {\n newList.push(_thand);\n }\n }\n }\n\n this.timedHandlers = newList;\n clearTimeout(this._idleTimeout);\n\n this._proto._onIdle(); // reactivate the timer only if connected\n\n\n if (this.connected) {\n this._idleTimeout = setTimeout(function () {\n return _this8._onIdle();\n }, 100);\n }\n }\n };\n /** Class: Strophe.SASLMechanism\n *\n * encapsulates SASL authentication mechanisms.\n *\n * User code may override the priority for each mechanism or disable it completely.\n * See for information about changing priority and for informatian on\n * how to disable a mechanism.\n *\n * By default, all mechanisms are enabled and the priorities are\n *\n * OAUTHBEARER - 60\n * SCRAM-SHA1 - 50\n * DIGEST-MD5 - 40\n * PLAIN - 30\n * ANONYMOUS - 20\n * EXTERNAL - 10\n *\n * See: Strophe.Connection.addSupportedSASLMechanisms\n */\n\n /**\n * PrivateConstructor: Strophe.SASLMechanism\n * SASL auth mechanism abstraction.\n *\n * Parameters:\n * (String) name - SASL Mechanism name.\n * (Boolean) isClientFirst - If client should send response first without challenge.\n * (Number) priority - Priority.\n *\n * Returns:\n * A new Strophe.SASLMechanism object.\n */\n\n Strophe.SASLMechanism = function (name, isClientFirst, priority) {\n /** PrivateVariable: name\n * Mechanism name.\n */\n this.name = name;\n /** PrivateVariable: isClientFirst\n * If client sends response without initial server challenge.\n */\n\n this.isClientFirst = isClientFirst;\n /** Variable: priority\n * Determines which is chosen for authentication (Higher is better).\n * Users may override this to prioritize mechanisms differently.\n *\n * In the default configuration the priorities are\n *\n * SCRAM-SHA1 - 40\n * DIGEST-MD5 - 30\n * Plain - 20\n *\n * Example: (This will cause Strophe to choose the mechanism that the server sent first)\n *\n * > Strophe.SASLMD5.priority = Strophe.SASLSHA1.priority;\n *\n * See for a list of available mechanisms.\n *\n */\n\n this.priority = priority;\n };\n\n Strophe.SASLMechanism.prototype = {\n /**\n * Function: test\n * Checks if mechanism able to run.\n * To disable a mechanism, make this return false;\n *\n * To disable plain authentication run\n * > Strophe.SASLPlain.test = function() {\n * > return false;\n * > }\n *\n * See for a list of available mechanisms.\n *\n * Parameters:\n * (Strophe.Connection) connection - Target Connection.\n *\n * Returns:\n * (Boolean) If mechanism was able to run.\n */\n test: function test(connection) {\n return true;\n },\n\n /** PrivateFunction: onStart\n * Called before starting mechanism on some connection.\n *\n * Parameters:\n * (Strophe.Connection) connection - Target Connection.\n */\n onStart: function onStart(connection) {\n this._connection = connection;\n },\n\n /** PrivateFunction: onChallenge\n * Called by protocol implementation on incoming challenge. If client is\n * first (isClientFirst === true) challenge will be null on the first call.\n *\n * Parameters:\n * (Strophe.Connection) connection - Target Connection.\n * (String) challenge - current challenge to handle.\n *\n * Returns:\n * (String) Mechanism response.\n */\n onChallenge: function onChallenge(connection, challenge) {\n throw new Error(\"You should implement challenge handling!\");\n },\n\n /** PrivateFunction: onFailure\n * Protocol informs mechanism implementation about SASL failure.\n */\n onFailure: function onFailure() {\n this._connection = null;\n },\n\n /** PrivateFunction: onSuccess\n * Protocol informs mechanism implementation about SASL success.\n */\n onSuccess: function onSuccess() {\n this._connection = null;\n }\n };\n /** Constants: SASL mechanisms\n * Available authentication mechanisms\n *\n * Strophe.SASLAnonymous - SASL ANONYMOUS authentication.\n * Strophe.SASLPlain - SASL PLAIN authentication.\n * Strophe.SASLMD5 - SASL DIGEST-MD5 authentication\n * Strophe.SASLSHA1 - SASL SCRAM-SHA1 authentication\n * Strophe.SASLOAuthBearer - SASL OAuth Bearer authentication\n * Strophe.SASLExternal - SASL EXTERNAL authentication\n * Strophe.SASLXOAuth2 - SASL X-OAuth2 authentication\n */\n // Building SASL callbacks\n\n /** PrivateConstructor: SASLAnonymous\n * SASL ANONYMOUS authentication.\n */\n\n Strophe.SASLAnonymous = function () {};\n\n Strophe.SASLAnonymous.prototype = new Strophe.SASLMechanism(\"ANONYMOUS\", false, 20);\n\n Strophe.SASLAnonymous.prototype.test = function (connection) {\n return connection.authcid === null;\n };\n /** PrivateConstructor: SASLPlain\n * SASL PLAIN authentication.\n */\n\n\n Strophe.SASLPlain = function () {};\n\n Strophe.SASLPlain.prototype = new Strophe.SASLMechanism(\"PLAIN\", true, 50);\n\n Strophe.SASLPlain.prototype.test = function (connection) {\n return connection.authcid !== null;\n };\n\n Strophe.SASLPlain.prototype.onChallenge = function (connection) {\n var auth_str = connection.authzid;\n auth_str = auth_str + \"\\0\";\n auth_str = auth_str + connection.authcid;\n auth_str = auth_str + \"\\0\";\n auth_str = auth_str + connection.pass;\n return utils.utf16to8(auth_str);\n };\n /** PrivateConstructor: SASLSHA1\n * SASL SCRAM SHA 1 authentication.\n */\n\n\n Strophe.SASLSHA1 = function () {};\n\n Strophe.SASLSHA1.prototype = new Strophe.SASLMechanism(\"SCRAM-SHA-1\", true, 70);\n\n Strophe.SASLSHA1.prototype.test = function (connection) {\n return connection.authcid !== null;\n };\n\n Strophe.SASLSHA1.prototype.onChallenge = function (connection, challenge, test_cnonce) {\n var cnonce = test_cnonce || MD5.hexdigest(\"\" + Math.random() * 1234567890);\n var auth_str = \"n=\" + utils.utf16to8(connection.authcid);\n auth_str += \",r=\";\n auth_str += cnonce;\n connection._sasl_data.cnonce = cnonce;\n connection._sasl_data[\"client-first-message-bare\"] = auth_str;\n auth_str = \"n,,\" + auth_str;\n\n this.onChallenge = function (connection, challenge) {\n var nonce, salt, iter, Hi, U, U_old, i, k;\n var responseText = \"c=biws,\";\n var authMessage = \"\".concat(connection._sasl_data[\"client-first-message-bare\"], \",\").concat(challenge, \",\");\n var cnonce = connection._sasl_data.cnonce;\n var attribMatch = /([a-z]+)=([^,]+)(,|$)/;\n\n while (challenge.match(attribMatch)) {\n var matches = challenge.match(attribMatch);\n challenge = challenge.replace(matches[0], \"\");\n\n switch (matches[1]) {\n case \"r\":\n nonce = matches[2];\n break;\n\n case \"s\":\n salt = matches[2];\n break;\n\n case \"i\":\n iter = matches[2];\n break;\n }\n }\n\n if (nonce.substr(0, cnonce.length) !== cnonce) {\n connection._sasl_data = {};\n return connection._sasl_failure_cb();\n }\n\n responseText += \"r=\" + nonce;\n authMessage += responseText;\n salt = atob(salt);\n salt += \"\\x00\\x00\\x00\\x01\";\n var pass = utils.utf16to8(connection.pass);\n Hi = U_old = SHA1.core_hmac_sha1(pass, salt);\n\n for (i = 1; i < iter; i++) {\n U = SHA1.core_hmac_sha1(pass, SHA1.binb2str(U_old));\n\n for (k = 0; k < 5; k++) {\n Hi[k] ^= U[k];\n }\n\n U_old = U;\n }\n\n Hi = SHA1.binb2str(Hi);\n var clientKey = SHA1.core_hmac_sha1(Hi, \"Client Key\");\n var serverKey = SHA1.str_hmac_sha1(Hi, \"Server Key\");\n var clientSignature = SHA1.core_hmac_sha1(SHA1.str_sha1(SHA1.binb2str(clientKey)), authMessage);\n connection._sasl_data[\"server-signature\"] = SHA1.b64_hmac_sha1(serverKey, authMessage);\n\n for (k = 0; k < 5; k++) {\n clientKey[k] ^= clientSignature[k];\n }\n\n responseText += \",p=\" + btoa(SHA1.binb2str(clientKey));\n return responseText;\n };\n\n return auth_str;\n };\n /** PrivateConstructor: SASLMD5\n * SASL DIGEST MD5 authentication.\n */\n\n\n Strophe.SASLMD5 = function () {};\n\n Strophe.SASLMD5.prototype = new Strophe.SASLMechanism(\"DIGEST-MD5\", false, 60);\n\n Strophe.SASLMD5.prototype.test = function (connection) {\n return connection.authcid !== null;\n };\n /** PrivateFunction: _quote\n * _Private_ utility function to backslash escape and quote strings.\n *\n * Parameters:\n * (String) str - The string to be quoted.\n *\n * Returns:\n * quoted string\n */\n\n\n Strophe.SASLMD5.prototype._quote = function (str) {\n return '\"' + str.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"') + '\"'; //\" end string workaround for emacs\n };\n\n Strophe.SASLMD5.prototype.onChallenge = function (connection, challenge, test_cnonce) {\n var attribMatch = /([a-z]+)=(\"[^\"]+\"|[^,\"]+)(?:,|$)/;\n var cnonce = test_cnonce || MD5.hexdigest(\"\" + Math.random() * 1234567890);\n var realm = \"\";\n var host = null;\n var nonce = \"\";\n var qop = \"\";\n\n while (challenge.match(attribMatch)) {\n var matches = challenge.match(attribMatch);\n challenge = challenge.replace(matches[0], \"\");\n matches[2] = matches[2].replace(/^\"(.+)\"$/, \"$1\");\n\n switch (matches[1]) {\n case \"realm\":\n realm = matches[2];\n break;\n\n case \"nonce\":\n nonce = matches[2];\n break;\n\n case \"qop\":\n qop = matches[2];\n break;\n\n case \"host\":\n host = matches[2];\n break;\n }\n }\n\n var digest_uri = connection.servtype + \"/\" + connection.domain;\n\n if (host !== null) {\n digest_uri = digest_uri + \"/\" + host;\n }\n\n var cred = utils.utf16to8(connection.authcid + \":\" + realm + \":\" + this._connection.pass);\n var A1 = MD5.hash(cred) + \":\" + nonce + \":\" + cnonce;\n var A2 = 'AUTHENTICATE:' + digest_uri;\n var responseText = \"\";\n responseText += 'charset=utf-8,';\n responseText += 'username=' + this._quote(utils.utf16to8(connection.authcid)) + ',';\n responseText += 'realm=' + this._quote(realm) + ',';\n responseText += 'nonce=' + this._quote(nonce) + ',';\n responseText += 'nc=00000001,';\n responseText += 'cnonce=' + this._quote(cnonce) + ',';\n responseText += 'digest-uri=' + this._quote(digest_uri) + ',';\n responseText += 'response=' + MD5.hexdigest(MD5.hexdigest(A1) + \":\" + nonce + \":00000001:\" + cnonce + \":auth:\" + MD5.hexdigest(A2)) + \",\";\n responseText += 'qop=auth';\n\n this.onChallenge = function () {\n return \"\";\n };\n\n return responseText;\n };\n /** PrivateConstructor: SASLOAuthBearer\n * SASL OAuth Bearer authentication.\n */\n\n\n Strophe.SASLOAuthBearer = function () {};\n\n Strophe.SASLOAuthBearer.prototype = new Strophe.SASLMechanism(\"OAUTHBEARER\", true, 40);\n\n Strophe.SASLOAuthBearer.prototype.test = function (connection) {\n return connection.pass !== null;\n };\n\n Strophe.SASLOAuthBearer.prototype.onChallenge = function (connection) {\n var auth_str = 'n,';\n\n if (connection.authcid !== null) {\n auth_str = auth_str + 'a=' + connection.authzid;\n }\n\n auth_str = auth_str + ',';\n auth_str = auth_str + \"\\x01\";\n auth_str = auth_str + 'auth=Bearer ';\n auth_str = auth_str + connection.pass;\n auth_str = auth_str + \"\\x01\";\n auth_str = auth_str + \"\\x01\";\n return utils.utf16to8(auth_str);\n };\n /** PrivateConstructor: SASLExternal\n * SASL EXTERNAL authentication.\n *\n * The EXTERNAL mechanism allows a client to request the server to use\n * credentials established by means external to the mechanism to\n * authenticate the client. The external means may be, for instance,\n * TLS services.\n */\n\n\n Strophe.SASLExternal = function () {};\n\n Strophe.SASLExternal.prototype = new Strophe.SASLMechanism(\"EXTERNAL\", true, 10);\n\n Strophe.SASLExternal.prototype.onChallenge = function (connection) {\n /** According to XEP-178, an authzid SHOULD NOT be presented when the\n * authcid contained or implied in the client certificate is the JID (i.e.\n * authzid) with which the user wants to log in as.\n *\n * To NOT send the authzid, the user should therefore set the authcid equal\n * to the JID when instantiating a new Strophe.Connection object.\n */\n return connection.authcid === connection.authzid ? '' : connection.authzid;\n };\n /** PrivateConstructor: SASLXOAuth2\n * SASL X-OAuth2 authentication.\n */\n\n\n Strophe.SASLXOAuth2 = function () {};\n\n Strophe.SASLXOAuth2.prototype = new Strophe.SASLMechanism(\"X-OAUTH2\", true, 30);\n\n Strophe.SASLXOAuth2.prototype.test = function (connection) {\n return connection.pass !== null;\n };\n\n Strophe.SASLXOAuth2.prototype.onChallenge = function (connection) {\n var auth_str = \"\\0\";\n\n if (connection.authcid !== null) {\n auth_str = auth_str + connection.authzid;\n }\n\n auth_str = auth_str + \"\\0\";\n auth_str = auth_str + connection.pass;\n return utils.utf16to8(auth_str);\n };\n var core = {\n 'Strophe': Strophe,\n '$build': $build,\n '$iq': $iq,\n '$msg': $msg,\n '$pres': $pres,\n 'SHA1': SHA1,\n 'MD5': MD5,\n 'b64_hmac_sha1': SHA1.b64_hmac_sha1,\n 'b64_sha1': SHA1.b64_sha1,\n 'str_hmac_sha1': SHA1.str_hmac_sha1,\n 'str_sha1': SHA1.str_sha1\n };\n\n /*\n This program is distributed under the terms of the MIT license.\n Please see the LICENSE file for details.\n\n Copyright 2006-2008, OGG, LLC\n */\n var Strophe$1 = core.Strophe;\n var $build$1 = core.$build;\n /** PrivateClass: Strophe.Request\n * _Private_ helper class that provides a cross implementation abstraction\n * for a BOSH related XMLHttpRequest.\n *\n * The Strophe.Request class is used internally to encapsulate BOSH request\n * information. It is not meant to be used from user's code.\n */\n\n /** PrivateConstructor: Strophe.Request\n * Create and initialize a new Strophe.Request object.\n *\n * Parameters:\n * (XMLElement) elem - The XML data to be sent in the request.\n * (Function) func - The function that will be called when the\n * XMLHttpRequest readyState changes.\n * (Integer) rid - The BOSH rid attribute associated with this request.\n * (Integer) sends - The number of times this same request has been sent.\n */\n\n Strophe$1.Request = function (elem, func, rid, sends) {\n this.id = ++Strophe$1._requestId;\n this.xmlData = elem;\n this.data = Strophe$1.serialize(elem); // save original function in case we need to make a new request\n // from this one.\n\n this.origFunc = func;\n this.func = func;\n this.rid = rid;\n this.date = NaN;\n this.sends = sends || 0;\n this.abort = false;\n this.dead = null;\n\n this.age = function () {\n if (!this.date) {\n return 0;\n }\n\n var now = new Date();\n return (now - this.date) / 1000;\n };\n\n this.timeDead = function () {\n if (!this.dead) {\n return 0;\n }\n\n var now = new Date();\n return (now - this.dead) / 1000;\n };\n\n this.xhr = this._newXHR();\n };\n\n Strophe$1.Request.prototype = {\n /** PrivateFunction: getResponse\n * Get a response from the underlying XMLHttpRequest.\n *\n * This function attempts to get a response from the request and checks\n * for errors.\n *\n * Throws:\n * \"parsererror\" - A parser error occured.\n * \"bad-format\" - The entity has sent XML that cannot be processed.\n *\n * Returns:\n * The DOM element tree of the response.\n */\n getResponse: function getResponse() {\n var node = null;\n\n if (this.xhr.responseXML && this.xhr.responseXML.documentElement) {\n node = this.xhr.responseXML.documentElement;\n\n if (node.tagName === \"parsererror\") {\n Strophe$1.error(\"invalid response received\");\n Strophe$1.error(\"responseText: \" + this.xhr.responseText);\n Strophe$1.error(\"responseXML: \" + Strophe$1.serialize(this.xhr.responseXML));\n throw new Error(\"parsererror\");\n }\n } else if (this.xhr.responseText) {\n // In React Native, we may get responseText but no responseXML. We can try to parse it manually.\n Strophe$1.debug(\"Got responseText but no responseXML; attempting to parse it with DOMParser...\");\n node = new DOMParser().parseFromString(this.xhr.responseText, 'application/xml').documentElement;\n\n if (!node) {\n throw new Error('Parsing produced null node');\n } else if (node.querySelector('parsererror')) {\n Strophe$1.error(\"invalid response received: \" + node.querySelector('parsererror').textContent);\n Strophe$1.error(\"responseText: \" + this.xhr.responseText);\n var error = new Error();\n error.name = Strophe$1.ErrorCondition.BAD_FORMAT;\n throw error;\n }\n }\n\n return node;\n },\n\n /** PrivateFunction: _newXHR\n * _Private_ helper function to create XMLHttpRequests.\n *\n * This function creates XMLHttpRequests across all implementations.\n *\n * Returns:\n * A new XMLHttpRequest.\n */\n _newXHR: function _newXHR() {\n var xhr = null;\n\n if (window.XMLHttpRequest) {\n xhr = new XMLHttpRequest();\n\n if (xhr.overrideMimeType) {\n xhr.overrideMimeType(\"text/xml; charset=utf-8\");\n }\n } else if (window.ActiveXObject) {\n xhr = new ActiveXObject(\"Microsoft.XMLHTTP\");\n } // use Function.bind() to prepend ourselves as an argument\n\n\n xhr.onreadystatechange = this.func.bind(null, this);\n return xhr;\n }\n };\n /** Class: Strophe.Bosh\n * _Private_ helper class that handles BOSH Connections\n *\n * The Strophe.Bosh class is used internally by Strophe.Connection\n * to encapsulate BOSH sessions. It is not meant to be used from user's code.\n */\n\n /** File: bosh.js\n * A JavaScript library to enable BOSH in Strophejs.\n *\n * this library uses Bidirectional-streams Over Synchronous HTTP (BOSH)\n * to emulate a persistent, stateful, two-way connection to an XMPP server.\n * More information on BOSH can be found in XEP 124.\n */\n\n /** PrivateConstructor: Strophe.Bosh\n * Create and initialize a Strophe.Bosh object.\n *\n * Parameters:\n * (Strophe.Connection) connection - The Strophe.Connection that will use BOSH.\n *\n * Returns:\n * A new Strophe.Bosh object.\n */\n\n Strophe$1.Bosh = function (connection) {\n this._conn = connection;\n /* request id for body tags */\n\n this.rid = Math.floor(Math.random() * 4294967295);\n /* The current session ID. */\n\n this.sid = null; // default BOSH values\n\n this.hold = 1;\n this.wait = 60;\n this.window = 5;\n this.errors = 0;\n this.inactivity = null;\n this.lastResponseHeaders = null;\n this._requests = [];\n };\n\n Strophe$1.Bosh.prototype = {\n /** Variable: strip\n *\n * BOSH-Connections will have all stanzas wrapped in a tag when\n * passed to or .\n * To strip this tag, User code can set to \"body\":\n *\n * > Strophe.Bosh.prototype.strip = \"body\";\n *\n * This will enable stripping of the body tag in both\n * and .\n */\n strip: null,\n\n /** PrivateFunction: _buildBody\n * _Private_ helper function to generate the wrapper for BOSH.\n *\n * Returns:\n * A Strophe.Builder with a element.\n */\n _buildBody: function _buildBody() {\n var bodyWrap = $build$1('body', {\n 'rid': this.rid++,\n 'xmlns': Strophe$1.NS.HTTPBIND\n });\n\n if (this.sid !== null) {\n bodyWrap.attrs({\n 'sid': this.sid\n });\n }\n\n if (this._conn.options.keepalive && this._conn._sessionCachingSupported()) {\n this._cacheSession();\n }\n\n return bodyWrap;\n },\n\n /** PrivateFunction: _reset\n * Reset the connection.\n *\n * This function is called by the reset function of the Strophe Connection\n */\n _reset: function _reset() {\n this.rid = Math.floor(Math.random() * 4294967295);\n this.sid = null;\n this.errors = 0;\n\n if (this._conn._sessionCachingSupported()) {\n window.sessionStorage.removeItem('strophe-bosh-session');\n }\n\n this._conn.nextValidRid(this.rid);\n },\n\n /** PrivateFunction: _connect\n * _Private_ function that initializes the BOSH connection.\n *\n * Creates and sends the Request that initializes the BOSH connection.\n */\n _connect: function _connect(wait, hold, route) {\n this.wait = wait || this.wait;\n this.hold = hold || this.hold;\n this.errors = 0;\n\n var body = this._buildBody().attrs({\n \"to\": this._conn.domain,\n \"xml:lang\": \"en\",\n \"wait\": this.wait,\n \"hold\": this.hold,\n \"content\": \"text/xml; charset=utf-8\",\n \"ver\": \"1.6\",\n \"xmpp:version\": \"1.0\",\n \"xmlns:xmpp\": Strophe$1.NS.BOSH\n });\n\n if (route) {\n body.attrs({\n 'route': route\n });\n }\n\n var _connect_cb = this._conn._connect_cb;\n\n this._requests.push(new Strophe$1.Request(body.tree(), this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)), body.tree().getAttribute(\"rid\")));\n\n this._throttledRequestHandler();\n },\n\n /** PrivateFunction: _attach\n * Attach to an already created and authenticated BOSH session.\n *\n * This function is provided to allow Strophe to attach to BOSH\n * sessions which have been created externally, perhaps by a Web\n * application. This is often used to support auto-login type features\n * without putting user credentials into the page.\n *\n * Parameters:\n * (String) jid - The full JID that is bound by the session.\n * (String) sid - The SID of the BOSH session.\n * (String) rid - The current RID of the BOSH session. This RID\n * will be used by the next request.\n * (Function) callback The connect callback function.\n * (Integer) wait - The optional HTTPBIND wait value. This is the\n * time the server will wait before returning an empty result for\n * a request. The default setting of 60 seconds is recommended.\n * Other settings will require tweaks to the Strophe.TIMEOUT value.\n * (Integer) hold - The optional HTTPBIND hold value. This is the\n * number of connections the server will hold at one time. This\n * should almost always be set to 1 (the default).\n * (Integer) wind - The optional HTTBIND window value. This is the\n * allowed range of request ids that are valid. The default is 5.\n */\n _attach: function _attach(jid, sid, rid, callback, wait, hold, wind) {\n this._conn.jid = jid;\n this.sid = sid;\n this.rid = rid;\n this._conn.connect_callback = callback;\n this._conn.domain = Strophe$1.getDomainFromJid(this._conn.jid);\n this._conn.authenticated = true;\n this._conn.connected = true;\n this.wait = wait || this.wait;\n this.hold = hold || this.hold;\n this.window = wind || this.window;\n\n this._conn._changeConnectStatus(Strophe$1.Status.ATTACHED, null);\n },\n\n /** PrivateFunction: _restore\n * Attempt to restore a cached BOSH session\n *\n * Parameters:\n * (String) jid - The full JID that is bound by the session.\n * This parameter is optional but recommended, specifically in cases\n * where prebinded BOSH sessions are used where it's important to know\n * that the right session is being restored.\n * (Function) callback The connect callback function.\n * (Integer) wait - The optional HTTPBIND wait value. This is the\n * time the server will wait before returning an empty result for\n * a request. The default setting of 60 seconds is recommended.\n * Other settings will require tweaks to the Strophe.TIMEOUT value.\n * (Integer) hold - The optional HTTPBIND hold value. This is the\n * number of connections the server will hold at one time. This\n * should almost always be set to 1 (the default).\n * (Integer) wind - The optional HTTBIND window value. This is the\n * allowed range of request ids that are valid. The default is 5.\n */\n _restore: function _restore(jid, callback, wait, hold, wind) {\n var session = JSON.parse(window.sessionStorage.getItem('strophe-bosh-session'));\n\n if (typeof session !== \"undefined\" && session !== null && session.rid && session.sid && session.jid && (typeof jid === \"undefined\" || jid === null || Strophe$1.getBareJidFromJid(session.jid) === Strophe$1.getBareJidFromJid(jid) || // If authcid is null, then it's an anonymous login, so\n // we compare only the domains:\n Strophe$1.getNodeFromJid(jid) === null && Strophe$1.getDomainFromJid(session.jid) === jid)) {\n this._conn.restored = true;\n\n this._attach(session.jid, session.sid, session.rid, callback, wait, hold, wind);\n } else {\n var error = new Error(\"_restore: no restoreable session.\");\n error.name = \"StropheSessionError\";\n throw error;\n }\n },\n\n /** PrivateFunction: _cacheSession\n * _Private_ handler for the beforeunload event.\n *\n * This handler is used to process the Bosh-part of the initial request.\n * Parameters:\n * (Strophe.Request) bodyWrap - The received stanza.\n */\n _cacheSession: function _cacheSession() {\n if (this._conn.authenticated) {\n if (this._conn.jid && this.rid && this.sid) {\n window.sessionStorage.setItem('strophe-bosh-session', JSON.stringify({\n 'jid': this._conn.jid,\n 'rid': this.rid,\n 'sid': this.sid\n }));\n }\n } else {\n window.sessionStorage.removeItem('strophe-bosh-session');\n }\n },\n\n /** PrivateFunction: _connect_cb\n * _Private_ handler for initial connection request.\n *\n * This handler is used to process the Bosh-part of the initial request.\n * Parameters:\n * (Strophe.Request) bodyWrap - The received stanza.\n */\n _connect_cb: function _connect_cb(bodyWrap) {\n var typ = bodyWrap.getAttribute(\"type\");\n\n if (typ !== null && typ === \"terminate\") {\n // an error occurred\n var cond = bodyWrap.getAttribute(\"condition\");\n Strophe$1.error(\"BOSH-Connection failed: \" + cond);\n var conflict = bodyWrap.getElementsByTagName(\"conflict\");\n\n if (cond !== null) {\n if (cond === \"remote-stream-error\" && conflict.length > 0) {\n cond = \"conflict\";\n }\n\n this._conn._changeConnectStatus(Strophe$1.Status.CONNFAIL, cond);\n } else {\n this._conn._changeConnectStatus(Strophe$1.Status.CONNFAIL, \"unknown\");\n }\n\n this._conn._doDisconnect(cond);\n\n return Strophe$1.Status.CONNFAIL;\n } // check to make sure we don't overwrite these if _connect_cb is\n // called multiple times in the case of missing stream:features\n\n\n if (!this.sid) {\n this.sid = bodyWrap.getAttribute(\"sid\");\n }\n\n var wind = bodyWrap.getAttribute('requests');\n\n if (wind) {\n this.window = parseInt(wind, 10);\n }\n\n var hold = bodyWrap.getAttribute('hold');\n\n if (hold) {\n this.hold = parseInt(hold, 10);\n }\n\n var wait = bodyWrap.getAttribute('wait');\n\n if (wait) {\n this.wait = parseInt(wait, 10);\n }\n\n var inactivity = bodyWrap.getAttribute('inactivity');\n\n if (inactivity) {\n this.inactivity = parseInt(inactivity, 10);\n }\n },\n\n /** PrivateFunction: _disconnect\n * _Private_ part of Connection.disconnect for Bosh\n *\n * Parameters:\n * (Request) pres - This stanza will be sent before disconnecting.\n */\n _disconnect: function _disconnect(pres) {\n this._sendTerminate(pres);\n },\n\n /** PrivateFunction: _doDisconnect\n * _Private_ function to disconnect.\n *\n * Resets the SID and RID.\n */\n _doDisconnect: function _doDisconnect() {\n this.sid = null;\n this.rid = Math.floor(Math.random() * 4294967295);\n\n if (this._conn._sessionCachingSupported()) {\n window.sessionStorage.removeItem('strophe-bosh-session');\n }\n\n this._conn.nextValidRid(this.rid);\n },\n\n /** PrivateFunction: _emptyQueue\n * _Private_ function to check if the Request queue is empty.\n *\n * Returns:\n * True, if there are no Requests queued, False otherwise.\n */\n _emptyQueue: function _emptyQueue() {\n return this._requests.length === 0;\n },\n\n /** PrivateFunction: _callProtocolErrorHandlers\n * _Private_ function to call error handlers registered for HTTP errors.\n *\n * Parameters:\n * (Strophe.Request) req - The request that is changing readyState.\n */\n _callProtocolErrorHandlers: function _callProtocolErrorHandlers(req) {\n var reqStatus = this._getRequestStatus(req);\n\n var err_callback = this._conn.protocolErrorHandlers.HTTP[reqStatus];\n\n if (err_callback) {\n err_callback.call(this, reqStatus);\n }\n },\n\n /** PrivateFunction: _hitError\n * _Private_ function to handle the error count.\n *\n * Requests are resent automatically until their error count reaches\n * 5. Each time an error is encountered, this function is called to\n * increment the count and disconnect if the count is too high.\n *\n * Parameters:\n * (Integer) reqStatus - The request status.\n */\n _hitError: function _hitError(reqStatus) {\n this.errors++;\n Strophe$1.warn(\"request errored, status: \" + reqStatus + \", number of errors: \" + this.errors);\n\n if (this.errors > 4) {\n this._conn._onDisconnectTimeout();\n }\n },\n\n /** PrivateFunction: _no_auth_received\n *\n * Called on stream start/restart when no stream:features\n * has been received and sends a blank poll request.\n */\n _no_auth_received: function _no_auth_received(callback) {\n Strophe$1.warn(\"Server did not yet offer a supported authentication \" + \"mechanism. Sending a blank poll request.\");\n\n if (callback) {\n callback = callback.bind(this._conn);\n } else {\n callback = this._conn._connect_cb.bind(this._conn);\n }\n\n var body = this._buildBody();\n\n this._requests.push(new Strophe$1.Request(body.tree(), this._onRequestStateChange.bind(this, callback), body.tree().getAttribute(\"rid\")));\n\n this._throttledRequestHandler();\n },\n\n /** PrivateFunction: _onDisconnectTimeout\n * _Private_ timeout handler for handling non-graceful disconnection.\n *\n * Cancels all remaining Requests and clears the queue.\n */\n _onDisconnectTimeout: function _onDisconnectTimeout() {\n this._abortAllRequests();\n },\n\n /** PrivateFunction: _abortAllRequests\n * _Private_ helper function that makes sure all pending requests are aborted.\n */\n _abortAllRequests: function _abortAllRequests() {\n while (this._requests.length > 0) {\n var req = this._requests.pop();\n\n req.abort = true;\n req.xhr.abort();\n\n req.xhr.onreadystatechange = function () {};\n }\n },\n\n /** PrivateFunction: _onIdle\n * _Private_ handler called by Strophe.Connection._onIdle\n *\n * Sends all queued Requests or polls with empty Request if there are none.\n */\n _onIdle: function _onIdle() {\n var data = this._conn._data; // if no requests are in progress, poll\n\n if (this._conn.authenticated && this._requests.length === 0 && data.length === 0 && !this._conn.disconnecting) {\n Strophe$1.debug(\"no requests during idle cycle, sending blank request\");\n data.push(null);\n }\n\n if (this._conn.paused) {\n return;\n }\n\n if (this._requests.length < 2 && data.length > 0) {\n var body = this._buildBody();\n\n for (var i = 0; i < data.length; i++) {\n if (data[i] !== null) {\n if (data[i] === \"restart\") {\n body.attrs({\n \"to\": this._conn.domain,\n \"xml:lang\": \"en\",\n \"xmpp:restart\": \"true\",\n \"xmlns:xmpp\": Strophe$1.NS.BOSH\n });\n } else {\n body.cnode(data[i]).up();\n }\n }\n }\n\n delete this._conn._data;\n this._conn._data = [];\n\n this._requests.push(new Strophe$1.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute(\"rid\")));\n\n this._throttledRequestHandler();\n }\n\n if (this._requests.length > 0) {\n var time_elapsed = this._requests[0].age();\n\n if (this._requests[0].dead !== null) {\n if (this._requests[0].timeDead() > Math.floor(Strophe$1.SECONDARY_TIMEOUT * this.wait)) {\n this._throttledRequestHandler();\n }\n }\n\n if (time_elapsed > Math.floor(Strophe$1.TIMEOUT * this.wait)) {\n Strophe$1.warn(\"Request \" + this._requests[0].id + \" timed out, over \" + Math.floor(Strophe$1.TIMEOUT * this.wait) + \" seconds since last activity\");\n\n this._throttledRequestHandler();\n }\n }\n },\n\n /** PrivateFunction: _getRequestStatus\n *\n * Returns the HTTP status code from a Strophe.Request\n *\n * Parameters:\n * (Strophe.Request) req - The Strophe.Request instance.\n * (Integer) def - The default value that should be returned if no\n * status value was found.\n */\n _getRequestStatus: function _getRequestStatus(req, def) {\n var reqStatus;\n\n if (req.xhr.readyState === 4) {\n try {\n reqStatus = req.xhr.status;\n } catch (e) {\n // ignore errors from undefined status attribute. Works\n // around a browser bug\n Strophe$1.error(\"Caught an error while retrieving a request's status, \" + \"reqStatus: \" + reqStatus);\n }\n }\n\n if (typeof reqStatus === \"undefined\") {\n reqStatus = typeof def === 'number' ? def : 0;\n }\n\n return reqStatus;\n },\n\n /** PrivateFunction: _onRequestStateChange\n * _Private_ handler for Strophe.Request state changes.\n *\n * This function is called when the XMLHttpRequest readyState changes.\n * It contains a lot of error handling logic for the many ways that\n * requests can fail, and calls the request callback when requests\n * succeed.\n *\n * Parameters:\n * (Function) func - The handler for the request.\n * (Strophe.Request) req - The request that is changing readyState.\n */\n _onRequestStateChange: function _onRequestStateChange(func, req) {\n Strophe$1.debug(\"request id \" + req.id + \".\" + req.sends + \" state changed to \" + req.xhr.readyState);\n\n if (req.abort) {\n req.abort = false;\n return;\n }\n\n if (req.xhr.readyState !== 4) {\n // The request is not yet complete\n return;\n }\n\n var reqStatus = this._getRequestStatus(req);\n\n this.lastResponseHeaders = req.xhr.getAllResponseHeaders();\n\n if (this.disconnecting && reqStatus >= 400) {\n this._hitError(reqStatus);\n\n this._callProtocolErrorHandlers(req);\n\n return;\n }\n\n var valid_request = reqStatus > 0 && reqStatus < 500;\n var too_many_retries = req.sends > this._conn.maxRetries;\n\n if (valid_request || too_many_retries) {\n // remove from internal queue\n this._removeRequest(req);\n\n Strophe$1.debug(\"request id \" + req.id + \" should now be removed\");\n }\n\n if (reqStatus === 200) {\n // request succeeded\n var reqIs0 = this._requests[0] === req;\n var reqIs1 = this._requests[1] === req; // if request 1 finished, or request 0 finished and request\n // 1 is over Strophe.SECONDARY_TIMEOUT seconds old, we need to\n // restart the other - both will be in the first spot, as the\n // completed request has been removed from the queue already\n\n if (reqIs1 || reqIs0 && this._requests.length > 0 && this._requests[0].age() > Math.floor(Strophe$1.SECONDARY_TIMEOUT * this.wait)) {\n this._restartRequest(0);\n }\n\n this._conn.nextValidRid(Number(req.rid) + 1);\n\n Strophe$1.debug(\"request id \" + req.id + \".\" + req.sends + \" got 200\");\n func(req); // call handler\n\n this.errors = 0;\n } else if (reqStatus === 0 || reqStatus >= 400 && reqStatus < 600 || reqStatus >= 12000) {\n // request failed\n Strophe$1.error(\"request id \" + req.id + \".\" + req.sends + \" error \" + reqStatus + \" happened\");\n\n this._hitError(reqStatus);\n\n this._callProtocolErrorHandlers(req);\n\n if (reqStatus >= 400 && reqStatus < 500) {\n this._conn._changeConnectStatus(Strophe$1.Status.DISCONNECTING, null);\n\n this._conn._doDisconnect();\n }\n } else {\n Strophe$1.error(\"request id \" + req.id + \".\" + req.sends + \" error \" + reqStatus + \" happened\");\n }\n\n if (!valid_request && !too_many_retries) {\n this._throttledRequestHandler();\n } else if (too_many_retries && !this._conn.connected) {\n this._conn._changeConnectStatus(Strophe$1.Status.CONNFAIL, \"giving-up\");\n }\n },\n\n /** PrivateFunction: _processRequest\n * _Private_ function to process a request in the queue.\n *\n * This function takes requests off the queue and sends them and\n * restarts dead requests.\n *\n * Parameters:\n * (Integer) i - The index of the request in the queue.\n */\n _processRequest: function _processRequest(i) {\n var _this = this;\n\n var req = this._requests[i];\n\n var reqStatus = this._getRequestStatus(req, -1); // make sure we limit the number of retries\n\n\n if (req.sends > this._conn.maxRetries) {\n this._conn._onDisconnectTimeout();\n\n return;\n }\n\n var time_elapsed = req.age();\n var primary_timeout = !isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe$1.TIMEOUT * this.wait);\n var secondary_timeout = req.dead !== null && req.timeDead() > Math.floor(Strophe$1.SECONDARY_TIMEOUT * this.wait);\n var server_error = req.xhr.readyState === 4 && (reqStatus < 1 || reqStatus >= 500);\n\n if (primary_timeout || secondary_timeout || server_error) {\n if (secondary_timeout) {\n Strophe$1.error(\"Request \".concat(this._requests[i].id, \" timed out (secondary), restarting\"));\n }\n\n req.abort = true;\n req.xhr.abort(); // setting to null fails on IE6, so set to empty function\n\n req.xhr.onreadystatechange = function () {};\n\n this._requests[i] = new Strophe$1.Request(req.xmlData, req.origFunc, req.rid, req.sends);\n req = this._requests[i];\n }\n\n if (req.xhr.readyState === 0) {\n Strophe$1.debug(\"request id \" + req.id + \".\" + req.sends + \" posting\");\n\n try {\n var content_type = this._conn.options.contentType || \"text/xml; charset=utf-8\";\n req.xhr.open(\"POST\", this._conn.service, this._conn.options.sync ? false : true);\n\n if (typeof req.xhr.setRequestHeader !== 'undefined') {\n // IE9 doesn't have setRequestHeader\n req.xhr.setRequestHeader(\"Content-Type\", content_type);\n }\n\n if (this._conn.options.withCredentials) {\n req.xhr.withCredentials = true;\n }\n } catch (e2) {\n Strophe$1.error(\"XHR open failed: \" + e2.toString());\n\n if (!this._conn.connected) {\n this._conn._changeConnectStatus(Strophe$1.Status.CONNFAIL, \"bad-service\");\n }\n\n this._conn.disconnect();\n\n return;\n } // Fires the XHR request -- may be invoked immediately\n // or on a gradually expanding retry window for reconnects\n\n\n var sendFunc = function sendFunc() {\n req.date = new Date();\n\n if (_this._conn.options.customHeaders) {\n var headers = _this._conn.options.customHeaders;\n\n for (var header in headers) {\n if (Object.prototype.hasOwnProperty.call(headers, header)) {\n req.xhr.setRequestHeader(header, headers[header]);\n }\n }\n }\n\n req.xhr.send(req.data);\n }; // Implement progressive backoff for reconnects --\n // First retry (send === 1) should also be instantaneous\n\n\n if (req.sends > 1) {\n // Using a cube of the retry number creates a nicely\n // expanding retry window\n var backoff = Math.min(Math.floor(Strophe$1.TIMEOUT * this.wait), Math.pow(req.sends, 3)) * 1000;\n setTimeout(function () {\n // XXX: setTimeout should be called only with function expressions (23974bc1)\n sendFunc();\n }, backoff);\n } else {\n sendFunc();\n }\n\n req.sends++;\n\n if (this._conn.xmlOutput !== Strophe$1.Connection.prototype.xmlOutput) {\n if (req.xmlData.nodeName === this.strip && req.xmlData.childNodes.length) {\n this._conn.xmlOutput(req.xmlData.childNodes[0]);\n } else {\n this._conn.xmlOutput(req.xmlData);\n }\n }\n\n if (this._conn.rawOutput !== Strophe$1.Connection.prototype.rawOutput) {\n this._conn.rawOutput(req.data);\n }\n } else {\n Strophe$1.debug(\"_processRequest: \" + (i === 0 ? \"first\" : \"second\") + \" request has readyState of \" + req.xhr.readyState);\n }\n },\n\n /** PrivateFunction: _removeRequest\n * _Private_ function to remove a request from the queue.\n *\n * Parameters:\n * (Strophe.Request) req - The request to remove.\n */\n _removeRequest: function _removeRequest(req) {\n Strophe$1.debug(\"removing request\");\n\n for (var i = this._requests.length - 1; i >= 0; i--) {\n if (req === this._requests[i]) {\n this._requests.splice(i, 1);\n }\n } // IE6 fails on setting to null, so set to empty function\n\n\n req.xhr.onreadystatechange = function () {};\n\n this._throttledRequestHandler();\n },\n\n /** PrivateFunction: _restartRequest\n * _Private_ function to restart a request that is presumed dead.\n *\n * Parameters:\n * (Integer) i - The index of the request in the queue.\n */\n _restartRequest: function _restartRequest(i) {\n var req = this._requests[i];\n\n if (req.dead === null) {\n req.dead = new Date();\n }\n\n this._processRequest(i);\n },\n\n /** PrivateFunction: _reqToData\n * _Private_ function to get a stanza out of a request.\n *\n * Tries to extract a stanza out of a Request Object.\n * When this fails the current connection will be disconnected.\n *\n * Parameters:\n * (Object) req - The Request.\n *\n * Returns:\n * The stanza that was passed.\n */\n _reqToData: function _reqToData(req) {\n try {\n return req.getResponse();\n } catch (e) {\n if (e.message !== \"parsererror\") {\n throw e;\n }\n\n this._conn.disconnect(\"strophe-parsererror\");\n }\n },\n\n /** PrivateFunction: _sendTerminate\n * _Private_ function to send initial disconnect sequence.\n *\n * This is the first step in a graceful disconnect. It sends\n * the BOSH server a terminate body and includes an unavailable\n * presence if authentication has completed.\n */\n _sendTerminate: function _sendTerminate(pres) {\n Strophe$1.debug(\"_sendTerminate was called\");\n\n var body = this._buildBody().attrs({\n type: \"terminate\"\n });\n\n if (pres) {\n body.cnode(pres.tree());\n }\n\n var req = new Strophe$1.Request(body.tree(), this._onRequestStateChange.bind(this, this._conn._dataRecv.bind(this._conn)), body.tree().getAttribute(\"rid\"));\n\n this._requests.push(req);\n\n this._throttledRequestHandler();\n },\n\n /** PrivateFunction: _send\n * _Private_ part of the Connection.send function for BOSH\n *\n * Just triggers the RequestHandler to send the messages that are in the queue\n */\n _send: function _send() {\n var _this2 = this;\n\n clearTimeout(this._conn._idleTimeout);\n\n this._throttledRequestHandler();\n\n this._conn._idleTimeout = setTimeout(function () {\n return _this2._conn._onIdle();\n }, 100);\n },\n\n /** PrivateFunction: _sendRestart\n *\n * Send an xmpp:restart stanza.\n */\n _sendRestart: function _sendRestart() {\n this._throttledRequestHandler();\n\n clearTimeout(this._conn._idleTimeout);\n },\n\n /** PrivateFunction: _throttledRequestHandler\n * _Private_ function to throttle requests to the connection window.\n *\n * This function makes sure we don't send requests so fast that the\n * request ids overflow the connection window in the case that one\n * request died.\n */\n _throttledRequestHandler: function _throttledRequestHandler() {\n if (!this._requests) {\n Strophe$1.debug(\"_throttledRequestHandler called with \" + \"undefined requests\");\n } else {\n Strophe$1.debug(\"_throttledRequestHandler called with \" + this._requests.length + \" requests\");\n }\n\n if (!this._requests || this._requests.length === 0) {\n return;\n }\n\n if (this._requests.length > 0) {\n this._processRequest(0);\n }\n\n if (this._requests.length > 1 && Math.abs(this._requests[0].rid - this._requests[1].rid) < this.window) {\n this._processRequest(1);\n }\n }\n };\n\n /*\n This program is distributed under the terms of the MIT license.\n Please see the LICENSE file for details.\n\n Copyright 2006-2008, OGG, LLC\n */\n var Strophe$2 = core.Strophe;\n var $build$2 = core.$build;\n /** Class: Strophe.WebSocket\n * _Private_ helper class that handles WebSocket Connections\n *\n * The Strophe.WebSocket class is used internally by Strophe.Connection\n * to encapsulate WebSocket sessions. It is not meant to be used from user's code.\n */\n\n /** File: websocket.js\n * A JavaScript library to enable XMPP over Websocket in Strophejs.\n *\n * This file implements XMPP over WebSockets for Strophejs.\n * If a Connection is established with a Websocket url (ws://...)\n * Strophe will use WebSockets.\n * For more information on XMPP-over-WebSocket see RFC 7395:\n * http://tools.ietf.org/html/rfc7395\n *\n * WebSocket support implemented by Andreas Guth (andreas.guth@rwth-aachen.de)\n */\n\n /** PrivateConstructor: Strophe.Websocket\n * Create and initialize a Strophe.WebSocket object.\n * Currently only sets the connection Object.\n *\n * Parameters:\n * (Strophe.Connection) connection - The Strophe.Connection that will use WebSockets.\n *\n * Returns:\n * A new Strophe.WebSocket object.\n */\n\n Strophe$2.Websocket = function (connection) {\n this._conn = connection;\n this.strip = \"wrapper\";\n var service = connection.service;\n\n if (service.indexOf(\"ws:\") !== 0 && service.indexOf(\"wss:\") !== 0) {\n // If the service is not an absolute URL, assume it is a path and put the absolute\n // URL together from options, current URL and the path.\n var new_service = \"\";\n\n if (connection.options.protocol === \"ws\" && window.location.protocol !== \"https:\") {\n new_service += \"ws\";\n } else {\n new_service += \"wss\";\n }\n\n new_service += \"://\" + window.location.host;\n\n if (service.indexOf(\"/\") !== 0) {\n new_service += window.location.pathname + service;\n } else {\n new_service += service;\n }\n\n connection.service = new_service;\n }\n };\n\n Strophe$2.Websocket.prototype = {\n /** PrivateFunction: _buildStream\n * _Private_ helper function to generate the start tag for WebSockets\n *\n * Returns:\n * A Strophe.Builder with a element.\n */\n _buildStream: function _buildStream() {\n return $build$2(\"open\", {\n \"xmlns\": Strophe$2.NS.FRAMING,\n \"to\": this._conn.domain,\n \"version\": '1.0'\n });\n },\n\n /** PrivateFunction: _check_streamerror\n * _Private_ checks a message for stream:error\n *\n * Parameters:\n * (Strophe.Request) bodyWrap - The received stanza.\n * connectstatus - The ConnectStatus that will be set on error.\n * Returns:\n * true if there was a streamerror, false otherwise.\n */\n _check_streamerror: function _check_streamerror(bodyWrap, connectstatus) {\n var errors;\n\n if (bodyWrap.getElementsByTagNameNS) {\n errors = bodyWrap.getElementsByTagNameNS(Strophe$2.NS.STREAM, \"error\");\n } else {\n errors = bodyWrap.getElementsByTagName(\"stream:error\");\n }\n\n if (errors.length === 0) {\n return false;\n }\n\n var error = errors[0];\n var condition = \"\";\n var text = \"\";\n var ns = \"urn:ietf:params:xml:ns:xmpp-streams\";\n\n for (var i = 0; i < error.childNodes.length; i++) {\n var e = error.childNodes[i];\n\n if (e.getAttribute(\"xmlns\") !== ns) {\n break;\n }\n\n if (e.nodeName === \"text\") {\n text = e.textContent;\n } else {\n condition = e.nodeName;\n }\n }\n\n var errorString = \"WebSocket stream error: \";\n\n if (condition) {\n errorString += condition;\n } else {\n errorString += \"unknown\";\n }\n\n if (text) {\n errorString += \" - \" + text;\n }\n\n Strophe$2.error(errorString); // close the connection on stream_error\n\n this._conn._changeConnectStatus(connectstatus, condition);\n\n this._conn._doDisconnect();\n\n return true;\n },\n\n /** PrivateFunction: _reset\n * Reset the connection.\n *\n * This function is called by the reset function of the Strophe Connection.\n * Is not needed by WebSockets.\n */\n _reset: function _reset() {\n return;\n },\n\n /** PrivateFunction: _connect\n * _Private_ function called by Strophe.Connection.connect\n *\n * Creates a WebSocket for a connection and assigns Callbacks to it.\n * Does nothing if there already is a WebSocket.\n */\n _connect: function _connect() {\n // Ensure that there is no open WebSocket from a previous Connection.\n this._closeSocket(); // Create the new WobSocket\n\n\n this.socket = new WebSocket(this._conn.service, \"xmpp\");\n this.socket.onopen = this._onOpen.bind(this);\n this.socket.onerror = this._onError.bind(this);\n this.socket.onclose = this._onClose.bind(this);\n this.socket.onmessage = this._connect_cb_wrapper.bind(this);\n },\n\n /** PrivateFunction: _connect_cb\n * _Private_ function called by Strophe.Connection._connect_cb\n *\n * checks for stream:error\n *\n * Parameters:\n * (Strophe.Request) bodyWrap - The received stanza.\n */\n _connect_cb: function _connect_cb(bodyWrap) {\n var error = this._check_streamerror(bodyWrap, Strophe$2.Status.CONNFAIL);\n\n if (error) {\n return Strophe$2.Status.CONNFAIL;\n }\n },\n\n /** PrivateFunction: _handleStreamStart\n * _Private_ function that checks the opening tag for errors.\n *\n * Disconnects if there is an error and returns false, true otherwise.\n *\n * Parameters:\n * (Node) message - Stanza containing the tag.\n */\n _handleStreamStart: function _handleStreamStart(message) {\n var error = false; // Check for errors in the tag\n\n var ns = message.getAttribute(\"xmlns\");\n\n if (typeof ns !== \"string\") {\n error = \"Missing xmlns in \";\n } else if (ns !== Strophe$2.NS.FRAMING) {\n error = \"Wrong xmlns in : \" + ns;\n }\n\n var ver = message.getAttribute(\"version\");\n\n if (typeof ver !== \"string\") {\n error = \"Missing version in \";\n } else if (ver !== \"1.0\") {\n error = \"Wrong version in : \" + ver;\n }\n\n if (error) {\n this._conn._changeConnectStatus(Strophe$2.Status.CONNFAIL, error);\n\n this._conn._doDisconnect();\n\n return false;\n }\n\n return true;\n },\n\n /** PrivateFunction: _connect_cb_wrapper\n * _Private_ function that handles the first connection messages.\n *\n * On receiving an opening stream tag this callback replaces itself with the real\n * message handler. On receiving a stream error the connection is terminated.\n */\n _connect_cb_wrapper: function _connect_cb_wrapper(message) {\n if (message.data.indexOf(\"\\s*)*/, \"\");\n if (data === '') return;\n var streamStart = new DOMParser().parseFromString(data, \"text/xml\").documentElement;\n\n this._conn.xmlInput(streamStart);\n\n this._conn.rawInput(message.data); //_handleStreamSteart will check for XML errors and disconnect on error\n\n\n if (this._handleStreamStart(streamStart)) {\n //_connect_cb will check for stream:error and disconnect on error\n this._connect_cb(streamStart);\n }\n } else if (message.data.indexOf(\"\n // Parse the raw string to an XML element\n var parsedMessage = new DOMParser().parseFromString(message.data, \"text/xml\").documentElement; // Report this input to the raw and xml handlers\n\n this._conn.xmlInput(parsedMessage);\n\n this._conn.rawInput(message.data);\n\n var see_uri = parsedMessage.getAttribute(\"see-other-uri\");\n\n if (see_uri) {\n var service = this._conn.service; // Valid scenarios: WSS->WSS, WS->ANY\n\n var isSecureRedirect = service.indexOf(\"wss:\") >= 0 && see_uri.indexOf(\"wss:\") >= 0 || service.indexOf(\"ws:\") >= 0;\n\n if (isSecureRedirect) {\n this._conn._changeConnectStatus(Strophe$2.Status.REDIRECT, \"Received see-other-uri, resetting connection\");\n\n this._conn.reset();\n\n this._conn.service = see_uri;\n\n this._connect();\n }\n } else {\n this._conn._changeConnectStatus(Strophe$2.Status.CONNFAIL, \"Received closing stream\");\n\n this._conn._doDisconnect();\n }\n } else {\n var string = this._streamWrap(message.data);\n\n var elem = new DOMParser().parseFromString(string, \"text/xml\").documentElement;\n this.socket.onmessage = this._onMessage.bind(this);\n\n this._conn._connect_cb(elem, null, message.data);\n }\n },\n\n /** PrivateFunction: _disconnect\n * _Private_ function called by Strophe.Connection.disconnect\n *\n * Disconnects and sends a last stanza if one is given\n *\n * Parameters:\n * (Request) pres - This stanza will be sent before disconnecting.\n */\n _disconnect: function _disconnect(pres) {\n if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {\n if (pres) {\n this._conn.send(pres);\n }\n\n var close = $build$2(\"close\", {\n \"xmlns\": Strophe$2.NS.FRAMING\n });\n\n this._conn.xmlOutput(close.tree());\n\n var closeString = Strophe$2.serialize(close);\n\n this._conn.rawOutput(closeString);\n\n try {\n this.socket.send(closeString);\n } catch (e) {\n Strophe$2.warn(\"Couldn't send tag.\");\n }\n }\n\n this._conn._doDisconnect();\n },\n\n /** PrivateFunction: _doDisconnect\n * _Private_ function to disconnect.\n *\n * Just closes the Socket for WebSockets\n */\n _doDisconnect: function _doDisconnect() {\n Strophe$2.debug(\"WebSockets _doDisconnect was called\");\n\n this._closeSocket();\n },\n\n /** PrivateFunction _streamWrap\n * _Private_ helper function to wrap a stanza in a tag.\n * This is used so Strophe can process stanzas from WebSockets like BOSH\n */\n _streamWrap: function _streamWrap(stanza) {\n return \"\" + stanza + '';\n },\n\n /** PrivateFunction: _closeSocket\n * _Private_ function to close the WebSocket.\n *\n * Closes the socket if it is still open and deletes it\n */\n _closeSocket: function _closeSocket() {\n if (this.socket) {\n try {\n this.socket.onclose = null;\n this.socket.onerror = null;\n this.socket.onmessage = null;\n this.socket.close();\n } catch (e) {\n Strophe$2.debug(e.message);\n }\n }\n\n this.socket = null;\n },\n\n /** PrivateFunction: _emptyQueue\n * _Private_ function to check if the message queue is empty.\n *\n * Returns:\n * True, because WebSocket messages are send immediately after queueing.\n */\n _emptyQueue: function _emptyQueue() {\n return true;\n },\n\n /** PrivateFunction: _onClose\n * _Private_ function to handle websockets closing.\n *\n * Nothing to do here for WebSockets\n */\n _onClose: function _onClose(e) {\n if (this._conn.connected && !this._conn.disconnecting) {\n Strophe$2.error(\"Websocket closed unexpectedly\");\n\n this._conn._doDisconnect();\n } else if (e && e.code === 1006 && !this._conn.connected && this.socket) {\n // in case the onError callback was not called (Safari 10 does not\n // call onerror when the initial connection fails) we need to\n // dispatch a CONNFAIL status update to be consistent with the\n // behavior on other browsers.\n Strophe$2.error(\"Websocket closed unexcectedly\");\n\n this._conn._changeConnectStatus(Strophe$2.Status.CONNFAIL, \"The WebSocket connection could not be established or was disconnected.\");\n\n this._conn._doDisconnect();\n } else {\n Strophe$2.debug(\"Websocket closed\");\n }\n },\n\n /** PrivateFunction: _no_auth_received\n *\n * Called on stream start/restart when no stream:features\n * has been received.\n */\n _no_auth_received: function _no_auth_received(callback) {\n Strophe$2.error(\"Server did not offer a supported authentication mechanism\");\n\n this._conn._changeConnectStatus(Strophe$2.Status.CONNFAIL, Strophe$2.ErrorCondition.NO_AUTH_MECH);\n\n if (callback) {\n callback.call(this._conn);\n }\n\n this._conn._doDisconnect();\n },\n\n /** PrivateFunction: _onDisconnectTimeout\n * _Private_ timeout handler for handling non-graceful disconnection.\n *\n * This does nothing for WebSockets\n */\n _onDisconnectTimeout: function _onDisconnectTimeout() {},\n\n /** PrivateFunction: _abortAllRequests\n * _Private_ helper function that makes sure all pending requests are aborted.\n */\n _abortAllRequests: function _abortAllRequests() {},\n\n /** PrivateFunction: _onError\n * _Private_ function to handle websockets errors.\n *\n * Parameters:\n * (Object) error - The websocket error.\n */\n _onError: function _onError(error) {\n Strophe$2.error(\"Websocket error \" + error);\n\n this._conn._changeConnectStatus(Strophe$2.Status.CONNFAIL, \"The WebSocket connection could not be established or was disconnected.\");\n\n this._disconnect();\n },\n\n /** PrivateFunction: _onIdle\n * _Private_ function called by Strophe.Connection._onIdle\n *\n * sends all queued stanzas\n */\n _onIdle: function _onIdle() {\n var data = this._conn._data;\n\n if (data.length > 0 && !this._conn.paused) {\n for (var i = 0; i < data.length; i++) {\n if (data[i] !== null) {\n var stanza = void 0;\n\n if (data[i] === \"restart\") {\n stanza = this._buildStream().tree();\n } else {\n stanza = data[i];\n }\n\n var rawStanza = Strophe$2.serialize(stanza);\n\n this._conn.xmlOutput(stanza);\n\n this._conn.rawOutput(rawStanza);\n\n this.socket.send(rawStanza);\n }\n }\n\n this._conn._data = [];\n }\n },\n\n /** PrivateFunction: _onMessage\n * _Private_ function to handle websockets messages.\n *\n * This function parses each of the messages as if they are full documents.\n * [TODO : We may actually want to use a SAX Push parser].\n *\n * Since all XMPP traffic starts with\n * \n *\n * The first stanza will always fail to be parsed.\n *\n * Additionally, the seconds stanza will always be with\n * the stream NS defined in the previous stanza, so we need to 'force'\n * the inclusion of the NS in this stanza.\n *\n * Parameters:\n * (string) message - The websocket message.\n */\n _onMessage: function _onMessage(message) {\n var elem; // check for closing stream\n\n var close = '';\n\n if (message.data === close) {\n this._conn.rawInput(close);\n\n this._conn.xmlInput(message);\n\n if (!this._conn.disconnecting) {\n this._conn._doDisconnect();\n }\n\n return;\n } else if (message.data.search(\" tag before we close the connection\n\n\n return;\n }\n\n this._conn._dataRecv(elem, message.data);\n },\n\n /** PrivateFunction: _onOpen\n * _Private_ function to handle websockets connection setup.\n *\n * The opening stream tag is sent here.\n */\n _onOpen: function _onOpen() {\n Strophe$2.debug(\"Websocket open\");\n\n var start = this._buildStream();\n\n this._conn.xmlOutput(start.tree());\n\n var startString = Strophe$2.serialize(start);\n\n this._conn.rawOutput(startString);\n\n this.socket.send(startString);\n },\n\n /** PrivateFunction: _reqToData\n * _Private_ function to get a stanza out of a request.\n *\n * WebSockets don't use requests, so the passed argument is just returned.\n *\n * Parameters:\n * (Object) stanza - The stanza.\n *\n * Returns:\n * The stanza that was passed.\n */\n _reqToData: function _reqToData(stanza) {\n return stanza;\n },\n\n /** PrivateFunction: _send\n * _Private_ part of the Connection.send function for WebSocket\n *\n * Just flushes the messages that are in the queue\n */\n _send: function _send() {\n this._conn.flush();\n },\n\n /** PrivateFunction: _sendRestart\n *\n * Send an xmpp:restart stanza.\n */\n _sendRestart: function _sendRestart() {\n clearTimeout(this._conn._idleTimeout);\n\n this._conn._onIdle.bind(this._conn)();\n }\n };\n\n global$1.Strophe = core.Strophe;\n global$1.$build = core.$build;\n global$1.$iq = core.$iq;\n global$1.$msg = core.$msg;\n global$1.$pres = core.$pres;\n\n return core;\n\n}));\n","import { $iq, Strophe } from 'strophe.js';\n\nStrophe.addConnectionPlugin('disco',\n{\n _connection: null,\n _identities : [],\n _features : [],\n _items : [],\n /** Function: init\n * Plugin init\n *\n * Parameters:\n * (Strophe.Connection) conn - Strophe connection\n */\n init: function(conn)\n {\n this._connection = conn;\n this._identities = [];\n this._features = [];\n this._items = [];\n // disco info\n conn.addHandler(this._onDiscoInfo.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);\n // disco items\n conn.addHandler(this._onDiscoItems.bind(this), Strophe.NS.DISCO_ITEMS, 'iq', 'get', null, null);\n },\n /** Function: addIdentity\n * See http://xmpp.org/registrar/disco-categories.html\n * Parameters:\n * (String) category - category of identity (like client, automation, etc ...)\n * (String) type - type of identity (like pc, web, bot , etc ...)\n * (String) name - name of identity in natural language\n * (String) lang - lang of name parameter\n *\n * Returns:\n * Boolean\n */\n addIdentity: function(category, type, name, lang)\n {\n for (var i=0; i 0;\n\n\t\treturn this._originalOnStreamFeaturesAfterSASL.apply(this._c, arguments);\n\t},\n\n\tstatusChanged: function (status) {\n\t\tthis._connectionStatus = status;\n\t\tif (!this.getResumeToken()\n\t\t\t&& (status === Strophe.Status.CONNECTED || status === Strophe.Status.DISCONNECTED)) {\n\t\t\tthis.logging && Strophe.debug('SM reset state');\n\n\t\t\tthis._serverProcesssedStanzasCounter = 0;\n\t\t\tthis._clientProcessedStanzasCounter = 0;\n\n\t\t\tthis._clientSentStanzasCounter = 0;\n\n\t\t\tthis._isStreamManagementEnabled = false;\n\t\t\tthis._requestResponseIntervalCount = 0;\n\n\t\t\t// FIXME not described in JSDocs\n\t\t\tthis._resuming = false;\n\n\t\t\tif (status === Strophe.Status.DISCONNECTED) {\n\t\t\t\tthis._isSupported = false;\n\t\t\t}\n\n\t\t\tthis._unacknowledgedStanzas = [];\n\n\t\t\tif (this._requestHandler) {\n\t\t\t\tthis._c.deleteHandler(this._requestHandler);\n\t\t\t}\n\n\t\t\tif (this._incomingHandler) {\n\t\t\t\tthis._c.deleteHandler(this._incomingHandler);\n\t\t\t}\n\n\t\t\tthis._requestHandler = this._c.addHandler(this._handleServerRequestHandler.bind(this), this._NS, 'r');\n\t\t\tthis._ackHandler = this._c.addHandler(this._handleServerAck.bind(this), this._NS, 'a');\n\t\t\tthis._incomingHandler = this._c.addHandler(this._incomingStanzaHandler.bind(this));\n\n\t\t\t// FIXME handler instances stored, but never used\n\t\t\tthis._enabledHandler = this._c._addSysHandler(this._handleEnabled.bind(this), this._NS, 'enabled');\n\t\t\tthis._resumeFailedHandler = this._c._addSysHandler(this._handleResumeFailed.bind(this), this._NS, 'failed');\n\t\t\tthis._resumedHandler = this._c._addSysHandler(this._handleResumed.bind(this), this._NS,'resumed');\n\n\t\t} else if (status === Strophe.Status.BINDREQUIRED) {\n\t\t\tthis._c.jid = this._storedJid;\n\n\t\t\t// Restore Strophe handlers\n\t\t\tfor (const property in this._resumeState) {\n\t\t\t\tthis._c[property] = this._resumeState[property];\n\t\t\t}\n\n\t\t\t// FIXME check conditions if there's session ID and if enabled\n\t\t\tthis._c.send($build('resume', {\n\t\t\t\txmlns: this._NS,\n\t\t\t\th: this._clientProcessedStanzasCounter,\n\t\t\t\tprevid: this._resumeToken\n\t\t\t}));\n\t\t\tthis._c.flush();\n\t\t} else if (status === Strophe.Status.ERROR) {\n\t\t\tthis.logging && Strophe.debug('SM cleared resume token on error');\n\t\t\tthis._resumeToken = undefined;\n\t\t}\n\t},\n\n\t/**\n\t* This method overrides the send method implemented by Strophe.Connection\n\t* to count outgoing stanzas\n\t*\n\t* @method Send\n\t* @public\n\t*/\n\txmlOutput: function(elem) {\n\t\tif (Strophe.isTagEqual(elem, 'iq') ||\n\t\t\tStrophe.isTagEqual(elem, 'presence') ||\n\t\t\tStrophe.isTagEqual(elem, 'message')) {\n\t\t\tthis._increaseSentStanzasCounter(elem);\n\t\t}\n\n\t\treturn this._originalXMLOutput.call(this._c, elem);\n\t},\n\n\t_handleEnabled: function(elem) {\n\t\tthis._isStreamManagementEnabled = true;\n\t\t// FIXME fail if requested, but not enabled\n\t\tthis._resumeToken = elem.getAttribute('resume') === 'true' && elem.getAttribute('id');\n\n\t\tthis._c.resume();\n\n\t\treturn true;\n\t},\n\n\t_handleResumeFailed: function(elem) {\n\t\tconst error = elem && elem.firstElementChild && elem.firstElementChild.tagName;\n\n\t\tthis._c._changeConnectStatus(Strophe.Status.ERROR, error, elem);\n\t\tthis._c._doDisconnect();\n\n\t\treturn true;\n\t},\n\n\t_handleResumed: function(elem) {\n\t\t// FIXME check if in the correct state\n\t\tvar handledCount = parseInt(elem.getAttribute('h'));\n\t\tthis._handleAcknowledgedStanzas(handledCount, this._serverProcesssedStanzasCounter);\n\n\t\tthis._resuming = false;\n\t\tthis._c.do_bind = false; // No need to bind our resource anymore\n\t\tthis._c.authenticated = true;\n\t\tthis._c.restored = true;\n\n\t\tif (this._unacknowledgedStanzas.length > 0) {\n\t\t\tthis.logging && Strophe.debug('SM Sending unacknowledged stanzas', this._unacknowledgedStanzas);\n\t\t\tfor(const stanza of this._unacknowledgedStanzas) {\n\t\t\t\tthis._c.send(stanza);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.logging && Strophe.debug('SM No unacknowledged stanzas', this._unacknowledgedStanzas);\n\t\t}\n\n\t\tthis._c._changeConnectStatus(Strophe.Status.CONNECTED, null);\n\n\t\treturn true;\n\t},\n\n\t_incomingStanzaHandler: function(elem) {\n\t\tif (Strophe.isTagEqual(elem, 'iq') || Strophe.isTagEqual(elem, 'presence') || Strophe.isTagEqual(elem, 'message')) {\n\t\t\tthis._increaseReceivedStanzasCounter();\n\n\t\t\tif (this.autoSendCountOnEveryIncomingStanza) {\n\t\t\t\tthis._answerProcessedStanzas();\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t},\n\n\t_handleAcknowledgedStanzas: function(reportedHandledCount, lastKnownHandledCount) {\n\t\tvar delta = reportedHandledCount - lastKnownHandledCount;\n\n\t\tif (delta < 0) {\n\t\t\tthis._throwError('New reported stanza count lower than previous. New: ' + reportedHandledCount + ' - Previous: ' + lastKnownHandledCount);\n\t\t}\n\n\t\tif (delta > this._unacknowledgedStanzas.length) {\n\t\t\tthis._throwError('Higher reported acknowledge count than unacknowledged stanzas. Reported Acknowledge Count: ' + delta + ' - Unacknowledge Stanza Count: ' + this._unacknowledgedStanzas.length + ' - New: ' + reportedHandledCount + ' - Previous: ' + lastKnownHandledCount);\n\t\t}\n\n\t\tfor(var i = 0; i < delta; i++) {\n\t\t\tvar stanza = this._unacknowledgedStanzas.shift();\n\t\t\tfor (var j = 0; j < this._acknowledgedStanzaListeners.length; j++) {\n\t\t\t\tthis._acknowledgedStanzaListeners[j](stanza);\n\t\t\t}\n\t\t}\n\n\t\tif (this.logging && this._unacknowledgedStanzas.length > 0) {\n\t\t\tStrophe.warn('SM Unacknowledged stanzas', this._unacknowledgedStanzas);\n\t\t}\n\n\t\tthis._serverProcesssedStanzasCounter = reportedHandledCount;\n\n\t\tif (this.requestResponseInterval > 0) {\n\t\t\tthis._requestResponseIntervalCount = 0;\n\t\t}\n\t},\n\n\t_handleServerRequestHandler: function() {\n\t\tthis._answerProcessedStanzas();\n\n\t\treturn true;\n\t},\n\n\t_handleServerAck: function(elem){\n\t\tvar handledCount = parseInt(elem.getAttribute('h'));\n\t\tthis._handleAcknowledgedStanzas(handledCount, this._serverProcesssedStanzasCounter);\n\n\t\treturn true;\n\t},\n\n\t_answerProcessedStanzas: function() {\n\t\tif (this._isStreamManagementEnabled) {\n\t\t\tthis._c.send($build('a', { xmlns: this._NS, h: this._clientProcessedStanzasCounter }));\n\t\t}\n\t},\n\n\t_increaseSentStanzasCounter: function(elem) {\n\t\tif (this._isStreamManagementEnabled) {\n\t\t\tif (this._unacknowledgedStanzas.indexOf(elem) !== -1) {\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis._unacknowledgedStanzas.push(elem);\n\t\t\tthis._clientSentStanzasCounter++;\n\n\t\t\tif (this.requestResponseInterval > 0) {\n\t\t\t\tthis._requestResponseIntervalCount++;\n\n\t\t\t\tif (this._requestResponseIntervalCount === this.requestResponseInterval) {\n\t\t\t\t\t// FIXME Can not call send from onIdle.\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tif (this._connectionStatus === Strophe.Status.CONNECTED) {\n\t\t\t\t\t\t\tthis.requestAcknowledgement();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t_increaseReceivedStanzasCounter: function() {\n\t\tif (this._isStreamManagementEnabled) {\n\t\t\tthis._clientProcessedStanzasCounter++;\n\t\t}\n\t},\n\n\t_throwError: function(msg) {\n\t\tStrophe.error(msg);\n\t\tthrow new Error(msg);\n\t}\n\n});\n","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\n/**\n * Convert array of 16 byte values to UUID string format of the form:\n * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX\n */\nconst byteToHex = [];\n\nfor (let i = 0; i < 256; ++i) {\n byteToHex.push((i + 0x100).toString(16).substr(1));\n}\n\nfunction bytesToUuid(buf, offset) {\n const i = offset || 0;\n const bth = byteToHex; // Note: Be careful editing this code! It's been tuned for performance\n // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434\n\n return (bth[buf[i + 0]] + bth[buf[i + 1]] + bth[buf[i + 2]] + bth[buf[i + 3]] + '-' + bth[buf[i + 4]] + bth[buf[i + 5]] + '-' + bth[buf[i + 6]] + bth[buf[i + 7]] + '-' + bth[buf[i + 8]] + bth[buf[i + 9]] + '-' + bth[buf[i + 10]] + bth[buf[i + 11]] + bth[buf[i + 12]] + bth[buf[i + 13]] + bth[buf[i + 14]] + bth[buf[i + 15]]).toLowerCase();\n}\n\nvar _default = bytesToUuid;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"v1\", {\n enumerable: true,\n get: function () {\n return _v.default;\n }\n});\nObject.defineProperty(exports, \"v3\", {\n enumerable: true,\n get: function () {\n return _v2.default;\n }\n});\nObject.defineProperty(exports, \"v4\", {\n enumerable: true,\n get: function () {\n return _v3.default;\n }\n});\nObject.defineProperty(exports, \"v5\", {\n enumerable: true,\n get: function () {\n return _v4.default;\n }\n});\n\nvar _v = _interopRequireDefault(require(\"./v1.js\"));\n\nvar _v2 = _interopRequireDefault(require(\"./v3.js\"));\n\nvar _v3 = _interopRequireDefault(require(\"./v4.js\"));\n\nvar _v4 = _interopRequireDefault(require(\"./v5.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\n/*\n * Browser-compatible JavaScript MD5\n *\n * Modification of JavaScript MD5\n * https://github.com/blueimp/JavaScript-MD5\n *\n * Copyright 2011, Sebastian Tschan\n * https://blueimp.net\n *\n * Licensed under the MIT license:\n * https://opensource.org/licenses/MIT\n *\n * Based on\n * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message\n * Digest Algorithm, as defined in RFC 1321.\n * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n * Distributed under the BSD License\n * See http://pajhome.org.uk/crypt/md5 for more info.\n */\nfunction md5(bytes) {\n if (typeof bytes === 'string') {\n const msg = unescape(encodeURIComponent(bytes)); // UTF8 escape\n\n bytes = new Uint8Array(msg.length);\n\n for (let i = 0; i < msg.length; ++i) {\n bytes[i] = msg.charCodeAt(i);\n }\n }\n\n return md5ToHexEncodedArray(wordsToMd5(bytesToWords(bytes), bytes.length * 8));\n}\n/*\n * Convert an array of little-endian words to an array of bytes\n */\n\n\nfunction md5ToHexEncodedArray(input) {\n const output = [];\n const length32 = input.length * 32;\n const hexTab = '0123456789abcdef';\n\n for (let i = 0; i < length32; i += 8) {\n const x = input[i >> 5] >>> i % 32 & 0xff;\n const hex = parseInt(hexTab.charAt(x >>> 4 & 0x0f) + hexTab.charAt(x & 0x0f), 16);\n output.push(hex);\n }\n\n return output;\n}\n/**\n * Calculate output length with padding and bit length\n */\n\n\nfunction getOutputLength(inputLength8) {\n return (inputLength8 + 64 >>> 9 << 4) + 14 + 1;\n}\n/*\n * Calculate the MD5 of an array of little-endian words, and a bit length.\n */\n\n\nfunction wordsToMd5(x, len) {\n /* append padding */\n x[len >> 5] |= 0x80 << len % 32;\n x[getOutputLength(len) - 1] = len;\n let a = 1732584193;\n let b = -271733879;\n let c = -1732584194;\n let d = 271733878;\n\n for (let i = 0; i < x.length; i += 16) {\n const olda = a;\n const oldb = b;\n const oldc = c;\n const oldd = d;\n a = md5ff(a, b, c, d, x[i], 7, -680876936);\n d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);\n c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);\n b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);\n a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);\n d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);\n c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);\n b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);\n a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);\n d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);\n c = md5ff(c, d, a, b, x[i + 10], 17, -42063);\n b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);\n a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);\n d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);\n c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);\n b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);\n a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);\n d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);\n c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);\n b = md5gg(b, c, d, a, x[i], 20, -373897302);\n a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);\n d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);\n c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);\n b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);\n a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);\n d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);\n c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);\n b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);\n a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);\n d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);\n c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);\n b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);\n a = md5hh(a, b, c, d, x[i + 5], 4, -378558);\n d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);\n c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);\n b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);\n a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);\n d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);\n c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);\n b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);\n a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);\n d = md5hh(d, a, b, c, x[i], 11, -358537222);\n c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);\n b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);\n a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);\n d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);\n c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);\n b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);\n a = md5ii(a, b, c, d, x[i], 6, -198630844);\n d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);\n c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);\n b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);\n a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);\n d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);\n c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);\n b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);\n a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);\n d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);\n c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);\n b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);\n a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);\n d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);\n c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);\n b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);\n a = safeAdd(a, olda);\n b = safeAdd(b, oldb);\n c = safeAdd(c, oldc);\n d = safeAdd(d, oldd);\n }\n\n return [a, b, c, d];\n}\n/*\n * Convert an array bytes to an array of little-endian words\n * Characters >255 have their high-byte silently ignored.\n */\n\n\nfunction bytesToWords(input) {\n if (input.length === 0) {\n return [];\n }\n\n const length8 = input.length * 8;\n const output = new Uint32Array(getOutputLength(length8));\n\n for (let i = 0; i < length8; i += 8) {\n output[i >> 5] |= (input[i / 8] & 0xff) << i % 32;\n }\n\n return output;\n}\n/*\n * Add integers, wrapping at 2^32. This uses 16-bit operations internally\n * to work around bugs in some JS interpreters.\n */\n\n\nfunction safeAdd(x, y) {\n const lsw = (x & 0xffff) + (y & 0xffff);\n const msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n return msw << 16 | lsw & 0xffff;\n}\n/*\n * Bitwise rotate a 32-bit number to the left.\n */\n\n\nfunction bitRotateLeft(num, cnt) {\n return num << cnt | num >>> 32 - cnt;\n}\n/*\n * These functions implement the four basic operations the algorithm uses.\n */\n\n\nfunction md5cmn(q, a, b, x, s, t) {\n return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);\n}\n\nfunction md5ff(a, b, c, d, x, s, t) {\n return md5cmn(b & c | ~b & d, a, b, x, s, t);\n}\n\nfunction md5gg(a, b, c, d, x, s, t) {\n return md5cmn(b & d | c & ~d, a, b, x, s, t);\n}\n\nfunction md5hh(a, b, c, d, x, s, t) {\n return md5cmn(b ^ c ^ d, a, b, x, s, t);\n}\n\nfunction md5ii(a, b, c, d, x, s, t) {\n return md5cmn(c ^ (b | ~d), a, b, x, s, t);\n}\n\nvar _default = md5;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = rng;\n// Unique ID creation requires a high quality random # generator. In the browser we therefore\n// require the crypto API and do not support built-in fallback to lower quality random number\n// generators (like Math.random()).\n// getRandomValues needs to be invoked in a context where \"this\" is a Crypto implementation. Also,\n// find the complete implementation of crypto (msCrypto) on IE11.\nconst getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || typeof msCrypto !== 'undefined' && typeof msCrypto.getRandomValues === 'function' && msCrypto.getRandomValues.bind(msCrypto);\nconst rnds8 = new Uint8Array(16);\n\nfunction rng() {\n if (!getRandomValues) {\n throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');\n }\n\n return getRandomValues(rnds8);\n}","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\n// Adapted from Chris Veness' SHA1 code at\n// http://www.movable-type.co.uk/scripts/sha1.html\nfunction f(s, x, y, z) {\n switch (s) {\n case 0:\n return x & y ^ ~x & z;\n\n case 1:\n return x ^ y ^ z;\n\n case 2:\n return x & y ^ x & z ^ y & z;\n\n case 3:\n return x ^ y ^ z;\n }\n}\n\nfunction ROTL(x, n) {\n return x << n | x >>> 32 - n;\n}\n\nfunction sha1(bytes) {\n const K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];\n const H = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];\n\n if (typeof bytes === 'string') {\n const msg = unescape(encodeURIComponent(bytes)); // UTF8 escape\n\n bytes = [];\n\n for (let i = 0; i < msg.length; ++i) {\n bytes.push(msg.charCodeAt(i));\n }\n }\n\n bytes.push(0x80);\n const l = bytes.length / 4 + 2;\n const N = Math.ceil(l / 16);\n const M = new Array(N);\n\n for (let i = 0; i < N; ++i) {\n const arr = new Uint32Array(16);\n\n for (let j = 0; j < 16; ++j) {\n arr[j] = bytes[i * 64 + j * 4] << 24 | bytes[i * 64 + j * 4 + 1] << 16 | bytes[i * 64 + j * 4 + 2] << 8 | bytes[i * 64 + j * 4 + 3];\n }\n\n M[i] = arr;\n }\n\n M[N - 1][14] = (bytes.length - 1) * 8 / Math.pow(2, 32);\n M[N - 1][14] = Math.floor(M[N - 1][14]);\n M[N - 1][15] = (bytes.length - 1) * 8 & 0xffffffff;\n\n for (let i = 0; i < N; ++i) {\n const W = new Uint32Array(80);\n\n for (let t = 0; t < 16; ++t) {\n W[t] = M[i][t];\n }\n\n for (let t = 16; t < 80; ++t) {\n W[t] = ROTL(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1);\n }\n\n let a = H[0];\n let b = H[1];\n let c = H[2];\n let d = H[3];\n let e = H[4];\n\n for (let t = 0; t < 80; ++t) {\n const s = Math.floor(t / 20);\n const T = ROTL(a, 5) + f(s, b, c, d) + e + K[s] + W[t] >>> 0;\n e = d;\n d = c;\n c = ROTL(b, 30) >>> 0;\n b = a;\n a = T;\n }\n\n H[0] = H[0] + a >>> 0;\n H[1] = H[1] + b >>> 0;\n H[2] = H[2] + c >>> 0;\n H[3] = H[3] + d >>> 0;\n H[4] = H[4] + e >>> 0;\n }\n\n return [H[0] >> 24 & 0xff, H[0] >> 16 & 0xff, H[0] >> 8 & 0xff, H[0] & 0xff, H[1] >> 24 & 0xff, H[1] >> 16 & 0xff, H[1] >> 8 & 0xff, H[1] & 0xff, H[2] >> 24 & 0xff, H[2] >> 16 & 0xff, H[2] >> 8 & 0xff, H[2] & 0xff, H[3] >> 24 & 0xff, H[3] >> 16 & 0xff, H[3] >> 8 & 0xff, H[3] & 0xff, H[4] >> 24 & 0xff, H[4] >> 16 & 0xff, H[4] >> 8 & 0xff, H[4] & 0xff];\n}\n\nvar _default = sha1;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _rng = _interopRequireDefault(require(\"./rng.js\"));\n\nvar _bytesToUuid = _interopRequireDefault(require(\"./bytesToUuid.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n// **`v1()` - Generate time-based UUID**\n//\n// Inspired by https://github.com/LiosK/UUID.js\n// and http://docs.python.org/library/uuid.html\nlet _nodeId;\n\nlet _clockseq; // Previous uuid creation time\n\n\nlet _lastMSecs = 0;\nlet _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details\n\nfunction v1(options, buf, offset) {\n let i = buf && offset || 0;\n const b = buf || [];\n options = options || {};\n let node = options.node || _nodeId;\n let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not\n // specified. We do this lazily to minimize issues related to insufficient\n // system entropy. See #189\n\n if (node == null || clockseq == null) {\n const seedBytes = options.random || (options.rng || _rng.default)();\n\n if (node == null) {\n // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)\n node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]];\n }\n\n if (clockseq == null) {\n // Per 4.2.2, randomize (14 bit) clockseq\n clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff;\n }\n } // UUID timestamps are 100 nano-second units since the Gregorian epoch,\n // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so\n // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'\n // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.\n\n\n let msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock\n // cycle to simulate higher resolution clock\n\n let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs)\n\n const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression\n\n if (dt < 0 && options.clockseq === undefined) {\n clockseq = clockseq + 1 & 0x3fff;\n } // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new\n // time interval\n\n\n if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {\n nsecs = 0;\n } // Per 4.2.1.2 Throw error if too many uuids are requested\n\n\n if (nsecs >= 10000) {\n throw new Error(\"uuid.v1(): Can't create more than 10M uuids/sec\");\n }\n\n _lastMSecs = msecs;\n _lastNSecs = nsecs;\n _clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch\n\n msecs += 12219292800000; // `time_low`\n\n const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;\n b[i++] = tl >>> 24 & 0xff;\n b[i++] = tl >>> 16 & 0xff;\n b[i++] = tl >>> 8 & 0xff;\n b[i++] = tl & 0xff; // `time_mid`\n\n const tmh = msecs / 0x100000000 * 10000 & 0xfffffff;\n b[i++] = tmh >>> 8 & 0xff;\n b[i++] = tmh & 0xff; // `time_high_and_version`\n\n b[i++] = tmh >>> 24 & 0xf | 0x10; // include version\n\n b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)\n\n b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low`\n\n b[i++] = clockseq & 0xff; // `node`\n\n for (let n = 0; n < 6; ++n) {\n b[i + n] = node[n];\n }\n\n return buf || (0, _bytesToUuid.default)(b);\n}\n\nvar _default = v1;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _v = _interopRequireDefault(require(\"./v35.js\"));\n\nvar _md = _interopRequireDefault(require(\"./md5.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nconst v3 = (0, _v.default)('v3', 0x30, _md.default);\nvar _default = v3;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = _default;\nexports.URL = exports.DNS = void 0;\n\nvar _bytesToUuid = _interopRequireDefault(require(\"./bytesToUuid.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction uuidToBytes(uuid) {\n // Note: We assume we're being passed a valid uuid string\n const bytes = [];\n uuid.replace(/[a-fA-F0-9]{2}/g, function (hex) {\n bytes.push(parseInt(hex, 16));\n });\n return bytes;\n}\n\nfunction stringToBytes(str) {\n str = unescape(encodeURIComponent(str)); // UTF8 escape\n\n const bytes = [];\n\n for (let i = 0; i < str.length; ++i) {\n bytes.push(str.charCodeAt(i));\n }\n\n return bytes;\n}\n\nconst DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';\nexports.DNS = DNS;\nconst URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';\nexports.URL = URL;\n\nfunction _default(name, version, hashfunc) {\n function generateUUID(value, namespace, buf, offset) {\n const off = buf && offset || 0;\n if (typeof value === 'string') value = stringToBytes(value);\n if (typeof namespace === 'string') namespace = uuidToBytes(namespace);\n\n if (!Array.isArray(value)) {\n throw TypeError('value must be an array of bytes');\n }\n\n if (!Array.isArray(namespace) || namespace.length !== 16) {\n throw TypeError('namespace must be uuid string or an Array of 16 byte values');\n } // Per 4.3\n\n\n const bytes = hashfunc(namespace.concat(value));\n bytes[6] = bytes[6] & 0x0f | version;\n bytes[8] = bytes[8] & 0x3f | 0x80;\n\n if (buf) {\n for (let idx = 0; idx < 16; ++idx) {\n buf[off + idx] = bytes[idx];\n }\n }\n\n return buf || (0, _bytesToUuid.default)(bytes);\n } // Function#name is not settable on some platforms (#270)\n\n\n try {\n generateUUID.name = name; // eslint-disable-next-line no-empty\n } catch (err) {} // For CommonJS default export support\n\n\n generateUUID.DNS = DNS;\n generateUUID.URL = URL;\n return generateUUID;\n}","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _rng = _interopRequireDefault(require(\"./rng.js\"));\n\nvar _bytesToUuid = _interopRequireDefault(require(\"./bytesToUuid.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction v4(options, buf, offset) {\n if (typeof options === 'string') {\n buf = options === 'binary' ? new Uint8Array(16) : null;\n options = null;\n }\n\n options = options || {};\n\n const rnds = options.random || (options.rng || _rng.default)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`\n\n\n rnds[6] = rnds[6] & 0x0f | 0x40;\n rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided\n\n if (buf) {\n const start = offset || 0;\n\n for (let i = 0; i < 16; ++i) {\n buf[start + i] = rnds[i];\n }\n\n return buf;\n }\n\n return (0, _bytesToUuid.default)(rnds);\n}\n\nvar _default = v4;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _v = _interopRequireDefault(require(\"./v35.js\"));\n\nvar _sha = _interopRequireDefault(require(\"./sha1.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nconst v5 = (0, _v.default)('v5', 0x50, _sha.default);\nvar _default = v5;\nexports.default = _default;","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n /* eslint-env node */\n'use strict';\n\nlet logDisabled_ = true;\nlet deprecationWarnings_ = true;\n\n/**\n * Extract browser version out of the provided user agent string.\n *\n * @param {!string} uastring userAgent string.\n * @param {!string} expr Regular expression used as match criteria.\n * @param {!number} pos position in the version string to be returned.\n * @return {!number} browser version.\n */\nexport function extractVersion(uastring, expr, pos) {\n const match = uastring.match(expr);\n return match && match.length >= pos && parseInt(match[pos], 10);\n}\n\n// Wraps the peerconnection event eventNameToWrap in a function\n// which returns the modified event object (or false to prevent\n// the event).\nexport function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {\n if (!window.RTCPeerConnection) {\n return;\n }\n const proto = window.RTCPeerConnection.prototype;\n const nativeAddEventListener = proto.addEventListener;\n proto.addEventListener = function(nativeEventName, cb) {\n if (nativeEventName !== eventNameToWrap) {\n return nativeAddEventListener.apply(this, arguments);\n }\n const wrappedCallback = (e) => {\n const modifiedEvent = wrapper(e);\n if (modifiedEvent) {\n if (cb.handleEvent) {\n cb.handleEvent(modifiedEvent);\n } else {\n cb(modifiedEvent);\n }\n }\n };\n this._eventMap = this._eventMap || {};\n if (!this._eventMap[eventNameToWrap]) {\n this._eventMap[eventNameToWrap] = new Map();\n }\n this._eventMap[eventNameToWrap].set(cb, wrappedCallback);\n return nativeAddEventListener.apply(this, [nativeEventName,\n wrappedCallback]);\n };\n\n const nativeRemoveEventListener = proto.removeEventListener;\n proto.removeEventListener = function(nativeEventName, cb) {\n if (nativeEventName !== eventNameToWrap || !this._eventMap\n || !this._eventMap[eventNameToWrap]) {\n return nativeRemoveEventListener.apply(this, arguments);\n }\n if (!this._eventMap[eventNameToWrap].has(cb)) {\n return nativeRemoveEventListener.apply(this, arguments);\n }\n const unwrappedCb = this._eventMap[eventNameToWrap].get(cb);\n this._eventMap[eventNameToWrap].delete(cb);\n if (this._eventMap[eventNameToWrap].size === 0) {\n delete this._eventMap[eventNameToWrap];\n }\n if (Object.keys(this._eventMap).length === 0) {\n delete this._eventMap;\n }\n return nativeRemoveEventListener.apply(this, [nativeEventName,\n unwrappedCb]);\n };\n\n Object.defineProperty(proto, 'on' + eventNameToWrap, {\n get() {\n return this['_on' + eventNameToWrap];\n },\n set(cb) {\n if (this['_on' + eventNameToWrap]) {\n this.removeEventListener(eventNameToWrap,\n this['_on' + eventNameToWrap]);\n delete this['_on' + eventNameToWrap];\n }\n if (cb) {\n this.addEventListener(eventNameToWrap,\n this['_on' + eventNameToWrap] = cb);\n }\n },\n enumerable: true,\n configurable: true\n });\n}\n\nexport function disableLog(bool) {\n if (typeof bool !== 'boolean') {\n return new Error('Argument type: ' + typeof bool +\n '. Please use a boolean.');\n }\n logDisabled_ = bool;\n return (bool) ? 'adapter.js logging disabled' :\n 'adapter.js logging enabled';\n}\n\n/**\n * Disable or enable deprecation warnings\n * @param {!boolean} bool set to true to disable warnings.\n */\nexport function disableWarnings(bool) {\n if (typeof bool !== 'boolean') {\n return new Error('Argument type: ' + typeof bool +\n '. Please use a boolean.');\n }\n deprecationWarnings_ = !bool;\n return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');\n}\n\nexport function log() {\n if (typeof window === 'object') {\n if (logDisabled_) {\n return;\n }\n if (typeof console !== 'undefined' && typeof console.log === 'function') {\n console.log.apply(console, arguments);\n }\n }\n}\n\n/**\n * Shows a deprecation warning suggesting the modern and spec-compatible API.\n */\nexport function deprecated(oldMethod, newMethod) {\n if (!deprecationWarnings_) {\n return;\n }\n console.warn(oldMethod + ' is deprecated, please use ' + newMethod +\n ' instead.');\n}\n\n/**\n * Browser detector.\n *\n * @return {object} result containing browser and version\n * properties.\n */\nexport function detectBrowser(window) {\n // Returned result object.\n const result = {browser: null, version: null};\n\n // Fail early if it's not a browser\n if (typeof window === 'undefined' || !window.navigator) {\n result.browser = 'Not a browser.';\n return result;\n }\n\n const {navigator} = window;\n\n if (navigator.mozGetUserMedia) { // Firefox.\n result.browser = 'firefox';\n result.version = extractVersion(navigator.userAgent,\n /Firefox\\/(\\d+)\\./, 1);\n } else if (navigator.webkitGetUserMedia ||\n (window.isSecureContext === false && window.webkitRTCPeerConnection &&\n !window.RTCIceGatherer)) {\n // Chrome, Chromium, Webview, Opera.\n // Version matches Chrome/WebRTC version.\n // Chrome 74 removed webkitGetUserMedia on http as well so we need the\n // more complicated fallback to webkitRTCPeerConnection.\n result.browser = 'chrome';\n result.version = extractVersion(navigator.userAgent,\n /Chrom(e|ium)\\/(\\d+)\\./, 2);\n } else if (window.RTCPeerConnection &&\n navigator.userAgent.match(/AppleWebKit\\/(\\d+)\\./)) { // Safari.\n result.browser = 'safari';\n result.version = extractVersion(navigator.userAgent,\n /AppleWebKit\\/(\\d+)\\./, 1);\n result.supportsUnifiedPlan = window.RTCRtpTransceiver &&\n 'currentDirection' in window.RTCRtpTransceiver.prototype;\n } else { // Default fallthrough: not supported.\n result.browser = 'Not a supported browser.';\n return result;\n }\n\n return result;\n}\n\n/**\n * Checks if something is an object.\n *\n * @param {*} val The something you want to check.\n * @return true if val is an object, false otherwise.\n */\nfunction isObject(val) {\n return Object.prototype.toString.call(val) === '[object Object]';\n}\n\n/**\n * Remove all empty objects and undefined values\n * from a nested object -- an enhanced and vanilla version\n * of Lodash's `compact`.\n */\nexport function compactObject(data) {\n if (!isObject(data)) {\n return data;\n }\n\n return Object.keys(data).reduce(function(accumulator, key) {\n const isObj = isObject(data[key]);\n const value = isObj ? compactObject(data[key]) : data[key];\n const isEmptyObject = isObj && !Object.keys(value).length;\n if (value === undefined || isEmptyObject) {\n return accumulator;\n }\n return Object.assign(accumulator, {[key]: value});\n }, {});\n}\n\n/* iterates the stats graph recursively. */\nexport function walkStats(stats, base, resultSet) {\n if (!base || resultSet.has(base.id)) {\n return;\n }\n resultSet.set(base.id, base);\n Object.keys(base).forEach(name => {\n if (name.endsWith('Id')) {\n walkStats(stats, stats.get(base[name]), resultSet);\n } else if (name.endsWith('Ids')) {\n base[name].forEach(id => {\n walkStats(stats, stats.get(id), resultSet);\n });\n }\n });\n}\n\n/* filter getStats for a sender/receiver track. */\nexport function filterStats(result, track, outbound) {\n const streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp';\n const filteredResult = new Map();\n if (track === null) {\n return filteredResult;\n }\n const trackStats = [];\n result.forEach(value => {\n if (value.type === 'track' &&\n value.trackIdentifier === track.id) {\n trackStats.push(value);\n }\n });\n trackStats.forEach(trackStat => {\n result.forEach(stats => {\n if (stats.type === streamStatsType && stats.trackId === trackStat.id) {\n walkStats(result, stats, filteredResult);\n }\n });\n });\n return filteredResult;\n}\n\n","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n/* eslint-env node */\n'use strict';\nimport * as utils from '../utils.js';\nconst logging = utils.log;\n\nexport function shimGetUserMedia(window, browserDetails) {\n const navigator = window && window.navigator;\n\n if (!navigator.mediaDevices) {\n return;\n }\n\n const constraintsToChrome_ = function(c) {\n if (typeof c !== 'object' || c.mandatory || c.optional) {\n return c;\n }\n const cc = {};\n Object.keys(c).forEach(key => {\n if (key === 'require' || key === 'advanced' || key === 'mediaSource') {\n return;\n }\n const r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};\n if (r.exact !== undefined && typeof r.exact === 'number') {\n r.min = r.max = r.exact;\n }\n const oldname_ = function(prefix, name) {\n if (prefix) {\n return prefix + name.charAt(0).toUpperCase() + name.slice(1);\n }\n return (name === 'deviceId') ? 'sourceId' : name;\n };\n if (r.ideal !== undefined) {\n cc.optional = cc.optional || [];\n let oc = {};\n if (typeof r.ideal === 'number') {\n oc[oldname_('min', key)] = r.ideal;\n cc.optional.push(oc);\n oc = {};\n oc[oldname_('max', key)] = r.ideal;\n cc.optional.push(oc);\n } else {\n oc[oldname_('', key)] = r.ideal;\n cc.optional.push(oc);\n }\n }\n if (r.exact !== undefined && typeof r.exact !== 'number') {\n cc.mandatory = cc.mandatory || {};\n cc.mandatory[oldname_('', key)] = r.exact;\n } else {\n ['min', 'max'].forEach(mix => {\n if (r[mix] !== undefined) {\n cc.mandatory = cc.mandatory || {};\n cc.mandatory[oldname_(mix, key)] = r[mix];\n }\n });\n }\n });\n if (c.advanced) {\n cc.optional = (cc.optional || []).concat(c.advanced);\n }\n return cc;\n };\n\n const shimConstraints_ = function(constraints, func) {\n if (browserDetails.version >= 61) {\n return func(constraints);\n }\n constraints = JSON.parse(JSON.stringify(constraints));\n if (constraints && typeof constraints.audio === 'object') {\n const remap = function(obj, a, b) {\n if (a in obj && !(b in obj)) {\n obj[b] = obj[a];\n delete obj[a];\n }\n };\n constraints = JSON.parse(JSON.stringify(constraints));\n remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');\n remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');\n constraints.audio = constraintsToChrome_(constraints.audio);\n }\n if (constraints && typeof constraints.video === 'object') {\n // Shim facingMode for mobile & surface pro.\n let face = constraints.video.facingMode;\n face = face && ((typeof face === 'object') ? face : {ideal: face});\n const getSupportedFacingModeLies = browserDetails.version < 66;\n\n if ((face && (face.exact === 'user' || face.exact === 'environment' ||\n face.ideal === 'user' || face.ideal === 'environment')) &&\n !(navigator.mediaDevices.getSupportedConstraints &&\n navigator.mediaDevices.getSupportedConstraints().facingMode &&\n !getSupportedFacingModeLies)) {\n delete constraints.video.facingMode;\n let matches;\n if (face.exact === 'environment' || face.ideal === 'environment') {\n matches = ['back', 'rear'];\n } else if (face.exact === 'user' || face.ideal === 'user') {\n matches = ['front'];\n }\n if (matches) {\n // Look for matches in label, or use last cam for back (typical).\n return navigator.mediaDevices.enumerateDevices()\n .then(devices => {\n devices = devices.filter(d => d.kind === 'videoinput');\n let dev = devices.find(d => matches.some(match =>\n d.label.toLowerCase().includes(match)));\n if (!dev && devices.length && matches.includes('back')) {\n dev = devices[devices.length - 1]; // more likely the back cam\n }\n if (dev) {\n constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :\n {ideal: dev.deviceId};\n }\n constraints.video = constraintsToChrome_(constraints.video);\n logging('chrome: ' + JSON.stringify(constraints));\n return func(constraints);\n });\n }\n }\n constraints.video = constraintsToChrome_(constraints.video);\n }\n logging('chrome: ' + JSON.stringify(constraints));\n return func(constraints);\n };\n\n const shimError_ = function(e) {\n if (browserDetails.version >= 64) {\n return e;\n }\n return {\n name: {\n PermissionDeniedError: 'NotAllowedError',\n PermissionDismissedError: 'NotAllowedError',\n InvalidStateError: 'NotAllowedError',\n DevicesNotFoundError: 'NotFoundError',\n ConstraintNotSatisfiedError: 'OverconstrainedError',\n TrackStartError: 'NotReadableError',\n MediaDeviceFailedDueToShutdown: 'NotAllowedError',\n MediaDeviceKillSwitchOn: 'NotAllowedError',\n TabCaptureError: 'AbortError',\n ScreenCaptureError: 'AbortError',\n DeviceCaptureError: 'AbortError'\n }[e.name] || e.name,\n message: e.message,\n constraint: e.constraint || e.constraintName,\n toString() {\n return this.name + (this.message && ': ') + this.message;\n }\n };\n };\n\n const getUserMedia_ = function(constraints, onSuccess, onError) {\n shimConstraints_(constraints, c => {\n navigator.webkitGetUserMedia(c, onSuccess, e => {\n if (onError) {\n onError(shimError_(e));\n }\n });\n });\n };\n navigator.getUserMedia = getUserMedia_.bind(navigator);\n\n // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia\n // function which returns a Promise, it does not accept spec-style\n // constraints.\n if (navigator.mediaDevices.getUserMedia) {\n const origGetUserMedia = navigator.mediaDevices.getUserMedia.\n bind(navigator.mediaDevices);\n navigator.mediaDevices.getUserMedia = function(cs) {\n return shimConstraints_(cs, c => origGetUserMedia(c).then(stream => {\n if (c.audio && !stream.getAudioTracks().length ||\n c.video && !stream.getVideoTracks().length) {\n stream.getTracks().forEach(track => {\n track.stop();\n });\n throw new DOMException('', 'NotFoundError');\n }\n return stream;\n }, e => Promise.reject(shimError_(e))));\n };\n }\n}\n","/*\n * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n/* eslint-env node */\n'use strict';\nexport function shimGetDisplayMedia(window, getSourceId) {\n if (window.navigator.mediaDevices &&\n 'getDisplayMedia' in window.navigator.mediaDevices) {\n return;\n }\n if (!(window.navigator.mediaDevices)) {\n return;\n }\n // getSourceId is a function that returns a promise resolving with\n // the sourceId of the screen/window/tab to be shared.\n if (typeof getSourceId !== 'function') {\n console.error('shimGetDisplayMedia: getSourceId argument is not ' +\n 'a function');\n return;\n }\n window.navigator.mediaDevices.getDisplayMedia =\n function getDisplayMedia(constraints) {\n return getSourceId(constraints)\n .then(sourceId => {\n const widthSpecified = constraints.video && constraints.video.width;\n const heightSpecified = constraints.video &&\n constraints.video.height;\n const frameRateSpecified = constraints.video &&\n constraints.video.frameRate;\n constraints.video = {\n mandatory: {\n chromeMediaSource: 'desktop',\n chromeMediaSourceId: sourceId,\n maxFrameRate: frameRateSpecified || 3\n }\n };\n if (widthSpecified) {\n constraints.video.mandatory.maxWidth = widthSpecified;\n }\n if (heightSpecified) {\n constraints.video.mandatory.maxHeight = heightSpecified;\n }\n return window.navigator.mediaDevices.getUserMedia(constraints);\n });\n };\n}\n","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n /* eslint-env node */\n'use strict';\nimport * as utils from '../utils.js';\n\nexport {shimGetUserMedia} from './getusermedia';\nexport {shimGetDisplayMedia} from './getdisplaymedia';\n\nexport function shimMediaStream(window) {\n window.MediaStream = window.MediaStream || window.webkitMediaStream;\n}\n\nexport function shimOnTrack(window) {\n if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in\n window.RTCPeerConnection.prototype)) {\n Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {\n get() {\n return this._ontrack;\n },\n set(f) {\n if (this._ontrack) {\n this.removeEventListener('track', this._ontrack);\n }\n this.addEventListener('track', this._ontrack = f);\n },\n enumerable: true,\n configurable: true\n });\n const origSetRemoteDescription =\n window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription() {\n if (!this._ontrackpoly) {\n this._ontrackpoly = (e) => {\n // onaddstream does not fire when a track is added to an existing\n // stream. But stream.onaddtrack is implemented so we use that.\n e.stream.addEventListener('addtrack', te => {\n let receiver;\n if (window.RTCPeerConnection.prototype.getReceivers) {\n receiver = this.getReceivers()\n .find(r => r.track && r.track.id === te.track.id);\n } else {\n receiver = {track: te.track};\n }\n\n const event = new Event('track');\n event.track = te.track;\n event.receiver = receiver;\n event.transceiver = {receiver};\n event.streams = [e.stream];\n this.dispatchEvent(event);\n });\n e.stream.getTracks().forEach(track => {\n let receiver;\n if (window.RTCPeerConnection.prototype.getReceivers) {\n receiver = this.getReceivers()\n .find(r => r.track && r.track.id === track.id);\n } else {\n receiver = {track};\n }\n const event = new Event('track');\n event.track = track;\n event.receiver = receiver;\n event.transceiver = {receiver};\n event.streams = [e.stream];\n this.dispatchEvent(event);\n });\n };\n this.addEventListener('addstream', this._ontrackpoly);\n }\n return origSetRemoteDescription.apply(this, arguments);\n };\n } else {\n // even if RTCRtpTransceiver is in window, it is only used and\n // emitted in unified-plan. Unfortunately this means we need\n // to unconditionally wrap the event.\n utils.wrapPeerConnectionEvent(window, 'track', e => {\n if (!e.transceiver) {\n Object.defineProperty(e, 'transceiver',\n {value: {receiver: e.receiver}});\n }\n return e;\n });\n }\n}\n\nexport function shimGetSendersWithDtmf(window) {\n // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.\n if (typeof window === 'object' && window.RTCPeerConnection &&\n !('getSenders' in window.RTCPeerConnection.prototype) &&\n 'createDTMFSender' in window.RTCPeerConnection.prototype) {\n const shimSenderWithDtmf = function(pc, track) {\n return {\n track,\n get dtmf() {\n if (this._dtmf === undefined) {\n if (track.kind === 'audio') {\n this._dtmf = pc.createDTMFSender(track);\n } else {\n this._dtmf = null;\n }\n }\n return this._dtmf;\n },\n _pc: pc\n };\n };\n\n // augment addTrack when getSenders is not available.\n if (!window.RTCPeerConnection.prototype.getSenders) {\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n this._senders = this._senders || [];\n return this._senders.slice(); // return a copy of the internal state.\n };\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, stream) {\n let sender = origAddTrack.apply(this, arguments);\n if (!sender) {\n sender = shimSenderWithDtmf(this, track);\n this._senders.push(sender);\n }\n return sender;\n };\n\n const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;\n window.RTCPeerConnection.prototype.removeTrack =\n function removeTrack(sender) {\n origRemoveTrack.apply(this, arguments);\n const idx = this._senders.indexOf(sender);\n if (idx !== -1) {\n this._senders.splice(idx, 1);\n }\n };\n }\n const origAddStream = window.RTCPeerConnection.prototype.addStream;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n this._senders = this._senders || [];\n origAddStream.apply(this, [stream]);\n stream.getTracks().forEach(track => {\n this._senders.push(shimSenderWithDtmf(this, track));\n });\n };\n\n const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n this._senders = this._senders || [];\n origRemoveStream.apply(this, [stream]);\n\n stream.getTracks().forEach(track => {\n const sender = this._senders.find(s => s.track === track);\n if (sender) { // remove sender\n this._senders.splice(this._senders.indexOf(sender), 1);\n }\n });\n };\n } else if (typeof window === 'object' && window.RTCPeerConnection &&\n 'getSenders' in window.RTCPeerConnection.prototype &&\n 'createDTMFSender' in window.RTCPeerConnection.prototype &&\n window.RTCRtpSender &&\n !('dtmf' in window.RTCRtpSender.prototype)) {\n const origGetSenders = window.RTCPeerConnection.prototype.getSenders;\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n const senders = origGetSenders.apply(this, []);\n senders.forEach(sender => sender._pc = this);\n return senders;\n };\n\n Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {\n get() {\n if (this._dtmf === undefined) {\n if (this.track.kind === 'audio') {\n this._dtmf = this._pc.createDTMFSender(this.track);\n } else {\n this._dtmf = null;\n }\n }\n return this._dtmf;\n }\n });\n }\n}\n\nexport function shimGetStats(window) {\n if (!window.RTCPeerConnection) {\n return;\n }\n\n const origGetStats = window.RTCPeerConnection.prototype.getStats;\n window.RTCPeerConnection.prototype.getStats = function getStats() {\n const [selector, onSucc, onErr] = arguments;\n\n // If selector is a function then we are in the old style stats so just\n // pass back the original getStats format to avoid breaking old users.\n if (arguments.length > 0 && typeof selector === 'function') {\n return origGetStats.apply(this, arguments);\n }\n\n // When spec-style getStats is supported, return those when called with\n // either no arguments or the selector argument is null.\n if (origGetStats.length === 0 && (arguments.length === 0 ||\n typeof selector !== 'function')) {\n return origGetStats.apply(this, []);\n }\n\n const fixChromeStats_ = function(response) {\n const standardReport = {};\n const reports = response.result();\n reports.forEach(report => {\n const standardStats = {\n id: report.id,\n timestamp: report.timestamp,\n type: {\n localcandidate: 'local-candidate',\n remotecandidate: 'remote-candidate'\n }[report.type] || report.type\n };\n report.names().forEach(name => {\n standardStats[name] = report.stat(name);\n });\n standardReport[standardStats.id] = standardStats;\n });\n\n return standardReport;\n };\n\n // shim getStats with maplike support\n const makeMapStats = function(stats) {\n return new Map(Object.keys(stats).map(key => [key, stats[key]]));\n };\n\n if (arguments.length >= 2) {\n const successCallbackWrapper_ = function(response) {\n onSucc(makeMapStats(fixChromeStats_(response)));\n };\n\n return origGetStats.apply(this, [successCallbackWrapper_,\n selector]);\n }\n\n // promise-support\n return new Promise((resolve, reject) => {\n origGetStats.apply(this, [\n function(response) {\n resolve(makeMapStats(fixChromeStats_(response)));\n }, reject]);\n }).then(onSucc, onErr);\n };\n}\n\nexport function shimSenderReceiverGetStats(window) {\n if (!(typeof window === 'object' && window.RTCPeerConnection &&\n window.RTCRtpSender && window.RTCRtpReceiver)) {\n return;\n }\n\n // shim sender stats.\n if (!('getStats' in window.RTCRtpSender.prototype)) {\n const origGetSenders = window.RTCPeerConnection.prototype.getSenders;\n if (origGetSenders) {\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n const senders = origGetSenders.apply(this, []);\n senders.forEach(sender => sender._pc = this);\n return senders;\n };\n }\n\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n if (origAddTrack) {\n window.RTCPeerConnection.prototype.addTrack = function addTrack() {\n const sender = origAddTrack.apply(this, arguments);\n sender._pc = this;\n return sender;\n };\n }\n window.RTCRtpSender.prototype.getStats = function getStats() {\n const sender = this;\n return this._pc.getStats().then(result =>\n /* Note: this will include stats of all senders that\n * send a track with the same id as sender.track as\n * it is not possible to identify the RTCRtpSender.\n */\n utils.filterStats(result, sender.track, true));\n };\n }\n\n // shim receiver stats.\n if (!('getStats' in window.RTCRtpReceiver.prototype)) {\n const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;\n if (origGetReceivers) {\n window.RTCPeerConnection.prototype.getReceivers =\n function getReceivers() {\n const receivers = origGetReceivers.apply(this, []);\n receivers.forEach(receiver => receiver._pc = this);\n return receivers;\n };\n }\n utils.wrapPeerConnectionEvent(window, 'track', e => {\n e.receiver._pc = e.srcElement;\n return e;\n });\n window.RTCRtpReceiver.prototype.getStats = function getStats() {\n const receiver = this;\n return this._pc.getStats().then(result =>\n utils.filterStats(result, receiver.track, false));\n };\n }\n\n if (!('getStats' in window.RTCRtpSender.prototype &&\n 'getStats' in window.RTCRtpReceiver.prototype)) {\n return;\n }\n\n // shim RTCPeerConnection.getStats(track).\n const origGetStats = window.RTCPeerConnection.prototype.getStats;\n window.RTCPeerConnection.prototype.getStats = function getStats() {\n if (arguments.length > 0 &&\n arguments[0] instanceof window.MediaStreamTrack) {\n const track = arguments[0];\n let sender;\n let receiver;\n let err;\n this.getSenders().forEach(s => {\n if (s.track === track) {\n if (sender) {\n err = true;\n } else {\n sender = s;\n }\n }\n });\n this.getReceivers().forEach(r => {\n if (r.track === track) {\n if (receiver) {\n err = true;\n } else {\n receiver = r;\n }\n }\n return r.track === track;\n });\n if (err || (sender && receiver)) {\n return Promise.reject(new DOMException(\n 'There are more than one sender or receiver for the track.',\n 'InvalidAccessError'));\n } else if (sender) {\n return sender.getStats();\n } else if (receiver) {\n return receiver.getStats();\n }\n return Promise.reject(new DOMException(\n 'There is no sender or receiver for the track.',\n 'InvalidAccessError'));\n }\n return origGetStats.apply(this, arguments);\n };\n}\n\nexport function shimAddTrackRemoveTrackWithNative(window) {\n // shim addTrack/removeTrack with native variants in order to make\n // the interactions with legacy getLocalStreams behave as in other browsers.\n // Keeps a mapping stream.id => [stream, rtpsenders...]\n window.RTCPeerConnection.prototype.getLocalStreams =\n function getLocalStreams() {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n return Object.keys(this._shimmedLocalStreams)\n .map(streamId => this._shimmedLocalStreams[streamId][0]);\n };\n\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, stream) {\n if (!stream) {\n return origAddTrack.apply(this, arguments);\n }\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n\n const sender = origAddTrack.apply(this, arguments);\n if (!this._shimmedLocalStreams[stream.id]) {\n this._shimmedLocalStreams[stream.id] = [stream, sender];\n } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) {\n this._shimmedLocalStreams[stream.id].push(sender);\n }\n return sender;\n };\n\n const origAddStream = window.RTCPeerConnection.prototype.addStream;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n\n stream.getTracks().forEach(track => {\n const alreadyExists = this.getSenders().find(s => s.track === track);\n if (alreadyExists) {\n throw new DOMException('Track already exists.',\n 'InvalidAccessError');\n }\n });\n const existingSenders = this.getSenders();\n origAddStream.apply(this, arguments);\n const newSenders = this.getSenders()\n .filter(newSender => existingSenders.indexOf(newSender) === -1);\n this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders);\n };\n\n const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n delete this._shimmedLocalStreams[stream.id];\n return origRemoveStream.apply(this, arguments);\n };\n\n const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;\n window.RTCPeerConnection.prototype.removeTrack =\n function removeTrack(sender) {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n if (sender) {\n Object.keys(this._shimmedLocalStreams).forEach(streamId => {\n const idx = this._shimmedLocalStreams[streamId].indexOf(sender);\n if (idx !== -1) {\n this._shimmedLocalStreams[streamId].splice(idx, 1);\n }\n if (this._shimmedLocalStreams[streamId].length === 1) {\n delete this._shimmedLocalStreams[streamId];\n }\n });\n }\n return origRemoveTrack.apply(this, arguments);\n };\n}\n\nexport function shimAddTrackRemoveTrack(window, browserDetails) {\n if (!window.RTCPeerConnection) {\n return;\n }\n // shim addTrack and removeTrack.\n if (window.RTCPeerConnection.prototype.addTrack &&\n browserDetails.version >= 65) {\n return shimAddTrackRemoveTrackWithNative(window);\n }\n\n // also shim pc.getLocalStreams when addTrack is shimmed\n // to return the original streams.\n const origGetLocalStreams = window.RTCPeerConnection.prototype\n .getLocalStreams;\n window.RTCPeerConnection.prototype.getLocalStreams =\n function getLocalStreams() {\n const nativeStreams = origGetLocalStreams.apply(this);\n this._reverseStreams = this._reverseStreams || {};\n return nativeStreams.map(stream => this._reverseStreams[stream.id]);\n };\n\n const origAddStream = window.RTCPeerConnection.prototype.addStream;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n this._streams = this._streams || {};\n this._reverseStreams = this._reverseStreams || {};\n\n stream.getTracks().forEach(track => {\n const alreadyExists = this.getSenders().find(s => s.track === track);\n if (alreadyExists) {\n throw new DOMException('Track already exists.',\n 'InvalidAccessError');\n }\n });\n // Add identity mapping for consistency with addTrack.\n // Unless this is being used with a stream from addTrack.\n if (!this._reverseStreams[stream.id]) {\n const newStream = new window.MediaStream(stream.getTracks());\n this._streams[stream.id] = newStream;\n this._reverseStreams[newStream.id] = stream;\n stream = newStream;\n }\n origAddStream.apply(this, [stream]);\n };\n\n const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n this._streams = this._streams || {};\n this._reverseStreams = this._reverseStreams || {};\n\n origRemoveStream.apply(this, [(this._streams[stream.id] || stream)]);\n delete this._reverseStreams[(this._streams[stream.id] ?\n this._streams[stream.id].id : stream.id)];\n delete this._streams[stream.id];\n };\n\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, stream) {\n if (this.signalingState === 'closed') {\n throw new DOMException(\n 'The RTCPeerConnection\\'s signalingState is \\'closed\\'.',\n 'InvalidStateError');\n }\n const streams = [].slice.call(arguments, 1);\n if (streams.length !== 1 ||\n !streams[0].getTracks().find(t => t === track)) {\n // this is not fully correct but all we can manage without\n // [[associated MediaStreams]] internal slot.\n throw new DOMException(\n 'The adapter.js addTrack polyfill only supports a single ' +\n ' stream which is associated with the specified track.',\n 'NotSupportedError');\n }\n\n const alreadyExists = this.getSenders().find(s => s.track === track);\n if (alreadyExists) {\n throw new DOMException('Track already exists.',\n 'InvalidAccessError');\n }\n\n this._streams = this._streams || {};\n this._reverseStreams = this._reverseStreams || {};\n const oldStream = this._streams[stream.id];\n if (oldStream) {\n // this is using odd Chrome behaviour, use with caution:\n // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815\n // Note: we rely on the high-level addTrack/dtmf shim to\n // create the sender with a dtmf sender.\n oldStream.addTrack(track);\n\n // Trigger ONN async.\n Promise.resolve().then(() => {\n this.dispatchEvent(new Event('negotiationneeded'));\n });\n } else {\n const newStream = new window.MediaStream([track]);\n this._streams[stream.id] = newStream;\n this._reverseStreams[newStream.id] = stream;\n this.addStream(newStream);\n }\n return this.getSenders().find(s => s.track === track);\n };\n\n // replace the internal stream id with the external one and\n // vice versa.\n function replaceInternalStreamId(pc, description) {\n let sdp = description.sdp;\n Object.keys(pc._reverseStreams || []).forEach(internalId => {\n const externalStream = pc._reverseStreams[internalId];\n const internalStream = pc._streams[externalStream.id];\n sdp = sdp.replace(new RegExp(internalStream.id, 'g'),\n externalStream.id);\n });\n return new RTCSessionDescription({\n type: description.type,\n sdp\n });\n }\n function replaceExternalStreamId(pc, description) {\n let sdp = description.sdp;\n Object.keys(pc._reverseStreams || []).forEach(internalId => {\n const externalStream = pc._reverseStreams[internalId];\n const internalStream = pc._streams[externalStream.id];\n sdp = sdp.replace(new RegExp(externalStream.id, 'g'),\n internalStream.id);\n });\n return new RTCSessionDescription({\n type: description.type,\n sdp\n });\n }\n ['createOffer', 'createAnswer'].forEach(function(method) {\n const nativeMethod = window.RTCPeerConnection.prototype[method];\n const methodObj = {[method]() {\n const args = arguments;\n const isLegacyCall = arguments.length &&\n typeof arguments[0] === 'function';\n if (isLegacyCall) {\n return nativeMethod.apply(this, [\n (description) => {\n const desc = replaceInternalStreamId(this, description);\n args[0].apply(null, [desc]);\n },\n (err) => {\n if (args[1]) {\n args[1].apply(null, err);\n }\n }, arguments[2]\n ]);\n }\n return nativeMethod.apply(this, arguments)\n .then(description => replaceInternalStreamId(this, description));\n }};\n window.RTCPeerConnection.prototype[method] = methodObj[method];\n });\n\n const origSetLocalDescription =\n window.RTCPeerConnection.prototype.setLocalDescription;\n window.RTCPeerConnection.prototype.setLocalDescription =\n function setLocalDescription() {\n if (!arguments.length || !arguments[0].type) {\n return origSetLocalDescription.apply(this, arguments);\n }\n arguments[0] = replaceExternalStreamId(this, arguments[0]);\n return origSetLocalDescription.apply(this, arguments);\n };\n\n // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier\n\n const origLocalDescription = Object.getOwnPropertyDescriptor(\n window.RTCPeerConnection.prototype, 'localDescription');\n Object.defineProperty(window.RTCPeerConnection.prototype,\n 'localDescription', {\n get() {\n const description = origLocalDescription.get.apply(this);\n if (description.type === '') {\n return description;\n }\n return replaceInternalStreamId(this, description);\n }\n });\n\n window.RTCPeerConnection.prototype.removeTrack =\n function removeTrack(sender) {\n if (this.signalingState === 'closed') {\n throw new DOMException(\n 'The RTCPeerConnection\\'s signalingState is \\'closed\\'.',\n 'InvalidStateError');\n }\n // We can not yet check for sender instanceof RTCRtpSender\n // since we shim RTPSender. So we check if sender._pc is set.\n if (!sender._pc) {\n throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' +\n 'does not implement interface RTCRtpSender.', 'TypeError');\n }\n const isLocal = sender._pc === this;\n if (!isLocal) {\n throw new DOMException('Sender was not created by this connection.',\n 'InvalidAccessError');\n }\n\n // Search for the native stream the senders track belongs to.\n this._streams = this._streams || {};\n let stream;\n Object.keys(this._streams).forEach(streamid => {\n const hasTrack = this._streams[streamid].getTracks()\n .find(track => sender.track === track);\n if (hasTrack) {\n stream = this._streams[streamid];\n }\n });\n\n if (stream) {\n if (stream.getTracks().length === 1) {\n // if this is the last track of the stream, remove the stream. This\n // takes care of any shimmed _senders.\n this.removeStream(this._reverseStreams[stream.id]);\n } else {\n // relying on the same odd chrome behaviour as above.\n stream.removeTrack(sender.track);\n }\n this.dispatchEvent(new Event('negotiationneeded'));\n }\n };\n}\n\nexport function shimPeerConnection(window, browserDetails) {\n if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {\n // very basic support for old versions.\n window.RTCPeerConnection = window.webkitRTCPeerConnection;\n }\n if (!window.RTCPeerConnection) {\n return;\n }\n\n // shim implicit creation of RTCSessionDescription/RTCIceCandidate\n if (browserDetails.version < 53) {\n ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']\n .forEach(function(method) {\n const nativeMethod = window.RTCPeerConnection.prototype[method];\n const methodObj = {[method]() {\n arguments[0] = new ((method === 'addIceCandidate') ?\n window.RTCIceCandidate :\n window.RTCSessionDescription)(arguments[0]);\n return nativeMethod.apply(this, arguments);\n }};\n window.RTCPeerConnection.prototype[method] = methodObj[method];\n });\n }\n}\n\n// Attempt to fix ONN in plan-b mode.\nexport function fixNegotiationNeeded(window, browserDetails) {\n utils.wrapPeerConnectionEvent(window, 'negotiationneeded', e => {\n const pc = e.target;\n if (browserDetails.version < 72 || (pc.getConfiguration &&\n pc.getConfiguration().sdpSemantics === 'plan-b')) {\n if (pc.signalingState !== 'stable') {\n return;\n }\n }\n return e;\n });\n}\n","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n/* eslint-env node */\n'use strict';\n\nimport * as utils from '../utils';\n\nexport function shimGetUserMedia(window, browserDetails) {\n const navigator = window && window.navigator;\n const MediaStreamTrack = window && window.MediaStreamTrack;\n\n navigator.getUserMedia = function(constraints, onSuccess, onError) {\n // Replace Firefox 44+'s deprecation warning with unprefixed version.\n utils.deprecated('navigator.getUserMedia',\n 'navigator.mediaDevices.getUserMedia');\n navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);\n };\n\n if (!(browserDetails.version > 55 &&\n 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {\n const remap = function(obj, a, b) {\n if (a in obj && !(b in obj)) {\n obj[b] = obj[a];\n delete obj[a];\n }\n };\n\n const nativeGetUserMedia = navigator.mediaDevices.getUserMedia.\n bind(navigator.mediaDevices);\n navigator.mediaDevices.getUserMedia = function(c) {\n if (typeof c === 'object' && typeof c.audio === 'object') {\n c = JSON.parse(JSON.stringify(c));\n remap(c.audio, 'autoGainControl', 'mozAutoGainControl');\n remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');\n }\n return nativeGetUserMedia(c);\n };\n\n if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {\n const nativeGetSettings = MediaStreamTrack.prototype.getSettings;\n MediaStreamTrack.prototype.getSettings = function() {\n const obj = nativeGetSettings.apply(this, arguments);\n remap(obj, 'mozAutoGainControl', 'autoGainControl');\n remap(obj, 'mozNoiseSuppression', 'noiseSuppression');\n return obj;\n };\n }\n\n if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {\n const nativeApplyConstraints =\n MediaStreamTrack.prototype.applyConstraints;\n MediaStreamTrack.prototype.applyConstraints = function(c) {\n if (this.kind === 'audio' && typeof c === 'object') {\n c = JSON.parse(JSON.stringify(c));\n remap(c, 'autoGainControl', 'mozAutoGainControl');\n remap(c, 'noiseSuppression', 'mozNoiseSuppression');\n }\n return nativeApplyConstraints.apply(this, [c]);\n };\n }\n }\n}\n","/*\n * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n/* eslint-env node */\n'use strict';\n\nexport function shimGetDisplayMedia(window, preferredMediaSource) {\n if (window.navigator.mediaDevices &&\n 'getDisplayMedia' in window.navigator.mediaDevices) {\n return;\n }\n if (!(window.navigator.mediaDevices)) {\n return;\n }\n window.navigator.mediaDevices.getDisplayMedia =\n function getDisplayMedia(constraints) {\n if (!(constraints && constraints.video)) {\n const err = new DOMException('getDisplayMedia without video ' +\n 'constraints is undefined');\n err.name = 'NotFoundError';\n // from https://heycam.github.io/webidl/#idl-DOMException-error-names\n err.code = 8;\n return Promise.reject(err);\n }\n if (constraints.video === true) {\n constraints.video = {mediaSource: preferredMediaSource};\n } else {\n constraints.video.mediaSource = preferredMediaSource;\n }\n return window.navigator.mediaDevices.getUserMedia(constraints);\n };\n}\n","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n/* eslint-env node */\n'use strict';\n\nimport * as utils from '../utils';\nexport {shimGetUserMedia} from './getusermedia';\nexport {shimGetDisplayMedia} from './getdisplaymedia';\n\nexport function shimOnTrack(window) {\n if (typeof window === 'object' && window.RTCTrackEvent &&\n ('receiver' in window.RTCTrackEvent.prototype) &&\n !('transceiver' in window.RTCTrackEvent.prototype)) {\n Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {\n get() {\n return {receiver: this.receiver};\n }\n });\n }\n}\n\nexport function shimPeerConnection(window, browserDetails) {\n if (typeof window !== 'object' ||\n !(window.RTCPeerConnection || window.mozRTCPeerConnection)) {\n return; // probably media.peerconnection.enabled=false in about:config\n }\n if (!window.RTCPeerConnection && window.mozRTCPeerConnection) {\n // very basic support for old versions.\n window.RTCPeerConnection = window.mozRTCPeerConnection;\n }\n\n if (browserDetails.version < 53) {\n // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.\n ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']\n .forEach(function(method) {\n const nativeMethod = window.RTCPeerConnection.prototype[method];\n const methodObj = {[method]() {\n arguments[0] = new ((method === 'addIceCandidate') ?\n window.RTCIceCandidate :\n window.RTCSessionDescription)(arguments[0]);\n return nativeMethod.apply(this, arguments);\n }};\n window.RTCPeerConnection.prototype[method] = methodObj[method];\n });\n }\n\n const modernStatsTypes = {\n inboundrtp: 'inbound-rtp',\n outboundrtp: 'outbound-rtp',\n candidatepair: 'candidate-pair',\n localcandidate: 'local-candidate',\n remotecandidate: 'remote-candidate'\n };\n\n const nativeGetStats = window.RTCPeerConnection.prototype.getStats;\n window.RTCPeerConnection.prototype.getStats = function getStats() {\n const [selector, onSucc, onErr] = arguments;\n return nativeGetStats.apply(this, [selector || null])\n .then(stats => {\n if (browserDetails.version < 53 && !onSucc) {\n // Shim only promise getStats with spec-hyphens in type names\n // Leave callback version alone; misc old uses of forEach before Map\n try {\n stats.forEach(stat => {\n stat.type = modernStatsTypes[stat.type] || stat.type;\n });\n } catch (e) {\n if (e.name !== 'TypeError') {\n throw e;\n }\n // Avoid TypeError: \"type\" is read-only, in old versions. 34-43ish\n stats.forEach((stat, i) => {\n stats.set(i, Object.assign({}, stat, {\n type: modernStatsTypes[stat.type] || stat.type\n }));\n });\n }\n }\n return stats;\n })\n .then(onSucc, onErr);\n };\n}\n\nexport function shimSenderGetStats(window) {\n if (!(typeof window === 'object' && window.RTCPeerConnection &&\n window.RTCRtpSender)) {\n return;\n }\n if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) {\n return;\n }\n const origGetSenders = window.RTCPeerConnection.prototype.getSenders;\n if (origGetSenders) {\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n const senders = origGetSenders.apply(this, []);\n senders.forEach(sender => sender._pc = this);\n return senders;\n };\n }\n\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n if (origAddTrack) {\n window.RTCPeerConnection.prototype.addTrack = function addTrack() {\n const sender = origAddTrack.apply(this, arguments);\n sender._pc = this;\n return sender;\n };\n }\n window.RTCRtpSender.prototype.getStats = function getStats() {\n return this.track ? this._pc.getStats(this.track) :\n Promise.resolve(new Map());\n };\n}\n\nexport function shimReceiverGetStats(window) {\n if (!(typeof window === 'object' && window.RTCPeerConnection &&\n window.RTCRtpSender)) {\n return;\n }\n if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) {\n return;\n }\n const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;\n if (origGetReceivers) {\n window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {\n const receivers = origGetReceivers.apply(this, []);\n receivers.forEach(receiver => receiver._pc = this);\n return receivers;\n };\n }\n utils.wrapPeerConnectionEvent(window, 'track', e => {\n e.receiver._pc = e.srcElement;\n return e;\n });\n window.RTCRtpReceiver.prototype.getStats = function getStats() {\n return this._pc.getStats(this.track);\n };\n}\n\nexport function shimRemoveStream(window) {\n if (!window.RTCPeerConnection ||\n 'removeStream' in window.RTCPeerConnection.prototype) {\n return;\n }\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n utils.deprecated('removeStream', 'removeTrack');\n this.getSenders().forEach(sender => {\n if (sender.track && stream.getTracks().includes(sender.track)) {\n this.removeTrack(sender);\n }\n });\n };\n}\n\nexport function shimRTCDataChannel(window) {\n // rename DataChannel to RTCDataChannel (native fix in FF60):\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851\n if (window.DataChannel && !window.RTCDataChannel) {\n window.RTCDataChannel = window.DataChannel;\n }\n}\n\nexport function shimAddTransceiver(window) {\n // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647\n // Firefox ignores the init sendEncodings options passed to addTransceiver\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918\n if (!(typeof window === 'object' && window.RTCPeerConnection)) {\n return;\n }\n const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver;\n if (origAddTransceiver) {\n window.RTCPeerConnection.prototype.addTransceiver =\n function addTransceiver() {\n this.setParametersPromises = [];\n const initParameters = arguments[1];\n const shouldPerformCheck = initParameters &&\n 'sendEncodings' in initParameters;\n if (shouldPerformCheck) {\n // If sendEncodings params are provided, validate grammar\n initParameters.sendEncodings.forEach((encodingParam) => {\n if ('rid' in encodingParam) {\n const ridRegex = /^[a-z0-9]{0,16}$/i;\n if (!ridRegex.test(encodingParam.rid)) {\n throw new TypeError('Invalid RID value provided.');\n }\n }\n if ('scaleResolutionDownBy' in encodingParam) {\n if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) {\n throw new RangeError('scale_resolution_down_by must be >= 1.0');\n }\n }\n if ('maxFramerate' in encodingParam) {\n if (!(parseFloat(encodingParam.maxFramerate) >= 0)) {\n throw new RangeError('max_framerate must be >= 0.0');\n }\n }\n });\n }\n const transceiver = origAddTransceiver.apply(this, arguments);\n if (shouldPerformCheck) {\n // Check if the init options were applied. If not we do this in an\n // asynchronous way and save the promise reference in a global object.\n // This is an ugly hack, but at the same time is way more robust than\n // checking the sender parameters before and after the createOffer\n // Also note that after the createoffer we are not 100% sure that\n // the params were asynchronously applied so we might miss the\n // opportunity to recreate offer.\n const {sender} = transceiver;\n const params = sender.getParameters();\n if (!('encodings' in params) ||\n // Avoid being fooled by patched getParameters() below.\n (params.encodings.length === 1 &&\n Object.keys(params.encodings[0]).length === 0)) {\n params.encodings = initParameters.sendEncodings;\n sender.sendEncodings = initParameters.sendEncodings;\n this.setParametersPromises.push(sender.setParameters(params)\n .then(() => {\n delete sender.sendEncodings;\n }).catch(() => {\n delete sender.sendEncodings;\n })\n );\n }\n }\n return transceiver;\n };\n }\n}\n\nexport function shimGetParameters(window) {\n if (!(typeof window === 'object' && window.RTCRtpSender)) {\n return;\n }\n const origGetParameters = window.RTCRtpSender.prototype.getParameters;\n if (origGetParameters) {\n window.RTCRtpSender.prototype.getParameters =\n function getParameters() {\n const params = origGetParameters.apply(this, arguments);\n if (!('encodings' in params)) {\n params.encodings = [].concat(this.sendEncodings || [{}]);\n }\n return params;\n };\n }\n}\n\nexport function shimCreateOffer(window) {\n // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647\n // Firefox ignores the init sendEncodings options passed to addTransceiver\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918\n if (!(typeof window === 'object' && window.RTCPeerConnection)) {\n return;\n }\n const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;\n window.RTCPeerConnection.prototype.createOffer = function createOffer() {\n if (this.setParametersPromises && this.setParametersPromises.length) {\n return Promise.all(this.setParametersPromises)\n .then(() => {\n return origCreateOffer.apply(this, arguments);\n })\n .finally(() => {\n this.setParametersPromises = [];\n });\n }\n return origCreateOffer.apply(this, arguments);\n };\n}\n\nexport function shimCreateAnswer(window) {\n // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647\n // Firefox ignores the init sendEncodings options passed to addTransceiver\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918\n if (!(typeof window === 'object' && window.RTCPeerConnection)) {\n return;\n }\n const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer;\n window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {\n if (this.setParametersPromises && this.setParametersPromises.length) {\n return Promise.all(this.setParametersPromises)\n .then(() => {\n return origCreateAnswer.apply(this, arguments);\n })\n .finally(() => {\n this.setParametersPromises = [];\n });\n }\n return origCreateAnswer.apply(this, arguments);\n };\n}\n","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n'use strict';\nimport * as utils from '../utils';\n\nexport function shimLocalStreamsAPI(window) {\n if (typeof window !== 'object' || !window.RTCPeerConnection) {\n return;\n }\n if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {\n window.RTCPeerConnection.prototype.getLocalStreams =\n function getLocalStreams() {\n if (!this._localStreams) {\n this._localStreams = [];\n }\n return this._localStreams;\n };\n }\n if (!('addStream' in window.RTCPeerConnection.prototype)) {\n const _addTrack = window.RTCPeerConnection.prototype.addTrack;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n if (!this._localStreams) {\n this._localStreams = [];\n }\n if (!this._localStreams.includes(stream)) {\n this._localStreams.push(stream);\n }\n // Try to emulate Chrome's behaviour of adding in audio-video order.\n // Safari orders by track id.\n stream.getAudioTracks().forEach(track => _addTrack.call(this, track,\n stream));\n stream.getVideoTracks().forEach(track => _addTrack.call(this, track,\n stream));\n };\n\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, ...streams) {\n if (streams) {\n streams.forEach((stream) => {\n if (!this._localStreams) {\n this._localStreams = [stream];\n } else if (!this._localStreams.includes(stream)) {\n this._localStreams.push(stream);\n }\n });\n }\n return _addTrack.apply(this, arguments);\n };\n }\n if (!('removeStream' in window.RTCPeerConnection.prototype)) {\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n if (!this._localStreams) {\n this._localStreams = [];\n }\n const index = this._localStreams.indexOf(stream);\n if (index === -1) {\n return;\n }\n this._localStreams.splice(index, 1);\n const tracks = stream.getTracks();\n this.getSenders().forEach(sender => {\n if (tracks.includes(sender.track)) {\n this.removeTrack(sender);\n }\n });\n };\n }\n}\n\nexport function shimRemoteStreamsAPI(window) {\n if (typeof window !== 'object' || !window.RTCPeerConnection) {\n return;\n }\n if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {\n window.RTCPeerConnection.prototype.getRemoteStreams =\n function getRemoteStreams() {\n return this._remoteStreams ? this._remoteStreams : [];\n };\n }\n if (!('onaddstream' in window.RTCPeerConnection.prototype)) {\n Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {\n get() {\n return this._onaddstream;\n },\n set(f) {\n if (this._onaddstream) {\n this.removeEventListener('addstream', this._onaddstream);\n this.removeEventListener('track', this._onaddstreampoly);\n }\n this.addEventListener('addstream', this._onaddstream = f);\n this.addEventListener('track', this._onaddstreampoly = (e) => {\n e.streams.forEach(stream => {\n if (!this._remoteStreams) {\n this._remoteStreams = [];\n }\n if (this._remoteStreams.includes(stream)) {\n return;\n }\n this._remoteStreams.push(stream);\n const event = new Event('addstream');\n event.stream = stream;\n this.dispatchEvent(event);\n });\n });\n }\n });\n const origSetRemoteDescription =\n window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription() {\n const pc = this;\n if (!this._onaddstreampoly) {\n this.addEventListener('track', this._onaddstreampoly = function(e) {\n e.streams.forEach(stream => {\n if (!pc._remoteStreams) {\n pc._remoteStreams = [];\n }\n if (pc._remoteStreams.indexOf(stream) >= 0) {\n return;\n }\n pc._remoteStreams.push(stream);\n const event = new Event('addstream');\n event.stream = stream;\n pc.dispatchEvent(event);\n });\n });\n }\n return origSetRemoteDescription.apply(pc, arguments);\n };\n }\n}\n\nexport function shimCallbacksAPI(window) {\n if (typeof window !== 'object' || !window.RTCPeerConnection) {\n return;\n }\n const prototype = window.RTCPeerConnection.prototype;\n const origCreateOffer = prototype.createOffer;\n const origCreateAnswer = prototype.createAnswer;\n const setLocalDescription = prototype.setLocalDescription;\n const setRemoteDescription = prototype.setRemoteDescription;\n const addIceCandidate = prototype.addIceCandidate;\n\n prototype.createOffer =\n function createOffer(successCallback, failureCallback) {\n const options = (arguments.length >= 2) ? arguments[2] : arguments[0];\n const promise = origCreateOffer.apply(this, [options]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n\n prototype.createAnswer =\n function createAnswer(successCallback, failureCallback) {\n const options = (arguments.length >= 2) ? arguments[2] : arguments[0];\n const promise = origCreateAnswer.apply(this, [options]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n\n let withCallback = function(description, successCallback, failureCallback) {\n const promise = setLocalDescription.apply(this, [description]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n prototype.setLocalDescription = withCallback;\n\n withCallback = function(description, successCallback, failureCallback) {\n const promise = setRemoteDescription.apply(this, [description]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n prototype.setRemoteDescription = withCallback;\n\n withCallback = function(candidate, successCallback, failureCallback) {\n const promise = addIceCandidate.apply(this, [candidate]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n prototype.addIceCandidate = withCallback;\n}\n\nexport function shimGetUserMedia(window) {\n const navigator = window && window.navigator;\n\n if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {\n // shim not needed in Safari 12.1\n const mediaDevices = navigator.mediaDevices;\n const _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices);\n navigator.mediaDevices.getUserMedia = (constraints) => {\n return _getUserMedia(shimConstraints(constraints));\n };\n }\n\n if (!navigator.getUserMedia && navigator.mediaDevices &&\n navigator.mediaDevices.getUserMedia) {\n navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) {\n navigator.mediaDevices.getUserMedia(constraints)\n .then(cb, errcb);\n }.bind(navigator);\n }\n}\n\nexport function shimConstraints(constraints) {\n if (constraints && constraints.video !== undefined) {\n return Object.assign({},\n constraints,\n {video: utils.compactObject(constraints.video)}\n );\n }\n\n return constraints;\n}\n\nexport function shimRTCIceServerUrls(window) {\n if (!window.RTCPeerConnection) {\n return;\n }\n // migrate from non-spec RTCIceServer.url to RTCIceServer.urls\n const OrigPeerConnection = window.RTCPeerConnection;\n window.RTCPeerConnection =\n function RTCPeerConnection(pcConfig, pcConstraints) {\n if (pcConfig && pcConfig.iceServers) {\n const newIceServers = [];\n for (let i = 0; i < pcConfig.iceServers.length; i++) {\n let server = pcConfig.iceServers[i];\n if (!server.hasOwnProperty('urls') &&\n server.hasOwnProperty('url')) {\n utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls');\n server = JSON.parse(JSON.stringify(server));\n server.urls = server.url;\n delete server.url;\n newIceServers.push(server);\n } else {\n newIceServers.push(pcConfig.iceServers[i]);\n }\n }\n pcConfig.iceServers = newIceServers;\n }\n return new OrigPeerConnection(pcConfig, pcConstraints);\n };\n window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;\n // wrap static methods. Currently just generateCertificate.\n if ('generateCertificate' in OrigPeerConnection) {\n Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {\n get() {\n return OrigPeerConnection.generateCertificate;\n }\n });\n }\n}\n\nexport function shimTrackEventTransceiver(window) {\n // Add event.transceiver member over deprecated event.receiver\n if (typeof window === 'object' && window.RTCTrackEvent &&\n 'receiver' in window.RTCTrackEvent.prototype &&\n !('transceiver' in window.RTCTrackEvent.prototype)) {\n Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {\n get() {\n return {receiver: this.receiver};\n }\n });\n }\n}\n\nexport function shimCreateOfferLegacy(window) {\n const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;\n window.RTCPeerConnection.prototype.createOffer =\n function createOffer(offerOptions) {\n if (offerOptions) {\n if (typeof offerOptions.offerToReceiveAudio !== 'undefined') {\n // support bit values\n offerOptions.offerToReceiveAudio =\n !!offerOptions.offerToReceiveAudio;\n }\n const audioTransceiver = this.getTransceivers().find(transceiver =>\n transceiver.receiver.track.kind === 'audio');\n if (offerOptions.offerToReceiveAudio === false && audioTransceiver) {\n if (audioTransceiver.direction === 'sendrecv') {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection('sendonly');\n } else {\n audioTransceiver.direction = 'sendonly';\n }\n } else if (audioTransceiver.direction === 'recvonly') {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection('inactive');\n } else {\n audioTransceiver.direction = 'inactive';\n }\n }\n } else if (offerOptions.offerToReceiveAudio === true &&\n !audioTransceiver) {\n this.addTransceiver('audio');\n }\n\n if (typeof offerOptions.offerToReceiveVideo !== 'undefined') {\n // support bit values\n offerOptions.offerToReceiveVideo =\n !!offerOptions.offerToReceiveVideo;\n }\n const videoTransceiver = this.getTransceivers().find(transceiver =>\n transceiver.receiver.track.kind === 'video');\n if (offerOptions.offerToReceiveVideo === false && videoTransceiver) {\n if (videoTransceiver.direction === 'sendrecv') {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection('sendonly');\n } else {\n videoTransceiver.direction = 'sendonly';\n }\n } else if (videoTransceiver.direction === 'recvonly') {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection('inactive');\n } else {\n videoTransceiver.direction = 'inactive';\n }\n }\n } else if (offerOptions.offerToReceiveVideo === true &&\n !videoTransceiver) {\n this.addTransceiver('video');\n }\n }\n return origCreateOffer.apply(this, arguments);\n };\n}\n\nexport function shimAudioContext(window) {\n if (typeof window !== 'object' || window.AudioContext) {\n return;\n }\n window.AudioContext = window.webkitAudioContext;\n}\n","/*\n * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n/* eslint-env node */\n'use strict';\n\nimport SDPUtils from 'sdp';\nimport * as utils from './utils';\n\nexport function shimRTCIceCandidate(window) {\n // foundation is arbitrarily chosen as an indicator for full support for\n // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface\n if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in\n window.RTCIceCandidate.prototype)) {\n return;\n }\n\n const NativeRTCIceCandidate = window.RTCIceCandidate;\n window.RTCIceCandidate = function RTCIceCandidate(args) {\n // Remove the a= which shouldn't be part of the candidate string.\n if (typeof args === 'object' && args.candidate &&\n args.candidate.indexOf('a=') === 0) {\n args = JSON.parse(JSON.stringify(args));\n args.candidate = args.candidate.substr(2);\n }\n\n if (args.candidate && args.candidate.length) {\n // Augment the native candidate with the parsed fields.\n const nativeCandidate = new NativeRTCIceCandidate(args);\n const parsedCandidate = SDPUtils.parseCandidate(args.candidate);\n const augmentedCandidate = Object.assign(nativeCandidate,\n parsedCandidate);\n\n // Add a serializer that does not serialize the extra attributes.\n augmentedCandidate.toJSON = function toJSON() {\n return {\n candidate: augmentedCandidate.candidate,\n sdpMid: augmentedCandidate.sdpMid,\n sdpMLineIndex: augmentedCandidate.sdpMLineIndex,\n usernameFragment: augmentedCandidate.usernameFragment,\n };\n };\n return augmentedCandidate;\n }\n return new NativeRTCIceCandidate(args);\n };\n window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;\n\n // Hook up the augmented candidate in onicecandidate and\n // addEventListener('icecandidate', ...)\n utils.wrapPeerConnectionEvent(window, 'icecandidate', e => {\n if (e.candidate) {\n Object.defineProperty(e, 'candidate', {\n value: new window.RTCIceCandidate(e.candidate),\n writable: 'false'\n });\n }\n return e;\n });\n}\n\nexport function shimMaxMessageSize(window, browserDetails) {\n if (!window.RTCPeerConnection) {\n return;\n }\n\n if (!('sctp' in window.RTCPeerConnection.prototype)) {\n Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {\n get() {\n return typeof this._sctp === 'undefined' ? null : this._sctp;\n }\n });\n }\n\n const sctpInDescription = function(description) {\n if (!description || !description.sdp) {\n return false;\n }\n const sections = SDPUtils.splitSections(description.sdp);\n sections.shift();\n return sections.some(mediaSection => {\n const mLine = SDPUtils.parseMLine(mediaSection);\n return mLine && mLine.kind === 'application'\n && mLine.protocol.indexOf('SCTP') !== -1;\n });\n };\n\n const getRemoteFirefoxVersion = function(description) {\n // TODO: Is there a better solution for detecting Firefox?\n const match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\\d+)/);\n if (match === null || match.length < 2) {\n return -1;\n }\n const version = parseInt(match[1], 10);\n // Test for NaN (yes, this is ugly)\n return version !== version ? -1 : version;\n };\n\n const getCanSendMaxMessageSize = function(remoteIsFirefox) {\n // Every implementation we know can send at least 64 KiB.\n // Note: Although Chrome is technically able to send up to 256 KiB, the\n // data does not reach the other peer reliably.\n // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419\n let canSendMaxMessageSize = 65536;\n if (browserDetails.browser === 'firefox') {\n if (browserDetails.version < 57) {\n if (remoteIsFirefox === -1) {\n // FF < 57 will send in 16 KiB chunks using the deprecated PPID\n // fragmentation.\n canSendMaxMessageSize = 16384;\n } else {\n // However, other FF (and RAWRTC) can reassemble PPID-fragmented\n // messages. Thus, supporting ~2 GiB when sending.\n canSendMaxMessageSize = 2147483637;\n }\n } else if (browserDetails.version < 60) {\n // Currently, all FF >= 57 will reset the remote maximum message size\n // to the default value when a data channel is created at a later\n // stage. :(\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831\n canSendMaxMessageSize =\n browserDetails.version === 57 ? 65535 : 65536;\n } else {\n // FF >= 60 supports sending ~2 GiB\n canSendMaxMessageSize = 2147483637;\n }\n }\n return canSendMaxMessageSize;\n };\n\n const getMaxMessageSize = function(description, remoteIsFirefox) {\n // Note: 65536 bytes is the default value from the SDP spec. Also,\n // every implementation we know supports receiving 65536 bytes.\n let maxMessageSize = 65536;\n\n // FF 57 has a slightly incorrect default remote max message size, so\n // we need to adjust it here to avoid a failure when sending.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697\n if (browserDetails.browser === 'firefox'\n && browserDetails.version === 57) {\n maxMessageSize = 65535;\n }\n\n const match = SDPUtils.matchPrefix(description.sdp,\n 'a=max-message-size:');\n if (match.length > 0) {\n maxMessageSize = parseInt(match[0].substr(19), 10);\n } else if (browserDetails.browser === 'firefox' &&\n remoteIsFirefox !== -1) {\n // If the maximum message size is not present in the remote SDP and\n // both local and remote are Firefox, the remote peer can receive\n // ~2 GiB.\n maxMessageSize = 2147483637;\n }\n return maxMessageSize;\n };\n\n const origSetRemoteDescription =\n window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription() {\n this._sctp = null;\n // Chrome decided to not expose .sctp in plan-b mode.\n // As usual, adapter.js has to do an 'ugly worakaround'\n // to cover up the mess.\n if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) {\n const {sdpSemantics} = this.getConfiguration();\n if (sdpSemantics === 'plan-b') {\n Object.defineProperty(this, 'sctp', {\n get() {\n return typeof this._sctp === 'undefined' ? null : this._sctp;\n },\n enumerable: true,\n configurable: true,\n });\n }\n }\n\n if (sctpInDescription(arguments[0])) {\n // Check if the remote is FF.\n const isFirefox = getRemoteFirefoxVersion(arguments[0]);\n\n // Get the maximum message size the local peer is capable of sending\n const canSendMMS = getCanSendMaxMessageSize(isFirefox);\n\n // Get the maximum message size of the remote peer.\n const remoteMMS = getMaxMessageSize(arguments[0], isFirefox);\n\n // Determine final maximum message size\n let maxMessageSize;\n if (canSendMMS === 0 && remoteMMS === 0) {\n maxMessageSize = Number.POSITIVE_INFINITY;\n } else if (canSendMMS === 0 || remoteMMS === 0) {\n maxMessageSize = Math.max(canSendMMS, remoteMMS);\n } else {\n maxMessageSize = Math.min(canSendMMS, remoteMMS);\n }\n\n // Create a dummy RTCSctpTransport object and the 'maxMessageSize'\n // attribute.\n const sctp = {};\n Object.defineProperty(sctp, 'maxMessageSize', {\n get() {\n return maxMessageSize;\n }\n });\n this._sctp = sctp;\n }\n\n return origSetRemoteDescription.apply(this, arguments);\n };\n}\n\nexport function shimSendThrowTypeError(window) {\n if (!(window.RTCPeerConnection &&\n 'createDataChannel' in window.RTCPeerConnection.prototype)) {\n return;\n }\n\n // Note: Although Firefox >= 57 has a native implementation, the maximum\n // message size can be reset for all data channels at a later stage.\n // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831\n\n function wrapDcSend(dc, pc) {\n const origDataChannelSend = dc.send;\n dc.send = function send() {\n const data = arguments[0];\n const length = data.length || data.size || data.byteLength;\n if (dc.readyState === 'open' &&\n pc.sctp && length > pc.sctp.maxMessageSize) {\n throw new TypeError('Message too large (can send a maximum of ' +\n pc.sctp.maxMessageSize + ' bytes)');\n }\n return origDataChannelSend.apply(dc, arguments);\n };\n }\n const origCreateDataChannel =\n window.RTCPeerConnection.prototype.createDataChannel;\n window.RTCPeerConnection.prototype.createDataChannel =\n function createDataChannel() {\n const dataChannel = origCreateDataChannel.apply(this, arguments);\n wrapDcSend(dataChannel, this);\n return dataChannel;\n };\n utils.wrapPeerConnectionEvent(window, 'datachannel', e => {\n wrapDcSend(e.channel, e.target);\n return e;\n });\n}\n\n\n/* shims RTCConnectionState by pretending it is the same as iceConnectionState.\n * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12\n * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect\n * since DTLS failures would be hidden. See\n * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827\n * for the Firefox tracking bug.\n */\nexport function shimConnectionState(window) {\n if (!window.RTCPeerConnection ||\n 'connectionState' in window.RTCPeerConnection.prototype) {\n return;\n }\n const proto = window.RTCPeerConnection.prototype;\n Object.defineProperty(proto, 'connectionState', {\n get() {\n return {\n completed: 'connected',\n checking: 'connecting'\n }[this.iceConnectionState] || this.iceConnectionState;\n },\n enumerable: true,\n configurable: true\n });\n Object.defineProperty(proto, 'onconnectionstatechange', {\n get() {\n return this._onconnectionstatechange || null;\n },\n set(cb) {\n if (this._onconnectionstatechange) {\n this.removeEventListener('connectionstatechange',\n this._onconnectionstatechange);\n delete this._onconnectionstatechange;\n }\n if (cb) {\n this.addEventListener('connectionstatechange',\n this._onconnectionstatechange = cb);\n }\n },\n enumerable: true,\n configurable: true\n });\n\n ['setLocalDescription', 'setRemoteDescription'].forEach((method) => {\n const origMethod = proto[method];\n proto[method] = function() {\n if (!this._connectionstatechangepoly) {\n this._connectionstatechangepoly = e => {\n const pc = e.target;\n if (pc._lastConnectionState !== pc.connectionState) {\n pc._lastConnectionState = pc.connectionState;\n const newEvent = new Event('connectionstatechange', e);\n pc.dispatchEvent(newEvent);\n }\n return e;\n };\n this.addEventListener('iceconnectionstatechange',\n this._connectionstatechangepoly);\n }\n return origMethod.apply(this, arguments);\n };\n });\n}\n\nexport function removeExtmapAllowMixed(window, browserDetails) {\n /* remove a=extmap-allow-mixed for webrtc.org < M71 */\n if (!window.RTCPeerConnection) {\n return;\n }\n if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) {\n return;\n }\n if (browserDetails.browser === 'safari' && browserDetails.version >= 605) {\n return;\n }\n const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription(desc) {\n if (desc && desc.sdp && desc.sdp.indexOf('\\na=extmap-allow-mixed') !== -1) {\n const sdp = desc.sdp.split('\\n').filter((line) => {\n return line.trim() !== 'a=extmap-allow-mixed';\n }).join('\\n');\n // Safari enforces read-only-ness of RTCSessionDescription fields.\n if (window.RTCSessionDescription &&\n desc instanceof window.RTCSessionDescription) {\n arguments[0] = new window.RTCSessionDescription({\n type: desc.type,\n sdp,\n });\n } else {\n desc.sdp = sdp;\n }\n }\n return nativeSRD.apply(this, arguments);\n };\n}\n\nexport function shimAddIceCandidateNullOrEmpty(window, browserDetails) {\n // Support for addIceCandidate(null or undefined)\n // as well as addIceCandidate({candidate: \"\", ...})\n // https://bugs.chromium.org/p/chromium/issues/detail?id=978582\n // Note: must be called before other polyfills which change the signature.\n if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) {\n return;\n }\n const nativeAddIceCandidate =\n window.RTCPeerConnection.prototype.addIceCandidate;\n if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) {\n return;\n }\n window.RTCPeerConnection.prototype.addIceCandidate =\n function addIceCandidate() {\n if (!arguments[0]) {\n if (arguments[1]) {\n arguments[1].apply(null);\n }\n return Promise.resolve();\n }\n // Firefox 68+ emits and processes {candidate: \"\", ...}, ignore\n // in older versions.\n // Native support for ignoring exists for Chrome M77+.\n // Safari ignores as well, exact version unknown but works in the same\n // version that also ignores addIceCandidate(null).\n if (((browserDetails.browser === 'chrome' && browserDetails.version < 78)\n || (browserDetails.browser === 'firefox'\n && browserDetails.version < 68)\n || (browserDetails.browser === 'safari'))\n && arguments[0] && arguments[0].candidate === '') {\n return Promise.resolve();\n }\n return nativeAddIceCandidate.apply(this, arguments);\n };\n}\n","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\n/* eslint-env node */\n\n'use strict';\n\nimport {adapterFactory} from './adapter_factory.js';\n\nconst adapter =\n adapterFactory({window: typeof window === 'undefined' ? undefined : window});\nexport default adapter;\n","/*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n */\nimport * as utils from './utils';\n\n // Browser shims.\nimport * as chromeShim from './chrome/chrome_shim';\nimport * as firefoxShim from './firefox/firefox_shim';\nimport * as safariShim from './safari/safari_shim';\nimport * as commonShim from './common_shim';\nimport * as sdp from 'sdp';\n\n// Shimming starts here.\nexport function adapterFactory({window} = {}, options = {\n shimChrome: true,\n shimFirefox: true,\n shimSafari: true,\n}) {\n // Utils.\n const logging = utils.log;\n const browserDetails = utils.detectBrowser(window);\n\n const adapter = {\n browserDetails,\n commonShim,\n extractVersion: utils.extractVersion,\n disableLog: utils.disableLog,\n disableWarnings: utils.disableWarnings,\n // Expose sdp as a convenience. For production apps include directly.\n sdp,\n };\n\n // Shim browser if found.\n switch (browserDetails.browser) {\n case 'chrome':\n if (!chromeShim || !chromeShim.shimPeerConnection ||\n !options.shimChrome) {\n logging('Chrome shim is not included in this adapter release.');\n return adapter;\n }\n if (browserDetails.version === null) {\n logging('Chrome shim can not determine version, not shimming.');\n return adapter;\n }\n logging('adapter.js shimming chrome.');\n // Export to the adapter global object visible in the browser.\n adapter.browserShim = chromeShim;\n\n // Must be called before shimPeerConnection.\n commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails);\n\n chromeShim.shimGetUserMedia(window, browserDetails);\n chromeShim.shimMediaStream(window, browserDetails);\n chromeShim.shimPeerConnection(window, browserDetails);\n chromeShim.shimOnTrack(window, browserDetails);\n chromeShim.shimAddTrackRemoveTrack(window, browserDetails);\n chromeShim.shimGetSendersWithDtmf(window, browserDetails);\n chromeShim.shimGetStats(window, browserDetails);\n chromeShim.shimSenderReceiverGetStats(window, browserDetails);\n chromeShim.fixNegotiationNeeded(window, browserDetails);\n\n commonShim.shimRTCIceCandidate(window, browserDetails);\n commonShim.shimConnectionState(window, browserDetails);\n commonShim.shimMaxMessageSize(window, browserDetails);\n commonShim.shimSendThrowTypeError(window, browserDetails);\n commonShim.removeExtmapAllowMixed(window, browserDetails);\n break;\n case 'firefox':\n if (!firefoxShim || !firefoxShim.shimPeerConnection ||\n !options.shimFirefox) {\n logging('Firefox shim is not included in this adapter release.');\n return adapter;\n }\n logging('adapter.js shimming firefox.');\n // Export to the adapter global object visible in the browser.\n adapter.browserShim = firefoxShim;\n\n // Must be called before shimPeerConnection.\n commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails);\n\n firefoxShim.shimGetUserMedia(window, browserDetails);\n firefoxShim.shimPeerConnection(window, browserDetails);\n firefoxShim.shimOnTrack(window, browserDetails);\n firefoxShim.shimRemoveStream(window, browserDetails);\n firefoxShim.shimSenderGetStats(window, browserDetails);\n firefoxShim.shimReceiverGetStats(window, browserDetails);\n firefoxShim.shimRTCDataChannel(window, browserDetails);\n firefoxShim.shimAddTransceiver(window, browserDetails);\n firefoxShim.shimGetParameters(window, browserDetails);\n firefoxShim.shimCreateOffer(window, browserDetails);\n firefoxShim.shimCreateAnswer(window, browserDetails);\n\n commonShim.shimRTCIceCandidate(window, browserDetails);\n commonShim.shimConnectionState(window, browserDetails);\n commonShim.shimMaxMessageSize(window, browserDetails);\n commonShim.shimSendThrowTypeError(window, browserDetails);\n break;\n case 'safari':\n if (!safariShim || !options.shimSafari) {\n logging('Safari shim is not included in this adapter release.');\n return adapter;\n }\n logging('adapter.js shimming safari.');\n // Export to the adapter global object visible in the browser.\n adapter.browserShim = safariShim;\n\n // Must be called before shimCallbackAPI.\n commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails);\n\n safariShim.shimRTCIceServerUrls(window, browserDetails);\n safariShim.shimCreateOfferLegacy(window, browserDetails);\n safariShim.shimCallbacksAPI(window, browserDetails);\n safariShim.shimLocalStreamsAPI(window, browserDetails);\n safariShim.shimRemoteStreamsAPI(window, browserDetails);\n safariShim.shimTrackEventTransceiver(window, browserDetails);\n safariShim.shimGetUserMedia(window, browserDetails);\n safariShim.shimAudioContext(window, browserDetails);\n\n commonShim.shimRTCIceCandidate(window, browserDetails);\n commonShim.shimMaxMessageSize(window, browserDetails);\n commonShim.shimSendThrowTypeError(window, browserDetails);\n commonShim.removeExtmapAllowMixed(window, browserDetails);\n break;\n default:\n logging('Unsupported browser!');\n break;\n }\n\n return adapter;\n}\n","/**\r\n * Enumeration of the video types that are signaled to the bridge\r\n * @type {{CAMERA: string, DESKTOP: string, DESKTOP_HIGH_FPS: string, NONE: string}}\r\n */\r\nconst BridgeVideoType = {\r\n /**\r\n * The camera video type.\r\n */\r\n CAMERA: 'camera',\r\n\r\n /**\r\n * The low fps desktop video type.\r\n */\r\n DESKTOP: 'desktop',\r\n\r\n /**\r\n * The high fps desktop video type.\r\n */\r\n DESKTOP_HIGH_FPS: 'desktop_high_fps',\r\n\r\n /**\r\n * Video type when no local source is present.\r\n */\r\n NONE: 'none'\r\n};\r\n\r\nmodule.exports = BridgeVideoType;\r\n","/**\r\n * The possible camera facing modes. For now support only 'user' and\r\n * 'environment' because 'left' and 'right' are not used anywhere in our\r\n * projects at the time of this writing. For more information please refer to\r\n * https://w3c.github.io/mediacapture-main/getusermedia.html\r\n * #def-constraint-facingMode.\r\n *\r\n * @enum {string}\r\n */\r\nconst CameraFacingMode = {\r\n /**\r\n * The mode which specifies the environment-facing camera.\r\n */\r\n ENVIRONMENT: 'environment',\r\n\r\n /**\r\n * The mode which specifies the user-facing camera.\r\n */\r\n USER: 'user'\r\n};\r\n\r\nmodule.exports = CameraFacingMode;\r\n","/**\r\n * Enumeration of the codec mime types\r\n * @type {{H264: string, OPUS: string, ULPFEC: string, VP8: string, VP9: string}}\r\n */\r\nconst CodecMimeType = {\r\n /**\r\n * AV1 codec mime type.\r\n */\r\n AV1: 'av1',\r\n\r\n /**\r\n * The h264 codec mime type.\r\n */\r\n H264: 'h264',\r\n\r\n /**\r\n * The opus codec mime type.\r\n */\r\n OPUS: 'opus',\r\n\r\n /**\r\n * The ulpfec codec mime type.\r\n */\r\n ULPFEC: 'ulpfec',\r\n\r\n /**\r\n * The vp8 codec mime type.\r\n */\r\n VP8: 'vp8',\r\n\r\n /**\r\n * The vp9 codec mime type.\r\n */\r\n VP9: 'vp9'\r\n\r\n};\r\n\r\nmodule.exports = CodecMimeType;\r\n","export enum RTCEvents {\r\n /**\r\n * Indicates error while create answer call.\r\n */\r\n CREATE_ANSWER_FAILED = 'rtc.create_answer_failed',\r\n\r\n /**\r\n * Indicates error while create offer call.\r\n */\r\n CREATE_OFFER_FAILED = 'rtc.create_offer_failed',\r\n DATA_CHANNEL_OPEN = 'rtc.data_channel_open',\r\n ENDPOINT_CONN_STATUS_CHANGED = 'rtc.endpoint_conn_status_changed',\r\n DOMINANT_SPEAKER_CHANGED = 'rtc.dominant_speaker_changed',\r\n LASTN_ENDPOINT_CHANGED = 'rtc.lastn_endpoint_changed',\r\n FORWARDED_SOURCES_CHANGED = 'rtc.forwarded_sources_changed',\r\n\r\n /**\r\n * Event emitted when the user granted/blocked a permission for the camera / mic.\r\n * Used to keep track of the granted permissions on browsers which don't\r\n * support the Permissions API.\r\n */\r\n PERMISSIONS_CHANGED = 'rtc.permissions_changed',\r\n\r\n SENDER_VIDEO_CONSTRAINTS_CHANGED = 'rtc.sender_video_constraints_changed',\r\n\r\n /**\r\n * Event emitted when {@link RTC.setLastN} method is called to update with\r\n * the new value set.\r\n * The first argument is the value passed to {@link RTC.setLastN}.\r\n */\r\n LASTN_VALUE_CHANGED = 'rtc.lastn_value_changed',\r\n\r\n /**\r\n * Event emitted when ssrc for a local track is extracted and stored\r\n * in {@link TraceablePeerConnection}.\r\n * @param {JitsiLocalTrack} track which ssrc was updated\r\n * @param {string} ssrc that was stored\r\n */\r\n LOCAL_TRACK_SSRC_UPDATED = 'rtc.local_track_ssrc_updated',\r\n\r\n /**\r\n * The max enabled resolution of a local video track was changed.\r\n */\r\n LOCAL_TRACK_MAX_ENABLED_RESOLUTION_CHANGED = 'rtc.local_track_max_enabled_resolution_changed',\r\n\r\n TRACK_ATTACHED = 'rtc.track_attached',\r\n\r\n /**\r\n * Event fired when we remote track is added to the conference.\r\n * 1st event argument is the added JitsiRemoteTrack instance.\r\n **/\r\n REMOTE_TRACK_ADDED = 'rtc.remote_track_added',\r\n\r\n // FIXME get rid of this event in favour of NO_DATA_FROM_SOURCE event\r\n // (currently implemented for local tracks only)\r\n REMOTE_TRACK_MUTE = 'rtc.remote_track_mute',\r\n\r\n /**\r\n * Indicates that the remote track has been removed from the conference.\r\n * 1st event argument is the removed {@link JitsiRemoteTrack} instance.\r\n */\r\n REMOTE_TRACK_REMOVED = 'rtc.remote_track_removed',\r\n\r\n // FIXME get rid of this event in favour of NO_DATA_FROM_SOURCE event\r\n // (currently implemented for local tracks only)\r\n REMOTE_TRACK_UNMUTE = 'rtc.remote_track_unmute',\r\n\r\n /**\r\n * Indicates error while set local description.\r\n */\r\n SET_LOCAL_DESCRIPTION_FAILED = 'rtc.set_local_description_failed',\r\n\r\n /**\r\n * Indicates error while set remote description.\r\n */\r\n SET_REMOTE_DESCRIPTION_FAILED = 'rtc.set_remote_description_failed',\r\n AUDIO_OUTPUT_DEVICE_CHANGED = 'rtc.audio_output_device_changed',\r\n DEVICE_LIST_CHANGED = 'rtc.device_list_changed',\r\n\r\n /**\r\n * Indicates that the list with available devices will change.\r\n */\r\n DEVICE_LIST_WILL_CHANGE = 'rtc.device_list_will_change',\r\n DEVICE_LIST_AVAILABLE = 'rtc.device_list_available',\r\n\r\n /**\r\n * Indicates that a message from another participant is received on\r\n * data channel.\r\n */\r\n ENDPOINT_MESSAGE_RECEIVED = 'rtc.endpoint_message_received',\r\n\r\n /**\r\n * Indicates that the remote endpoint stats have been received on data channel.\r\n */\r\n ENDPOINT_STATS_RECEIVED = 'rtc.endpoint_stats_received',\r\n\r\n /**\r\n * Designates an event indicating that the local ICE username fragment of\r\n * the jingle session has changed.\r\n * The first argument of the vent is TraceablePeerConnection which\r\n * is the source of the event.\r\n * The second argument is the actual \"ufrag\" string.\r\n */\r\n LOCAL_UFRAG_CHANGED = 'rtc.local_ufrag_changed',\r\n\r\n /**\r\n * Designates an event indicating that the local ICE username fragment of\r\n * the jingle session has changed.\r\n * The first argument of the vent is TraceablePeerConnection which\r\n * is the source of the event.\r\n * The second argument is the actual \"ufrag\" string.\r\n */\r\n REMOTE_UFRAG_CHANGED = 'rtc.remote_ufrag_changed'\r\n};\r\n\r\nexport const CREATE_ANSWER_FAILED = RTCEvents.CREATE_ANSWER_FAILED;\r\nexport const CREATE_OFFER_FAILED = RTCEvents.CREATE_OFFER_FAILED;\r\nexport const DATA_CHANNEL_OPEN = RTCEvents.DATA_CHANNEL_OPEN;\r\nexport const ENDPOINT_CONN_STATUS_CHANGED = RTCEvents.ENDPOINT_CONN_STATUS_CHANGED;\r\nexport const DOMINANT_SPEAKER_CHANGED = RTCEvents.DOMINANT_SPEAKER_CHANGED;\r\nexport const LASTN_ENDPOINT_CHANGED = RTCEvents.LASTN_ENDPOINT_CHANGED;\r\nexport const FORWARDED_SOURCES_CHANGED = RTCEvents.FORWARDED_SOURCES_CHANGED;\r\nexport const PERMISSIONS_CHANGED = RTCEvents.PERMISSIONS_CHANGED;\r\nexport const SENDER_VIDEO_CONSTRAINTS_CHANGED = RTCEvents.SENDER_VIDEO_CONSTRAINTS_CHANGED;\r\nexport const LASTN_VALUE_CHANGED = RTCEvents.LASTN_VALUE_CHANGED;\r\nexport const LOCAL_TRACK_SSRC_UPDATED = RTCEvents.LOCAL_TRACK_SSRC_UPDATED;\r\nexport const LOCAL_TRACK_MAX_ENABLED_RESOLUTION_CHANGED = RTCEvents.LOCAL_TRACK_MAX_ENABLED_RESOLUTION_CHANGED;\r\nexport const TRACK_ATTACHED = RTCEvents.TRACK_ATTACHED;\r\nexport const REMOTE_TRACK_ADDED = RTCEvents.REMOTE_TRACK_ADDED;\r\nexport const REMOTE_TRACK_MUTE = RTCEvents.REMOTE_TRACK_MUTE;\r\nexport const REMOTE_TRACK_REMOVED = RTCEvents.REMOTE_TRACK_REMOVED;\r\nexport const REMOTE_TRACK_UNMUTE = RTCEvents.REMOTE_TRACK_UNMUTE;\r\nexport const SET_LOCAL_DESCRIPTION_FAILED = RTCEvents.SET_LOCAL_DESCRIPTION_FAILED;\r\nexport const SET_REMOTE_DESCRIPTION_FAILED = RTCEvents.SET_REMOTE_DESCRIPTION_FAILED;\r\nexport const AUDIO_OUTPUT_DEVICE_CHANGED = RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED;\r\nexport const DEVICE_LIST_CHANGED = RTCEvents.DEVICE_LIST_CHANGED;\r\nexport const DEVICE_LIST_WILL_CHANGE = RTCEvents.DEVICE_LIST_WILL_CHANGE;\r\nexport const DEVICE_LIST_AVAILABLE = RTCEvents.DEVICE_LIST_AVAILABLE;\r\nexport const ENDPOINT_MESSAGE_RECEIVED = RTCEvents.ENDPOINT_MESSAGE_RECEIVED;\r\nexport const ENDPOINT_STATS_RECEIVED = RTCEvents.ENDPOINT_STATS_RECEIVED;\r\nexport const LOCAL_UFRAG_CHANGED = RTCEvents.LOCAL_UFRAG_CHANGED;\r\nexport const REMOTE_UFRAG_CHANGED = RTCEvents.REMOTE_UFRAG_CHANGED;\r\n\r\n// TODO: this was a pre-ES6 module using module.exports = RTCEvents which doesn't translate well\r\n// it is used in a number of places and should be updated to use the named export\r\n\r\nexport default RTCEvents;","const Resolutions = {\r\n '2160': {\r\n width: 3840,\r\n height: 2160\r\n },\r\n '4k': {\r\n width: 3840,\r\n height: 2160\r\n },\r\n '1080': {\r\n width: 1920,\r\n height: 1080\r\n },\r\n 'fullhd': {\r\n width: 1920,\r\n height: 1080\r\n },\r\n '720': {\r\n width: 1280,\r\n height: 720\r\n },\r\n 'hd': {\r\n width: 1280,\r\n height: 720\r\n },\r\n '540': {\r\n width: 960,\r\n height: 540\r\n },\r\n 'qhd': {\r\n width: 960,\r\n height: 540\r\n },\r\n '480': {\r\n width: 640,\r\n height: 480\r\n },\r\n 'vga': {\r\n width: 640,\r\n height: 480\r\n },\r\n '360': {\r\n width: 640,\r\n height: 360\r\n },\r\n '240': {\r\n width: 320,\r\n height: 240\r\n },\r\n '180': {\r\n width: 320,\r\n height: 180\r\n }\r\n};\r\n\r\nmodule.exports = Resolutions;\r\n","/**\r\n * Enumeration of the video types\r\n */\r\nexport enum VideoType {\r\n /**\r\n * The camera video type.\r\n */\r\n CAMERA = 'camera',\r\n\r\n /**\r\n * The desktop video type.\r\n */\r\n DESKTOP = 'desktop'\r\n};\r\n","const AuthenticationEvents = {\r\n /**\r\n * Event callback arguments:\r\n * function(authenticationEnabled, userIdentity)\r\n * authenticationEnabled - indicates whether authentication has been enabled\r\n * in this session\r\n * userIdentity - if user has been logged in then it contains user name. If\r\n * contains 'null' or 'undefined' then user is not logged in.\r\n */\r\n IDENTITY_UPDATED: 'authentication.identity_updated'\r\n};\r\n\r\nmodule.exports = AuthenticationEvents;\r\n","export const LOCAL_JID = 'local'\r\n","export enum XMPPEvents {\r\n /**\r\n * Indicates error while adding ice candidate.\r\n */\r\n ADD_ICE_CANDIDATE_FAILED = 'xmpp.add_ice_candidate_failed',\r\n\r\n // Designates an event indicating that the focus has asked us to mute our\r\n // audio.\r\n AUDIO_MUTED_BY_FOCUS = 'xmpp.audio_muted_by_focus',\r\n\r\n // Designates an event indicating that the focus has asked us to disable our\r\n // camera.\r\n VIDEO_MUTED_BY_FOCUS = 'xmpp.video_muted_by_focus',\r\n AUTHENTICATION_REQUIRED = 'xmpp.authentication_required',\r\n BRIDGE_DOWN = 'xmpp.bridge_down',\r\n\r\n /**\r\n * Triggered when 'session-accept' is received from the responder.\r\n */\r\n CALL_ACCEPTED = 'xmpp.callaccepted.jingle',\r\n\r\n // Designates an event indicating that an offer (e.g. Jingle\r\n // session-initiate) was received.\r\n CALL_INCOMING = 'xmpp.callincoming.jingle',\r\n\r\n // Triggered when Jicofo kills our media session, this can happen while\r\n // we're still in the MUC, when it decides to terminate the media session.\r\n // For example when the session is idle for too long, because we're the only\r\n // person in the conference room.\r\n CALL_ENDED = 'xmpp.callended.jingle',\r\n CHAT_ERROR_RECEIVED = 'xmpp.chat_error_received',\r\n SETTINGS_ERROR_RECEIVED = 'xmpp.settings_error_received',\r\n\r\n // The conference properties (as advertised by jicofo) have changed\r\n CONFERENCE_PROPERTIES_CHANGED = 'xmpp.conference_properties_changed',\r\n\r\n /**\r\n * This event is triggered when the ICE connects for the first time.\r\n */\r\n CONNECTION_ESTABLISHED = 'xmpp.connection.connected',\r\n\r\n // Designates an event indicating that the connection to the XMPP server\r\n // failed.\r\n CONNECTION_FAILED = 'xmpp.connection.failed',\r\n\r\n // Designates an event indicating that the media (ICE) connection was\r\n // interrupted. This should go to the RTC module.\r\n CONNECTION_INTERRUPTED = 'xmpp.connection.interrupted',\r\n\r\n // Designates an event indicating that the media (ICE) connection was\r\n // restored. This should go to the RTC module.\r\n CONNECTION_RESTORED = 'xmpp.connection.restored',\r\n\r\n // Designates an event indicating that the media (ICE) connection failed.\r\n // This should go to the RTC module.\r\n CONNECTION_ICE_FAILED = 'xmpp.connection.ice.failed',\r\n\r\n // Designates an event indicating that the call has been migrated to a different\r\n // bridge and that the client needs to be restarted for a successful transition.\r\n CONNECTION_RESTARTED = 'xmpp.connection.restart',\r\n\r\n /**\r\n * Designates an event indicating connection status changes.\r\n */\r\n CONNECTION_STATUS_CHANGED = 'xmpp.connection.status.changed',\r\n\r\n // Designates an event indicating that the display name of a participant\r\n // has changed.\r\n DISPLAY_NAME_CHANGED = 'xmpp.display_name_changed',\r\n\r\n /**\r\n * Chat room instance have been added to Strophe.emuc plugin.\r\n */\r\n EMUC_ROOM_ADDED = 'xmpp.emuc_room_added',\r\n\r\n /**\r\n * Chat room instance have been removed from Strophe.emuc plugin.\r\n */\r\n EMUC_ROOM_REMOVED = 'xmpp.emuc_room_removed',\r\n ETHERPAD = 'xmpp.etherpad',\r\n FOCUS_DISCONNECTED = 'xmpp.focus_disconnected',\r\n FOCUS_LEFT = 'xmpp.focus_left',\r\n GRACEFUL_SHUTDOWN = 'xmpp.graceful_shutdown',\r\n\r\n /**\r\n * Event fired when 'transport-replace' Jingle message has been received,\r\n * before the new offer is set on the PeerConnection.\r\n */\r\n ICE_RESTARTING = 'rtc.ice_restarting',\r\n\r\n /**\r\n * Event fired after the 'transport-replace' message has been processed\r\n * and the new offer has been set successfully.\r\n */\r\n ICE_RESTART_SUCCESS = 'rtc.ice_restart_success',\r\n\r\n /**\r\n * Designates an event indicating that we were kicked from the XMPP MUC.\r\n * @param {boolean} isSelfPresence - whether it is for local participant\r\n * or another participant.\r\n * @param {string} actorJid - the jid of the participant who was initiator\r\n * of the kick.\r\n * @param {?string} participantJid - when it is not a kick for local participant,\r\n * this is the jid of the participant which was kicked.\r\n */\r\n KICKED = 'xmpp.kicked',\r\n\r\n // Designates an event indicating that our role in the XMPP MUC has changed.\r\n LOCAL_ROLE_CHANGED = 'xmpp.localrole_changed',\r\n\r\n /**\r\n * Event fired when the unique meeting id is set.\r\n */\r\n MEETING_ID_SET = 'xmpp.meeting_id_set',\r\n\r\n // Designates an event indicating that an XMPP message in the MUC was\r\n // received.\r\n MESSAGE_RECEIVED = 'xmpp.message_received',\r\n\r\n // Designates an event indicating that an invite XMPP message in the MUC was\r\n // received.\r\n INVITE_MESSAGE_RECEIVED = 'xmpp.invite_message_received',\r\n\r\n // Designates an event indicating that a private XMPP message in the MUC was\r\n // received.\r\n PRIVATE_MESSAGE_RECEIVED = 'xmpp.private_message_received',\r\n\r\n // Designates an event indicating that a bot participant type had changed\r\n MUC_MEMBER_BOT_TYPE_CHANGED = 'xmpp.muc_member_bot_type_changed',\r\n\r\n // Designates an event indicating that the XMPP MUC was destroyed.\r\n MUC_DESTROYED = 'xmpp.muc_destroyed',\r\n\r\n // Designates an event indicating that we are currently in process of joining the XMPP MUC.\r\n MUC_JOIN_IN_PROGRESS = 'xmpp.muc_join_in_progress',\r\n\r\n // Designates an event indicating that we have joined the XMPP MUC.\r\n MUC_JOINED = 'xmpp.muc_joined',\r\n\r\n // Designates an event indicating that a participant joined the XMPP MUC.\r\n MUC_MEMBER_JOINED = 'xmpp.muc_member_joined',\r\n\r\n // Designates an event indicating that a participant left the XMPP MUC.\r\n MUC_MEMBER_LEFT = 'xmpp.muc_member_left',\r\n\r\n // Designates an event indicating that a participant joined the lobby XMPP MUC.\r\n MUC_LOBBY_MEMBER_JOINED = 'xmpp.muc_lobby_member_joined',\r\n\r\n // Designates an event indicating that a participant in the lobby XMPP MUC has been updated\r\n MUC_LOBBY_MEMBER_UPDATED = 'xmpp.muc_lobby_member_updated',\r\n\r\n // Designates an event indicating that a participant left the XMPP MUC.\r\n MUC_LOBBY_MEMBER_LEFT = 'xmpp.muc_lobby_member_left',\r\n\r\n // Designates an event indicating that a participant was denied access to a conference from the lobby XMPP MUC.\r\n MUC_DENIED_ACCESS = 'xmpp.muc_denied access',\r\n\r\n // Designates an event indicating that local participant left the muc\r\n MUC_LEFT = 'xmpp.muc_left',\r\n\r\n // Designates an event indicating that the MUC role of a participant has\r\n // changed.\r\n MUC_ROLE_CHANGED = 'xmpp.muc_role_changed',\r\n\r\n // Designates an event indicating that the MUC has been locked or unlocked.\r\n MUC_LOCK_CHANGED = 'xmpp.muc_lock_changed',\r\n\r\n // Designates an event indicating that the MUC members only config has changed.\r\n MUC_MEMBERS_ONLY_CHANGED = 'xmpp.muc_members_only_changed',\r\n\r\n // Designates an event indicating that a participant in the XMPP MUC has\r\n // advertised that they have audio muted (or unmuted).\r\n PARTICIPANT_AUDIO_MUTED = 'xmpp.audio_muted',\r\n\r\n // Designates an event indicating that a participant in the XMPP MUC has\r\n // advertised that they have video muted (or unmuted).\r\n PARTICIPANT_VIDEO_MUTED = 'xmpp.video_muted',\r\n\r\n // Designates an event indicating that the video type (e.g. 'camera' or\r\n // 'screen') for a participant has changed.\r\n // Note = currently this event fires every time we receive presence from\r\n // someone (regardless of whether or not the \"video type\" changed).\r\n PARTICIPANT_VIDEO_TYPE_CHANGED = 'xmpp.video_type',\r\n\r\n /**\r\n * Indicates that the features of the participant has been changed.\r\n */\r\n PARTICIPANT_FEATURES_CHANGED = 'xmpp.participant_features_changed',\r\n PASSWORD_REQUIRED = 'xmpp.password_required',\r\n\r\n /**\r\n * Indicates that phone number changed.\r\n */\r\n PHONE_NUMBER_CHANGED = 'conference.phoneNumberChanged',\r\n PRESENCE_RECEIVED = 'xmpp.presence_received',\r\n PRESENCE_STATUS = 'xmpp.presence_status',\r\n PROMPT_FOR_LOGIN = 'xmpp.prompt_for_login',\r\n\r\n // xmpp is connected and obtained user media\r\n READY_TO_JOIN = 'xmpp.ready_to_join',\r\n\r\n /**\r\n * Indicates that recording state changed.\r\n */\r\n RECORDER_STATE_CHANGED = 'xmpp.recorderStateChanged',\r\n\r\n // Designates an event indicating that we received statistics from a\r\n // participant in the MUC.\r\n REMOTE_STATS = 'xmpp.remote_stats',\r\n\r\n /**\r\n * Indicates that the offer / answer renegotiation has failed.\r\n */\r\n RENEGOTIATION_FAILED = 'xmpp.renegotiation_failed',\r\n RESERVATION_ERROR = 'xmpp.room_reservation_error',\r\n ROOM_CONNECT_ERROR = 'xmpp.room_connect_error',\r\n ROOM_CONNECT_NOT_ALLOWED_ERROR = 'xmpp.room_connect_error.not_allowed',\r\n ROOM_JOIN_ERROR = 'xmpp.room_join_error',\r\n ROOM_CONNECT_MEMBERS_ONLY_ERROR = 'xmpp.room_connect_error.members_only',\r\n\r\n /**\r\n * Indicates that max users limit has been reached.\r\n */\r\n ROOM_MAX_USERS_ERROR = 'xmpp.room_max_users_error',\r\n\r\n // Designates an event indicating that we sent an XMPP message to the MUC.\r\n SENDING_CHAT_MESSAGE = 'xmpp.sending_chat_message',\r\n\r\n // Designates an event indicating that we sent a private XMPP message to\r\n // a specific user of the muc.\r\n SENDING_PRIVATE_CHAT_MESSAGE = 'xmpp.sending_private_chat_message',\r\n\r\n /**\r\n * Event fired after receiving the confirmation about session accept.\r\n */\r\n SESSION_ACCEPT = 'xmpp.session_accept',\r\n\r\n /**\r\n * Event fired if we receive an error after sending the session accept.\r\n */\r\n SESSION_ACCEPT_ERROR = 'xmpp.session_accept_error',\r\n\r\n /**\r\n * Event fired when we do not get our 'session-accept' acknowledged by\r\n * Jicofo. It most likely means that there is serious problem with our\r\n * connection or XMPP server and we should reload the conference.\r\n *\r\n * We have seen that to happen in BOSH requests race condition when the BOSH\r\n * request table containing the 'session-accept' was discarded by Prosody.\r\n * Jicofo does send the RESULT immediately without any condition, so missing\r\n * packets means that most likely it has never seen our IQ.\r\n */\r\n SESSION_ACCEPT_TIMEOUT = 'xmpp.session_accept_timeout',\r\n\r\n /**\r\n * Event fired after successful sending of jingle source-add.\r\n */\r\n SOURCE_ADD = 'xmpp.source_add',\r\n\r\n /**\r\n * Event fired after receiving an error sending of jingle source-add.\r\n */\r\n SOURCE_ADD_ERROR = 'xmpp.source_add_error',\r\n\r\n /**\r\n * Event fired after successful sending of jingle source-remove.\r\n */\r\n SOURCE_REMOVE = 'xmpp.source_remove',\r\n\r\n /**\r\n * Event fired after receiving an error sending of jingle source-remove.\r\n */\r\n SOURCE_REMOVE_ERROR = 'xmpp.source_remove_error',\r\n\r\n /**\r\n * Event fired when speaker stats update message is received.\r\n */\r\n SPEAKER_STATS_RECEIVED = 'xmpp.speaker_stats_received',\r\n\r\n /**\r\n * Event fired when conference creation timestamp is received.\r\n */\r\n CONFERENCE_TIMESTAMP_RECEIVED = 'xmpp.conference_timestamp_received',\r\n\r\n /**\r\n * Event fired when we receive a message for AV moderation approved for the local participant.\r\n */\r\n AV_MODERATION_APPROVED = 'xmpp.av_moderation.approved',\r\n\r\n /**\r\n * Event fired when we receive a message for AV moderation rejected for the local participant.\r\n */\r\n AV_MODERATION_REJECTED = 'xmpp.av_moderation.rejected',\r\n\r\n /**\r\n * Event fired when we receive a message for AV moderation.\r\n */\r\n AV_MODERATION_RECEIVED = 'xmpp.av_moderation.received',\r\n\r\n /**\r\n * Event fired when the moderation enable/disable changes.\r\n */\r\n AV_MODERATION_CHANGED = 'xmpp.av_moderation.changed',\r\n\r\n /**\r\n * Event fired when we receive message that a new jid was approved.\r\n */\r\n AV_MODERATION_PARTICIPANT_APPROVED = 'xmpp.av_moderation.participant.approved',\r\n\r\n /**\r\n * Event fired when we receive message that a new jid was approved.\r\n */\r\n AV_MODERATION_PARTICIPANT_REJECTED = 'xmpp.av_moderation.participant.rejected',\r\n\r\n /**\r\n * Event fired when a participant is requested to join a given (breakout) room.\r\n */\r\n BREAKOUT_ROOMS_MOVE_TO_ROOM = 'xmpp.breakout-rooms.move-to-room',\r\n\r\n /**\r\n * Event fired when we receive a message for breakout rooms.\r\n */\r\n BREAKOUT_ROOMS_EVENT = 'xmpp.breakout-rooms.event',\r\n\r\n /**\r\n * Event fired when the breakout rooms data was updated.\r\n */\r\n BREAKOUT_ROOMS_UPDATED = 'xmpp.breakout-rooms.updated',\r\n\r\n // Designates an event indicating that we should join the conference with\r\n // audio and/or video muted.\r\n START_MUTED_FROM_FOCUS = 'xmpp.start_muted_from_focus',\r\n\r\n // Designates an event indicating that the subject of the XMPP MUC has\r\n // changed.\r\n SUBJECT_CHANGED = 'xmpp.subject_changed',\r\n\r\n // FIXME: how does it belong to XMPP ? - it's detected by the PeerConnection\r\n // suspending detected\r\n SUSPEND_DETECTED = 'xmpp.suspend_detected',\r\n\r\n /**\r\n * Notifies for transcription status changes. The event provides the\r\n * following parameters to its listeners:\r\n *\r\n * @param {String} status - The new status.\r\n */\r\n TRANSCRIPTION_STATUS_CHANGED = 'xmpp.transcription_status_changed',\r\n\r\n /**\r\n * Event fired when 'transport-info' with new ICE candidates is received.\r\n */\r\n TRANSPORT_INFO = 'xmpp.transportinfo.jingle',\r\n\r\n /**\r\n * Indicates that video SIP GW state changed.\r\n *\r\n * @param {VideoSIPGWConstants} status - Any of the following statuses:\r\n * STATUS_BUSY, STATUS_AVAILABLE or STATUS_UNDEFINED.\r\n */\r\n VIDEO_SIP_GW_AVAILABILITY_CHANGED = 'xmpp.videoSIPGWAvailabilityChanged',\r\n\r\n /**\r\n * Indicates that video SIP GW Session state changed.\r\n * The statuses are any of the following statuses:\r\n * STATE_ON, STATE_OFF, STATE_PENDING, STATE_RETRYING, STATE_FAILED.\r\n * {@see VideoSIPGWConstants}\r\n *\r\n * @param {options} event - {address, oldState, newState, displayName}.\r\n */\r\n VIDEO_SIP_GW_SESSION_STATE_CHANGED = 'xmpp.videoSIPGWSessionStateChanged',\r\n\r\n // Designates an event indicating that the local ICE connection state has\r\n // changed.\r\n ICE_CONNECTION_STATE_CHANGED = 'xmpp.ice_connection_state_changed',\r\n\r\n /**\r\n * Event which is emitted when the body in an XMPP message in the MUC\r\n * contains JSON\r\n * TODO: this event contains a typo (xmmp vs xmpp) but it's unlikely this can be changed now\r\n */\r\n JSON_MESSAGE_RECEIVED = 'xmmp.json_message_received'\r\n};\r\n","/**\r\n * The errors for the conference.\r\n */\r\n\r\nexport enum JitsiConferenceErrors {\r\n /**\r\n * Indicates that client must be authenticated to create the conference.\r\n */\r\n AUTHENTICATION_REQUIRED = 'conference.authenticationRequired',\r\n\r\n /**\r\n * Indicates that chat error occurred.\r\n */\r\n CHAT_ERROR = 'conference.chatError',\r\n\r\n /**\r\n * Indicates that a settings error occurred.\r\n */\r\n SETTINGS_ERROR = 'conference.settingsError',\r\n\r\n /**\r\n * Indicates that conference has been destroyed.\r\n */\r\n CONFERENCE_DESTROYED = 'conference.destroyed',\r\n\r\n /**\r\n * Indicates that max users limit has been reached.\r\n */\r\n CONFERENCE_MAX_USERS = 'conference.max_users',\r\n\r\n /**\r\n * Indicates that a connection error occurred when trying to join a conference.\r\n */\r\n CONNECTION_ERROR = 'conference.connectionError',\r\n\r\n /**\r\n * Indicates that the client has been forced to restart by jicofo when the\r\n * conference was migrated from one bridge to another.\r\n */\r\n CONFERENCE_RESTARTED = 'conference.restarted',\r\n\r\n /**\r\n * Indicates that a connection error is due to not allowed,\r\n * occurred when trying to join a conference.\r\n */\r\n NOT_ALLOWED_ERROR = 'conference.connectionError.notAllowed',\r\n\r\n /**\r\n * Indicates that a connection error is due to not allowed,\r\n * occurred when trying to join a conference, only approved members are allowed to join.\r\n */\r\n MEMBERS_ONLY_ERROR = 'conference.connectionError.membersOnly',\r\n\r\n /**\r\n * Indicates that a connection error is due to denied access to the room,\r\n * occurred after joining a lobby room and access is denied by the room moderators.\r\n */\r\n CONFERENCE_ACCESS_DENIED = 'conference.connectionError.accessDenied',\r\n\r\n /**\r\n * Indicates that focus error happened.\r\n */\r\n FOCUS_DISCONNECTED = 'conference.focusDisconnected',\r\n\r\n /**\r\n * Indicates that focus left the conference.\r\n */\r\n FOCUS_LEFT = 'conference.focusLeft',\r\n\r\n /**\r\n * Indicates that graceful shutdown happened.\r\n */\r\n GRACEFUL_SHUTDOWN = 'conference.gracefulShutdown',\r\n\r\n /**\r\n * Indicates that the media connection has failed.\r\n */\r\n ICE_FAILED = 'conference.iceFailed',\r\n\r\n /**\r\n * Indicates that the versions of the server side components are incompatible\r\n * with the client side.\r\n */\r\n INCOMPATIBLE_SERVER_VERSIONS = 'conference.incompatible_server_versions',\r\n\r\n /**\r\n * Indicates that offer/answer had failed.\r\n */\r\n OFFER_ANSWER_FAILED = 'conference.offerAnswerFailed',\r\n\r\n /**\r\n * Indicates that password cannot be set for this conference.\r\n */\r\n PASSWORD_NOT_SUPPORTED = 'conference.passwordNotSupported',\r\n\r\n /**\r\n * Indicates that a password is required in order to join the conference.\r\n */\r\n PASSWORD_REQUIRED = 'conference.passwordRequired',\r\n\r\n /**\r\n * Indicates that reservation system returned error.\r\n */\r\n RESERVATION_ERROR = 'conference.reservationError',\r\n\r\n /**\r\n * Indicates that there is no available videobridge.\r\n */\r\n VIDEOBRIDGE_NOT_AVAILABLE = 'conference.videobridgeNotAvailable'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const AUTHENTICATION_REQUIRED = JitsiConferenceErrors.AUTHENTICATION_REQUIRED;\r\nexport const CHAT_ERROR = JitsiConferenceErrors.CHAT_ERROR;\r\nexport const SETTINGS_ERROR = JitsiConferenceErrors.SETTINGS_ERROR;\r\nexport const CONFERENCE_DESTROYED = JitsiConferenceErrors.CONFERENCE_DESTROYED;\r\nexport const CONFERENCE_MAX_USERS = JitsiConferenceErrors.CONFERENCE_MAX_USERS;\r\nexport const CONNECTION_ERROR = JitsiConferenceErrors.CONNECTION_ERROR;\r\nexport const CONFERENCE_RESTARTED = JitsiConferenceErrors.CONFERENCE_RESTARTED;\r\nexport const NOT_ALLOWED_ERROR = JitsiConferenceErrors.NOT_ALLOWED_ERROR;\r\nexport const MEMBERS_ONLY_ERROR = JitsiConferenceErrors.MEMBERS_ONLY_ERROR;\r\nexport const CONFERENCE_ACCESS_DENIED = JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED;\r\nexport const FOCUS_DISCONNECTED = JitsiConferenceErrors.FOCUS_DISCONNECTED;\r\nexport const FOCUS_LEFT = JitsiConferenceErrors.FOCUS_LEFT;\r\nexport const GRACEFUL_SHUTDOWN = JitsiConferenceErrors.GRACEFUL_SHUTDOWN;\r\nexport const ICE_FAILED = JitsiConferenceErrors.ICE_FAILED;\r\nexport const INCOMPATIBLE_SERVER_VERSIONS = JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS;\r\nexport const OFFER_ANSWER_FAILED = JitsiConferenceErrors.OFFER_ANSWER_FAILED;\r\nexport const PASSWORD_NOT_SUPPORTED = JitsiConferenceErrors.PASSWORD_NOT_SUPPORTED;\r\nexport const PASSWORD_REQUIRED = JitsiConferenceErrors.PASSWORD_REQUIRED;\r\nexport const RESERVATION_ERROR = JitsiConferenceErrors.RESERVATION_ERROR;\r\nexport const VIDEOBRIDGE_NOT_AVAILABLE = JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE;\r\n","/**\r\n * The events for the conference.\r\n */\r\n\r\nexport enum JitsiConferenceEvents {\r\n /**\r\n * Event indicates that the current conference audio input switched between audio\r\n * input states,i.e. with or without audio input.\r\n */\r\n AUDIO_INPUT_STATE_CHANGE = 'conference.audio_input_state_changed',\r\n\r\n /**\r\n * Event indicates that the permission for unmuting audio has changed based on the number of audio senders in the call\r\n * and the audio sender limit configured in Jicofo.\r\n */\r\n AUDIO_UNMUTE_PERMISSIONS_CHANGED = 'conference.audio_unmute_permissions_changed',\r\n\r\n /**\r\n * Indicates that authentication status changed.\r\n */\r\n AUTH_STATUS_CHANGED = 'conference.auth_status_changed',\r\n\r\n /**\r\n * Fired just before the statistics module is disposed and it's the last chance\r\n * to submit some logs to the statistics service (ex. CallStats if enabled),\r\n * before it's disconnected.\r\n */\r\n BEFORE_STATISTICS_DISPOSED = 'conference.beforeStatisticsDisposed',\r\n\r\n /**\r\n * Indicates that an error occurred.\r\n */\r\n CONFERENCE_ERROR = 'conference.error',\r\n\r\n /**\r\n * Indicates that conference failed.\r\n */\r\n CONFERENCE_FAILED = 'conference.failed',\r\n\r\n /**\r\n * Indicates that conference is in progress of joining.\r\n */\r\n CONFERENCE_JOIN_IN_PROGRESS = 'conference.join_in_progress',\r\n\r\n /**\r\n * Indicates that conference has been joined. The event does NOT provide any\r\n * parameters to its listeners.\r\n */\r\n CONFERENCE_JOINED = 'conference.joined',\r\n\r\n /**\r\n * Indicates that conference has been left.\r\n */\r\n CONFERENCE_LEFT = 'conference.left',\r\n\r\n /**\r\n * Indicates that the conference unique identifier has been set.\r\n */\r\n CONFERENCE_UNIQUE_ID_SET = 'conference.unique_id_set',\r\n\r\n /**\r\n * Indicates that the connection to the conference has been established\r\n * XXX This is currently fired when the *ICE* connection enters 'connected'\r\n * state for the first time.\r\n */\r\n CONNECTION_ESTABLISHED = 'conference.connectionEstablished',\r\n\r\n /**\r\n * Indicates that the connection to the conference has been interrupted for some\r\n * reason.\r\n * XXX This is currently fired when the *ICE* connection is interrupted.\r\n */\r\n CONNECTION_INTERRUPTED = 'conference.connectionInterrupted',\r\n\r\n /**\r\n * Indicates that the connection to the conference has been restored.\r\n * XXX This is currently fired when the *ICE* connection is restored.\r\n */\r\n CONNECTION_RESTORED = 'conference.connectionRestored',\r\n\r\n /**\r\n * A connection to the video bridge's data channel has been established.\r\n */\r\n DATA_CHANNEL_OPENED = 'conference.dataChannelOpened',\r\n\r\n /**\r\n * A user has changed it display name\r\n */\r\n DISPLAY_NAME_CHANGED = 'conference.displayNameChanged',\r\n\r\n /**\r\n * The dominant speaker was changed.\r\n */\r\n DOMINANT_SPEAKER_CHANGED = 'conference.dominantSpeaker',\r\n\r\n /**\r\n * UTC conference timestamp when first participant joined.\r\n */\r\n CONFERENCE_CREATED_TIMESTAMP = 'conference.createdTimestamp',\r\n\r\n /**\r\n * Indicates that DTMF support changed.\r\n */\r\n DTMF_SUPPORT_CHANGED = 'conference.dtmfSupportChanged',\r\n\r\n /**\r\n * Indicates that a message from another participant is received on data\r\n * channel.\r\n */\r\n ENDPOINT_MESSAGE_RECEIVED = 'conference.endpoint_message_received',\r\n\r\n /**\r\n * Indicates that a message for the remote endpoint statistics has been received on the bridge channel.\r\n */\r\n ENDPOINT_STATS_RECEIVED = 'conference.endpoint_stats_received',\r\n\r\n /**\r\n * NOTE This is lib-jitsi-meet internal event and can be removed at any time !\r\n *\r\n * Event emitted when conference transits, between one to one and multiparty JVB\r\n * conference. If the conference switches to P2P it's neither one to one nor\r\n * a multiparty JVB conference, but P2P (the status argument of this event will\r\n * be false).\r\n *\r\n * The first argument is a boolean which carries the previous value and\r\n * the seconds argument is a boolean with the new status. The event is emitted\r\n * only if the previous and the new values are different.\r\n *\r\n * @type {string}\r\n */\r\n JVB121_STATUS = 'conference.jvb121Status',\r\n\r\n /**\r\n * You are kicked from the conference.\r\n * @param {JitsiParticipant} the participant that initiated the kick.\r\n */\r\n KICKED = 'conference.kicked',\r\n\r\n /**\r\n * Participant was kicked from the conference.\r\n * @param {JitsiParticipant} the participant that initiated the kick.\r\n * @param {JitsiParticipant} the participant that was kicked.\r\n */\r\n PARTICIPANT_KICKED = 'conference.participant_kicked',\r\n\r\n /**\r\n * The Last N set is changed.\r\n *\r\n * @param {Array|null} leavingEndpointIds the ids of all the endpoints\r\n * which are leaving Last N\r\n * @param {Array|null} enteringEndpointIds the ids of all the endpoints\r\n * which are entering Last N\r\n */\r\n LAST_N_ENDPOINTS_CHANGED = 'conference.lastNEndpointsChanged',\r\n\r\n /**\r\n * The forwarded sources set is changed.\r\n *\r\n * @param {Array} leavingForwardedSources the sourceNames of all the tracks which are leaving forwarded\r\n * sources\r\n * @param {Array} enteringForwardedSources the sourceNames of all the tracks which are entering forwarded\r\n * sources\r\n */\r\n FORWARDED_SOURCES_CHANGED = 'conference.forwardedSourcesChanged',\r\n\r\n /**\r\n * Indicates that the room has been locked or unlocked.\r\n */\r\n LOCK_STATE_CHANGED = 'conference.lock_state_changed',\r\n\r\n /**\r\n * Indicates that the region of the media server (jitsi-videobridge) that we\r\n * are connected to changed (or was initially set).\r\n * @type {string} the region.\r\n */\r\n SERVER_REGION_CHANGED = 'conference.server_region_changed',\r\n\r\n /**\r\n * An event(library-private) fired when a new media session is added to the conference.\r\n * @type {string}\r\n * @private\r\n */\r\n _MEDIA_SESSION_STARTED = 'conference.media_session.started',\r\n\r\n /**\r\n * An event(library-private) fired when the conference switches the currently active media session.\r\n * @type {string}\r\n * @private\r\n */\r\n _MEDIA_SESSION_ACTIVE_CHANGED = 'conference.media_session.active_changed',\r\n\r\n /**\r\n * Indicates that the conference had changed to members only enabled/disabled.\r\n * The first argument of this event is a boolean which when set to\r\n * true means that the conference is running in members only mode.\r\n * You may need to use Lobby if supported to ask for permissions to enter the conference.\r\n */\r\n MEMBERS_ONLY_CHANGED = 'conference.membersOnlyChanged',\r\n\r\n /**\r\n * New text message was received.\r\n */\r\n MESSAGE_RECEIVED = 'conference.messageReceived',\r\n\r\n /**\r\n * Event indicates that the current selected input device has no signal\r\n */\r\n NO_AUDIO_INPUT = 'conference.no_audio_input',\r\n\r\n /**\r\n * Event indicates that the current microphone used by the conference is noisy.\r\n */\r\n NOISY_MIC = 'conference.noisy_mic',\r\n\r\n /**\r\n * Indicates that a message from the local user or from the Prosody backend\r\n * was received on the data channel.\r\n */\r\n NON_PARTICIPANT_MESSAGE_RECEIVED = 'conference.non_participant_message_received',\r\n\r\n /**\r\n * New private text message was received.\r\n */\r\n PRIVATE_MESSAGE_RECEIVED = 'conference.privateMessageReceived',\r\n\r\n /**\r\n * Event fired when JVB sends notification about interrupted/restored user's\r\n * ICE connection status or we detect local problem with the video track.\r\n * First argument is the ID of the participant and\r\n * the seconds is a string indicating if the connection is currently\r\n * - active - the connection is active\r\n * - inactive - the connection is inactive, was intentionally interrupted by\r\n * the bridge\r\n * - interrupted - a network problem occurred\r\n * - restoring - the connection was inactive and is restoring now\r\n *\r\n * The current status value can be obtained by calling\r\n * JitsiParticipant.getConnectionStatus().\r\n */\r\n PARTICIPANT_CONN_STATUS_CHANGED = 'conference.participant_conn_status_changed',\r\n\r\n /**\r\n * Indicates that the features of the participant has been changed.\r\n * TODO: there is a spelling mistake in this event name and associated constants\r\n */\r\n PARTCIPANT_FEATURES_CHANGED = 'conference.partcipant_features_changed',\r\n\r\n /**\r\n * Indicates that a the value of a specific property of a specific participant\r\n * has changed.\r\n */\r\n PARTICIPANT_PROPERTY_CHANGED = 'conference.participant_property_changed',\r\n\r\n /**\r\n * Indicates that the conference has switched between JVB and P2P connections.\r\n * The first argument of this event is a boolean which when set to\r\n * true means that the conference is running on the P2P connection.\r\n */\r\n P2P_STATUS = 'conference.p2pStatus',\r\n\r\n /**\r\n * Indicates that phone number changed.\r\n */\r\n PHONE_NUMBER_CHANGED = 'conference.phoneNumberChanged',\r\n\r\n /**\r\n * The conference properties changed.\r\n * @type {string}\r\n */\r\n PROPERTIES_CHANGED = 'conference.propertiesChanged',\r\n\r\n /**\r\n * Indicates that recording state changed.\r\n */\r\n RECORDER_STATE_CHANGED = 'conference.recorderStateChanged',\r\n\r\n /**\r\n * Indicates that video SIP GW state changed.\r\n * @param {VideoSIPGWConstants} status.\r\n */\r\n VIDEO_SIP_GW_AVAILABILITY_CHANGED = 'conference.videoSIPGWAvailabilityChanged',\r\n\r\n /**\r\n * Indicates that video SIP GW Session state changed.\r\n * @param {options} event - {\r\n * {string} address,\r\n * {VideoSIPGWConstants} oldState,\r\n * {VideoSIPGWConstants} newState,\r\n * {string} displayName}\r\n * }.\r\n */\r\n VIDEO_SIP_GW_SESSION_STATE_CHANGED = 'conference.videoSIPGWSessionStateChanged',\r\n\r\n /**\r\n * Indicates that start muted settings changed.\r\n */\r\n START_MUTED_POLICY_CHANGED = 'conference.start_muted_policy_changed',\r\n\r\n /**\r\n * Indicates that the local user has started muted.\r\n */\r\n STARTED_MUTED = 'conference.started_muted',\r\n\r\n /**\r\n * Indicates that subject of the conference has changed.\r\n */\r\n SUBJECT_CHANGED = 'conference.subjectChanged',\r\n\r\n /**\r\n * Indicates that DTMF support changed.\r\n */\r\n SUSPEND_DETECTED = 'conference.suspendDetected',\r\n\r\n /**\r\n * Event indicates that local user is talking while he muted himself\r\n */\r\n TALK_WHILE_MUTED = 'conference.talk_while_muted',\r\n\r\n /**\r\n * A new media track was added to the conference. The event provides the\r\n * following parameters to its listeners:\r\n *\r\n * @param {JitsiTrack} track the added JitsiTrack\r\n */\r\n TRACK_ADDED = 'conference.trackAdded',\r\n\r\n /**\r\n * Audio levels of a media track ( attached to the conference) was changed.\r\n */\r\n TRACK_AUDIO_LEVEL_CHANGED = 'conference.audioLevelsChanged',\r\n\r\n /**\r\n * A media track ( attached to the conference) mute status was changed.\r\n * @param {JitsiParticipant|null} the participant that initiated the mute\r\n * if it is a remote mute.\r\n */\r\n TRACK_MUTE_CHANGED = 'conference.trackMuteChanged',\r\n\r\n /**\r\n * The media track was removed from the conference. The event provides the\r\n * following parameters to its listeners:\r\n *\r\n * @param {JitsiTrack} track the removed JitsiTrack\r\n */\r\n TRACK_REMOVED = 'conference.trackRemoved',\r\n\r\n /**\r\n * The source-add for unmuting of a media track was rejected by Jicofo.\r\n *\r\n */\r\n TRACK_UNMUTE_REJECTED = 'conference.trackUnmuteRejected',\r\n\r\n /**\r\n * Notifies for transcription status changes. The event provides the\r\n * following parameters to its listeners:\r\n *\r\n * @param {String} status - The new status.\r\n */\r\n TRANSCRIPTION_STATUS_CHANGED = 'conference.transcriptionStatusChanged',\r\n\r\n /**\r\n * A new user joined the conference.\r\n */\r\n USER_JOINED = 'conference.userJoined',\r\n\r\n /**\r\n * A user has left the conference.\r\n */\r\n USER_LEFT = 'conference.userLeft',\r\n\r\n /**\r\n * User role changed.\r\n */\r\n USER_ROLE_CHANGED = 'conference.roleChanged',\r\n\r\n /**\r\n * User status changed.\r\n */\r\n USER_STATUS_CHANGED = 'conference.statusChanged',\r\n\r\n /**\r\n * Event indicates that the permission for unmuting video has changed based on the number of video senders in the call\r\n * and the video sender limit configured in Jicofo.\r\n */\r\n VIDEO_UNMUTE_PERMISSIONS_CHANGED = 'conference.video_unmute_permissions_changed',\r\n\r\n /**\r\n * Event indicates that the bot participant type changed.\r\n */\r\n BOT_TYPE_CHANGED = 'conference.bot_type_changed',\r\n\r\n /**\r\n * A new user joined the lobby room.\r\n */\r\n LOBBY_USER_JOINED = 'conference.lobby.userJoined',\r\n\r\n /**\r\n * A user from the lobby room has been update.\r\n */\r\n LOBBY_USER_UPDATED = 'conference.lobby.userUpdated',\r\n\r\n /**\r\n * A user left the lobby room.\r\n */\r\n LOBBY_USER_LEFT = 'conference.lobby.userLeft',\r\n\r\n /**\r\n * The local participant was approved to be able to unmute.\r\n * @param {options} event - {\r\n * {MediaType} mediaType\r\n * }.\r\n */\r\n AV_MODERATION_APPROVED = 'conference.av_moderation.approved',\r\n\r\n /**\r\n * The local participant was blocked to be able to unmute.\r\n * @param {options} event - {\r\n * {MediaType} mediaType\r\n * }.\r\n */\r\n AV_MODERATION_REJECTED = 'conference.av_moderation.rejected',\r\n\r\n /**\r\n * AV Moderation was enabled/disabled. The actor is the participant that is currently in the meeting,\r\n * or undefined if that participant has left the meeting.\r\n *\r\n * @param {options} event - {\r\n * {boolean} enabled,\r\n * {MediaType} mediaType,\r\n * {JitsiParticipant} actor\r\n * }.\r\n */\r\n AV_MODERATION_CHANGED = 'conference.av_moderation.changed',\r\n\r\n /**\r\n * AV Moderation, report for user being approved to unmute.\r\n * @param {options} event - {\r\n * {JitsiParticipant} participant,\r\n * {MediaType} mediaType\r\n * }.\r\n */\r\n AV_MODERATION_PARTICIPANT_APPROVED = 'conference.av_moderation.participant.approved',\r\n\r\n /**\r\n * AV Moderation, report for user being blocked to unmute.\r\n * @param {options} event - {\r\n * {JitsiParticipant} participant,\r\n * {MediaType} mediaType\r\n * }.\r\n */\r\n AV_MODERATION_PARTICIPANT_REJECTED = 'conference.av_moderation.participant.rejected',\r\n\r\n /**\r\n * A new face landmark object is added for a participant\r\n */\r\n FACE_LANDMARK_ADDED = 'conference.face_landmark.added',\r\n\r\n /**\r\n * Event fired when a participant is requested to join a given (breakout) room.\r\n */\r\n BREAKOUT_ROOMS_MOVE_TO_ROOM = 'conference.breakout-rooms.move-to-room',\r\n\r\n /**\r\n * Event fired when the breakout rooms data was updated.\r\n */\r\n BREAKOUT_ROOMS_UPDATED = 'conference.breakout-rooms.updated'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const AUDIO_INPUT_STATE_CHANGE = JitsiConferenceEvents.AUDIO_INPUT_STATE_CHANGE;\r\nexport const AUDIO_UNMUTE_PERMISSIONS_CHANGED = JitsiConferenceEvents.AUDIO_UNMUTE_PERMISSIONS_CHANGED;\r\nexport const AUTH_STATUS_CHANGED = JitsiConferenceEvents.AUTH_STATUS_CHANGED;\r\nexport const BEFORE_STATISTICS_DISPOSED = JitsiConferenceEvents.BEFORE_STATISTICS_DISPOSED;\r\nexport const CONFERENCE_ERROR = JitsiConferenceEvents.CONFERENCE_ERROR;\r\nexport const CONFERENCE_FAILED = JitsiConferenceEvents.CONFERENCE_FAILED;\r\nexport const CONFERENCE_JOIN_IN_PROGRESS = JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS;\r\nexport const CONFERENCE_JOINED = JitsiConferenceEvents.CONFERENCE_JOINED;\r\nexport const CONFERENCE_LEFT = JitsiConferenceEvents.CONFERENCE_LEFT;\r\nexport const CONFERENCE_UNIQUE_ID_SET = JitsiConferenceEvents.CONFERENCE_UNIQUE_ID_SET;\r\nexport const CONNECTION_ESTABLISHED = JitsiConferenceEvents.CONNECTION_ESTABLISHED;\r\nexport const CONNECTION_INTERRUPTED = JitsiConferenceEvents.CONNECTION_INTERRUPTED;\r\nexport const CONNECTION_RESTORED = JitsiConferenceEvents.CONNECTION_RESTORED;\r\nexport const DATA_CHANNEL_OPENED = JitsiConferenceEvents.DATA_CHANNEL_OPENED;\r\nexport const DISPLAY_NAME_CHANGED = JitsiConferenceEvents.DISPLAY_NAME_CHANGED;\r\nexport const DOMINANT_SPEAKER_CHANGED = JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED;\r\nexport const CONFERENCE_CREATED_TIMESTAMP = JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP;\r\nexport const DTMF_SUPPORT_CHANGED = JitsiConferenceEvents.DTMF_SUPPORT_CHANGED;\r\nexport const ENDPOINT_MESSAGE_RECEIVED = JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED;\r\nexport const ENDPOINT_STATS_RECEIVED = JitsiConferenceEvents.ENDPOINT_STATS_RECEIVED;\r\nexport const JVB121_STATUS = JitsiConferenceEvents.JVB121_STATUS;\r\nexport const KICKED = JitsiConferenceEvents.KICKED;\r\nexport const PARTICIPANT_KICKED = JitsiConferenceEvents.PARTICIPANT_KICKED;\r\nexport const LAST_N_ENDPOINTS_CHANGED = JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED;\r\nexport const FORWARDED_SOURCES_CHANGED = JitsiConferenceEvents.FORWARDED_SOURCES_CHANGED;\r\nexport const LOCK_STATE_CHANGED = JitsiConferenceEvents.LOCK_STATE_CHANGED;\r\nexport const SERVER_REGION_CHANGED = JitsiConferenceEvents.SERVER_REGION_CHANGED;\r\nexport const _MEDIA_SESSION_STARTED = JitsiConferenceEvents._MEDIA_SESSION_STARTED;\r\nexport const _MEDIA_SESSION_ACTIVE_CHANGED = JitsiConferenceEvents._MEDIA_SESSION_ACTIVE_CHANGED;\r\nexport const MEMBERS_ONLY_CHANGED = JitsiConferenceEvents.MEMBERS_ONLY_CHANGED;\r\nexport const MESSAGE_RECEIVED = JitsiConferenceEvents.MESSAGE_RECEIVED;\r\nexport const NO_AUDIO_INPUT = JitsiConferenceEvents.NO_AUDIO_INPUT;\r\nexport const NOISY_MIC = JitsiConferenceEvents.NOISY_MIC;\r\nexport const NON_PARTICIPANT_MESSAGE_RECEIVED = JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED;\r\nexport const PRIVATE_MESSAGE_RECEIVED = JitsiConferenceEvents.PRIVATE_MESSAGE_RECEIVED;\r\nexport const PARTICIPANT_CONN_STATUS_CHANGED = JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED;\r\nexport const PARTCIPANT_FEATURES_CHANGED = JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED;\r\nexport const PARTICIPANT_PROPERTY_CHANGED = JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED;\r\nexport const P2P_STATUS = JitsiConferenceEvents.P2P_STATUS;\r\nexport const PHONE_NUMBER_CHANGED = JitsiConferenceEvents.PHONE_NUMBER_CHANGED;\r\nexport const PROPERTIES_CHANGED = JitsiConferenceEvents.PROPERTIES_CHANGED;\r\nexport const RECORDER_STATE_CHANGED = JitsiConferenceEvents.RECORDER_STATE_CHANGED;\r\nexport const VIDEO_SIP_GW_AVAILABILITY_CHANGED = JitsiConferenceEvents.VIDEO_SIP_GW_AVAILABILITY_CHANGED;\r\nexport const VIDEO_SIP_GW_SESSION_STATE_CHANGED = JitsiConferenceEvents.VIDEO_SIP_GW_SESSION_STATE_CHANGED;\r\nexport const START_MUTED_POLICY_CHANGED = JitsiConferenceEvents.START_MUTED_POLICY_CHANGED;\r\nexport const STARTED_MUTED = JitsiConferenceEvents.STARTED_MUTED;\r\nexport const SUBJECT_CHANGED = JitsiConferenceEvents.SUBJECT_CHANGED;\r\nexport const SUSPEND_DETECTED = JitsiConferenceEvents.SUSPEND_DETECTED;\r\nexport const TALK_WHILE_MUTED = JitsiConferenceEvents.TALK_WHILE_MUTED;\r\nexport const TRACK_ADDED = JitsiConferenceEvents.TRACK_ADDED;\r\nexport const TRACK_AUDIO_LEVEL_CHANGED = JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED;\r\nexport const TRACK_MUTE_CHANGED = JitsiConferenceEvents.TRACK_MUTE_CHANGED;\r\nexport const TRACK_REMOVED = JitsiConferenceEvents.TRACK_REMOVED;\r\nexport const TRACK_UNMUTE_REJECTED = JitsiConferenceEvents.TRACK_UNMUTE_REJECTED;\r\nexport const TRANSCRIPTION_STATUS_CHANGED = JitsiConferenceEvents.TRANSCRIPTION_STATUS_CHANGED;\r\nexport const USER_JOINED = JitsiConferenceEvents.USER_JOINED;\r\nexport const USER_LEFT = JitsiConferenceEvents.USER_LEFT;\r\nexport const USER_ROLE_CHANGED = JitsiConferenceEvents.USER_ROLE_CHANGED;\r\nexport const USER_STATUS_CHANGED = JitsiConferenceEvents.USER_STATUS_CHANGED;\r\nexport const VIDEO_UNMUTE_PERMISSIONS_CHANGED = JitsiConferenceEvents.VIDEO_UNMUTE_PERMISSIONS_CHANGED;\r\nexport const BOT_TYPE_CHANGED = JitsiConferenceEvents.BOT_TYPE_CHANGED;\r\nexport const LOBBY_USER_JOINED = JitsiConferenceEvents.LOBBY_USER_JOINED;\r\nexport const LOBBY_USER_UPDATED = JitsiConferenceEvents.LOBBY_USER_UPDATED;\r\nexport const LOBBY_USER_LEFT = JitsiConferenceEvents.LOBBY_USER_LEFT;\r\nexport const AV_MODERATION_APPROVED = JitsiConferenceEvents.AV_MODERATION_APPROVED;\r\nexport const AV_MODERATION_REJECTED = JitsiConferenceEvents.AV_MODERATION_REJECTED;\r\nexport const AV_MODERATION_CHANGED = JitsiConferenceEvents.AV_MODERATION_CHANGED;\r\nexport const AV_MODERATION_PARTICIPANT_APPROVED = JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_APPROVED;\r\nexport const AV_MODERATION_PARTICIPANT_REJECTED = JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_REJECTED;\r\nexport const FACE_LANDMARK_ADDED = JitsiConferenceEvents.FACE_LANDMARK_ADDED;\r\nexport const BREAKOUT_ROOMS_MOVE_TO_ROOM = JitsiConferenceEvents.BREAKOUT_ROOMS_MOVE_TO_ROOM;\r\nexport const BREAKOUT_ROOMS_UPDATED = JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED;\r\n","export const CALLSTATS_SCRIPT_URL = 'https://api.callstats.io/static/callstats-ws.min.js';\r\n\r\n/**\r\n * The number of remote speakers for which the audio levels will be calculated using\r\n * RTCRtpReceiver#getSynchronizationSources. Limit the number of endpoints to save cpu on the client as this API call\r\n * is known to take longer to execute when there are many audio receivers.\r\n */\r\nexport const SPEAKERS_AUDIO_LEVELS = 5;\r\n","/**\r\n * The errors for the JitsiTrack objects.\r\n */\r\n\r\nexport enum JitsiTrackErrors {\r\n /**\r\n * An error which indicates that some of requested constraints in\r\n * getUserMedia call were not satisfied.\r\n */\r\n CONSTRAINT_FAILED = 'gum.constraint_failed',\r\n\r\n /**\r\n * A generic error which indicates an error occurred while selecting\r\n * a DesktopCapturerSource from the electron app.\r\n */\r\n ELECTRON_DESKTOP_PICKER_ERROR = 'gum.electron_desktop_picker_error',\r\n\r\n /**\r\n * An error which indicates a custom desktop picker could not be detected\r\n * for the electron app.\r\n */\r\n ELECTRON_DESKTOP_PICKER_NOT_FOUND = 'gum.electron_desktop_picker_not_found',\r\n\r\n /**\r\n * Generic getUserMedia error.\r\n */\r\n GENERAL = 'gum.general',\r\n\r\n /**\r\n * An error which indicates that requested device was not found.\r\n */\r\n NOT_FOUND = 'gum.not_found',\r\n\r\n /**\r\n * An error which indicates that user denied permission to share requested\r\n * device.\r\n */\r\n PERMISSION_DENIED = 'gum.permission_denied',\r\n\r\n /**\r\n * Generic error for screensharing failure.\r\n */\r\n SCREENSHARING_GENERIC_ERROR = 'gum.screensharing_generic_error',\r\n\r\n /**\r\n * An error which indicates that user canceled screen sharing window\r\n * selection dialog.\r\n */\r\n SCREENSHARING_USER_CANCELED = 'gum.screensharing_user_canceled',\r\n\r\n /**\r\n * Indicates that the timeout passed to the obtainAudioAndVideoPermissions has expired without GUM resolving.\r\n */\r\n TIMEOUT = 'gum.timeout',\r\n\r\n /**\r\n * An error which indicates that track has been already disposed and cannot\r\n * be longer used.\r\n */\r\n TRACK_IS_DISPOSED = 'track.track_is_disposed',\r\n\r\n /**\r\n * An error which indicates that track has no MediaStream associated.\r\n */\r\n TRACK_NO_STREAM_FOUND = 'track.no_stream_found',\r\n\r\n /**\r\n * An error which indicates that requested video resolution is not supported\r\n * by a webcam.\r\n */\r\n UNSUPPORTED_RESOLUTION = 'gum.unsupported_resolution'\r\n}\r\n\r\n// exported for backward compatibility\r\nexport const CONSTRAINT_FAILED = JitsiTrackErrors.CONSTRAINT_FAILED;\r\nexport const ELECTRON_DESKTOP_PICKER_ERROR = JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_ERROR;\r\nexport const ELECTRON_DESKTOP_PICKER_NOT_FOUND = JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_NOT_FOUND;\r\nexport const GENERAL = JitsiTrackErrors.GENERAL;\r\nexport const NOT_FOUND = JitsiTrackErrors.NOT_FOUND;\r\nexport const PERMISSION_DENIED = JitsiTrackErrors.PERMISSION_DENIED;\r\nexport const SCREENSHARING_GENERIC_ERROR = JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR;\r\nexport const SCREENSHARING_USER_CANCELED = JitsiTrackErrors.SCREENSHARING_USER_CANCELED;\r\nexport const TIMEOUT = JitsiTrackErrors.TIMEOUT;\r\nexport const TRACK_IS_DISPOSED = JitsiTrackErrors.TRACK_IS_DISPOSED;\r\nexport const TRACK_NO_STREAM_FOUND = JitsiTrackErrors.TRACK_NO_STREAM_FOUND;\r\nexport const UNSUPPORTED_RESOLUTION = JitsiTrackErrors.UNSUPPORTED_RESOLUTION;\r\n","import * as JitsiTrackErrors from './JitsiTrackErrors';\r\n\r\nconst TRACK_ERROR_TO_MESSAGE_MAP = {};\r\n\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]\r\n = 'Video resolution is not supported: ';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.SCREENSHARING_USER_CANCELED]\r\n = 'User canceled screen sharing prompt';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR]\r\n = 'Unknown error from screensharing';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_ERROR]\r\n = 'Unkown error from desktop picker';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_NOT_FOUND]\r\n = 'Failed to detect desktop picker';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.GENERAL]\r\n = 'Generic getUserMedia error';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.PERMISSION_DENIED]\r\n = 'User denied permission to use device(s): ';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.NOT_FOUND]\r\n = 'Requested device(s) was/were not found: ';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.CONSTRAINT_FAILED]\r\n = 'Constraint could not be satisfied: ';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.TIMEOUT]\r\n = 'Could not start media source. Timeout occured!';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.TRACK_IS_DISPOSED]\r\n = 'Track has been already disposed';\r\nTRACK_ERROR_TO_MESSAGE_MAP[JitsiTrackErrors.TRACK_NO_STREAM_FOUND]\r\n = 'Track does not have an associated Media Stream';\r\n\r\n// FIXME: Using prototype inheritance because otherwise instanceof is not\r\n// working properly (see https://github.com/babel/babel/issues/3083)\r\n\r\n/**\r\n *\r\n * Represents an error that occurred to a JitsiTrack. Can represent various\r\n * types of errors. For error descriptions (@see JitsiTrackErrors).\r\n *\r\n * @extends Error\r\n *\r\n *\r\n * @constructor\r\n * @param {Object|string} error - error object or error name\r\n * @param {Object|string} (options) - getUserMedia constraints object or\r\n * error message\r\n * @param {('audio'|'video'|'desktop'|'screen'|'audiooutput')[]} (devices) -\r\n * list of getUserMedia requested devices\r\n */\r\nfunction JitsiTrackError(error, options, devices) {\r\n if (typeof error === 'object' && typeof error.name !== 'undefined') {\r\n /**\r\n * Additional information about original getUserMedia error\r\n * and constraints.\r\n * @type {{\r\n * error: Object,\r\n * constraints: Object,\r\n * devices: Array.<'audio'|'video'|'desktop'|'screen'>\r\n * }}\r\n */\r\n this.gum = {\r\n error,\r\n constraints: options,\r\n devices: devices && Array.isArray(devices)\r\n ? devices.slice(0)\r\n : undefined\r\n };\r\n\r\n switch (error.name) {\r\n case 'NotAllowedError':\r\n case 'PermissionDeniedError':\r\n case 'SecurityError':\r\n this.name = JitsiTrackErrors.PERMISSION_DENIED;\r\n this.message\r\n = TRACK_ERROR_TO_MESSAGE_MAP[this.name]\r\n + (this.gum.devices || []).join(', ');\r\n break;\r\n case 'DevicesNotFoundError':\r\n case 'NotFoundError':\r\n this.name = JitsiTrackErrors.NOT_FOUND;\r\n this.message\r\n = TRACK_ERROR_TO_MESSAGE_MAP[this.name]\r\n + (this.gum.devices || []).join(', ');\r\n break;\r\n case 'ConstraintNotSatisfiedError':\r\n case 'OverconstrainedError': {\r\n const constraintName = error.constraintName || error.constraint;\r\n\r\n // we treat deviceId as unsupported resolution, as we want to\r\n // retry and finally if everything fails to remove deviceId from\r\n // mandatory constraints\r\n if (options\r\n && options.video\r\n && (!devices || devices.indexOf('video') > -1)\r\n && (constraintName === 'minWidth'\r\n || constraintName === 'maxWidth'\r\n || constraintName === 'minHeight'\r\n || constraintName === 'maxHeight'\r\n || constraintName === 'width'\r\n || constraintName === 'height'\r\n || constraintName === 'deviceId')) {\r\n this.name = JitsiTrackErrors.UNSUPPORTED_RESOLUTION;\r\n this.message\r\n = TRACK_ERROR_TO_MESSAGE_MAP[this.name]\r\n + getResolutionFromFailedConstraint(\r\n constraintName,\r\n options);\r\n } else {\r\n this.name = JitsiTrackErrors.CONSTRAINT_FAILED;\r\n this.message\r\n = TRACK_ERROR_TO_MESSAGE_MAP[this.name]\r\n + error.constraintName;\r\n }\r\n break;\r\n }\r\n\r\n default:\r\n this.name = JitsiTrackErrors.GENERAL;\r\n this.message\r\n = error.message || TRACK_ERROR_TO_MESSAGE_MAP[this.name];\r\n break;\r\n }\r\n } else if (typeof error === 'string') {\r\n if (TRACK_ERROR_TO_MESSAGE_MAP[error]) {\r\n this.name = error;\r\n this.message = options || TRACK_ERROR_TO_MESSAGE_MAP[error];\r\n } else {\r\n // this is some generic error that do not fit any of our\r\n // pre-defined errors, so don't give it any specific name, just\r\n // store message\r\n this.message = error;\r\n }\r\n } else {\r\n throw new Error('Invalid arguments');\r\n }\r\n\r\n this.stack = error.stack || new Error().stack;\r\n}\r\n\r\nJitsiTrackError.prototype = Object.create(Error.prototype);\r\nJitsiTrackError.prototype.constructor = JitsiTrackError;\r\n\r\n/**\r\n * Gets failed resolution constraint from corresponding object.\r\n * @param {string} failedConstraintName\r\n * @param {Object} constraints\r\n * @returns {string|number}\r\n */\r\nfunction getResolutionFromFailedConstraint(failedConstraintName, constraints) {\r\n if (constraints && constraints.video && constraints.video.mandatory) {\r\n switch (failedConstraintName) {\r\n case 'width':\r\n return constraints.video.mandatory.minWidth;\r\n case 'height':\r\n return constraints.video.mandatory.minHeight;\r\n default:\r\n return constraints.video.mandatory[failedConstraintName] || '';\r\n }\r\n }\r\n\r\n return '';\r\n}\r\n\r\nexport default JitsiTrackError;\r\n","/**\r\n * This class exports constants and factory methods related to the analytics\r\n * API provided by AnalyticsAdapter. In order for entries in a database to be\r\n * somewhat easily traceable back to the code which produced them, events sent\r\n * through analytics should be defined here.\r\n *\r\n * Since the AnalyticsAdapter API can be used in different ways, for some events\r\n * it is more convenient to just define the event name as a constant. For other\r\n * events a factory function is easier.\r\n *\r\n * A general approach for adding a new event:\r\n * 1. Determine the event type: track, UI, page, or operational. If in doubt use\r\n * operational.\r\n * 2. Determine whether the event is related to other existing events, and\r\n * which fields are desired to be set: name, action, actionSubject, source.\r\n * 3. If the name is sufficient (the other fields are not important), use a\r\n * constant. Otherwise use a factory function.\r\n *\r\n * Note that the AnalyticsAdapter uses the events passed to its functions for\r\n * its own purposes, and might modify them. Because of this, factory functions\r\n * should create new objects.\r\n *\r\n */\r\n\r\nexport enum AnalyticsEvents {\r\n /**\r\n * The constant which identifies an event of type \"operational\".\r\n */\r\n TYPE_OPERATIONAL = 'operational',\r\n\r\n /**\r\n * The constant which identifies an event of type \"page\".\r\n */\r\n TYPE_PAGE = 'page',\r\n\r\n /**\r\n * The constant which identifies an event of type \"track\".\r\n */\r\n TYPE_TRACK = 'track',\r\n\r\n /**\r\n * The constant which identifies an event of type \"ui\".\r\n */\r\n TYPE_UI = 'ui',\r\n\r\n /**\r\n * The \"action\" value for Jingle events which indicates that the Jingle session\r\n * was restarted (TODO: verify/fix the documentation)\r\n */\r\n ACTION_JINGLE_RESTART = 'restart',\r\n\r\n /**\r\n * The \"action\" value for Jingle events which indicates that a session-accept\r\n * timed out (TODO: verify/fix the documentation)\r\n */\r\n ACTION_JINGLE_SA_TIMEOUT = 'session-accept.timeout',\r\n\r\n /**\r\n * The \"action\" value for Jingle events which indicates that a session-initiate\r\n * was received.\r\n */\r\n ACTION_JINGLE_SI_RECEIVED = 'session-initiate.received',\r\n\r\n /**\r\n * The \"action\" value for Jingle events which indicates that a session-initiate\r\n * not arrived within a timeout (the value is specified in\r\n * the {@link JingleSessionPC}.\r\n */\r\n ACTION_JINGLE_SI_TIMEOUT = 'session-initiate.timeout',\r\n\r\n /**\r\n * A constant for the \"terminate\" action for Jingle events. TODO: verify/fix\r\n * the documentation)\r\n */\r\n ACTION_JINGLE_TERMINATE = 'terminate',\r\n\r\n /**\r\n * The \"action\" value for Jingle events which indicates that a transport-replace\r\n * was received.\r\n */\r\n ACTION_JINGLE_TR_RECEIVED = 'transport-replace.received',\r\n\r\n /**\r\n * The \"action\" value for Jingle events which indicates that a transport-replace\r\n * succeeded (TODO: verify/fix the documentation)\r\n */\r\n ACTION_JINGLE_TR_SUCCESS = 'transport-replace.success',\r\n\r\n /**\r\n * The \"action\" value for P2P events which indicates that P2P session initiate message has been rejected by the client\r\n * because the mandatory requirements were not met.\r\n */\r\n ACTION_P2P_DECLINED = 'decline',\r\n\r\n /**\r\n * The \"action\" value for P2P events which indicates that a connection was\r\n * established (TODO: verify/fix the documentation)\r\n */\r\n ACTION_P2P_ESTABLISHED = 'established',\r\n\r\n /**\r\n * The \"action\" value for P2P events which indicates that something failed.\r\n */\r\n ACTION_P2P_FAILED = 'failed',\r\n\r\n /**\r\n * The \"action\" value for P2P events which indicates that a switch to\r\n * jitsi-videobridge happened.\r\n */\r\n ACTION_P2P_SWITCH_TO_JVB = 'switch.to.jvb',\r\n\r\n /**\r\n * The name of an event which indicates an available device. We send one such\r\n * event per available device once when the available devices are first known,\r\n * and every time that they change\r\n *\r\n * Properties:\r\n * audio_input_device_count: the number of audio input devices available at\r\n * the time the event was sent.\r\n * audio_output_device_count: the number of audio output devices available\r\n * at the time the event was sent.\r\n * video_input_device_count: the number of video input devices available at\r\n * the time the event was sent.\r\n * video_output_device_count: the number of video output devices available\r\n * at the time the event was sent.\r\n * device_id: an identifier of the device described in this event.\r\n * device_group_id:\r\n * device_kind: one of 'audioinput', 'audiooutput', 'videoinput' or\r\n * 'videooutput'.\r\n * device_label: a string which describes the device.\r\n */\r\n AVAILABLE_DEVICE = 'available.device',\r\n\r\n /**\r\n * This appears to be fired only in certain cases when the XMPP connection\r\n * disconnects (and it was intentional?). It is currently never observed to\r\n * fire in production.\r\n *\r\n * TODO: document\r\n *\r\n * Properties:\r\n * message: an error message\r\n */\r\n CONNECTION_DISCONNECTED = 'connection.disconnected',\r\n\r\n /**\r\n * Indicates that the user of the application provided feedback in terms of a\r\n * rating (an integer from 1 to 5) and an optional comment.\r\n * Properties:\r\n * value: the user's rating (an integer from 1 to 5)\r\n * comment: the user's comment\r\n */\r\n FEEDBACK = 'feedback',\r\n\r\n /**\r\n * Indicates the duration of a particular phase of the ICE connectivity\r\n * establishment.\r\n *\r\n * Properties:\r\n * phase: the ICE phase (e.g. 'gathering', 'checking', 'establishment')\r\n * value: the duration in milliseconds.\r\n * p2p: whether the associated ICE connection is p2p or towards a\r\n * jitsi-videobridge\r\n * initiator: whether the local Jingle peer is the initiator or responder\r\n * in the Jingle session. XXX we probably actually care about the ICE\r\n * role (controlling vs controlled), and we assume that this correlates\r\n * with the Jingle initiator.\r\n */\r\n ICE_DURATION = 'ice.duration',\r\n\r\n /**\r\n * Indicates the difference in milliseconds between the ICE establishment time\r\n * for the P2P and JVB connections (e.g. a value of 10 would indicate that the\r\n * P2P connection took 10ms more than JVB connection to establish).\r\n *\r\n * Properties:\r\n * value: the difference in establishment durations in milliseconds.\r\n *\r\n */\r\n ICE_ESTABLISHMENT_DURATION_DIFF = 'ice.establishment.duration.diff',\r\n\r\n /**\r\n * Indicates that the ICE state has changed.\r\n *\r\n * Properties:\r\n * state: the ICE state which was entered (e.g. 'checking', 'connected',\r\n * 'completed', etc).\r\n * value: the time in milliseconds (as reported by\r\n * window.performance.now()) that the state change occurred.\r\n * p2p: whether the associated ICE connection is p2p or towards a\r\n * jitsi-videobridge\r\n * signalingState: The signaling state of the associated PeerConnection\r\n * reconnect: whether the associated Jingle session is in the process of\r\n * reconnecting (or is it ICE? TODO: verify/fix the documentation)\r\n */\r\n ICE_STATE_CHANGED = 'ice.state.changed',\r\n\r\n /**\r\n * Indicates that no bytes have been sent for the track.\r\n *\r\n * Properties:\r\n * mediaType: the media type of the local track ('audio' or 'video').\r\n */\r\n NO_BYTES_SENT = 'track.no-bytes-sent',\r\n\r\n /**\r\n * Indicates that a track was unmuted (?).\r\n *\r\n * Properties:\r\n * mediaType: the media type of the local track ('audio' or 'video').\r\n * trackType: the type of the track ('local' or 'remote').\r\n * value: TODO: document\r\n */\r\n TRACK_UNMUTED = 'track.unmuted'\r\n}\r\n\r\n// exported for backward compatibility\r\nexport const TYPE_OPERATIONAL = AnalyticsEvents.TYPE_OPERATIONAL;\r\nexport const TYPE_PAGE = AnalyticsEvents.TYPE_PAGE;\r\nexport const TYPE_TRACK = AnalyticsEvents.TYPE_TRACK;\r\nexport const TYPE_UI = AnalyticsEvents.TYPE_UI;\r\nexport const ACTION_JINGLE_RESTART = AnalyticsEvents.ACTION_JINGLE_RESTART;\r\nexport const ACTION_JINGLE_SA_TIMEOUT = AnalyticsEvents.ACTION_JINGLE_SA_TIMEOUT;\r\nexport const ACTION_JINGLE_SI_RECEIVED = AnalyticsEvents.ACTION_JINGLE_SI_RECEIVED;\r\nexport const ACTION_JINGLE_SI_TIMEOUT = AnalyticsEvents.ACTION_JINGLE_SI_TIMEOUT;\r\nexport const ACTION_JINGLE_TERMINATE = AnalyticsEvents.ACTION_JINGLE_TERMINATE;\r\nexport const ACTION_JINGLE_TR_RECEIVED = AnalyticsEvents.ACTION_JINGLE_TR_RECEIVED;\r\nexport const ACTION_JINGLE_TR_SUCCESS = AnalyticsEvents.ACTION_JINGLE_TR_SUCCESS;\r\nexport const ACTION_P2P_DECLINED = AnalyticsEvents.ACTION_P2P_DECLINED;\r\nexport const ACTION_P2P_ESTABLISHED = AnalyticsEvents.ACTION_P2P_ESTABLISHED;\r\nexport const ACTION_P2P_FAILED = AnalyticsEvents.ACTION_P2P_FAILED;\r\nexport const ACTION_P2P_SWITCH_TO_JVB = AnalyticsEvents.ACTION_P2P_SWITCH_TO_JVB;\r\nexport const AVAILABLE_DEVICE = AnalyticsEvents.AVAILABLE_DEVICE;\r\nexport const CONNECTION_DISCONNECTED = AnalyticsEvents.CONNECTION_DISCONNECTED;\r\nexport const FEEDBACK = AnalyticsEvents.FEEDBACK;\r\nexport const ICE_DURATION = AnalyticsEvents.ICE_DURATION;\r\nexport const ICE_ESTABLISHMENT_DURATION_DIFF = AnalyticsEvents.ICE_ESTABLISHMENT_DURATION_DIFF;\r\nexport const ICE_STATE_CHANGED = AnalyticsEvents.ICE_STATE_CHANGED;\r\nexport const NO_BYTES_SENT = AnalyticsEvents.NO_BYTES_SENT;\r\nexport const TRACK_UNMUTED = AnalyticsEvents.TRACK_UNMUTED;\r\n\r\n/**\r\n * Creates an operational event which indicates that we have received a\r\n * \"bridge down\" event from jicofo.\r\n */\r\nexport const createBridgeDownEvent = () => ( {\r\n action: 'bridge.down',\r\n actionSubject: 'bridge.down',\r\n type: TYPE_OPERATIONAL\r\n} );\r\n\r\n/**\r\n * Creates an event which indicates that the XMPP connection failed\r\n * @param errorType TODO\r\n * @param errorMessage TODO\r\n * @param detail connection failed details.\r\n */\r\nexport const createConnectionFailedEvent = ( errorType: unknown, errorMessage: unknown, details: object ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action: 'connection.failed',\r\n attributes: {\r\n 'error_type': errorType,\r\n 'error_message': errorMessage,\r\n ...details\r\n }\r\n} );\r\n\r\n/**\r\n * Creates a conference event.\r\n *\r\n * @param action - The action of the event.\r\n * @param attributes - The attributes to be added to the event.\r\n */\r\nexport const createConferenceEvent = ( action: string, attributes: object ) => ( {\r\n action,\r\n attributes,\r\n source: 'conference',\r\n type: AnalyticsEvents.TYPE_OPERATIONAL\r\n} );\r\n\r\n/**\r\n * Creates an operational event which indicates that a particular connection\r\n * stage was reached (i.e. the XMPP connection transitioned to the \"connected\"\r\n * state).\r\n *\r\n * @param stage the stage which was reached\r\n * @param attributes additional attributes for the event. This should be an\r\n * object with a \"value\" property indicating a timestamp in milliseconds\r\n * relative to the beginning of the document's lifetime.\r\n *\r\n */\r\nexport const createConnectionStageReachedEvent = ( stage: unknown, attributes: object ) => ( {\r\n action: 'connection.stage.reached',\r\n actionSubject: stage,\r\n attributes,\r\n source: 'connection.stage.reached',\r\n type: AnalyticsEvents.TYPE_OPERATIONAL\r\n} );\r\n\r\n/**\r\n * Creates an operational event for the end-to-end round trip time to a\r\n * specific remote participant.\r\n * @param participantId the ID of the remote participant.\r\n * @param region the region of the remote participant\r\n * @param rtt the rtt\r\n */\r\nexport const createE2eRttEvent = ( participantId: unknown, region: unknown, rtt: unknown ) => ( {\r\n attributes: {\r\n 'participant_id': participantId,\r\n region,\r\n rtt\r\n },\r\n name: 'e2e_rtt',\r\n type: AnalyticsEvents.TYPE_OPERATIONAL\r\n} );\r\n\r\n/**\r\n * Creates an event which indicates that the focus has left the MUC.\r\n */\r\nexport const createFocusLeftEvent = () => ( {\r\n action: 'focus.left',\r\n actionSubject: 'focus.left',\r\n type: AnalyticsEvents.TYPE_OPERATIONAL\r\n} );\r\n\r\n/**\r\n * Creates an event related to a getUserMedia call.\r\n *\r\n * @param action the type of the result that the event represents: 'error',\r\n * 'success', 'warning', etc.\r\n * @param attributes the attributes to attach to the event.\r\n */\r\nexport const createGetUserMediaEvent = ( action: 'error' | 'success' | 'warning' | string, attributes: object = {} ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n source: 'get.user.media',\r\n action,\r\n attributes\r\n} );\r\n\r\n/**\r\n * Creates an event related to remote participant connection status changes.\r\n *\r\n * @param attributes the attributes to attach to the event.\r\n */\r\nexport const createParticipantConnectionStatusEvent = ( attributes: object = {} ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n source: 'peer.conn.status',\r\n action: 'duration',\r\n attributes\r\n} );\r\n\r\n/**\r\n * Creates an event related to remote track streaming status changes.\r\n *\r\n * @param attributes the attributes to attach to the event.\r\n */\r\nexport const createTrackStreamingStatusEvent = ( attributes: object = {} ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n source: 'track.streaming.status',\r\n action: 'duration',\r\n attributes\r\n} );\r\n\r\n/**\r\n * Creates an event for a Jingle-related event.\r\n * @param action the action of the event\r\n * @param attributes attributes to add to the event.\r\n */\r\nexport const createJingleEvent = ( action: unknown, attributes: object = {} ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action,\r\n source: 'jingle',\r\n attributes\r\n} );\r\n\r\n/**\r\n * Creates an event which indicates that a local track was not able to read\r\n * data from its source (a camera or a microphone).\r\n *\r\n * @param mediaType the media type of the local track ('audio' or\r\n * 'video').\r\n */\r\nexport const createNoDataFromSourceEvent = ( mediaType: 'audio' | 'video' | string, value: unknown ) => ( {\r\n attributes: {\r\n 'media_type': mediaType,\r\n value\r\n },\r\n action: 'track.no.data.from.source',\r\n type: AnalyticsEvents.TYPE_OPERATIONAL\r\n} );\r\n\r\n/**\r\n * Creates an event for a p2p-related event.\r\n * @param action the action of the event\r\n * @param attributes attributes to add to the event.\r\n */\r\nexport const createP2PEvent = ( action: unknown, attributes: object = {} ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action,\r\n source: 'p2p',\r\n attributes\r\n} )\r\n\r\n/**\r\n * Indicates that we received a remote command to mute.\r\n */\r\nexport const createRemotelyMutedEvent = ( mediaType: unknown ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action: 'remotely.muted',\r\n mediaType\r\n} );\r\n\r\n/**\r\n * Creates an event which contains RTP statistics such as RTT and packet loss.\r\n *\r\n * All average RTP stats are currently reported under 1 event name, but with\r\n * different properties that allows to distinguish between a P2P call, a\r\n * call relayed through TURN or the JVB, and multiparty vs 1:1.\r\n *\r\n * The structure of the event is:\r\n *\r\n * {\r\n * p2p: true,\r\n * conferenceSize: 2,\r\n * localCandidateType: \"relay\",\r\n * remoteCandidateType: \"relay\",\r\n * transportType: \"udp\",\r\n *\r\n * // Average RTT of 200ms\r\n * \"rtt.avg\": 200,\r\n * \"rtt.samples\": \"[100, 200, 300]\",\r\n *\r\n * // Average packet loss of 10%\r\n * \"packet.loss.avg\": 10,\r\n * \"packet.loss.samples\": '[5, 10, 15]'\r\n *\r\n * // Difference in milliseconds in the end-to-end RTT between p2p and jvb.\r\n * // The e2e RTT through jvb is 15ms shorter:\r\n * \"rtt.diff\": 15,\r\n *\r\n * // End-to-end RTT through JVB is ms.\r\n * \"end2end.rtt.avg\" = 100\r\n * }\r\n *\r\n * Note that the value of the \"samples\" properties are (JSON encoded) strings,\r\n * and not JSON arrays, as events' attributes can not be nested. The samples are\r\n * currently included for debug purposes only and can be removed anytime soon\r\n * from the structure.\r\n *\r\n * Also note that not all of values are present in each event, as values are\r\n * obtained and calculated as part of different process/event pipe. For example\r\n * {@link ConnectionAvgStats} instances are doing the reports for each\r\n * {@link TraceablePeerConnection} and work independently from the main stats\r\n * pipe.\r\n */\r\nexport const createRtpStatsEvent = ( attributes: object ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action: 'rtp.stats',\r\n attributes\r\n} );\r\n\r\n/**\r\n * Creates an event which contains the round trip time (RTT) to a set of\r\n * regions.\r\n *\r\n * @param attributes\r\n */\r\nexport const createRttByRegionEvent = ( attributes: object ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action: 'rtt.by.region',\r\n attributes\r\n} );\r\n\r\n/**\r\n * Creates an event which contains the local and remote ICE candidate types\r\n * for the transport that is currently selected.\r\n *\r\n * @param attributes\r\n */\r\nexport const createTransportStatsEvent = ( attributes: object ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action: 'transport.stats',\r\n attributes\r\n} );\r\n\r\n/**\r\n * Creates an event which contains information about the audio output problem (the user id of the affected participant,\r\n * the local audio levels and the remote audio levels that triggered the event).\r\n *\r\n * @param userID - The user id of the affected participant.\r\n * @param localAudioLevels - The local audio levels.\r\n * @param remoteAudioLevels - The audio levels received from the participant.\r\n */\r\nexport const createAudioOutputProblemEvent = ( userID: string, localAudioLevels: unknown, remoteAudioLevels: unknown ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action: 'audio.output.problem',\r\n attributes: {\r\n userID,\r\n localAudioLevels,\r\n remoteAudioLevels\r\n }\r\n} );\r\n\r\n/**\r\n * Creates an event which contains an information related to the bridge channel close event.\r\n *\r\n * @param code - A code from {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}\r\n * @param reason - A string which describes the reason for closing the bridge channel.\r\n */\r\nexport const createBridgeChannelClosedEvent = ( code: string, reason: string ) => ( {\r\n type: AnalyticsEvents.TYPE_OPERATIONAL,\r\n action: 'bridge-channel.error',\r\n attributes: {\r\n code,\r\n reason\r\n }\r\n} );\r\n\r\n/**\r\n * Creates an event which indicates the Time To First Media (TTFM).\r\n * It is measured in milliseconds relative to the beginning of the document's\r\n * lifetime (i.e. the origin used by window.performance.now()), and it excludes\r\n * the following:\r\n * 1. The delay due to getUserMedia()\r\n * 2. The period between the MUC being joined and the reception of the Jingle\r\n * session-initiate from jicofo. This is because jicofo will not start a Jingle\r\n * session until there are at least 2 participants in the room.\r\n *\r\n * @param attributes the attributes to add to the event. Currently used fields:\r\n * mediaType: the media type of the local track ('audio' or 'video').\r\n * muted: whether the track has ever been muted (?)\r\n * value: the TTMF in milliseconds.\r\n */\r\nexport const createTtfmEvent = ( attributes: object ) => createConnectionStageReachedEvent( 'ttfm', attributes );\r\n","export enum Events {\r\n /**\r\n * Notifies about audio level in RTP statistics by SSRC.\r\n *\r\n * @param ssrc - The synchronization source identifier (SSRC) of the\r\n * endpoint/participant whose audio level is being reported.\r\n * @param {number} audioLevel - The audio level of ssrc according to\r\n * RTP statistics.\r\n * @param {boolean} isLocal - true if ssrc identifies the\r\n * local endpoint/participant; otherwise, false.\r\n */\r\n AUDIO_LEVEL = 'statistics.audioLevel',\r\n\r\n /**\r\n * An event fired just before the statistics module gets disposes and it's\r\n * the last chance to submit some logs that will end up in stats services like\r\n * CallStats (if enabled).\r\n */\r\n BEFORE_DISPOSED = 'statistics.before_disposed',\r\n\r\n /**\r\n * An event carrying all statistics by ssrc.\r\n */\r\n BYTE_SENT_STATS = 'statistics.byte_sent_stats',\r\n\r\n /**\r\n * An event carrying connection statistics.\r\n *\r\n * @param {object} connectionStats - The connection statistics carried by the\r\n * event such as bandwidth, bitrate, packetLoss,\r\n * resolution, and transport.\r\n */\r\n CONNECTION_STATS = 'statistics.connectionstats',\r\n\r\n /**\r\n * An event carrying performance stats.\r\n */\r\n LONG_TASKS_STATS = 'statistics.long_tasks_stats'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const AUDIO_LEVEL = Events.AUDIO_LEVEL;\r\nexport const BEFORE_DISPOSED = Events.BEFORE_DISPOSED;\r\nexport const BYTE_SENT_STATS = Events.BYTE_SENT_STATS;\r\nexport const CONNECTION_STATS = Events.CONNECTION_STATS;\r\nexport const LONG_TASKS_STATS = Events.LONG_TASKS_STATS;\r\n","// TODO: Maybe fix the values to 'Chrome', 'Internet Explorer', etc. Currently\n// this values needs to be as they are becuse they are going to analytics,\n// callstats, etc.\n\nexport const CHROME = 'chrome';\n\nexport const OPERA = 'opera';\n\nexport const FIREFOX = 'firefox';\n\nexport const INTERNET_EXPLORER = 'iexplorer';\n\nexport const SAFARI = 'safari';\n\nexport const NWJS = 'nwjs';\n\nexport const ELECTRON = 'electron';\n\nexport const REACT_NATIVE = 'react-native';\n\nexport const UNKNOWN = 'unknown';\n","import Bowser from 'bowser';\n\nimport {\n CHROME,\n OPERA,\n FIREFOX,\n INTERNET_EXPLORER,\n SAFARI,\n NWJS,\n ELECTRON,\n REACT_NATIVE,\n UNKNOWN\n} from './browsers.js';\n\n/**\n * Maps the names of the browsers from bowser to the internal names defined in\n * ./browsers.js\n */\nconst bowserNameToJitsiName = {\n 'Chrome': CHROME,\n 'Chromium': CHROME,\n 'Opera': OPERA,\n 'Firefox': FIREFOX,\n 'Internet Explorer': INTERNET_EXPLORER,\n 'Safari': SAFARI\n};\n\n/**\n * Detects a Chromium based environent.\n *\n * NOTE: Here we cannot check solely for \"Chrome\" in the UA, because Edge has\n * it too. We need to check explicitly for chromium based Edge first and then\n * detect other chromium based browsers.\n *\n * @returns {Object|undefined} - The name (CHROME) and version.\n */\nfunction _detectChromiumBased() {\n const userAgent = navigator.userAgent;\n const browserInfo = {\n name: UNKNOWN,\n version: undefined\n };\n\n if (userAgent.match(/Chrome/) && !userAgent.match(/Edge/)) {\n // Edge is currenly supported only on desktop and android.\n if (userAgent.match(/Edg(A?)/)) {\n // Compare the underlying chromium version.\n const version = userAgent.match(/Chrome\\/([\\d.]+)/)[1];\n\n if (Number.parseInt(version, 10) > 72) {\n browserInfo.name = CHROME;\n browserInfo.version = version;\n }\n } else {\n browserInfo.name = CHROME;\n browserInfo.version = userAgent.match(/Chrome\\/([\\d.]+)/)[1];\n }\n }\n\n return browserInfo;\n}\n\n/**\n * Detects Electron environment.\n *\n * @returns {Object|undefined} - The name (ELECTRON) and version.\n */\nfunction _detectElectron() {\n const userAgent = navigator.userAgent;\n\n if (userAgent.match(/Electron/)) {\n const version = userAgent.match(/Electron(?:\\s|\\/)([\\d.]+)/)[1];\n\n return {\n name: ELECTRON,\n version\n };\n } else if (typeof window.JitsiMeetElectron !== 'undefined') {\n return {\n name: ELECTRON,\n version: undefined\n };\n }\n}\n\n/**\n * Detects NWJS environment.\n *\n * @returns {Object|undefined} - The name (NWJS) and version.\n */\nfunction _detectNWJS() {\n const userAgent = navigator.userAgent;\n\n if (userAgent.match(/JitsiMeetNW/)) {\n const version = userAgent.match(/JitsiMeetNW\\/([\\d.]+)/)[1];\n\n return {\n name: NWJS,\n version\n };\n }\n}\n\n/**\n * Detects React Native environment.\n * @returns {Object|undefined} - The name (REACT_NATIVE) and version.\n */\nfunction _detectReactNative() {\n const match\n = navigator.userAgent.match(/\\b(react[ \\t_-]*native)(?:\\/(\\S+))?/i);\n let version;\n\n // If we're remote debugging a React Native app, it may be treated as\n // Chrome. Check navigator.product as well and always return some version\n // even if we can't get the real one.\n\n if (match || navigator.product === 'ReactNative') {\n let name;\n\n if (match && match.length > 2) {\n name = match[1];\n version = match[2];\n }\n name || (name = 'react-native');\n version || (version = 'unknown');\n\n return {\n name: REACT_NATIVE,\n version\n };\n }\n}\n\n/**\n * Returns information about the current browser.\n * @param {Object} - The bowser instance.\n * @returns {Object} - The name and version of the browser.\n */\nfunction _detect(bowser) {\n let browserInfo;\n const detectors = [\n _detectReactNative,\n _detectElectron,\n _detectNWJS\n ];\n\n // Try all browser detectors\n for (let i = 0; i < detectors.length; i++) {\n browserInfo = detectors[i]();\n if (browserInfo) {\n return browserInfo;\n }\n }\n\n const name = bowser.getBrowserName();\n\n if (name in bowserNameToJitsiName) {\n return {\n name: bowserNameToJitsiName[name],\n version: bowser.getBrowserVersion()\n };\n }\n\n // Detect other browsers with the Chrome engine, such as Vivaldi and Brave.\n browserInfo = _detectChromiumBased();\n if (browserInfo) {\n return browserInfo;\n }\n\n return {\n name: UNKNOWN,\n version: undefined\n };\n}\n\n/**\n * Implements browser detection.\n */\nexport default class BrowserDetection {\n /**\n * Creates new BrowserDetection instance.\n *\n * @param {Object} [browserInfo] - Information about the browser.\n * @param {string} browserInfo.name - The name of the browser.\n * @param {string} browserInfo.version - The version of the browser.\n */\n constructor(browserInfo) {\n let name, version;\n\n this._bowser = Bowser.getParser(navigator.userAgent);\n if (typeof browserInfo === 'undefined') {\n const detectedBrowserInfo = _detect(this._bowser);\n\n name = detectedBrowserInfo.name;\n version = detectedBrowserInfo.version;\n } else if (browserInfo.name in bowserNameToJitsiName) {\n name = bowserNameToJitsiName[browserInfo.name];\n version = browserInfo.version;\n } else {\n name = UNKNOWN;\n version = undefined;\n }\n\n this._name = name;\n this._version = version;\n }\n\n /**\n * Gets current browser name.\n * @returns {string}\n */\n getName() {\n return this._name;\n }\n\n /**\n * Checks if current browser is Chrome.\n * @returns {boolean}\n */\n isChrome() {\n return this._name === CHROME;\n }\n\n /**\n * Checks if current browser is Opera.\n * @returns {boolean}\n */\n isOpera() {\n return this._name === OPERA;\n }\n\n /**\n * Checks if current browser is Firefox.\n * @returns {boolean}\n */\n isFirefox() {\n return this._name === FIREFOX;\n }\n\n /**\n * Checks if current browser is Internet Explorer.\n * @returns {boolean}\n */\n isIExplorer() {\n return this._name === INTERNET_EXPLORER;\n }\n\n /**\n * Checks if current browser is Safari.\n * @returns {boolean}\n */\n isSafari() {\n return this._name === SAFARI;\n }\n\n /**\n * Checks if current environment is NWJS.\n * @returns {boolean}\n */\n isNWJS() {\n return this._name === NWJS;\n }\n\n /**\n * Checks if current environment is Electron.\n * @returns {boolean}\n */\n isElectron() {\n return this._name === ELECTRON;\n }\n\n /**\n * Checks if current environment is React Native.\n * @returns {boolean}\n */\n isReactNative() {\n return this._name === REACT_NATIVE;\n }\n\n /**\n * Returns the version of the current browser.\n * @returns {string}\n */\n getVersion() {\n return this._version;\n }\n\n /**\n * Check if the parsed browser matches the passed condition.\n *\n * @param {Object} checkTree - It's one or two layered object, which can include a\n * platform or an OS on the first layer and should have browsers specs on the\n * bottom layer.\n * Eg. { chrome: '>71.1.0' }\n * { windows: { chrome: '<70.2' } }\n * @returns {boolean | undefined} - Returns true if the browser satisfies the set\n * conditions, false if not and undefined when the browser is not defined in the\n * checktree object or when the current browser's version is unknown.\n * @private\n */\n _checkCondition(checkTree) {\n if (this._version) {\n return this._bowser.satisfies(checkTree);\n }\n }\n\n /**\n * Compares the passed version with the current browser version.\n *\n * @param {*} version - The version to compare with. Anything different\n * than string will be converted to string.\n * @returns {boolean|undefined} - Returns true if the current version is\n * greater than the passed version and false otherwise. Returns undefined if\n * the current browser version is unknown.\n */\n isVersionGreaterThan(version) {\n return this._checkCondition({ [this._name]: `>${version}` });\n }\n\n /**\n * Compares the passed version with the current browser version.\n *\n * @param {*} version - The version to compare with. Anything different\n * than string will be converted to string.\n * @returns {boolean|undefined} - Returns true if the current version is\n * lower than the passed version and false otherwise. Returns undefined if\n * the current browser version is unknown.\n */\n isVersionLessThan(version) {\n return this._checkCondition({ [this._name]: `<${version}` });\n }\n\n /**\n * Compares the passed version with the current browser version.\n *\n * @param {*} version - The version to compare with. Anything different\n * than string will be converted to string.\n * @returns {boolean|undefined} - Returns true if the current version is\n * equal to the passed version and false otherwise. Returns undefined if\n * the current browser version is unknown.\n * A loose-equality operator is used here so that it matches the sub-versions as well.\n */\n isVersionEqualTo(version) {\n return this._checkCondition({ [this._name]: `~${version}` });\n }\n}\n","import EventEmitter from 'events';\n\n/**\n * Dummy implementation of Storage interface.\n */\nclass DummyLocalStorage extends EventEmitter {\n\n /**\n * The object used for storage.\n */\n _storage = {};\n\n /**\n * Empties all keys out of the storage.\n *\n * @returns {void}\n */\n clear() {\n this._storage = {};\n }\n\n /**\n * Returns the number of data items stored in the Storage object.\n *\n * @returns {number} - The number of data items stored in the Storage object.\n */\n get length() {\n return Object.keys(this._storage).length;\n }\n\n /**\n * Will return that key's value associated to the passed key name.\n *\n * @param {string} keyName - The key name.\n * @returns {*} - The key value.\n */\n getItem(keyName) {\n return this._storage[keyName];\n }\n\n /**\n * When passed a key name and value, will add that key to the storage,\n * or update that key's value if it already exists.\n *\n * @param {string} keyName - The key name.\n * @param {*} keyValue - The key value.\n * @returns {void}\n */\n setItem(keyName, keyValue) {\n this._storage[keyName] = keyValue;\n }\n\n /**\n * When passed a key name, will remove that key from the storage.\n *\n * @param {string} keyName - The key name.\n * @returns {void}\n */\n removeItem(keyName) {\n delete this._storage[keyName];\n }\n\n /**\n * When passed a number n, this method will return the name of the nth key in the storage.\n *\n * @param {number} idx - The index of the key.\n * @returns {string} - The nth key name.\n */\n key(n) {\n const keys = Object.keys(this._storage);\n\n if (keys.length <= n) {\n return undefined;\n }\n\n return keys[n];\n }\n\n /**\n * Serializes the content of the storage.\n *\n * @returns {string} - The serialized content.\n */\n serialize() {\n return JSON.stringify(this._storage);\n }\n}\n\n/**\n * Wrapper class for browser's local storage object.\n */\nclass JitsiLocalStorage extends EventEmitter {\n /**\n * @constructor\n * @param {Storage} storage browser's local storage object.\n */\n constructor() {\n super();\n\n try {\n this._storage = window.localStorage;\n this._localStorageDisabled = false;\n } catch (ignore) {\n // localStorage throws an exception.\n }\n\n if (!this._storage) { // Handles the case when window.localStorage is undefined or throws an exception.\n console.warn('Local storage is disabled.');\n this._storage = new DummyLocalStorage();\n this._localStorageDisabled = true;\n }\n }\n\n /**\n * Returns true if window.localStorage is disabled and false otherwise.\n *\n * @returns {boolean} - True if window.localStorage is disabled and false otherwise.\n */\n isLocalStorageDisabled() {\n return this._localStorageDisabled;\n }\n\n /**\n * Empties all keys out of the storage.\n *\n * @returns {void}\n */\n clear() {\n this._storage.clear();\n this.emit('changed');\n }\n\n /**\n * Returns the number of data items stored in the Storage object.\n *\n * @returns {number} - The number of data items stored in the Storage object.\n */\n get length() {\n return this._storage.length;\n }\n\n /**\n * Returns that passed key's value.\n * @param {string} keyName the name of the key you want to retrieve\n * the value of.\n * @returns {String|null} the value of the key. If the key does not exist,\n * null is returned.\n */\n getItem(keyName) {\n return this._storage.getItem(keyName);\n }\n\n /**\n * Adds a key to the storage, or update key's value if it already exists.\n * @param {string} keyName - the name of the key you want to create/update.\n * @param {string} keyValue - the value you want to give the key you are\n * creating/updating.\n * @param {boolean} dontEmitChangedEvent - If true a changed event won't be emitted.\n */\n setItem(keyName, keyValue, dontEmitChangedEvent = false) {\n this._storage.setItem(keyName, keyValue);\n\n if (!dontEmitChangedEvent) {\n this.emit('changed');\n }\n }\n\n /**\n * Remove a key from the storage.\n * @param {string} keyName the name of the key you want to remove.\n */\n removeItem(keyName) {\n this._storage.removeItem(keyName);\n this.emit('changed');\n }\n\n /**\n * Returns the name of the nth key in the list, or null if n is greater\n * than or equal to the number of key/value pairs in the object.\n *\n * @param {number} i - The index of the key in the list.\n * @returns {string}\n */\n key(i) {\n return this._storage.key(i);\n }\n\n /**\n * Serializes the content of the storage.\n *\n * @returns {string} - The serialized content.\n */\n serialize() {\n if (this.isLocalStorageDisabled()) {\n return this._storage.serialize();\n }\n\n const length = this._storage.length;\n const localStorageContent = {};\n\n for (let i = 0; i < length; i++) {\n const key = this._storage.key(i);\n\n localStorageContent[key] = this._storage.getItem(key);\n }\n\n return JSON.stringify(localStorageContent);\n }\n}\n\nexport const jitsiLocalStorage = new JitsiLocalStorage();\n","import { BrowserDetection } from '@jitsi/js-utils';\r\nimport { getLogger } from '@jitsi/logger';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/* Minimum required Chrome / Chromium version. This applies also to derivatives. */\r\nconst MIN_REQUIRED_CHROME_VERSION = 72;\r\nconst MIN_REQUIRED_SAFARI_VERSION = 14;\r\nconst MIN_REQUIRED_IOS_VERSION = 14;\r\n\r\n// TODO: Move this code to js-utils.\r\n\r\n// NOTE: Now we are extending BrowserDetection in order to preserve\r\n// RTCBrowserType interface but maybe it worth exporting BrowserCapabilities\r\n// and BrowserDetection as separate objects in future.\r\n\r\n/**\r\n * Implements browser capabilities for lib-jitsi-meet.\r\n */\r\nexport default class BrowserCapabilities extends BrowserDetection {\r\n /**\r\n * Creates new BrowserCapabilities instance.\r\n */\r\n constructor() {\r\n super();\r\n logger.info(\r\n `This appears to be ${this.getName()}, ver: ${this.getVersion()}`);\r\n }\r\n\r\n /**\r\n * Tells whether or not the MediaStream/tt> is removed from the PeerConnection and disposed on video\r\n * mute (in order to turn off the camera device). This is needed on Firefox because of the following bug\r\n * https://bugzilla.mozilla.org/show_bug.cgi?id=1735951\r\n *\r\n * @return {boolean} true if the current browser supports this strategy or false otherwise.\r\n */\r\n doesVideoMuteByStreamRemove() {\r\n return this.isChromiumBased() || this.isWebKitBased() || this.isFirefox();\r\n }\r\n\r\n /**\r\n * Checks if the current browser is Chromium based, i.e., it's either Chrome / Chromium or uses it as its engine,\r\n * but doesn't identify as Chrome.\r\n *\r\n * This includes the following browsers:\r\n * - Chrome and Chromium.\r\n * - Other browsers which use the Chrome engine, but are detected as Chrome, such as Brave and Vivaldi.\r\n * - Browsers which are NOT Chrome but use it as their engine, and have custom detection code: Opera, Electron\r\n * and NW.JS.\r\n * This excludes\r\n * - Chrome on iOS since it uses WKWebView.\r\n */\r\n isChromiumBased() {\r\n return (this.isChrome()\r\n || this.isElectron()\r\n || this.isNWJS()\r\n || this.isOpera())\r\n && !this.isWebKitBased();\r\n }\r\n\r\n /**\r\n * Checks if the current platform is iOS.\r\n *\r\n * @returns {boolean}\r\n */\r\n isIosBrowser() {\r\n const { userAgent, maxTouchPoints, platform } = navigator;\r\n\r\n return Boolean(userAgent.match(/iP(ad|hone|od)/i))\r\n || (maxTouchPoints && maxTouchPoints > 2 && /MacIntel/.test(platform));\r\n }\r\n\r\n /**\r\n * Checks if the current browser is WebKit based. It's either\r\n * Safari or uses WebKit as its engine.\r\n *\r\n * This includes Chrome and Firefox on iOS\r\n *\r\n * @returns {boolean}\r\n */\r\n isWebKitBased() {\r\n // https://trac.webkit.org/changeset/236144/webkit/trunk/LayoutTests/webrtc/video-addLegacyTransceiver.html\r\n return this._bowser.isEngine('webkit')\r\n && typeof navigator.mediaDevices !== 'undefined'\r\n && typeof navigator.mediaDevices.getUserMedia !== 'undefined'\r\n && typeof window.RTCRtpTransceiver !== 'undefined'\r\n // eslint-disable-next-line no-undef\r\n && Object.keys(RTCRtpTransceiver.prototype).indexOf('currentDirection') > -1;\r\n }\r\n\r\n /**\r\n * Checks whether current running context is a Trusted Web Application.\r\n *\r\n * @returns {boolean} Whether the current context is a TWA.\r\n */\r\n isTwa() {\r\n return 'matchMedia' in window && window.matchMedia('(display-mode:standalone)').matches;\r\n }\r\n\r\n /**\r\n * Checks if the current browser is supported.\r\n *\r\n * @returns {boolean} true if the browser is supported, false otherwise.\r\n */\r\n isSupported() {\r\n if (this.isSafari() && this._getSafariVersion() < MIN_REQUIRED_SAFARI_VERSION) {\r\n return false;\r\n }\r\n\r\n return (this.isChromiumBased() && this._getChromiumBasedVersion() >= MIN_REQUIRED_CHROME_VERSION)\r\n || this.isFirefox()\r\n || this.isReactNative()\r\n || this.isWebKitBased();\r\n }\r\n\r\n /**\r\n * Returns whether the browser is supported for Android\r\n * @returns {boolean} true if the browser is supported for Android devices\r\n */\r\n isSupportedAndroidBrowser() {\r\n return this.isChromiumBased() || this.isFirefox();\r\n }\r\n\r\n /**\r\n * Returns whether the browser is supported for iOS\r\n * @returns {boolean} true if the browser is supported for iOS devices\r\n */\r\n isSupportedIOSBrowser() {\r\n return this._getIOSVersion() >= MIN_REQUIRED_IOS_VERSION;\r\n }\r\n\r\n /**\r\n * Returns whether or not the current environment needs a user interaction\r\n * with the page before any unmute can occur.\r\n *\r\n * @returns {boolean}\r\n */\r\n isUserInteractionRequiredForUnmute() {\r\n return this.isFirefox() && this.isVersionLessThan('68');\r\n }\r\n\r\n /**\r\n * Checks if the current browser triggers 'onmute'/'onunmute' events when\r\n * user's connection is interrupted and the video stops playback.\r\n * @returns {*|boolean} 'true' if the event is supported or 'false'\r\n * otherwise.\r\n */\r\n supportsVideoMuteOnConnInterrupted() {\r\n return this.isChromiumBased() || this.isReactNative();\r\n }\r\n\r\n /**\r\n * Checks if the current browser reports upload and download bandwidth\r\n * statistics.\r\n * @return {boolean}\r\n */\r\n supportsBandwidthStatistics() {\r\n // FIXME bandwidth stats are currently not implemented for FF on our\r\n // side, but not sure if not possible ?\r\n return !this.isFirefox() && !this.isWebKitBased();\r\n }\r\n\r\n /**\r\n * Checks if the current browser supports setting codec preferences on the transceiver.\r\n * @returns {boolean}\r\n */\r\n supportsCodecPreferences() {\r\n return Boolean(window.RTCRtpTransceiver\r\n && 'setCodecPreferences' in window.RTCRtpTransceiver.prototype\r\n && window.RTCRtpReceiver\r\n && typeof window.RTCRtpReceiver.getCapabilities !== 'undefined')\r\n\r\n // this is not working on Safari because of the following bug\r\n // https://bugs.webkit.org/show_bug.cgi?id=215567\r\n && !this.isWebKitBased();\r\n }\r\n\r\n /**\r\n * Checks if the current browser support the device change event.\r\n * @return {boolean}\r\n */\r\n supportsDeviceChangeEvent() {\r\n return navigator.mediaDevices\r\n && typeof navigator.mediaDevices.ondevicechange !== 'undefined'\r\n && typeof navigator.mediaDevices.addEventListener !== 'undefined';\r\n }\r\n\r\n /**\r\n * Checks if the current browser supports RTT statistics for srflx local\r\n * candidates through the legacy getStats() API.\r\n */\r\n supportsLocalCandidateRttStatistics() {\r\n return this.isChromiumBased() || this.isReactNative() || this.isWebKitBased();\r\n }\r\n\r\n /**\r\n * Checks if the current browser supports the Long Tasks API that lets us observe\r\n * performance measurement events and be notified of tasks that take longer than\r\n * 50ms to execute on the main thread.\r\n */\r\n supportsPerformanceObserver() {\r\n return typeof window.PerformanceObserver !== 'undefined'\r\n && PerformanceObserver.supportedEntryTypes.indexOf('longtask') > -1;\r\n }\r\n\r\n /**\r\n * Checks if the current browser supports audio level stats on the receivers.\r\n */\r\n supportsReceiverStats() {\r\n return typeof window.RTCRtpReceiver !== 'undefined'\r\n && Object.keys(RTCRtpReceiver.prototype).indexOf('getSynchronizationSources') > -1\r\n\r\n // Disable this on Safari because it is reporting 0.000001 as the audio levels for all\r\n // remote audio tracks.\r\n && !this.isWebKitBased();\r\n }\r\n\r\n /**\r\n * Checks if the current browser reports round trip time statistics for\r\n * the ICE candidate pair.\r\n * @return {boolean}\r\n */\r\n supportsRTTStatistics() {\r\n // Firefox does not seem to report RTT for ICE candidate pair:\r\n // eslint-disable-next-line max-len\r\n // https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-currentroundtriptime\r\n // It does report mozRTT for RTP streams, but at the time of this\r\n // writing it's value does not make sense most of the time\r\n // (is reported as 1):\r\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1241066\r\n // For Chrome and others we rely on 'googRtt'.\r\n return !this.isFirefox();\r\n }\r\n\r\n /**\r\n * Returns true if VP9 is supported by the client on the browser. VP9 is currently disabled on Firefox and Safari\r\n * because of issues with rendering. Please check https://bugzilla.mozilla.org/show_bug.cgi?id=1492500,\r\n * https://bugs.webkit.org/show_bug.cgi?id=231071 and https://bugs.webkit.org/show_bug.cgi?id=231074 for details.\r\n */\r\n supportsVP9() {\r\n return this.isChromiumBased() || this.isReactNative();\r\n }\r\n\r\n /**\r\n * Checks if the browser uses SDP munging for turning on simulcast.\r\n *\r\n * @returns {boolean}\r\n */\r\n usesSdpMungingForSimulcast() {\r\n return this.isChromiumBased() || this.isReactNative() || this.isWebKitBased();\r\n }\r\n\r\n /**\r\n * Checks if the browser uses webrtc-adapter. All browsers except React Native do.\r\n *\r\n * @returns {boolean}\r\n */\r\n usesAdapter() {\r\n return !this.isReactNative();\r\n }\r\n\r\n /**\r\n * Checks if the browser uses RIDs/MIDs for siganling the simulcast streams\r\n * to the bridge instead of the ssrcs.\r\n */\r\n usesRidsForSimulcast() {\r\n return false;\r\n }\r\n\r\n /**\r\n * Checks if the browser supports getDisplayMedia.\r\n * @returns {boolean} {@code true} if the browser supports getDisplayMedia.\r\n */\r\n supportsGetDisplayMedia() {\r\n return typeof navigator.getDisplayMedia !== 'undefined'\r\n || (typeof navigator.mediaDevices !== 'undefined'\r\n && typeof navigator.mediaDevices.getDisplayMedia\r\n !== 'undefined');\r\n }\r\n\r\n /**\r\n * Checks if the browser supports WebRTC Encoded Transform, an alternative\r\n * to insertable streams.\r\n *\r\n * NOTE: At the time of this writing the only browser supporting this is\r\n * Safari / WebKit, behind a flag.\r\n *\r\n * @returns {boolean} {@code true} if the browser supports it.\r\n */\r\n supportsEncodedTransform() {\r\n return Boolean(window.RTCRtpScriptTransform);\r\n }\r\n\r\n /**\r\n * Checks if the browser supports insertable streams, needed for E2EE.\r\n * @returns {boolean} {@code true} if the browser supports insertable streams.\r\n */\r\n supportsInsertableStreams() {\r\n if (!(typeof window.RTCRtpSender !== 'undefined'\r\n && window.RTCRtpSender.prototype.createEncodedStreams)) {\r\n return false;\r\n }\r\n\r\n // Feature-detect transferable streams which we need to operate in a worker.\r\n // See https://groups.google.com/a/chromium.org/g/blink-dev/c/1LStSgBt6AM/m/hj0odB8pCAAJ\r\n const stream = new ReadableStream();\r\n\r\n try {\r\n window.postMessage(stream, '*', [ stream ]);\r\n\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Whether the browser supports the RED format for audio.\r\n */\r\n supportsAudioRed() {\r\n return Boolean(window.RTCRtpSender\r\n && window.RTCRtpSender.getCapabilities\r\n && window.RTCRtpSender.getCapabilities('audio').codecs.some(codec => codec.mimeType === 'audio/red')\r\n && window.RTCRtpReceiver\r\n && window.RTCRtpReceiver.getCapabilities\r\n && window.RTCRtpReceiver.getCapabilities('audio').codecs.some(codec => codec.mimeType === 'audio/red'));\r\n }\r\n\r\n /**\r\n * Checks if the browser supports unified plan.\r\n *\r\n * @returns {boolean}\r\n */\r\n supportsUnifiedPlan() {\r\n // We do not want to enable unified plan on Electron clients that have Chromium version < 96 because of\r\n // performance and screensharing issues.\r\n return !(this.isReactNative() || (this.isElectron() && (this._getChromiumBasedVersion() < 96)));\r\n }\r\n\r\n /**\r\n * Checks if the browser supports voice activity detection via the @type {VADAudioAnalyser} service.\r\n *\r\n * @returns {boolean}\r\n */\r\n supportsVADDetection() {\r\n return this.isChromiumBased();\r\n }\r\n\r\n /**\r\n * Check if the browser supports the RTP RTX feature (and it is usable).\r\n *\r\n * @returns {boolean}\r\n */\r\n supportsRTX() {\r\n // Disable RTX on Firefox up to 96 because we prefer simulcast over RTX\r\n // see https://bugzilla.mozilla.org/show_bug.cgi?id=1738504\r\n return !(this.isFirefox() && this.isVersionLessThan('96'));\r\n }\r\n\r\n /**\r\n * Returns the version of a Chromium based browser.\r\n *\r\n * @returns {Number}\r\n */\r\n _getChromiumBasedVersion() {\r\n if (this.isChromiumBased()) {\r\n // NW.JS doesn't expose the Chrome version in the UA string.\r\n if (this.isNWJS()) {\r\n // eslint-disable-next-line no-undef\r\n return Number.parseInt(process.versions.chromium, 10);\r\n }\r\n\r\n // Here we process all browsers which use the Chrome engine but\r\n // don't necessarily identify as Chrome. We cannot use the version\r\n // comparing functions because the Electron, Opera and NW.JS\r\n // versions are inconsequential here, as we need to know the actual\r\n // Chrome engine version.\r\n const ua = navigator.userAgent;\r\n\r\n if (ua.match(/Chrome/)) {\r\n const version\r\n = Number.parseInt(ua.match(/Chrome\\/([\\d.]+)/)[1], 10);\r\n\r\n return version;\r\n }\r\n }\r\n\r\n return -1;\r\n }\r\n\r\n /**\r\n * Returns the version of a Safari browser.\r\n *\r\n * @returns {Number}\r\n */\r\n _getSafariVersion() {\r\n if (this.isSafari()) {\r\n return Number.parseInt(this.getVersion(), 10);\r\n }\r\n\r\n return -1;\r\n }\r\n\r\n /**\r\n * Returns the version of an ios browser.\r\n *\r\n * @returns {Number}\r\n */\r\n _getIOSVersion() {\r\n if (this.isWebKitBased()) {\r\n return Number.parseInt(this.getVersion(), 10);\r\n }\r\n\r\n return -1;\r\n }\r\n}\r\n","import BrowserCapabilities from './BrowserCapabilities';\r\n\r\nexport default new BrowserCapabilities();\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport {\r\n TYPE_OPERATIONAL,\r\n TYPE_PAGE,\r\n TYPE_TRACK,\r\n TYPE_UI\r\n} from '../../service/statistics/AnalyticsEvents';\r\nimport browser from '../browser';\r\n\r\nconst MAX_CACHE_SIZE = 100;\r\n\r\n// eslist-disable-line no-undef\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * This class provides an API to lib-jitsi-meet and its users for sending\r\n * analytics events. It serves as a bridge to different backend implementations\r\n * (\"analytics handlers\") and a cache for events attempted to be sent before\r\n * the analytics handlers were enabled.\r\n *\r\n * The API is designed to be an easy replacement for the previous version of\r\n * this adapter, and is meant to be extended with more convenience methods.\r\n *\r\n *\r\n * The API calls are translated to objects with the following structure, which\r\n * are then passed to the sendEvent(event) function of the underlying handlers:\r\n *\r\n * {\r\n * type,\r\n *\r\n * action,\r\n * actionSubject,\r\n * actionSubjectId,\r\n * attributes,\r\n * categories,\r\n * containerId,\r\n * containerType,\r\n * name,\r\n * objectId,\r\n * objectType,\r\n * source,\r\n * tags\r\n * }\r\n *\r\n * The 'type' is one of 'operational', 'page', 'track' or 'ui', and some of the\r\n * other properties are considered required according to the type.\r\n *\r\n * For events with type 'page', the required properties are: name.\r\n *\r\n * For events with type 'operational' and 'ui', the required properties are:\r\n * action, actionSubject, source\r\n *\r\n * For events with type 'page', the required properties are:\r\n * action, actionSubject, source, containerType, containerId, objectType,\r\n * objectId\r\n */\r\nclass AnalyticsAdapter {\r\n /**\r\n * Creates new AnalyticsAdapter instance.\r\n */\r\n constructor() {\r\n this.reset();\r\n }\r\n\r\n /**\r\n * Reset the state to the initial one.\r\n *\r\n * @returns {void}\r\n */\r\n reset() {\r\n /**\r\n * Whether this AnalyticsAdapter has been disposed of or not. Once this\r\n * is set to true, the AnalyticsAdapter is disabled and does not accept\r\n * any more events, and it can not be re-enabled.\r\n * @type {boolean}\r\n */\r\n this.disposed = false;\r\n\r\n /**\r\n * The set of handlers to which events will be sent.\r\n * @type {Set}\r\n */\r\n this.analyticsHandlers = new Set();\r\n\r\n /**\r\n * The cache of events which are not sent yet. The cache is enabled\r\n * while this field is truthy, and disabled otherwise.\r\n * @type {Array}\r\n */\r\n this.cache = [];\r\n\r\n /**\r\n * Map of properties that will be added to every event. Note that the\r\n * keys will be prefixed with \"permanent.\".\r\n */\r\n this.permanentProperties = {};\r\n\r\n /**\r\n * The name of the conference that this AnalyticsAdapter is associated\r\n * with.\r\n * @type {null}\r\n */\r\n this.conferenceName = '';\r\n\r\n this.addPermanentProperties({\r\n 'user_agent': navigator.userAgent,\r\n 'browser_name': browser.getName()\r\n });\r\n }\r\n\r\n /**\r\n * Dispose analytics. Clears all handlers.\r\n */\r\n dispose() {\r\n logger.warn('Disposing of analytics adapter.');\r\n\r\n if (this.analyticsHandlers && this.analyticsHandlers.size > 0) {\r\n this.analyticsHandlers.forEach(handler => {\r\n if (typeof handler.dispose === 'function') {\r\n handler.dispose();\r\n }\r\n });\r\n }\r\n\r\n this.setAnalyticsHandlers([]);\r\n this.disposed = true;\r\n }\r\n\r\n /**\r\n * Sets the handlers that are going to be used to send analytics. Sends any\r\n * cached events.\r\n * @param {Array} handlers the handlers\r\n */\r\n setAnalyticsHandlers(handlers) {\r\n if (this.disposed) {\r\n return;\r\n }\r\n\r\n this.analyticsHandlers = new Set(handlers);\r\n\r\n this._setUserProperties();\r\n\r\n // Note that we disable the cache even if the set of handlers is empty.\r\n const cache = this.cache;\r\n\r\n this.cache = null;\r\n if (cache) {\r\n cache.forEach(event => this._sendEvent(event));\r\n }\r\n }\r\n\r\n /**\r\n * Set the user properties to the analytics handlers.\r\n *\r\n * @returns {void}\r\n */\r\n _setUserProperties() {\r\n this.analyticsHandlers.forEach(handler => {\r\n try {\r\n handler.setUserProperties(this.permanentProperties);\r\n } catch (error) {\r\n logger.warn('Error in setUserProperties method of one of the '\r\n + `analytics handlers: ${error}`);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Adds a set of permanent properties to this this AnalyticsAdapter.\r\n * Permanent properties will be added as \"attributes\" to events sent to\r\n * the underlying \"analytics handlers\", and their keys will be prefixed\r\n * by \"permanent_\", i.e. adding a permanent property {key: \"value\"} will\r\n * result in {\"permanent_key\": \"value\"} object to be added to the\r\n * \"attributes\" field of events.\r\n *\r\n * @param {Object} properties the properties to add\r\n */\r\n addPermanentProperties(properties) {\r\n this.permanentProperties = {\r\n ...this.permanentProperties,\r\n ...properties\r\n };\r\n\r\n this._setUserProperties();\r\n }\r\n\r\n /**\r\n * Sets the name of the conference that this AnalyticsAdapter is associated\r\n * with.\r\n * @param name the name to set.\r\n */\r\n setConferenceName(name) {\r\n this.conferenceName = name;\r\n this.addPermanentProperties({ 'conference_name': name });\r\n }\r\n\r\n /**\r\n * Sends an event with a given name and given properties. The first\r\n * parameter is either a string or an object. If it is a string, it is used\r\n * as the event name and the second parameter is used at the attributes to\r\n * attach to the event. If it is an object, it represents the whole event,\r\n * including any desired attributes, and the second parameter is ignored.\r\n *\r\n * @param {String|Object} eventName either a string to be used as the name\r\n * of the event, or an event object. If an event object is passed, the\r\n * properties parameters is ignored.\r\n * @param {Object} properties the properties/attributes to attach to the\r\n * event, if eventName is a string.\r\n */\r\n sendEvent(eventName, properties = {}) {\r\n if (this.disposed) {\r\n return;\r\n }\r\n\r\n let event = null;\r\n\r\n if (typeof eventName === 'string') {\r\n event = {\r\n type: TYPE_OPERATIONAL,\r\n action: eventName,\r\n actionSubject: eventName,\r\n source: eventName,\r\n attributes: properties\r\n };\r\n } else if (typeof eventName === 'object') {\r\n event = eventName;\r\n }\r\n\r\n if (!this._verifyRequiredFields(event)) {\r\n logger.error(\r\n `Dropping a mis-formatted event: ${JSON.stringify(event)}`);\r\n\r\n return;\r\n }\r\n\r\n this._sendEvent(event);\r\n }\r\n\r\n /**\r\n * Checks whether an event has all of the required fields set, and tries\r\n * to fill in some of the missing fields with reasonable default values.\r\n * Returns true if after this operation the event has all of the required\r\n * fields set, and false otherwise (if some of the required fields were not\r\n * set and the attempt to fill them in with a default failed).\r\n *\r\n * @param event the event object.\r\n * @return {boolean} true if the event (after the call to this function)\r\n * contains all of the required fields, and false otherwise.\r\n * @private\r\n */\r\n _verifyRequiredFields(event) {\r\n if (!event) {\r\n return false;\r\n }\r\n\r\n if (!event.type) {\r\n event.type = TYPE_OPERATIONAL;\r\n }\r\n\r\n const type = event.type;\r\n\r\n if (type !== TYPE_OPERATIONAL && type !== TYPE_PAGE\r\n && type !== TYPE_UI && type !== TYPE_TRACK) {\r\n logger.error(`Unknown event type: ${type}`);\r\n\r\n return false;\r\n }\r\n\r\n if (type === TYPE_PAGE) {\r\n return Boolean(event.name);\r\n }\r\n\r\n // Try to set some reasonable default values in case some of the\r\n // parameters required by the handler API are missing.\r\n event.action = event.action || event.name || event.actionSubject;\r\n event.actionSubject = event.actionSubject || event.name || event.action;\r\n event.source = event.source || event.name || event.action\r\n || event.actionSubject;\r\n\r\n if (!event.action || !event.actionSubject || !event.source) {\r\n logger.error(\r\n 'Required field missing (action, actionSubject or source)');\r\n\r\n return false;\r\n }\r\n\r\n // Track events have additional required fields.\r\n if (type === TYPE_TRACK) {\r\n event.objectType = event.objectType || 'generic-object-type';\r\n event.containerType = event.containerType || 'conference';\r\n if (event.containerType === 'conference' && !event.containerId) {\r\n event.containerId = this.conferenceName;\r\n }\r\n\r\n\r\n if (!event.objectType || !event.objectId\r\n || !event.containerType || !event.containerId) {\r\n logger.error(\r\n 'Required field missing (containerId, containerType, '\r\n + 'objectId or objectType)');\r\n\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Saves an event to the cache, if the cache is enabled.\r\n * @param event the event to save.\r\n * @returns {boolean} true if the event was saved, and false otherwise (i.e.\r\n * if the cache was disabled).\r\n * @private\r\n */\r\n _maybeCacheEvent(event) {\r\n if (this.cache) {\r\n this.cache.push(event);\r\n\r\n // We limit the size of the cache, in case the user fails to ever\r\n // set the analytics handlers.\r\n if (this.cache.length > MAX_CACHE_SIZE) {\r\n this.cache.splice(0, 1);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n return false;\r\n\r\n }\r\n\r\n /**\r\n *\r\n * @param event\r\n * @private\r\n */\r\n _sendEvent(event) {\r\n if (this._maybeCacheEvent(event)) {\r\n // The event was consumed by the cache.\r\n } else {\r\n this.analyticsHandlers.forEach(handler => {\r\n try {\r\n handler.sendEvent(event);\r\n } catch (e) {\r\n logger.warn(`Error sending analytics event: ${e}`);\r\n }\r\n });\r\n }\r\n }\r\n}\r\n\r\nexport default new AnalyticsAdapter();\r\n","/* global callstats */\r\n\r\nimport browser from '../browser';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\n\r\nconst logger = require('@jitsi/logger').getLogger(__filename);\r\n\r\n/**\r\n * We define enumeration of wrtcFuncNames as we need them before\r\n * callstats is initialized to queue events.\r\n * @const\r\n * @see http://www.callstats.io/api/#enumeration-of-wrtcfuncnames\r\n */\r\nconst wrtcFuncNames = {\r\n createOffer: 'createOffer',\r\n createAnswer: 'createAnswer',\r\n setLocalDescription: 'setLocalDescription',\r\n setRemoteDescription: 'setRemoteDescription',\r\n addIceCandidate: 'addIceCandidate',\r\n getUserMedia: 'getUserMedia',\r\n iceConnectionFailure: 'iceConnectionFailure',\r\n signalingError: 'signalingError',\r\n applicationLog: 'applicationLog'\r\n};\r\n\r\n/**\r\n * We define enumeration of fabricEvent as we need them before\r\n * callstats is initialized to queue events.\r\n * @const\r\n * @see http://www.callstats.io/api/#enumeration-of-fabricevent\r\n */\r\nconst fabricEvent = {\r\n fabricHold: 'fabricHold',\r\n fabricResume: 'fabricResume',\r\n audioMute: 'audioMute',\r\n audioUnmute: 'audioUnmute',\r\n videoPause: 'videoPause',\r\n videoResume: 'videoResume',\r\n fabricUsageEvent: 'fabricUsageEvent',\r\n fabricStats: 'fabricStats',\r\n fabricTerminated: 'fabricTerminated',\r\n screenShareStart: 'screenShareStart',\r\n screenShareStop: 'screenShareStop',\r\n dominantSpeaker: 'dominantSpeaker',\r\n activeDeviceList: 'activeDeviceList'\r\n};\r\n\r\n/**\r\n * The user id to report to callstats as destination.\r\n * @type {string}\r\n */\r\nconst DEFAULT_REMOTE_USER = 'jitsi';\r\n\r\n/**\r\n * Type of pending reports, can be event or an error.\r\n * @type {{ERROR: string, EVENT: string}}\r\n */\r\nconst reportType = {\r\n ERROR: 'error',\r\n EVENT: 'event',\r\n MST_WITH_USERID: 'mstWithUserID'\r\n};\r\n\r\n/**\r\n * Set of currently existing {@link CallStats} instances.\r\n * @type {Set}\r\n */\r\nlet _fabrics;\r\n\r\n/**\r\n * An instance of this class is a wrapper for the CallStats API fabric. A fabric\r\n * reports one peer connection to the CallStats backend and is allocated with\r\n * {@link callstats.addNewFabric}. It has a bunch of instance methods for\r\n * reporting various events. A fabric is considered disposed when\r\n * {@link CallStats.sendTerminateEvent} is executed.\r\n *\r\n * Currently only one backend instance can be created ever and it's done using\r\n * {@link CallStats.initBackend}. At the time of this writing there is no way to\r\n * explicitly shutdown the backend, but it's supposed to close it's connection\r\n * automatically, after all fabrics have been terminated.\r\n */\r\nexport default class CallStats {\r\n /**\r\n * A callback passed to {@link callstats.addNewFabric}.\r\n * @param {string} error 'success' means ok\r\n * @param {string} msg some more details\r\n * @private\r\n */\r\n static _addNewFabricCallback(error, msg) {\r\n if (CallStats.backend && error !== 'success') {\r\n logger.error(`Monitoring status: ${error} msg: ${msg}`);\r\n }\r\n }\r\n\r\n /**\r\n * Callback passed to {@link callstats.initialize} (backend initialization)\r\n * @param {string} error 'success' means ok\r\n * @param {String} msg\r\n * @private\r\n */\r\n static _initCallback(error, msg) {\r\n logger.log(`CallStats Status: err=${error} msg=${msg}`);\r\n\r\n // there is no lib, nothing to report to\r\n if (error !== 'success') {\r\n return;\r\n }\r\n\r\n CallStats.backendInitialized = true;\r\n\r\n // I hate that\r\n let atLeastOneFabric = false;\r\n let defaultInstance = null;\r\n\r\n for (const callStatsInstance of CallStats.fabrics.values()) {\r\n if (!callStatsInstance.hasFabric) {\r\n logger.debug('addNewFabric - initCallback');\r\n if (callStatsInstance._addNewFabric()) {\r\n atLeastOneFabric = true;\r\n if (!defaultInstance) {\r\n defaultInstance = callStatsInstance;\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (!atLeastOneFabric) {\r\n return;\r\n }\r\n\r\n CallStats._emptyReportQueue(defaultInstance);\r\n }\r\n\r\n /**\r\n * Empties report queue.\r\n *\r\n * @param {CallStats} csInstance - The callstats instance.\r\n * @private\r\n */\r\n static _emptyReportQueue(csInstance) {\r\n // There is no conference ID nor a PeerConnection available when some of\r\n // the events are scheduled on the reportsQueue, so those will be\r\n // reported on the first initialized fabric.\r\n const defaultConfID = csInstance.confID;\r\n const defaultPC = csInstance.peerconnection;\r\n\r\n // notify callstats about failures if there were any\r\n for (const report of CallStats.reportsQueue) {\r\n if (report.type === reportType.ERROR) {\r\n const errorData = report.data;\r\n\r\n CallStats._reportError(\r\n csInstance,\r\n errorData.type,\r\n errorData.error,\r\n errorData.pc || defaultPC);\r\n } else if (report.type === reportType.EVENT) {\r\n // if we have and event to report and we failed to add\r\n // fabric this event will not be reported anyway, returning\r\n // an error\r\n const eventData = report.data;\r\n\r\n CallStats.backend.sendFabricEvent(\r\n report.pc || defaultPC,\r\n eventData.event,\r\n defaultConfID,\r\n eventData.eventData);\r\n } else if (report.type === reportType.MST_WITH_USERID) {\r\n const data = report.data;\r\n\r\n CallStats.backend.associateMstWithUserID(\r\n report.pc || defaultPC,\r\n data.callStatsId,\r\n defaultConfID,\r\n data.ssrc,\r\n data.usageLabel,\r\n data.containerId\r\n );\r\n }\r\n }\r\n CallStats.reportsQueue.length = 0;\r\n }\r\n\r\n /* eslint-disable max-params */\r\n /**\r\n * Reports an error to callstats.\r\n *\r\n * @param {CallStats} [cs]\r\n * @param type the type of the error, which will be one of the wrtcFuncNames\r\n * @param error the error\r\n * @param pc the peerconnection\r\n * @private\r\n */\r\n static _reportError(cs, type, error, pc) {\r\n let _error = error;\r\n\r\n if (!_error) {\r\n logger.warn('No error is passed!');\r\n _error = new Error('Unknown error');\r\n }\r\n if (CallStats.backendInitialized && cs) {\r\n CallStats.backend.reportError(pc, cs.confID, type, _error);\r\n } else {\r\n CallStats.reportsQueue.push({\r\n type: reportType.ERROR,\r\n data: {\r\n error: _error,\r\n pc,\r\n type\r\n }\r\n });\r\n }\r\n\r\n // else just ignore it\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Reports an error to callstats.\r\n *\r\n * @param {CallStats} cs\r\n * @param event the type of the event, which will be one of the fabricEvent\r\n * @param eventData additional data to pass to event\r\n * @private\r\n */\r\n static _reportEvent(cs, event, eventData) {\r\n const pc = cs && cs.peerconnection;\r\n const confID = cs && cs.confID;\r\n\r\n if (CallStats.backendInitialized && cs) {\r\n CallStats.backend.sendFabricEvent(pc, event, confID, eventData);\r\n } else {\r\n CallStats.reportsQueue.push({\r\n confID,\r\n pc,\r\n type: reportType.EVENT,\r\n data: { event,\r\n eventData }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Wraps some of the CallStats API method and logs their calls with\r\n * arguments on the debug logging level. Also wraps some of the backend\r\n * methods execution into try catch blocks to not crash the app in case\r\n * there is a problem with the backend itself.\r\n * @param {callstats} theBackend\r\n * @private\r\n */\r\n static _traceAndCatchBackendCalls(theBackend) {\r\n const tryCatchMethods = [\r\n 'associateMstWithUserID',\r\n 'sendFabricEvent',\r\n 'sendUserFeedback'\r\n\r\n // 'reportError', - this one needs special handling - see code below\r\n ];\r\n\r\n for (const methodName of tryCatchMethods) {\r\n const originalMethod = theBackend[methodName];\r\n\r\n theBackend[methodName] = function(...theArguments) {\r\n try {\r\n return originalMethod.apply(theBackend, theArguments);\r\n } catch (e) {\r\n GlobalOnErrorHandler.callErrorHandler(e);\r\n }\r\n };\r\n }\r\n const debugMethods = [\r\n 'associateMstWithUserID',\r\n 'sendFabricEvent',\r\n 'sendUserFeedback'\r\n\r\n // 'reportError', - this one needs special handling - see code below\r\n ];\r\n\r\n for (const methodName of debugMethods) {\r\n const originalMethod = theBackend[methodName];\r\n\r\n theBackend[methodName] = function(...theArguments) {\r\n logger.debug(methodName, theArguments);\r\n originalMethod.apply(theBackend, theArguments);\r\n };\r\n }\r\n const originalReportError = theBackend.reportError;\r\n\r\n /* eslint-disable max-params */\r\n theBackend.reportError = function(pc, cs, type, ...args) {\r\n // Logs from the logger are submitted on the applicationLog event\r\n // \"type\". Logging the arguments on the logger will create endless\r\n // loop, because it will put all the logs to the logger queue again.\r\n if (type === wrtcFuncNames.applicationLog) {\r\n // NOTE otherArguments are not logged to the console on purpose\r\n // to not log the whole log batch\r\n // FIXME check the current logging level (currently not exposed\r\n // by the logger implementation)\r\n // NOTE it is not safe to log whole objects on react-native as\r\n // those contain too many circular references and may crash\r\n // the app.\r\n if (!browser.isReactNative()) {\r\n console && console.debug('reportError', pc, cs, type);\r\n }\r\n } else {\r\n logger.debug('reportError', pc, cs, type, ...args);\r\n }\r\n try {\r\n originalReportError.call(theBackend, pc, cs, type, ...args);\r\n } catch (exception) {\r\n if (type === wrtcFuncNames.applicationLog) {\r\n console && console.error('reportError', exception);\r\n } else {\r\n GlobalOnErrorHandler.callErrorHandler(exception);\r\n }\r\n }\r\n };\r\n\r\n /* eslint-enable max-params */\r\n }\r\n\r\n /**\r\n * Returns the Set with the currently existing {@link CallStats} instances.\r\n * Lazily initializes the Set to allow any Set polyfills to be applied.\r\n * @type {Set}\r\n */\r\n static get fabrics() {\r\n if (!_fabrics) {\r\n _fabrics = new Set();\r\n }\r\n\r\n return _fabrics;\r\n }\r\n\r\n /**\r\n * Initializes the CallStats backend. Should be called only if\r\n * {@link CallStats.isBackendInitialized} returns false.\r\n * @param {object} options\r\n * @param {String} options.callStatsID CallStats credentials - ID\r\n * @param {String} options.callStatsSecret CallStats credentials - secret\r\n * @param {string} options.aliasName the aliasName part of\r\n * the userID aka endpoint ID, see CallStats docs for more info.\r\n * @param {string} options.userName the userName part of\r\n * the userID aka display name, see CallStats docs for more info.\r\n * @param {object} options.configParams the set of parameters\r\n * to enable/disable certain features in the library. See CallStats docs for more info.\r\n *\r\n */\r\n static initBackend(options) {\r\n if (CallStats.backend) {\r\n throw new Error('CallStats backend has been initialized already!');\r\n }\r\n try {\r\n const CallStatsBackend = callstats;\r\n\r\n CallStats.backend = new CallStatsBackend();\r\n CallStats._traceAndCatchBackendCalls(CallStats.backend);\r\n CallStats.userID = {\r\n aliasName: options.aliasName,\r\n userName: options.userName\r\n };\r\n CallStats.callStatsID = options.callStatsID;\r\n CallStats.callStatsSecret = options.callStatsSecret;\r\n\r\n const configParams = { ...options.configParams };\r\n\r\n if (options.applicationName) {\r\n configParams.applicationVersion = `${options.applicationName} (${browser.getName()})`;\r\n }\r\n\r\n if (options.confID) {\r\n // we first check is there a tenant in the confID\r\n const match = options.confID.match(/.*\\/(.*)\\/.*/);\r\n\r\n // if there is no tenant, we will just set '/'\r\n configParams.siteID = options.siteID || (match && match[1]) || '/';\r\n }\r\n\r\n // userID is generated or given by the origin server\r\n CallStats.backend.initialize(\r\n CallStats.callStatsID,\r\n CallStats.callStatsSecret,\r\n CallStats.userID,\r\n CallStats._initCallback,\r\n undefined,\r\n configParams);\r\n\r\n const getWiFiStatsMethod = options.getWiFiStatsMethod;\r\n\r\n if (getWiFiStatsMethod) {\r\n CallStats.backend.attachWifiStatsHandler(getWiFiStatsMethod);\r\n\r\n getWiFiStatsMethod().then(result => {\r\n if (result) {\r\n logger.info('Reported wifi addresses:'\r\n , JSON.parse(result).addresses);\r\n }\r\n })\r\n .catch(() => {});// eslint-disable-line no-empty-function\r\n }\r\n\r\n return true;\r\n } catch (e) {\r\n // The callstats.io API failed to initialize (e.g. because its\r\n // download did not succeed in general or on time). Further attempts\r\n // to utilize it cannot possibly succeed.\r\n GlobalOnErrorHandler.callErrorHandler(e);\r\n CallStats.backend = null;\r\n logger.error(e);\r\n\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Checks if the CallStats backend has been created. It does not mean that\r\n * it has been initialized, but only that the API instance has been\r\n * allocated successfully.\r\n * @return {boolean} true if backend exists or false\r\n * otherwise\r\n */\r\n static isBackendInitialized() {\r\n return Boolean(CallStats.backend);\r\n }\r\n\r\n /**\r\n * Notifies CallStats about active device.\r\n * @param {{deviceList: {String:String}}} devicesData list of devices with\r\n * their data\r\n * @param {CallStats} cs callstats instance related to the event\r\n */\r\n static sendActiveDeviceListEvent(devicesData, cs) {\r\n CallStats._reportEvent(cs, fabricEvent.activeDeviceList, devicesData);\r\n }\r\n\r\n /**\r\n * Notifies CallStats that there is a log we want to report.\r\n *\r\n * @param {Error} e error to send or {String} message\r\n * @param {CallStats} cs callstats instance related to the error (optional)\r\n */\r\n static sendApplicationLog(e, cs) {\r\n try {\r\n CallStats._reportError(\r\n cs,\r\n wrtcFuncNames.applicationLog,\r\n e,\r\n cs && cs.peerconnection);\r\n } catch (error) {\r\n // If sendApplicationLog fails it should not be printed to\r\n // the logger, because it will try to push the logs again\r\n // (through sendApplicationLog) and an endless loop is created.\r\n if (console && (typeof console.error === 'function')) {\r\n // FIXME send analytics event as well\r\n console.error('sendApplicationLog failed', error);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Sends the given feedback through CallStats.\r\n *\r\n * @param {string} conferenceID the conference ID for which the feedback\r\n * will be reported.\r\n * @param overall an integer between 1 and 5 indicating the\r\n * user feedback\r\n * @param comment detailed feedback from the user.\r\n */\r\n static sendFeedback(conferenceID, overall, comment) {\r\n return new Promise((resolve, reject) => {\r\n if (CallStats.backend) {\r\n CallStats.backend.sendUserFeedback(\r\n conferenceID,\r\n {\r\n userID: CallStats.userID,\r\n overall,\r\n comment\r\n },\r\n (status, message) => {\r\n if (status === 'success') {\r\n resolve(message);\r\n } else {\r\n reject(message);\r\n }\r\n });\r\n } else {\r\n const reason = 'Failed to submit feedback to CallStats - no backend';\r\n\r\n logger.error(reason);\r\n reject(reason);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Notifies CallStats that getUserMedia failed.\r\n *\r\n * @param {Error} e error to send\r\n * @param {CallStats} cs callstats instance related to the error (optional)\r\n */\r\n static sendGetUserMediaFailed(e, cs) {\r\n CallStats._reportError(cs, wrtcFuncNames.getUserMedia, e, null);\r\n }\r\n\r\n /**\r\n * Notifies CallStats for mute events\r\n * @param mute {boolean} true for muted and false for not muted\r\n * @param type {String} \"audio\"/\"video\"\r\n * @param {CallStats} cs callstats instance related to the event\r\n */\r\n static sendMuteEvent(mute, type, cs) {\r\n let event;\r\n\r\n if (type === 'video') {\r\n event = mute ? fabricEvent.videoPause : fabricEvent.videoResume;\r\n } else {\r\n event = mute ? fabricEvent.audioMute : fabricEvent.audioUnmute;\r\n }\r\n\r\n CallStats._reportEvent(cs, event);\r\n }\r\n\r\n /**\r\n * Creates new CallStats instance that handles all callstats API calls for\r\n * given {@link TraceablePeerConnection}. Each instance is meant to handle\r\n * one CallStats fabric added with 'addFabric' API method for the\r\n * {@link TraceablePeerConnection} instance passed in the constructor.\r\n * @param {TraceablePeerConnection} tpc\r\n * @param {Object} options\r\n * @param {string} options.confID the conference ID that wil be used to\r\n * report the session.\r\n * @param {string} [options.remoteUserID='jitsi'] the remote user ID to\r\n * which given tpc is connected.\r\n */\r\n constructor(tpc, options) {\r\n this.confID = options.confID;\r\n this.tpc = tpc;\r\n this.peerconnection = tpc.peerconnection;\r\n this.remoteUserID = options.remoteUserID || DEFAULT_REMOTE_USER;\r\n this.hasFabric = false;\r\n\r\n CallStats.fabrics.add(this);\r\n\r\n if (CallStats.backendInitialized) {\r\n this._addNewFabric();\r\n\r\n // if this is the first fabric let's try to empty the\r\n // report queue. Reports all events that we recorded between\r\n // backend initialization and receiving the first fabric\r\n if (CallStats.fabrics.size === 1) {\r\n CallStats._emptyReportQueue(this);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Initializes CallStats fabric by calling \"addNewFabric\" for\r\n * the peer connection associated with this instance.\r\n * @return {boolean} true if the call was successful or false otherwise.\r\n */\r\n _addNewFabric() {\r\n logger.info('addNewFabric', this.remoteUserID);\r\n try {\r\n const fabricAttributes = {\r\n remoteEndpointType:\r\n this.tpc.isP2P\r\n ? CallStats.backend.endpointType.peer\r\n : CallStats.backend.endpointType.server\r\n };\r\n const ret\r\n = CallStats.backend.addNewFabric(\r\n this.peerconnection,\r\n this.remoteUserID,\r\n CallStats.backend.fabricUsage.multiplex,\r\n this.confID,\r\n fabricAttributes,\r\n CallStats._addNewFabricCallback);\r\n\r\n this.hasFabric = true;\r\n\r\n const success = ret.status === 'success';\r\n\r\n if (!success) {\r\n logger.error('callstats fabric not initilized', ret.message);\r\n }\r\n\r\n return success;\r\n\r\n } catch (error) {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n\r\n return false;\r\n }\r\n }\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n * Lets CallStats module know where is given SSRC rendered by providing\r\n * renderer tag ID.\r\n * If the lib is not initialized yet queue the call for later, when it's\r\n * ready.\r\n * @param {number} ssrc the SSRC of the stream\r\n * @param {boolean} isLocal indicates whether this the stream is local\r\n * @param {string|null} streamEndpointId if the stream is not local the it\r\n * needs to contain the stream owner's ID\r\n * @param {string} usageLabel meaningful usage label of this stream like\r\n * 'microphone', 'camera' or 'screen'.\r\n * @param {string} containerId the id of media 'audio' or 'video' tag which\r\n * renders the stream.\r\n */\r\n associateStreamWithVideoTag(\r\n ssrc,\r\n isLocal,\r\n streamEndpointId,\r\n usageLabel,\r\n containerId) {\r\n if (!CallStats.backend) {\r\n return;\r\n }\r\n\r\n const callStatsId = isLocal ? CallStats.userID : streamEndpointId;\r\n\r\n if (CallStats.backendInitialized) {\r\n CallStats.backend.associateMstWithUserID(\r\n this.peerconnection,\r\n callStatsId,\r\n this.confID,\r\n ssrc,\r\n usageLabel,\r\n containerId);\r\n } else {\r\n CallStats.reportsQueue.push({\r\n type: reportType.MST_WITH_USERID,\r\n pc: this.peerconnection,\r\n data: {\r\n callStatsId,\r\n containerId,\r\n ssrc,\r\n usageLabel\r\n }\r\n });\r\n }\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Notifies CallStats that we are the new dominant speaker in the\r\n * conference.\r\n */\r\n sendDominantSpeakerEvent() {\r\n CallStats._reportEvent(this, fabricEvent.dominantSpeaker);\r\n }\r\n\r\n /**\r\n * Notifies CallStats that the fabric for the underlying peerconnection was\r\n * closed and no evens should be reported, after this call.\r\n */\r\n sendTerminateEvent() {\r\n if (CallStats.backendInitialized) {\r\n CallStats.backend.sendFabricEvent(\r\n this.peerconnection,\r\n CallStats.backend.fabricEvent.fabricTerminated,\r\n this.confID);\r\n }\r\n CallStats.fabrics.delete(this);\r\n }\r\n\r\n /**\r\n * Notifies CallStats for ice connection failed\r\n */\r\n sendIceConnectionFailedEvent() {\r\n CallStats._reportError(\r\n this,\r\n wrtcFuncNames.iceConnectionFailure,\r\n null,\r\n this.peerconnection);\r\n }\r\n\r\n /**\r\n * Notifies CallStats that peer connection failed to create offer.\r\n *\r\n * @param {Error} e error to send\r\n */\r\n sendCreateOfferFailed(e) {\r\n CallStats._reportError(\r\n this, wrtcFuncNames.createOffer, e, this.peerconnection);\r\n }\r\n\r\n /**\r\n * Notifies CallStats that peer connection failed to create answer.\r\n *\r\n * @param {Error} e error to send\r\n */\r\n sendCreateAnswerFailed(e) {\r\n CallStats._reportError(\r\n this, wrtcFuncNames.createAnswer, e, this.peerconnection);\r\n }\r\n\r\n /**\r\n * Sends either resume or hold event for the fabric associated with\r\n * the underlying peerconnection.\r\n * @param {boolean} isResume true to resume or false to hold\r\n */\r\n sendResumeOrHoldEvent(isResume) {\r\n CallStats._reportEvent(\r\n this,\r\n isResume ? fabricEvent.fabricResume : fabricEvent.fabricHold);\r\n }\r\n\r\n /**\r\n * Notifies CallStats for screen sharing events\r\n * @param {boolean} start true for starting screen sharing and\r\n * false for not stopping\r\n * @param {string|null} ssrc - optional ssrc value, used only when\r\n * starting screen sharing.\r\n */\r\n sendScreenSharingEvent(start, ssrc) {\r\n let eventData;\r\n\r\n if (ssrc) {\r\n eventData = { ssrc };\r\n }\r\n\r\n CallStats._reportEvent(\r\n this,\r\n start ? fabricEvent.screenShareStart : fabricEvent.screenShareStop,\r\n eventData);\r\n }\r\n\r\n /**\r\n * Notifies CallStats that peer connection failed to set local description.\r\n *\r\n * @param {Error} e error to send\r\n */\r\n sendSetLocalDescFailed(e) {\r\n CallStats._reportError(\r\n this, wrtcFuncNames.setLocalDescription, e, this.peerconnection);\r\n }\r\n\r\n /**\r\n * Notifies CallStats that peer connection failed to set remote description.\r\n *\r\n * @param {Error} e error to send\r\n */\r\n sendSetRemoteDescFailed(e) {\r\n CallStats._reportError(\r\n this, wrtcFuncNames.setRemoteDescription, e, this.peerconnection);\r\n }\r\n\r\n /**\r\n * Notifies CallStats that peer connection failed to add ICE candidate.\r\n *\r\n * @param {Error} e error to send\r\n */\r\n sendAddIceCandidateFailed(e) {\r\n CallStats._reportError(\r\n this, wrtcFuncNames.addIceCandidate, e, this.peerconnection);\r\n }\r\n}\r\n\r\n/**\r\n * The CallStats API backend instance\r\n * @type {callstats}\r\n */\r\nCallStats.backend = null;\r\n\r\n// some errors/events may happen before CallStats init\r\n// in this case we accumulate them in this array\r\n// and send them to callstats on init\r\nCallStats.reportsQueue = [];\r\n\r\n/**\r\n * Whether the library was successfully initialized(the backend) using its\r\n * initialize method.\r\n * @type {boolean}\r\n */\r\nCallStats.backendInitialized = false;\r\n\r\n/**\r\n * Part of the CallStats credentials - application ID\r\n * @type {string}\r\n */\r\nCallStats.callStatsID = null;\r\n\r\n/**\r\n * Part of the CallStats credentials - application secret\r\n * @type {string}\r\n */\r\nCallStats.callStatsSecret = null;\r\n\r\n/**\r\n * Local CallStats user ID structure. Can be set only once when\r\n * {@link backend} is initialized, so it's static for the time being.\r\n * See CallStats API for more info:\r\n * https://www.callstats.io/api/#userid\r\n * @type {object}\r\n */\r\nCallStats.userID = null;\r\n","/**\r\n * Provides statistics for the local stream.\r\n */\r\n\r\n/**\r\n * Size of the webaudio analyzer buffer.\r\n * @type {number}\r\n */\r\nconst WEBAUDIO_ANALYZER_FFT_SIZE = 2048;\r\n\r\n/**\r\n * Value of the webaudio analyzer smoothing time parameter.\r\n * @type {number}\r\n */\r\nconst WEBAUDIO_ANALYZER_SMOOTING_TIME = 0.8;\r\n\r\nwindow.AudioContext = window.AudioContext || window.webkitAudioContext;\r\n\r\nlet context = null;\r\n\r\nif (window.AudioContext) {\r\n context = new AudioContext();\r\n\r\n // XXX Not all browsers define a suspend method on AudioContext. As the\r\n // invocation is at the (ES6 module) global execution level, it breaks the\r\n // loading of the lib-jitsi-meet library in such browsers and, consequently,\r\n // the loading of the very Web app that uses the lib-jitsi-meet library. For\r\n // example, Google Chrome 40 on Android does not define the method but we\r\n // still want to be able to load the lib-jitsi-meet library there and\r\n // display a page which notifies the user that the Web app is not supported\r\n // there.\r\n context.suspend && context.suspend();\r\n}\r\n\r\n/**\r\n * Converts time domain data array to audio level.\r\n * @param samples the time domain data array.\r\n * @returns {number} the audio level\r\n */\r\nfunction timeDomainDataToAudioLevel(samples) {\r\n\r\n let maxVolume = 0;\r\n\r\n const length = samples.length;\r\n\r\n for (let i = 0; i < length; i++) {\r\n if (maxVolume < samples[i]) {\r\n maxVolume = samples[i];\r\n }\r\n }\r\n\r\n return parseFloat(((maxVolume - 127) / 128).toFixed(3));\r\n}\r\n\r\n/**\r\n * Animates audio level change\r\n * @param newLevel the new audio level\r\n * @param lastLevel the last audio level\r\n * @returns {Number} the audio level to be set\r\n */\r\nfunction animateLevel(newLevel, lastLevel) {\r\n let value = 0;\r\n const diff = lastLevel - newLevel;\r\n\r\n if (diff > 0.2) {\r\n value = lastLevel - 0.2;\r\n } else if (diff < -0.4) {\r\n value = lastLevel + 0.4;\r\n } else {\r\n value = newLevel;\r\n }\r\n\r\n return parseFloat(value.toFixed(3));\r\n}\r\n\r\n\r\n/**\r\n * LocalStatsCollector calculates statistics for the local stream.\r\n *\r\n * @param stream the local stream\r\n * @param interval stats refresh interval given in ms.\r\n * @param callback function that receives the audio levels.\r\n * @constructor\r\n */\r\nexport default function LocalStatsCollector(stream, interval, callback) {\r\n this.stream = stream;\r\n this.intervalId = null;\r\n this.intervalMilis = interval;\r\n this.audioLevel = 0;\r\n this.callback = callback;\r\n}\r\n\r\n/**\r\n * Starts the collecting the statistics.\r\n */\r\nLocalStatsCollector.prototype.start = function() {\r\n if (!LocalStatsCollector.isLocalStatsSupported()) {\r\n return;\r\n }\r\n context.resume();\r\n const analyser = context.createAnalyser();\r\n\r\n analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;\r\n analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;\r\n\r\n const source = context.createMediaStreamSource(this.stream);\r\n\r\n source.connect(analyser);\r\n\r\n this.intervalId = setInterval(\r\n () => {\r\n const array = new Uint8Array(analyser.frequencyBinCount);\r\n\r\n analyser.getByteTimeDomainData(array);\r\n const audioLevel = timeDomainDataToAudioLevel(array);\r\n\r\n // Set the audio levels always as NoAudioSignalDetection now\r\n // uses audio levels from LocalStatsCollector and waits for\r\n // atleast 4 secs for a no audio signal before displaying the\r\n // notification on the UI.\r\n this.audioLevel = animateLevel(audioLevel, this.audioLevel);\r\n this.callback(this.audioLevel);\r\n },\r\n this.intervalMilis\r\n );\r\n};\r\n\r\n/**\r\n * Stops collecting the statistics.\r\n */\r\nLocalStatsCollector.prototype.stop = function() {\r\n if (this.intervalId) {\r\n clearInterval(this.intervalId);\r\n this.intervalId = null;\r\n }\r\n};\r\n\r\n/**\r\n * Checks if the environment has the necessary conditions to support\r\n * collecting stats from local streams.\r\n *\r\n * @returns {boolean}\r\n */\r\nLocalStatsCollector.isLocalStatsSupported = function() {\r\n return Boolean(context);\r\n};\r\n","\r\n/**\r\n * The method will increase the given number by 1. If the given counter is equal\r\n * or greater to {@link Number.MAX_SAFE_INTEGER} then it will be rolled back to\r\n * 1.\r\n * @param {number} number - An integer counter value to be incremented.\r\n * @return {number} the next counter value increased by 1 (see the description\r\n * above for exception).\r\n */\r\nexport function safeCounterIncrement(number) {\r\n let nextValue = number;\r\n\r\n if (number >= Number.MAX_SAFE_INTEGER) {\r\n nextValue = 0;\r\n }\r\n\r\n return nextValue + 1;\r\n}\r\n\r\n/**\r\n * Calculates the average value of am Array of numbers.\r\n *\r\n * @param {Float32Array} valueArray - Array of numbers.\r\n * @returns {number} - Number array average.\r\n */\r\nexport function calculateAverage(valueArray) {\r\n return valueArray.length > 0 ? valueArray.reduce((a, b) => a + b) / valueArray.length : 0;\r\n}\r\n\r\n/**\r\n * Calculates a unique hash for a given string similar to Java's\r\n * implementation of String.hashCode()\r\n *\r\n * @param {String} string - String whose hash has to be calculated.\r\n * @returns {number} - Unique hash code calculated.\r\n */\r\nexport function hashString(string) {\r\n let hash = 0;\r\n\r\n for (let i = 0; i < string.length; i++) {\r\n hash += Math.pow(string.charCodeAt(i) * 31, string.length - i);\r\n\r\n /* eslint-disable no-bitwise */\r\n hash = hash & hash; // Convert to 32bit integer\r\n }\r\n\r\n return Math.abs(hash);\r\n}\r\n\r\n/**\r\n * Returns only the positive values from an array of numbers.\r\n *\r\n * @param {Float32Array} valueArray - Array of vad scores.\r\n * @returns {Array} - Array of positive numbers.\r\n */\r\nexport function filterPositiveValues(valueArray) {\r\n return valueArray.filter(value => value >= 0);\r\n}\r\n\r\n/**\r\n * This class calculates a simple running average that continually changes\r\n * as more data points are collected and added.\r\n */\r\nexport class RunningAverage {\r\n /**\r\n * Creates an instance of the running average calculator.\r\n */\r\n constructor() {\r\n this.average = 0;\r\n this.n = 0;\r\n }\r\n\r\n /**\r\n * Adds a new data point to the existing set of values and recomputes\r\n * the running average.\r\n * @param {number} value\r\n * @returns {void}\r\n */\r\n addNext(value) {\r\n if (typeof value !== 'number') {\r\n return;\r\n }\r\n this.n += 1;\r\n this.average = this.average + ((value - this.average) / this.n);\r\n }\r\n\r\n /**\r\n * Obtains the average value for the current subset of values.\r\n * @returns {number} - computed average.\r\n */\r\n getAverage() {\r\n return this.average;\r\n }\r\n}\r\n","\r\nimport { getLogger } from '@jitsi/logger';\r\n\r\nimport * as StatisticsEvents from '../../service/statistics/Events';\r\nimport { RunningAverage } from '../util/MathUtil';\r\n\r\nconst logger = getLogger(__filename);\r\nconst MILLI_SECONDS = 1000;\r\nconst SECONDS = 60;\r\n\r\n/**\r\n * This class creates an observer that monitors browser's performance measurement events\r\n * as they are recorded in the browser's performance timeline and computes an average and\r\n * a maximum value for the long task events. Tasks are classified as long tasks if they take\r\n * longer than 50ms to execute on the main thread.\r\n */\r\nexport class PerformanceObserverStats {\r\n /**\r\n * Creates a new instance of Performance observer statistics.\r\n *\r\n * @param {*} emitter Event emitter for emitting stats periodically\r\n * @param {*} statsInterval interval for calculating the stats\r\n */\r\n constructor(emitter, statsInterval) {\r\n this.eventEmitter = emitter;\r\n this.longTasks = 0;\r\n this.maxDuration = 0;\r\n this.performanceStatsInterval = statsInterval;\r\n this.stats = new RunningAverage();\r\n }\r\n\r\n /**\r\n * Obtains the average rate of long tasks observed per min and the\r\n * duration of the longest task recorded by the observer.\r\n * @returns {Object}\r\n */\r\n getLongTasksStats() {\r\n return {\r\n avgRatePerMinute: (this.stats.getAverage() * SECONDS).toFixed(2), // calc rate per min\r\n maxDurationMs: this.maxDuration\r\n };\r\n }\r\n\r\n /**\r\n * Starts the performance observer by registering the callback function\r\n * that calculates the performance statistics periodically.\r\n * @returns {void}\r\n */\r\n startObserver() {\r\n // Create a handler for when the long task event is fired.\r\n this.longTaskEventHandler = list => {\r\n const entries = list.getEntries();\r\n\r\n for (const task of entries) {\r\n this.longTasks++;\r\n this.maxDuration = Math.max(this.maxDuration, task.duration).toFixed(3);\r\n }\r\n };\r\n\r\n // Create an observer for monitoring long tasks.\r\n logger.info('Creating a Performance Observer for monitoring Long Tasks');\r\n this.observer = new PerformanceObserver(this.longTaskEventHandler);\r\n this.observer.observe({ type: 'longtask',\r\n buffered: true });\r\n const startTime = Date.now();\r\n\r\n // Calculate the average # of events/sec and emit a stats event.\r\n this.longTasksIntervalId = setInterval(() => {\r\n const now = Date.now();\r\n const interval = this._lastTimeStamp\r\n ? (now - this._lastTimeStamp) / MILLI_SECONDS\r\n : (now - startTime) / MILLI_SECONDS;\r\n const rate = this.longTasks / interval;\r\n\r\n this.stats.addNext(rate);\r\n this.eventEmitter.emit(\r\n StatisticsEvents.LONG_TASKS_STATS, this.getLongTasksStats());\r\n\r\n // Reset the counter and start counting events again.\r\n this.longTasks = 0;\r\n this._lastTimeStamp = Date.now();\r\n }, this.performanceStatsInterval);\r\n }\r\n\r\n /**\r\n * Stops the performance observer.\r\n * @returns {void}\r\n */\r\n stopObserver() {\r\n this.observer && this.observer.disconnect();\r\n this.longTaskEventHandler = null;\r\n if (this.longTasksIntervalId) {\r\n clearInterval(this.longTasksIntervalId);\r\n this.longTasksIntervalId = null;\r\n }\r\n }\r\n}\r\n","export enum MediaType {\r\n /**\r\n * The audio type.\r\n */\r\n AUDIO = 'audio',\r\n\r\n /**\r\n * The presenter type.\r\n */\r\n PRESENTER = 'presenter',\r\n\r\n /**\r\n * The video type.\r\n */\r\n VIDEO = 'video'\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport browser from '../browser';\r\n\r\nconst logger = getLogger('FeatureFlags');\r\n\r\n/**\r\n * A global module for accessing information about different feature flags state.\r\n */\r\nclass FeatureFlags {\r\n /**\r\n * Configures the module.\r\n *\r\n * @param {boolean} flags.runInLiteMode - Enables lite mode for testing to disable media decoding.\r\n * @param {boolean} flags.sourceNameSignaling - Enables source names in the signaling.\r\n */\r\n init(flags) {\r\n this._runInLiteMode = Boolean(flags.runInLiteMode);\r\n\r\n this._sourceNameSignaling = Boolean(flags.sourceNameSignaling);\r\n this._sendMultipleVideoStreams = Boolean(flags.sendMultipleVideoStreams);\r\n this._ssrcRewriting = Boolean(flags.ssrcRewritingOnBridgeSupported);\r\n\r\n // For Chromium, check if Unified plan is enabled.\r\n this._usesUnifiedPlan = browser.supportsUnifiedPlan()\r\n && (!browser.isChromiumBased() || (flags.enableUnifiedOnChrome ?? true));\r\n\r\n logger.info(`Source name signaling: ${this._sourceNameSignaling},`\r\n + ` Send multiple video streams: ${this._sendMultipleVideoStreams},`\r\n + ` SSRC rewriting supported: ${this._ssrcRewriting},`\r\n + ` uses Unified plan: ${this._usesUnifiedPlan}`);\r\n }\r\n\r\n /**\r\n * Checks if multiple local video streams support is enabled.\r\n *\r\n * @returns {boolean}\r\n */\r\n isMultiStreamSupportEnabled() {\r\n return this._sourceNameSignaling && this._sendMultipleVideoStreams && this._usesUnifiedPlan;\r\n }\r\n\r\n /**\r\n * Checks if the run in lite mode is enabled.\r\n * This will cause any media to be received and not decoded. (Directions are inactive and no ssrc and ssrc-groups\r\n * are added to the remote description). This can be used for various test scenarios.\r\n *\r\n * @returns {boolean}\r\n */\r\n isRunInLiteModeEnabled() {\r\n return this._runInLiteMode;\r\n }\r\n\r\n /**\r\n * Checks if the source name signaling is enabled.\r\n *\r\n * @returns {boolean}\r\n */\r\n isSourceNameSignalingEnabled() {\r\n return this._sourceNameSignaling;\r\n }\r\n\r\n /**\r\n * Checks if the clients supports re-writing of the SSRCs on the media streams by the bridge.\r\n * @returns {boolean}\r\n */\r\n isSsrcRewritingSupported() {\r\n return this._ssrcRewriting;\r\n }\r\n}\r\n\r\nexport default new FeatureFlags();\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport * as StatisticsEvents from '../../service/statistics/Events';\r\nimport browser from '../browser';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\n\r\nconst GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Calculates packet lost percent using the number of lost packets and the\r\n * number of all packet.\r\n * @param lostPackets the number of lost packets\r\n * @param totalPackets the number of all packets.\r\n * @returns {number} packet loss percent\r\n */\r\nfunction calculatePacketLoss(lostPackets, totalPackets) {\r\n if (!totalPackets || totalPackets <= 0\r\n || !lostPackets || lostPackets <= 0) {\r\n return 0;\r\n }\r\n\r\n return Math.round((lostPackets / totalPackets) * 100);\r\n}\r\n\r\n/**\r\n * Holds \"statistics\" for a single SSRC.\r\n * @constructor\r\n */\r\nfunction SsrcStats() {\r\n this.loss = {};\r\n this.bitrate = {\r\n download: 0,\r\n upload: 0\r\n };\r\n this.resolution = {};\r\n this.framerate = 0;\r\n this.codec = '';\r\n}\r\n\r\n/**\r\n * Sets the \"loss\" object.\r\n * @param loss the value to set.\r\n */\r\nSsrcStats.prototype.setLoss = function(loss) {\r\n this.loss = loss || {};\r\n};\r\n\r\n/**\r\n * Sets resolution that belong to the ssrc represented by this instance.\r\n * @param resolution new resolution value to be set.\r\n */\r\nSsrcStats.prototype.setResolution = function(resolution) {\r\n this.resolution = resolution || {};\r\n};\r\n\r\n/**\r\n * Adds the \"download\" and \"upload\" fields from the \"bitrate\" parameter to\r\n * the respective fields of the \"bitrate\" field of this object.\r\n * @param bitrate an object holding the values to add.\r\n */\r\nSsrcStats.prototype.addBitrate = function(bitrate) {\r\n this.bitrate.download += bitrate.download;\r\n this.bitrate.upload += bitrate.upload;\r\n};\r\n\r\n/**\r\n * Resets the bit rate for given ssrc that belong to the peer\r\n * represented by this instance.\r\n */\r\nSsrcStats.prototype.resetBitrate = function() {\r\n this.bitrate.download = 0;\r\n this.bitrate.upload = 0;\r\n};\r\n\r\n/**\r\n * Sets the \"framerate\".\r\n * @param framerate the value to set.\r\n */\r\nSsrcStats.prototype.setFramerate = function(framerate) {\r\n this.framerate = framerate || 0;\r\n};\r\n\r\nSsrcStats.prototype.setCodec = function(codec) {\r\n this.codec = codec || '';\r\n};\r\n\r\n/**\r\n *\r\n */\r\nfunction ConferenceStats() {\r\n\r\n /**\r\n * The bandwidth\r\n * @type {{}}\r\n */\r\n this.bandwidth = {};\r\n\r\n /**\r\n * The bit rate\r\n * @type {{}}\r\n */\r\n this.bitrate = {};\r\n\r\n /**\r\n * The packet loss rate\r\n * @type {{}}\r\n */\r\n this.packetLoss = null;\r\n\r\n /**\r\n * Array with the transport information.\r\n * @type {Array}\r\n */\r\n this.transport = [];\r\n}\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * StatsCollector registers for stats updates of given\r\n * peerconnection in given interval. On each update particular\r\n * stats are extracted and put in {@link SsrcStats} objects. Once the processing\r\n * is done audioLevelsUpdateCallback is called with this\r\n * instance as an event source.\r\n *\r\n * @param peerconnection WebRTC PeerConnection object.\r\n * @param audioLevelsInterval\r\n * @param statsInterval stats refresh interval given in ms.\r\n * @param eventEmitter\r\n * @constructor\r\n */\r\nexport default function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter) {\r\n this.peerconnection = peerconnection;\r\n this.baselineAudioLevelsReport = null;\r\n this.currentAudioLevelsReport = null;\r\n this.currentStatsReport = null;\r\n this.previousStatsReport = null;\r\n this.audioLevelReportHistory = {};\r\n this.audioLevelsIntervalId = null;\r\n this.eventEmitter = eventEmitter;\r\n this.conferenceStats = new ConferenceStats();\r\n\r\n // Updates stats interval\r\n this.audioLevelsIntervalMilis = audioLevelsInterval;\r\n\r\n this.speakerList = [];\r\n this.statsIntervalId = null;\r\n this.statsIntervalMilis = statsInterval;\r\n\r\n /**\r\n * Maps SSRC numbers to {@link SsrcStats}.\r\n * @type {Map} speakerList - Endpoint ids.\r\n * @returns {void}\r\n */\r\nStatsCollector.prototype.setSpeakerList = function(speakerList) {\r\n this.speakerList = speakerList;\r\n};\r\n\r\n/**\r\n * Stops stats updates.\r\n */\r\nStatsCollector.prototype.stop = function() {\r\n if (this.audioLevelsIntervalId) {\r\n clearInterval(this.audioLevelsIntervalId);\r\n this.audioLevelsIntervalId = null;\r\n }\r\n\r\n if (this.statsIntervalId) {\r\n clearInterval(this.statsIntervalId);\r\n this.statsIntervalId = null;\r\n }\r\n};\r\n\r\n/**\r\n * Callback passed to getStats method.\r\n * @param error an error that occurred on getStats call.\r\n */\r\nStatsCollector.prototype.errorCallback = function(error) {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error('Get stats error', error);\r\n this.stop();\r\n};\r\n\r\n/**\r\n * Starts stats updates.\r\n */\r\nStatsCollector.prototype.start = function(startAudioLevelStats) {\r\n if (startAudioLevelStats) {\r\n if (browser.supportsReceiverStats()) {\r\n logger.info('Using RTCRtpSynchronizationSource for remote audio levels');\r\n }\r\n this.audioLevelsIntervalId = setInterval(\r\n () => {\r\n if (browser.supportsReceiverStats()) {\r\n const audioLevels = this.peerconnection.getAudioLevels(this.speakerList);\r\n\r\n for (const ssrc in audioLevels) {\r\n if (audioLevels.hasOwnProperty(ssrc)) {\r\n // Use a scaling factor of 2.5 to report the same\r\n // audio levels that getStats reports.\r\n const audioLevel = audioLevels[ssrc] * 2.5;\r\n\r\n this.eventEmitter.emit(\r\n StatisticsEvents.AUDIO_LEVEL,\r\n this.peerconnection,\r\n Number.parseInt(ssrc, 10),\r\n audioLevel,\r\n false /* isLocal */);\r\n }\r\n }\r\n } else {\r\n // Interval updates\r\n this.peerconnection.getStats()\r\n .then(report => {\r\n this.currentAudioLevelsReport = typeof report?.result === 'function'\r\n ? report.result()\r\n : report;\r\n this.processAudioLevelReport();\r\n this.baselineAudioLevelsReport = this.currentAudioLevelsReport;\r\n })\r\n .catch(error => this.errorCallback(error));\r\n }\r\n },\r\n this.audioLevelsIntervalMilis\r\n );\r\n }\r\n\r\n const processStats = () => {\r\n // Interval updates\r\n this.peerconnection.getStats()\r\n .then(report => {\r\n this.currentStatsReport = typeof report?.result === 'function'\r\n ? report.result()\r\n : report;\r\n\r\n try {\r\n this.processStatsReport();\r\n } catch (error) {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error('Processing of RTP stats failed:', error);\r\n }\r\n this.previousStatsReport = this.currentStatsReport;\r\n })\r\n .catch(error => this.errorCallback(error));\r\n };\r\n\r\n processStats();\r\n this.statsIntervalId = setInterval(processStats, this.statsIntervalMilis);\r\n};\r\n\r\n/**\r\n *\r\n */\r\nStatsCollector.prototype._processAndEmitReport = function() {\r\n // process stats\r\n const totalPackets = {\r\n download: 0,\r\n upload: 0\r\n };\r\n const lostPackets = {\r\n download: 0,\r\n upload: 0\r\n };\r\n let bitrateDownload = 0;\r\n let bitrateUpload = 0;\r\n const resolutions = {};\r\n const framerates = {};\r\n const codecs = {};\r\n let audioBitrateDownload = 0;\r\n let audioBitrateUpload = 0;\r\n let audioCodec;\r\n let videoBitrateDownload = 0;\r\n let videoBitrateUpload = 0;\r\n let videoCodec;\r\n\r\n for (const [ ssrc, ssrcStats ] of this.ssrc2stats) {\r\n // process packet loss stats\r\n const loss = ssrcStats.loss;\r\n const type = loss.isDownloadStream ? 'download' : 'upload';\r\n\r\n totalPackets[type] += loss.packetsTotal;\r\n lostPackets[type] += loss.packetsLost;\r\n\r\n // process bitrate stats\r\n bitrateDownload += ssrcStats.bitrate.download;\r\n bitrateUpload += ssrcStats.bitrate.upload;\r\n\r\n // collect resolutions and framerates\r\n const track = this.peerconnection.getTrackBySSRC(ssrc);\r\n\r\n if (track) {\r\n if (track.isAudioTrack()) {\r\n audioBitrateDownload += ssrcStats.bitrate.download;\r\n audioBitrateUpload += ssrcStats.bitrate.upload;\r\n audioCodec = ssrcStats.codec;\r\n } else {\r\n videoBitrateDownload += ssrcStats.bitrate.download;\r\n videoBitrateUpload += ssrcStats.bitrate.upload;\r\n videoCodec = ssrcStats.codec;\r\n }\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n const sourceName = track.getSourceName();\r\n\r\n if (sourceName) {\r\n const resolution = ssrcStats.resolution;\r\n\r\n if (resolution.width // eslint-disable-line max-depth\r\n && resolution.height\r\n && resolution.width !== -1\r\n && resolution.height !== -1) {\r\n resolutions[sourceName] = resolution;\r\n }\r\n if (ssrcStats.framerate !== 0) { // eslint-disable-line max-depth\r\n framerates[sourceName] = ssrcStats.framerate;\r\n }\r\n if (audioCodec && videoCodec) { // eslint-disable-line max-depth\r\n const codecDesc = {\r\n 'audio': audioCodec,\r\n 'video': videoCodec\r\n };\r\n\r\n codecs[sourceName] = codecDesc;\r\n }\r\n } else {\r\n logger.error(`No source name returned by ${track}`);\r\n }\r\n } else {\r\n const participantId = track.getParticipantId();\r\n\r\n if (participantId) {\r\n const resolution = ssrcStats.resolution;\r\n\r\n if (resolution.width // eslint-disable-line max-depth\r\n && resolution.height\r\n && resolution.width !== -1\r\n && resolution.height !== -1) {\r\n const userResolutions = resolutions[participantId] || {};\r\n\r\n userResolutions[ssrc] = resolution;\r\n resolutions[participantId] = userResolutions;\r\n }\r\n if (ssrcStats.framerate !== 0) { // eslint-disable-line max-depth\r\n const userFramerates = framerates[participantId] || {};\r\n\r\n userFramerates[ssrc] = ssrcStats.framerate;\r\n framerates[participantId] = userFramerates;\r\n }\r\n if (audioCodec && videoCodec) { // eslint-disable-line max-depth\r\n const codecDesc = {\r\n 'audio': audioCodec,\r\n 'video': videoCodec\r\n };\r\n\r\n const userCodecs = codecs[participantId] || {};\r\n\r\n userCodecs[ssrc] = codecDesc;\r\n codecs[participantId] = userCodecs;\r\n }\r\n } else {\r\n logger.error(`No participant ID returned by ${track}`);\r\n }\r\n }\r\n }\r\n\r\n ssrcStats.resetBitrate();\r\n }\r\n\r\n this.conferenceStats.bitrate = {\r\n 'upload': bitrateUpload,\r\n 'download': bitrateDownload\r\n };\r\n\r\n this.conferenceStats.bitrate.audio = {\r\n 'upload': audioBitrateUpload,\r\n 'download': audioBitrateDownload\r\n };\r\n\r\n this.conferenceStats.bitrate.video = {\r\n 'upload': videoBitrateUpload,\r\n 'download': videoBitrateDownload\r\n };\r\n\r\n this.conferenceStats.packetLoss = {\r\n total:\r\n calculatePacketLoss(\r\n lostPackets.download + lostPackets.upload,\r\n totalPackets.download + totalPackets.upload),\r\n download:\r\n calculatePacketLoss(lostPackets.download, totalPackets.download),\r\n upload:\r\n calculatePacketLoss(lostPackets.upload, totalPackets.upload)\r\n };\r\n\r\n const avgAudioLevels = {};\r\n let localAvgAudioLevels;\r\n\r\n Object.keys(this.audioLevelReportHistory).forEach(ssrc => {\r\n const { data, isLocal } = this.audioLevelReportHistory[ssrc];\r\n const avgAudioLevel = data.reduce((sum, currentValue) => sum + currentValue) / data.length;\r\n\r\n if (isLocal) {\r\n localAvgAudioLevels = avgAudioLevel;\r\n } else {\r\n const track = this.peerconnection.getTrackBySSRC(Number(ssrc));\r\n\r\n if (track) {\r\n const participantId = track.getParticipantId();\r\n\r\n if (participantId) {\r\n avgAudioLevels[participantId] = avgAudioLevel;\r\n }\r\n }\r\n }\r\n });\r\n this.audioLevelReportHistory = {};\r\n\r\n this.eventEmitter.emit(\r\n StatisticsEvents.CONNECTION_STATS,\r\n this.peerconnection,\r\n {\r\n 'bandwidth': this.conferenceStats.bandwidth,\r\n 'bitrate': this.conferenceStats.bitrate,\r\n 'packetLoss': this.conferenceStats.packetLoss,\r\n 'resolution': resolutions,\r\n 'framerate': framerates,\r\n 'codec': codecs,\r\n 'transport': this.conferenceStats.transport,\r\n localAvgAudioLevels,\r\n avgAudioLevels\r\n });\r\n this.conferenceStats.transport = [];\r\n};\r\n\r\n/**\r\n * Converts the value to a non-negative number.\r\n * If the value is either invalid or negative then 0 will be returned.\r\n * @param {*} v\r\n * @return {number}\r\n * @private\r\n */\r\nStatsCollector.prototype.getNonNegativeValue = function(v) {\r\n let value = v;\r\n\r\n if (typeof value !== 'number') {\r\n value = Number(value);\r\n }\r\n\r\n if (isNaN(value)) {\r\n return 0;\r\n }\r\n\r\n return Math.max(0, value);\r\n};\r\n\r\n/**\r\n * Calculates bitrate between before and now using a supplied field name and its\r\n * value in the stats.\r\n * @param {RTCInboundRtpStreamStats|RTCSentRtpStreamStats} now the current stats\r\n * @param {RTCInboundRtpStreamStats|RTCSentRtpStreamStats} before the\r\n * previous stats.\r\n * @param fieldName the field to use for calculations.\r\n * @return {number} the calculated bitrate between now and before.\r\n * @private\r\n */\r\nStatsCollector.prototype._calculateBitrate = function(now, before, fieldName) {\r\n const bytesNow = this.getNonNegativeValue(now[fieldName]);\r\n const bytesBefore = this.getNonNegativeValue(before[fieldName]);\r\n const bytesProcessed = Math.max(0, bytesNow - bytesBefore);\r\n\r\n const timeMs = now.timestamp - before.timestamp;\r\n let bitrateKbps = 0;\r\n\r\n if (timeMs > 0) {\r\n // TODO is there any reason to round here?\r\n bitrateKbps = Math.round((bytesProcessed * 8) / timeMs);\r\n }\r\n\r\n return bitrateKbps;\r\n};\r\n\r\n/**\r\n * Stats processing for spec-compliant RTCPeerConnection#getStats.\r\n */\r\nStatsCollector.prototype.processStatsReport = function() {\r\n if (!this.previousStatsReport) {\r\n return;\r\n }\r\n const byteSentStats = {};\r\n\r\n this.currentStatsReport.forEach(now => {\r\n // RTCIceCandidatePairStats - https://w3c.github.io/webrtc-stats/#candidatepair-dict*\r\n if (now.type === 'candidate-pair' && now.nominated && now.state === 'succeeded') {\r\n const availableIncomingBitrate = now.availableIncomingBitrate;\r\n const availableOutgoingBitrate = now.availableOutgoingBitrate;\r\n\r\n if (availableIncomingBitrate || availableOutgoingBitrate) {\r\n this.conferenceStats.bandwidth = {\r\n 'download': Math.round(availableIncomingBitrate / 1000),\r\n 'upload': Math.round(availableOutgoingBitrate / 1000)\r\n };\r\n }\r\n\r\n const remoteUsedCandidate = this.currentStatsReport.get(now.remoteCandidateId);\r\n const localUsedCandidate = this.currentStatsReport.get(now.localCandidateId);\r\n\r\n // RTCIceCandidateStats\r\n // https://w3c.github.io/webrtc-stats/#icecandidate-dict*\r\n if (remoteUsedCandidate && localUsedCandidate) {\r\n const remoteIpAddress = browser.isChromiumBased()\r\n ? remoteUsedCandidate.ip\r\n : remoteUsedCandidate.address;\r\n const remotePort = remoteUsedCandidate.port;\r\n const ip = `${remoteIpAddress}:${remotePort}`;\r\n\r\n const localIpAddress = browser.isChromiumBased()\r\n ? localUsedCandidate.ip\r\n : localUsedCandidate.address;\r\n const localPort = localUsedCandidate.port;\r\n const localip = `${localIpAddress}:${localPort}`;\r\n const type = remoteUsedCandidate.protocol;\r\n\r\n // Save the address unless it has been saved already.\r\n const conferenceStatsTransport = this.conferenceStats.transport;\r\n\r\n if (!conferenceStatsTransport.some(t =>\r\n t.ip === ip\r\n && t.type === type\r\n && t.localip === localip)) {\r\n conferenceStatsTransport.push({\r\n ip,\r\n type,\r\n localip,\r\n p2p: this.peerconnection.isP2P,\r\n localCandidateType: localUsedCandidate.candidateType,\r\n remoteCandidateType: remoteUsedCandidate.candidateType,\r\n networkType: localUsedCandidate.networkType,\r\n rtt: now.currentRoundTripTime * 1000\r\n });\r\n }\r\n }\r\n\r\n // RTCReceivedRtpStreamStats\r\n // https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict*\r\n // RTCSentRtpStreamStats\r\n // https://w3c.github.io/webrtc-stats/#sentrtpstats-dict*\r\n } else if (now.type === 'inbound-rtp' || now.type === 'outbound-rtp') {\r\n const before = this.previousStatsReport.get(now.id);\r\n const ssrc = this.getNonNegativeValue(now.ssrc);\r\n\r\n if (!before || !ssrc) {\r\n return;\r\n }\r\n\r\n let ssrcStats = this.ssrc2stats.get(ssrc);\r\n\r\n if (!ssrcStats) {\r\n ssrcStats = new SsrcStats();\r\n this.ssrc2stats.set(ssrc, ssrcStats);\r\n }\r\n\r\n let isDownloadStream = true;\r\n let key = 'packetsReceived';\r\n\r\n if (now.type === 'outbound-rtp') {\r\n isDownloadStream = false;\r\n key = 'packetsSent';\r\n }\r\n\r\n let packetsNow = now[key];\r\n\r\n if (!packetsNow || packetsNow < 0) {\r\n packetsNow = 0;\r\n }\r\n\r\n const packetsBefore = this.getNonNegativeValue(before[key]);\r\n const packetsDiff = Math.max(0, packetsNow - packetsBefore);\r\n\r\n const packetsLostNow = this.getNonNegativeValue(now.packetsLost);\r\n const packetsLostBefore = this.getNonNegativeValue(before.packetsLost);\r\n const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);\r\n\r\n ssrcStats.setLoss({\r\n packetsTotal: packetsDiff + packetsLostDiff,\r\n packetsLost: packetsLostDiff,\r\n isDownloadStream\r\n });\r\n\r\n // Get the resolution and framerate for only remote video sources here. For the local video sources,\r\n // 'track' stats will be used since they have the updated resolution based on the simulcast streams\r\n // currently being sent. Promise based getStats reports three 'outbound-rtp' streams and there will be\r\n // more calculations needed to determine what is the highest resolution stream sent by the client if the\r\n // 'outbound-rtp' stats are used.\r\n if (now.type === 'inbound-rtp') {\r\n const resolution = {\r\n height: now.frameHeight,\r\n width: now.frameWidth\r\n };\r\n const frameRate = now.framesPerSecond;\r\n\r\n if (resolution.height && resolution.width) {\r\n ssrcStats.setResolution(resolution);\r\n }\r\n ssrcStats.setFramerate(Math.round(frameRate || 0));\r\n\r\n ssrcStats.addBitrate({\r\n 'download': this._calculateBitrate(now, before, 'bytesReceived'),\r\n 'upload': 0\r\n });\r\n } else {\r\n byteSentStats[ssrc] = this.getNonNegativeValue(now.bytesSent);\r\n ssrcStats.addBitrate({\r\n 'download': 0,\r\n 'upload': this._calculateBitrate(now, before, 'bytesSent')\r\n });\r\n }\r\n\r\n const codec = this.currentStatsReport.get(now.codecId);\r\n\r\n if (codec) {\r\n /**\r\n * The mime type has the following form: video/VP8 or audio/ISAC,\r\n * so we what to keep just the type after the '/', audio and video\r\n * keys will be added on the processing side.\r\n */\r\n const codecShortType = codec.mimeType.split('/')[1];\r\n\r\n codecShortType && ssrcStats.setCodec(codecShortType);\r\n }\r\n\r\n // Use track stats for resolution and framerate of the local video source.\r\n // RTCVideoHandlerStats - https://w3c.github.io/webrtc-stats/#vststats-dict*\r\n // RTCMediaHandlerStats - https://w3c.github.io/webrtc-stats/#mststats-dict*\r\n } else if (now.type === 'track' && now.kind === MediaType.VIDEO && !now.remoteSource) {\r\n const resolution = {\r\n height: now.frameHeight,\r\n width: now.frameWidth\r\n };\r\n const localVideoTracks = this.peerconnection.getLocalTracks(MediaType.VIDEO);\r\n\r\n if (!localVideoTracks?.length) {\r\n return;\r\n }\r\n\r\n const ssrc = this.peerconnection.getSsrcByTrackId(now.trackIdentifier);\r\n\r\n if (!ssrc) {\r\n return;\r\n }\r\n let ssrcStats = this.ssrc2stats.get(ssrc);\r\n\r\n if (!ssrcStats) {\r\n ssrcStats = new SsrcStats();\r\n this.ssrc2stats.set(ssrc, ssrcStats);\r\n }\r\n if (resolution.height && resolution.width) {\r\n ssrcStats.setResolution(resolution);\r\n }\r\n\r\n // Calculate the frame rate. 'framesSent' is the total aggregate value for all the simulcast streams.\r\n // Therefore, it needs to be divided by the total number of active simulcast streams.\r\n let frameRate = now.framesPerSecond;\r\n\r\n if (!frameRate) {\r\n const before = this.previousStatsReport.get(now.id);\r\n\r\n if (before) {\r\n const timeMs = now.timestamp - before.timestamp;\r\n\r\n if (timeMs > 0 && now.framesSent) {\r\n const numberOfFramesSinceBefore = now.framesSent - before.framesSent;\r\n\r\n frameRate = (numberOfFramesSinceBefore / timeMs) * 1000;\r\n }\r\n }\r\n\r\n if (!frameRate) {\r\n return;\r\n }\r\n }\r\n\r\n // Get the number of simulcast streams currently enabled from TPC.\r\n const numberOfActiveStreams = this.peerconnection.getActiveSimulcastStreams();\r\n\r\n // Reset frame rate to 0 when video is suspended as a result of endpoint falling out of last-n.\r\n frameRate = numberOfActiveStreams ? Math.round(frameRate / numberOfActiveStreams) : 0;\r\n ssrcStats.setFramerate(frameRate);\r\n }\r\n });\r\n\r\n this.eventEmitter.emit(StatisticsEvents.BYTE_SENT_STATS, this.peerconnection, byteSentStats);\r\n this._processAndEmitReport();\r\n};\r\n\r\n/**\r\n * Stats processing logic.\r\n */\r\nStatsCollector.prototype.processAudioLevelReport = function() {\r\n if (!this.baselineAudioLevelsReport) {\r\n return;\r\n }\r\n\r\n this.currentAudioLevelsReport.forEach(now => {\r\n if (now.type !== 'track') {\r\n return;\r\n }\r\n\r\n // Audio level\r\n const audioLevel = now.audioLevel;\r\n\r\n if (!audioLevel) {\r\n return;\r\n }\r\n\r\n const trackIdentifier = now.trackIdentifier;\r\n const ssrc = this.peerconnection.getSsrcByTrackId(trackIdentifier);\r\n\r\n if (ssrc) {\r\n const isLocal\r\n = ssrc === this.peerconnection.getLocalSSRC(\r\n this.peerconnection.getLocalTracks(MediaType.AUDIO));\r\n\r\n this.eventEmitter.emit(\r\n StatisticsEvents.AUDIO_LEVEL,\r\n this.peerconnection,\r\n ssrc,\r\n audioLevel,\r\n isLocal);\r\n }\r\n });\r\n};\r\n\r\n","import EventEmitter from 'events';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport JitsiTrackError from '../../JitsiTrackError';\r\nimport { FEEDBACK } from '../../service/statistics/AnalyticsEvents';\r\nimport * as StatisticsEvents from '../../service/statistics/Events';\r\nimport browser from '../browser';\r\nimport ScriptUtil from '../util/ScriptUtil';\r\n\r\nimport analytics from './AnalyticsAdapter';\r\nimport CallStats from './CallStats';\r\nimport LocalStats from './LocalStatsCollector';\r\nimport { PerformanceObserverStats } from './PerformanceObserverStats';\r\nimport RTPStats from './RTPStatsCollector';\r\nimport { CALLSTATS_SCRIPT_URL } from './constants';\r\n\r\nconst logger = require('@jitsi/logger').getLogger(__filename);\r\n\r\n/**\r\n * Stores all active {@link Statistics} instances.\r\n * @type {Set}\r\n */\r\nlet _instances;\r\n\r\n/**\r\n * True if callstats API is loaded\r\n */\r\nlet isCallstatsLoaded = false;\r\n\r\n/**\r\n * Since callstats.io is a third party, we cannot guarantee the quality of their\r\n * service. More specifically, their server may take noticeably long time to\r\n * respond. Consequently, it is in our best interest (in the sense that the\r\n * intergration of callstats.io is pretty important to us but not enough to\r\n * allow it to prevent people from joining a conference) to (1) start\r\n * downloading their API as soon as possible and (2) do the downloading\r\n * asynchronously.\r\n *\r\n * @param {StatisticsOptions} options - Options to use for downloading and\r\n * initializing callstats backend.\r\n */\r\nfunction loadCallStatsAPI(options) {\r\n if (!isCallstatsLoaded) {\r\n ScriptUtil.loadScript(\r\n options.customScriptUrl || CALLSTATS_SCRIPT_URL,\r\n /* async */ true,\r\n /* prepend */ true,\r\n /* relativeURL */ undefined,\r\n /* loadCallback */ () => _initCallStatsBackend(options)\r\n );\r\n isCallstatsLoaded = true;\r\n }\r\n}\r\n\r\n/**\r\n * Initializes Callstats backend.\r\n *\r\n * @param {StatisticsOptions} options - The options to use for initializing\r\n * callstats backend.\r\n * @private\r\n */\r\nfunction _initCallStatsBackend(options) {\r\n if (CallStats.isBackendInitialized()) {\r\n return;\r\n }\r\n\r\n if (!CallStats.initBackend({\r\n callStatsID: options.callStatsID,\r\n callStatsSecret: options.callStatsSecret,\r\n userName: options.userName,\r\n aliasName: options.aliasName,\r\n applicationName: options.applicationName,\r\n getWiFiStatsMethod: options.getWiFiStatsMethod,\r\n confID: options.confID,\r\n siteID: options.siteID,\r\n configParams: options.configParams\r\n })) {\r\n logger.error('CallStats Backend initialization failed bad');\r\n }\r\n}\r\n\r\n/**\r\n * callstats strips any additional fields from Error except for \"name\", \"stack\",\r\n * \"message\" and \"constraintName\". So we need to bundle additional information\r\n * from JitsiTrackError into error passed to callstats to preserve valuable\r\n * information about error.\r\n * @param {JitsiTrackError} error\r\n */\r\nfunction formatJitsiTrackErrorForCallStats(error) {\r\n const err = new Error();\r\n\r\n // Just copy original stack from error\r\n err.stack = error.stack;\r\n\r\n // Combine name from error's name plus (possibly) name of original GUM error\r\n err.name = (error.name || 'Unknown error') + (error.gum && error.gum.error\r\n && error.gum.error.name ? ` - ${error.gum.error.name}` : '');\r\n\r\n // Put all constraints into this field. For constraint failed errors we will\r\n // still know which exactly constraint failed as it will be a part of\r\n // message.\r\n err.constraintName = error.gum && error.gum.constraints\r\n ? JSON.stringify(error.gum.constraints) : '';\r\n\r\n // Just copy error's message.\r\n err.message = error.message;\r\n\r\n return err;\r\n}\r\n\r\n/**\r\n * Init statistic options\r\n * @param options\r\n */\r\nStatistics.init = function(options) {\r\n Statistics.audioLevelsEnabled = !options.disableAudioLevels;\r\n if (typeof options.pcStatsInterval === 'number') {\r\n Statistics.pcStatsInterval = options.pcStatsInterval;\r\n }\r\n\r\n if (typeof options.audioLevelsInterval === 'number') {\r\n Statistics.audioLevelsInterval = options.audioLevelsInterval;\r\n }\r\n\r\n if (typeof options.longTasksStatsInterval === 'number') {\r\n Statistics.longTasksStatsInterval = options.longTasksStatsInterval;\r\n }\r\n\r\n Statistics.disableThirdPartyRequests = options.disableThirdPartyRequests;\r\n};\r\n\r\n/**\r\n * The options to configure Statistics.\r\n * @typedef {Object} StatisticsOptions\r\n * @property {string} applicationName - The application name to pass to\r\n * callstats.\r\n * @property {string} aliasName - The alias name to use when initializing callstats.\r\n * @property {string} userName - The user name to use when initializing callstats.\r\n * @property {string} confID - The callstats conference ID to use.\r\n * @property {string} callStatsID - Callstats credentials - the id.\r\n * @property {string} callStatsSecret - Callstats credentials - the secret.\r\n * @property {string} customScriptUrl - A custom lib url to use when downloading\r\n * callstats library.\r\n * @property {string} roomName - The room name we are currently in.\r\n * @property {string} configParams - The set of parameters\r\n * to enable/disable certain features in the library. See CallStats docs for more info.\r\n */\r\n/**\r\n *\r\n * @param xmpp\r\n * @param {StatisticsOptions} options - The options to use creating the\r\n * Statistics.\r\n */\r\nexport default function Statistics(xmpp, options) {\r\n /**\r\n * {@link RTPStats} mapped by {@link TraceablePeerConnection.id} which\r\n * collect RTP statistics for each peerconnection.\r\n * @type {Map}\r\n */\r\n this.callsStatsInstances = new Map();\r\n\r\n Statistics.instances.add(this);\r\n}\r\nStatistics.audioLevelsEnabled = false;\r\nStatistics.audioLevelsInterval = 200;\r\nStatistics.pcStatsInterval = 10000;\r\nStatistics.disableThirdPartyRequests = false;\r\nStatistics.analytics = analytics;\r\n\r\nObject.defineProperty(Statistics, 'instances', {\r\n /**\r\n * Returns the Set holding all active {@link Statistics} instances. Lazily\r\n * initializes the Set to allow any Set polyfills to be applied.\r\n * @type {Set}\r\n */\r\n get() {\r\n if (!_instances) {\r\n _instances = new Set();\r\n }\r\n\r\n return _instances;\r\n }\r\n});\r\n\r\n/**\r\n * Starts collecting RTP stats for given peerconnection.\r\n * @param {TraceablePeerConnection} peerconnection\r\n */\r\nStatistics.prototype.startRemoteStats = function(peerconnection) {\r\n this.stopRemoteStats(peerconnection);\r\n\r\n try {\r\n const rtpStats\r\n = new RTPStats(\r\n peerconnection,\r\n Statistics.audioLevelsInterval,\r\n Statistics.pcStatsInterval,\r\n this.eventEmitter);\r\n\r\n rtpStats.start(Statistics.audioLevelsEnabled);\r\n this.rtpStatsMap.set(peerconnection.id, rtpStats);\r\n } catch (e) {\r\n logger.error(`Failed to start collecting remote statistics: ${e}`);\r\n }\r\n};\r\n\r\nStatistics.localStats = [];\r\n\r\nStatistics.startLocalStats = function(stream, callback) {\r\n if (!Statistics.audioLevelsEnabled) {\r\n return;\r\n }\r\n const localStats = new LocalStats(stream, Statistics.audioLevelsInterval,\r\n callback);\r\n\r\n this.localStats.push(localStats);\r\n localStats.start();\r\n};\r\n\r\nStatistics.prototype.addAudioLevelListener = function(listener) {\r\n if (!Statistics.audioLevelsEnabled) {\r\n return;\r\n }\r\n this.eventEmitter.on(StatisticsEvents.AUDIO_LEVEL, listener);\r\n};\r\n\r\nStatistics.prototype.removeAudioLevelListener = function(listener) {\r\n if (!Statistics.audioLevelsEnabled) {\r\n return;\r\n }\r\n this.eventEmitter.removeListener(StatisticsEvents.AUDIO_LEVEL, listener);\r\n};\r\n\r\nStatistics.prototype.addBeforeDisposedListener = function(listener) {\r\n this.eventEmitter.on(StatisticsEvents.BEFORE_DISPOSED, listener);\r\n};\r\n\r\nStatistics.prototype.removeBeforeDisposedListener = function(listener) {\r\n this.eventEmitter.removeListener(\r\n StatisticsEvents.BEFORE_DISPOSED, listener);\r\n};\r\n\r\nStatistics.prototype.addConnectionStatsListener = function(listener) {\r\n this.eventEmitter.on(StatisticsEvents.CONNECTION_STATS, listener);\r\n};\r\n\r\nStatistics.prototype.removeConnectionStatsListener = function(listener) {\r\n this.eventEmitter.removeListener(\r\n StatisticsEvents.CONNECTION_STATS,\r\n listener);\r\n};\r\n\r\nStatistics.prototype.addByteSentStatsListener = function(listener) {\r\n this.eventEmitter.on(StatisticsEvents.BYTE_SENT_STATS, listener);\r\n};\r\n\r\nStatistics.prototype.removeByteSentStatsListener = function(listener) {\r\n this.eventEmitter.removeListener(StatisticsEvents.BYTE_SENT_STATS,\r\n listener);\r\n};\r\n\r\n/**\r\n * Add a listener that would be notified on a LONG_TASKS_STATS event.\r\n *\r\n * @param {Function} listener a function that would be called when notified.\r\n * @returns {void}\r\n */\r\nStatistics.prototype.addLongTasksStatsListener = function(listener) {\r\n this.eventEmitter.on(StatisticsEvents.LONG_TASKS_STATS, listener);\r\n};\r\n\r\n/**\r\n * Creates an instance of {@link PerformanceObserverStats} and starts the\r\n * observer that records the stats periodically.\r\n *\r\n * @returns {void}\r\n */\r\nStatistics.prototype.attachLongTasksStats = function(conference) {\r\n if (!browser.supportsPerformanceObserver()) {\r\n logger.warn('Performance observer for long tasks not supported by browser!');\r\n\r\n return;\r\n }\r\n\r\n this.performanceObserverStats = new PerformanceObserverStats(\r\n this.eventEmitter,\r\n Statistics.longTasksStatsInterval);\r\n\r\n conference.on(\r\n JitsiConferenceEvents.CONFERENCE_JOINED,\r\n () => this.performanceObserverStats.startObserver());\r\n conference.on(\r\n JitsiConferenceEvents.CONFERENCE_LEFT,\r\n () => this.performanceObserverStats.stopObserver());\r\n};\r\n\r\n/**\r\n * Obtains the current value of the LongTasks event statistics.\r\n *\r\n * @returns {Object|null} stats object if the observer has been\r\n * created, null otherwise.\r\n */\r\nStatistics.prototype.getLongTasksStats = function() {\r\n return this.performanceObserverStats\r\n ? this.performanceObserverStats.getLongTasksStats()\r\n : null;\r\n};\r\n\r\n/**\r\n * Removes the given listener for the LONG_TASKS_STATS event.\r\n *\r\n * @param {Function} listener the listener we want to remove.\r\n * @returns {void}\r\n */\r\nStatistics.prototype.removeLongTasksStatsListener = function(listener) {\r\n this.eventEmitter.removeListener(StatisticsEvents.LONG_TASKS_STATS, listener);\r\n};\r\n\r\n/**\r\n * Updates the list of speakers for which the audio levels are to be calculated. This is needed for the jvb pc only.\r\n *\r\n * @param {Array} speakerList The list of remote endpoint ids.\r\n * @returns {void}\r\n */\r\nStatistics.prototype.setSpeakerList = function(speakerList) {\r\n for (const rtpStats of Array.from(this.rtpStatsMap.values())) {\r\n if (!rtpStats.peerconnection.isP2P) {\r\n rtpStats.setSpeakerList(speakerList);\r\n }\r\n }\r\n};\r\n\r\nStatistics.prototype.dispose = function() {\r\n try {\r\n // NOTE Before reading this please see the comment in stopCallStats...\r\n //\r\n // Here we prevent from emitting the event twice in case it will be\r\n // triggered from stopCallStats.\r\n // If the event is triggered from here it means that the logs will not\r\n // be submitted anyway (because there is no CallStats instance), but\r\n // we're doing that for the sake of some kind of consistency.\r\n if (!this.callsStatsInstances.size) {\r\n this.eventEmitter.emit(StatisticsEvents.BEFORE_DISPOSED);\r\n }\r\n for (const callStats of this.callsStatsInstances.values()) {\r\n this.stopCallStats(callStats.tpc);\r\n }\r\n for (const tpcId of this.rtpStatsMap.keys()) {\r\n this._stopRemoteStats(tpcId);\r\n }\r\n if (this.eventEmitter) {\r\n this.eventEmitter.removeAllListeners();\r\n }\r\n } finally {\r\n Statistics.instances.delete(this);\r\n }\r\n};\r\n\r\nStatistics.stopLocalStats = function(stream) {\r\n if (!Statistics.audioLevelsEnabled) {\r\n return;\r\n }\r\n\r\n for (let i = 0; i < Statistics.localStats.length; i++) {\r\n if (Statistics.localStats[i].stream === stream) {\r\n const localStats = Statistics.localStats.splice(i, 1);\r\n\r\n localStats[0].stop();\r\n break;\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Stops remote RTP stats for given peerconnection ID.\r\n * @param {string} tpcId {@link TraceablePeerConnection.id}\r\n * @private\r\n */\r\nStatistics.prototype._stopRemoteStats = function(tpcId) {\r\n const rtpStats = this.rtpStatsMap.get(tpcId);\r\n\r\n if (rtpStats) {\r\n rtpStats.stop();\r\n this.rtpStatsMap.delete(tpcId);\r\n }\r\n};\r\n\r\n/**\r\n * Stops collecting RTP stats for given peerconnection\r\n * @param {TraceablePeerConnection} tpc\r\n */\r\nStatistics.prototype.stopRemoteStats = function(tpc) {\r\n this._stopRemoteStats(tpc.id);\r\n};\r\n\r\n// CALSTATS METHODS\r\n\r\n/**\r\n * Initializes the callstats.io API.\r\n * @param {TraceablePeerConnection} tpc the {@link TraceablePeerConnection}\r\n * instance for which CalStats will be started.\r\n * @param {string} remoteUserID\r\n */\r\nStatistics.prototype.startCallStats = function(tpc, remoteUserID) {\r\n if (!this.callStatsIntegrationEnabled) {\r\n return;\r\n } else if (this.callsStatsInstances.has(tpc.id)) {\r\n logger.error('CallStats instance for ${tpc} exists already');\r\n\r\n return;\r\n }\r\n\r\n logger.info(`Starting CallStats for ${tpc}...`);\r\n\r\n const newInstance\r\n = new CallStats(\r\n tpc,\r\n {\r\n confID: this.options.confID,\r\n remoteUserID\r\n });\r\n\r\n this.callsStatsInstances.set(tpc.id, newInstance);\r\n};\r\n\r\n/**\r\n * Obtains the list of *all* {@link CallStats} instances collected from every\r\n * valid {@link Statistics} instance.\r\n * @return {Set}\r\n * @private\r\n */\r\nStatistics._getAllCallStatsInstances = function() {\r\n const csInstances = new Set();\r\n\r\n for (const statistics of Statistics.instances) {\r\n for (const cs of statistics.callsStatsInstances.values()) {\r\n csInstances.add(cs);\r\n }\r\n }\r\n\r\n return csInstances;\r\n};\r\n\r\n/**\r\n * Removes the callstats.io instances.\r\n */\r\nStatistics.prototype.stopCallStats = function(tpc) {\r\n const callStatsInstance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (callStatsInstance) {\r\n // FIXME the original purpose of adding BEFORE_DISPOSED event was to be\r\n // able to submit the last log batch from jitsi-meet to CallStats. After\r\n // recent changes we dispose the CallStats earlier\r\n // (before Statistics.dispose), so we need to emit this event here to\r\n // give this last chance for final log batch submission.\r\n //\r\n // Eventually there should be a separate module called \"log storage\"\r\n // which should emit proper events when it's underlying\r\n // CallStats instance is going away.\r\n if (this.callsStatsInstances.size === 1) {\r\n this.eventEmitter.emit(StatisticsEvents.BEFORE_DISPOSED);\r\n }\r\n this.callsStatsInstances.delete(tpc.id);\r\n\r\n // The fabric needs to be terminated when being stopped\r\n callStatsInstance.sendTerminateEvent();\r\n }\r\n};\r\n\r\n/**\r\n * Returns true if the callstats integration is enabled, otherwise returns\r\n * false.\r\n *\r\n * @returns true if the callstats integration is enabled, otherwise returns\r\n * false.\r\n */\r\nStatistics.prototype.isCallstatsEnabled = function() {\r\n return this.callStatsIntegrationEnabled;\r\n};\r\n\r\n/**\r\n * Logs either resume or hold event for the given peer connection.\r\n * @param {TraceablePeerConnection} tpc the connection for which event will be\r\n * reported\r\n * @param {boolean} isResume true for resume or false for hold\r\n */\r\nStatistics.prototype.sendConnectionResumeOrHoldEvent = function(tpc, isResume) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.sendResumeOrHoldEvent(isResume);\r\n }\r\n};\r\n\r\n/**\r\n * Notifies CallStats and analytics (if present) for ice connection failed\r\n * @param {TraceablePeerConnection} tpc connection on which failure occurred.\r\n */\r\nStatistics.prototype.sendIceConnectionFailedEvent = function(tpc) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.sendIceConnectionFailedEvent();\r\n }\r\n};\r\n\r\n/**\r\n * Notifies CallStats for mute events\r\n * @param {TraceablePeerConnection} tpc connection on which failure occurred.\r\n * @param {boolean} muted true for muted and false for not muted\r\n * @param {String} type \"audio\"/\"video\"\r\n */\r\nStatistics.prototype.sendMuteEvent = function(tpc, muted, type) {\r\n const instance = tpc && this.callsStatsInstances.get(tpc.id);\r\n\r\n CallStats.sendMuteEvent(muted, type, instance);\r\n};\r\n\r\n/**\r\n * Notifies CallStats for screen sharing events\r\n * @param start {boolean} true for starting screen sharing and\r\n * false for not stopping\r\n * @param {string|null} ssrc - optional ssrc value, used only when\r\n * starting screen sharing.\r\n */\r\nStatistics.prototype.sendScreenSharingEvent\r\n = function(start, ssrc) {\r\n for (const cs of this.callsStatsInstances.values()) {\r\n cs.sendScreenSharingEvent(start, ssrc);\r\n }\r\n };\r\n\r\n/**\r\n * Notifies the statistics module that we are now the dominant speaker of the\r\n * conference.\r\n * @param {String} roomJid - The room jid where the speaker event occurred.\r\n */\r\nStatistics.prototype.sendDominantSpeakerEvent = function(roomJid) {\r\n for (const cs of this.callsStatsInstances.values()) {\r\n cs.sendDominantSpeakerEvent();\r\n }\r\n\r\n // xmpp send dominant speaker event\r\n this.xmpp.sendDominantSpeakerEvent(roomJid);\r\n};\r\n\r\n/**\r\n * Notifies about active device.\r\n * @param {{deviceList: {String:String}}} devicesData - list of devices with\r\n * their data\r\n */\r\nStatistics.sendActiveDeviceListEvent = function(devicesData) {\r\n const globalSet = Statistics._getAllCallStatsInstances();\r\n\r\n if (globalSet.size) {\r\n for (const cs of globalSet) {\r\n CallStats.sendActiveDeviceListEvent(devicesData, cs);\r\n }\r\n } else {\r\n CallStats.sendActiveDeviceListEvent(devicesData, null);\r\n }\r\n};\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * Lets the underlying statistics module know where is given SSRC rendered by\r\n * providing renderer tag ID.\r\n * @param {TraceablePeerConnection} tpc the connection to which the stream\r\n * belongs to\r\n * @param {number} ssrc the SSRC of the stream\r\n * @param {boolean} isLocal\r\n * @param {string} userId\r\n * @param {string} usageLabel meaningful usage label of this stream like\r\n * 'microphone', 'camera' or 'screen'.\r\n * @param {string} containerId the id of media 'audio' or 'video' tag which\r\n * renders the stream.\r\n */\r\nStatistics.prototype.associateStreamWithVideoTag = function(\r\n tpc,\r\n ssrc,\r\n isLocal,\r\n userId,\r\n usageLabel,\r\n containerId) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.associateStreamWithVideoTag(\r\n ssrc,\r\n isLocal,\r\n userId,\r\n usageLabel,\r\n containerId);\r\n }\r\n};\r\n\r\n/* eslint-enable max-params */\r\n\r\n/**\r\n * Notifies CallStats that getUserMedia failed.\r\n *\r\n * @param {Error} e error to send\r\n */\r\nStatistics.sendGetUserMediaFailed = function(e) {\r\n const error\r\n = e instanceof JitsiTrackError\r\n ? formatJitsiTrackErrorForCallStats(e) : e;\r\n const globalSet = Statistics._getAllCallStatsInstances();\r\n\r\n if (globalSet.size) {\r\n for (const cs of globalSet) {\r\n CallStats.sendGetUserMediaFailed(error, cs);\r\n }\r\n } else {\r\n CallStats.sendGetUserMediaFailed(error, null);\r\n }\r\n};\r\n\r\n/**\r\n * Notifies CallStats that peer connection failed to create offer.\r\n *\r\n * @param {Error} e error to send\r\n * @param {TraceablePeerConnection} tpc connection on which failure occurred.\r\n */\r\nStatistics.prototype.sendCreateOfferFailed = function(e, tpc) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.sendCreateOfferFailed(e);\r\n }\r\n};\r\n\r\n/**\r\n * Notifies CallStats that peer connection failed to create answer.\r\n *\r\n * @param {Error} e error to send\r\n * @param {TraceablePeerConnection} tpc connection on which failure occured.\r\n */\r\nStatistics.prototype.sendCreateAnswerFailed = function(e, tpc) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.sendCreateAnswerFailed(e);\r\n }\r\n};\r\n\r\n/**\r\n * Notifies CallStats that peer connection failed to set local description.\r\n *\r\n * @param {Error} e error to send\r\n * @param {TraceablePeerConnection} tpc connection on which failure occurred.\r\n */\r\nStatistics.prototype.sendSetLocalDescFailed = function(e, tpc) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.sendSetLocalDescFailed(e);\r\n }\r\n};\r\n\r\n/**\r\n * Notifies CallStats that peer connection failed to set remote description.\r\n *\r\n * @param {Error} e error to send\r\n * @param {TraceablePeerConnection} tpc connection on which failure occurred.\r\n */\r\nStatistics.prototype.sendSetRemoteDescFailed = function(e, tpc) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.sendSetRemoteDescFailed(e);\r\n }\r\n};\r\n\r\n/**\r\n * Notifies CallStats that peer connection failed to add ICE candidate.\r\n *\r\n * @param {Error} e error to send\r\n * @param {TraceablePeerConnection} tpc connection on which failure occurred.\r\n */\r\nStatistics.prototype.sendAddIceCandidateFailed = function(e, tpc) {\r\n const instance = this.callsStatsInstances.get(tpc.id);\r\n\r\n if (instance) {\r\n instance.sendAddIceCandidateFailed(e);\r\n }\r\n};\r\n\r\n/**\r\n * Adds to CallStats an application log.\r\n *\r\n * @param {String} m a log message to send or an {Error} object to be reported\r\n */\r\nStatistics.sendLog = function(m) {\r\n const globalSubSet = new Set();\r\n\r\n // FIXME we don't want to duplicate logs over P2P instance, but\r\n // here we should go over instances and call this method for each\r\n // unique conference ID rather than selecting the first one.\r\n // We don't have such use case though, so leaving as is for now.\r\n for (const stats of Statistics.instances) {\r\n if (stats.callStatsApplicationLogsDisabled) {\r\n return;\r\n }\r\n\r\n if (stats.callsStatsInstances.size) {\r\n globalSubSet.add(stats.callsStatsInstances.values().next().value);\r\n }\r\n }\r\n\r\n if (globalSubSet.size) {\r\n for (const csPerStats of globalSubSet) {\r\n CallStats.sendApplicationLog(m, csPerStats);\r\n }\r\n } else {\r\n CallStats.sendApplicationLog(m, null);\r\n }\r\n};\r\n\r\n/**\r\n * Sends the given feedback through CallStats.\r\n *\r\n * @param overall an integer between 1 and 5 indicating the user's rating.\r\n * @param comment the comment from the user.\r\n * @returns {Promise} Resolves when callstats feedback has been submitted\r\n * successfully.\r\n */\r\nStatistics.prototype.sendFeedback = function(overall, comment) {\r\n // Statistics.analytics.sendEvent is currently fire and forget, without\r\n // confirmation of successful send.\r\n Statistics.analytics.sendEvent(\r\n FEEDBACK,\r\n {\r\n rating: overall,\r\n comment\r\n });\r\n\r\n return CallStats.sendFeedback(this.options.confID, overall, comment);\r\n};\r\n\r\nStatistics.LOCAL_JID = require('../../service/statistics/constants').LOCAL_JID;\r\n\r\n/**\r\n * Reports global error to CallStats.\r\n *\r\n * @param {Error} error\r\n */\r\nStatistics.reportGlobalError = function(error) {\r\n if (error instanceof JitsiTrackError && error.gum) {\r\n Statistics.sendGetUserMediaFailed(error);\r\n } else {\r\n Statistics.sendLog(error);\r\n }\r\n};\r\n\r\n/**\r\n * Sends event to analytics and logs a message to the logger/console. Console\r\n * messages might also be logged to callstats automatically.\r\n *\r\n * @param {string | Object} event the event name, or an object which\r\n * represents the entire event.\r\n * @param {Object} properties properties to attach to the event (if an event\r\n * name as opposed to an event object is provided).\r\n */\r\nStatistics.sendAnalyticsAndLog = function(event, properties = {}) {\r\n if (!event) {\r\n logger.warn('No event or event name given.');\r\n\r\n return;\r\n }\r\n\r\n let eventToLog;\r\n\r\n // Also support an API with a single object as an event.\r\n if (typeof event === 'object') {\r\n eventToLog = event;\r\n } else {\r\n eventToLog = {\r\n name: event,\r\n properties\r\n };\r\n }\r\n\r\n logger.log(JSON.stringify(eventToLog));\r\n\r\n // We do this last, because it may modify the object which is passed.\r\n this.analytics.sendEvent(event, properties);\r\n};\r\n\r\n/**\r\n * Sends event to analytics.\r\n *\r\n * @param {string | Object} eventName the event name, or an object which\r\n * represents the entire event.\r\n * @param {Object} properties properties to attach to the event\r\n */\r\nStatistics.sendAnalytics = function(eventName, properties = {}) {\r\n this.analytics.sendEvent(eventName, properties);\r\n};\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { Strophe } from 'strophe.js';\r\n\r\nimport * as JitsiConferenceErrors from './JitsiConferenceErrors';\r\nimport * as JitsiConferenceEvents from './JitsiConferenceEvents';\r\nimport { SPEAKERS_AUDIO_LEVELS } from './modules/statistics/constants';\r\nimport Statistics from './modules/statistics/statistics';\r\nimport EventEmitterForwarder from './modules/util/EventEmitterForwarder';\r\nimport { MediaType } from './service/RTC/MediaType';\r\nimport RTCEvents from './service/RTC/RTCEvents';\r\nimport { VideoType } from './service/RTC/VideoType';\r\nimport AuthenticationEvents\r\n from './service/authentication/AuthenticationEvents';\r\nimport {\r\n ACTION_JINGLE_SA_TIMEOUT,\r\n createBridgeDownEvent,\r\n createConnectionStageReachedEvent,\r\n createFocusLeftEvent,\r\n createJingleEvent,\r\n createRemotelyMutedEvent\r\n} from './service/statistics/AnalyticsEvents';\r\nimport { XMPPEvents } from './service/xmpp/XMPPEvents';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Setups all event listeners related to conference\r\n * @param conference {JitsiConference} the conference\r\n */\r\nexport default function JitsiConferenceEventManager(conference) {\r\n this.conference = conference;\r\n this.xmppListeners = {};\r\n\r\n // Listeners related to the conference only\r\n conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED,\r\n track => {\r\n if (!track.isLocal() || !conference.statistics) {\r\n return;\r\n }\r\n const session\r\n = track.isP2P\r\n ? conference.p2pJingleSession : conference.jvbJingleSession;\r\n\r\n // TPC will be null, before the conference starts, but the event\r\n // still should be queued\r\n const tpc = (session && session.peerconnection) || null;\r\n\r\n conference.statistics.sendMuteEvent(\r\n tpc,\r\n track.isMuted(),\r\n track.getType());\r\n });\r\n}\r\n\r\n/**\r\n * Setups event listeners related to conference.chatRoom\r\n */\r\nJitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {\r\n const conference = this.conference;\r\n const chatRoom = conference.room;\r\n\r\n this.chatRoomForwarder = new EventEmitterForwarder(chatRoom,\r\n this.conference.eventEmitter);\r\n\r\n chatRoom.addListener(XMPPEvents.ICE_RESTARTING, jingleSession => {\r\n if (!jingleSession.isP2P) {\r\n // If using DataChannel as bridge channel, it must be closed\r\n // before ICE restart, otherwise Chrome will not trigger \"opened\"\r\n // event for the channel established with the new bridge.\r\n // TODO: This may be bypassed when using a WebSocket as bridge\r\n // channel.\r\n conference.rtc.closeBridgeChannel();\r\n }\r\n\r\n // else: there are no DataChannels in P2P session (at least for now)\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.PARTICIPANT_FEATURES_CHANGED, (from, features) => {\r\n const participant = conference.getParticipantById(Strophe.getResourceFromJid(from));\r\n\r\n if (participant) {\r\n participant.setFeatures(features);\r\n conference.eventEmitter.emit(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED, participant);\r\n }\r\n });\r\n\r\n chatRoom.addListener(\r\n XMPPEvents.ICE_RESTART_SUCCESS,\r\n (jingleSession, offerIq) => {\r\n // The JVB data chanel needs to be reopened in case the conference\r\n // has been moved to a new bridge.\r\n !jingleSession.isP2P\r\n && conference._setBridgeChannel(\r\n offerIq, jingleSession.peerconnection);\r\n });\r\n\r\n\r\n chatRoom.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,\r\n actor => {\r\n // TODO: Add a way to differentiate between commands which caused\r\n // us to mute and those that did not change our state (i.e. we were\r\n // already muted).\r\n Statistics.sendAnalytics(createRemotelyMutedEvent(MediaType.AUDIO));\r\n\r\n conference.mutedByFocusActor = actor;\r\n\r\n // set isMutedByFocus when setAudioMute Promise ends\r\n conference.rtc.setAudioMute(true).then(\r\n () => {\r\n conference.isMutedByFocus = true;\r\n conference.mutedByFocusActor = null;\r\n })\r\n .catch(\r\n error => {\r\n conference.mutedByFocusActor = null;\r\n logger.warn(\r\n 'Error while audio muting due to focus request', error);\r\n });\r\n }\r\n );\r\n\r\n chatRoom.addListener(XMPPEvents.VIDEO_MUTED_BY_FOCUS,\r\n actor => {\r\n // TODO: Add a way to differentiate between commands which caused\r\n // us to mute and those that did not change our state (i.e. we were\r\n // already muted).\r\n Statistics.sendAnalytics(createRemotelyMutedEvent(MediaType.VIDEO));\r\n\r\n conference.mutedVideoByFocusActor = actor;\r\n\r\n // set isVideoMutedByFocus when setVideoMute Promise ends\r\n conference.rtc.setVideoMute(true).then(\r\n () => {\r\n conference.isVideoMutedByFocus = true;\r\n conference.mutedVideoByFocusActor = null;\r\n })\r\n .catch(\r\n error => {\r\n conference.mutedVideoByFocusActor = null;\r\n logger.warn(\r\n 'Error while video muting due to focus request', error);\r\n });\r\n }\r\n );\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.SUBJECT_CHANGED,\r\n JitsiConferenceEvents.SUBJECT_CHANGED);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_JOINED,\r\n JitsiConferenceEvents.CONFERENCE_JOINED);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_JOIN_IN_PROGRESS,\r\n JitsiConferenceEvents.CONFERENCE_JOIN_IN_PROGRESS);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.MEETING_ID_SET,\r\n JitsiConferenceEvents.CONFERENCE_UNIQUE_ID_SET);\r\n\r\n // send some analytics events\r\n chatRoom.addListener(XMPPEvents.MUC_JOINED,\r\n () => {\r\n this.conference._onMucJoined();\r\n\r\n this.conference.isJvbConnectionInterrupted = false;\r\n\r\n // TODO: Move all of the 'connectionTimes' logic to its own module.\r\n Object.keys(chatRoom.connectionTimes).forEach(key => {\r\n const event\r\n = createConnectionStageReachedEvent(\r\n `conference_${key}`,\r\n { value: chatRoom.connectionTimes[key] });\r\n\r\n Statistics.sendAnalytics(event);\r\n });\r\n\r\n // TODO: Move all of the 'connectionTimes' logic to its own module.\r\n Object.keys(chatRoom.xmpp.connectionTimes).forEach(key => {\r\n const event\r\n = createConnectionStageReachedEvent(\r\n `xmpp_${key}`,\r\n { value: chatRoom.xmpp.connectionTimes[key] });\r\n\r\n Statistics.sendAnalytics(event);\r\n });\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.RENEGOTIATION_FAILED, (e, session) => {\r\n if (!session.isP2P) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.OFFER_ANSWER_FAILED, e);\r\n }\r\n });\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.ROOM_JOIN_ERROR,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.CONNECTION_ERROR);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.ROOM_CONNECT_ERROR,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.CONNECTION_ERROR);\r\n this.chatRoomForwarder.forward(XMPPEvents.ROOM_CONNECT_NOT_ALLOWED_ERROR,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.NOT_ALLOWED_ERROR);\r\n this.chatRoomForwarder.forward(XMPPEvents.ROOM_CONNECT_MEMBERS_ONLY_ERROR,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.MEMBERS_ONLY_ERROR);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.ROOM_MAX_USERS_ERROR,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.CONFERENCE_MAX_USERS);\r\n chatRoom.addListener(XMPPEvents.ROOM_MAX_USERS_ERROR, () => conference.leave());\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.PASSWORD_REQUIRED,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.PASSWORD_REQUIRED);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.AUTHENTICATION_REQUIRED,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.AUTHENTICATION_REQUIRED);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.BRIDGE_DOWN,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);\r\n chatRoom.addListener(\r\n XMPPEvents.BRIDGE_DOWN,\r\n () => Statistics.sendAnalytics(createBridgeDownEvent()));\r\n\r\n chatRoom.addListener(XMPPEvents.CONNECTION_RESTARTED,\r\n jingleSession => {\r\n conference._onConferenceRestarted(jingleSession);\r\n });\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.RESERVATION_ERROR,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.RESERVATION_ERROR);\r\n chatRoom.addListener(XMPPEvents.RESERVATION_ERROR, () => conference.leave());\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.GRACEFUL_SHUTDOWN,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.GRACEFUL_SHUTDOWN);\r\n\r\n chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,\r\n jingleSession => {\r\n conference._onIceConnectionFailed(jingleSession);\r\n });\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_DESTROYED,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.CONFERENCE_DESTROYED);\r\n chatRoom.addListener(XMPPEvents.MUC_DESTROYED, () => conference.leave());\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.CHAT_ERROR_RECEIVED,\r\n JitsiConferenceEvents.CONFERENCE_ERROR,\r\n JitsiConferenceErrors.CHAT_ERROR);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.SETTINGS_ERROR_RECEIVED,\r\n JitsiConferenceEvents.CONFERENCE_ERROR,\r\n JitsiConferenceErrors.SETTINGS_ERROR);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.FOCUS_DISCONNECTED,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.FOCUS_DISCONNECTED);\r\n\r\n chatRoom.addListener(XMPPEvents.FOCUS_LEFT,\r\n () => {\r\n Statistics.sendAnalytics(createFocusLeftEvent());\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.FOCUS_LEFT);\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.SESSION_ACCEPT_TIMEOUT,\r\n jingleSession => {\r\n Statistics.sendAnalyticsAndLog(\r\n createJingleEvent(\r\n ACTION_JINGLE_SA_TIMEOUT,\r\n { p2p: jingleSession.isP2P }));\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.RECORDER_STATE_CHANGED,\r\n (session, jid) => {\r\n\r\n if (jid) {\r\n const resource = Strophe.getResourceFromJid(jid);\r\n const participant = conference.getParticipantById(resource) || resource;\r\n\r\n if (session.getStatus() === 'off') {\r\n session.setTerminator(participant);\r\n } else if (session.getStatus() === 'on') {\r\n session.setInitiator(participant);\r\n }\r\n }\r\n\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.RECORDER_STATE_CHANGED,\r\n session);\r\n });\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.TRANSCRIPTION_STATUS_CHANGED,\r\n JitsiConferenceEvents.TRANSCRIPTION_STATUS_CHANGED);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.VIDEO_SIP_GW_AVAILABILITY_CHANGED,\r\n JitsiConferenceEvents.VIDEO_SIP_GW_AVAILABILITY_CHANGED);\r\n\r\n this.chatRoomForwarder.forward(\r\n XMPPEvents.VIDEO_SIP_GW_SESSION_STATE_CHANGED,\r\n JitsiConferenceEvents.VIDEO_SIP_GW_SESSION_STATE_CHANGED);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.PHONE_NUMBER_CHANGED,\r\n JitsiConferenceEvents.PHONE_NUMBER_CHANGED);\r\n\r\n chatRoom.setParticipantPropertyListener((node, from) => {\r\n const participant = conference.getParticipantById(from);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n\r\n participant.setProperty(\r\n node.tagName.substring('jitsi_participant_'.length),\r\n node.value);\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.KICKED,\r\n conference.onMemberKicked.bind(conference));\r\n chatRoom.addListener(XMPPEvents.SUSPEND_DETECTED,\r\n conference.onSuspendDetected.bind(conference));\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_LOCK_CHANGED,\r\n JitsiConferenceEvents.LOCK_STATE_CHANGED);\r\n\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_MEMBERS_ONLY_CHANGED,\r\n JitsiConferenceEvents.MEMBERS_ONLY_CHANGED);\r\n\r\n chatRoom.addListener(XMPPEvents.MUC_MEMBER_JOINED,\r\n conference.onMemberJoined.bind(conference));\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_LOBBY_MEMBER_JOINED,\r\n JitsiConferenceEvents.LOBBY_USER_JOINED);\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_LOBBY_MEMBER_UPDATED,\r\n JitsiConferenceEvents.LOBBY_USER_UPDATED);\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_LOBBY_MEMBER_LEFT,\r\n JitsiConferenceEvents.LOBBY_USER_LEFT);\r\n chatRoom.addListener(XMPPEvents.MUC_MEMBER_BOT_TYPE_CHANGED,\r\n conference._onMemberBotTypeChanged.bind(conference));\r\n chatRoom.addListener(XMPPEvents.MUC_MEMBER_LEFT,\r\n conference.onMemberLeft.bind(conference));\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_LEFT,\r\n JitsiConferenceEvents.CONFERENCE_LEFT);\r\n this.chatRoomForwarder.forward(XMPPEvents.MUC_DENIED_ACCESS,\r\n JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED);\r\n\r\n chatRoom.addListener(XMPPEvents.DISPLAY_NAME_CHANGED,\r\n conference.onDisplayNameChanged.bind(conference));\r\n\r\n chatRoom.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, role => {\r\n conference.onLocalRoleChanged(role);\r\n\r\n // log all events for the recorder operated by the moderator\r\n if (conference.statistics && conference.isModerator()) {\r\n conference.on(JitsiConferenceEvents.RECORDER_STATE_CHANGED,\r\n recorderSession => {\r\n const logObject = {\r\n error: recorderSession.getError(),\r\n id: 'recorder_status',\r\n status: recorderSession.getStatus()\r\n };\r\n\r\n Statistics.sendLog(JSON.stringify(logObject));\r\n });\r\n }\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.MUC_ROLE_CHANGED,\r\n conference.onUserRoleChanged.bind(conference));\r\n\r\n chatRoom.addListener(AuthenticationEvents.IDENTITY_UPDATED,\r\n (authEnabled, authIdentity) => {\r\n conference.authEnabled = authEnabled;\r\n conference.authIdentity = authIdentity;\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.AUTH_STATUS_CHANGED, authEnabled,\r\n authIdentity);\r\n });\r\n\r\n chatRoom.addListener(\r\n XMPPEvents.MESSAGE_RECEIVED,\r\n\r\n // eslint-disable-next-line max-params\r\n (jid, txt, myJid, ts) => {\r\n const id = Strophe.getResourceFromJid(jid);\r\n\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.MESSAGE_RECEIVED,\r\n id, txt, ts);\r\n });\r\n\r\n chatRoom.addListener(\r\n XMPPEvents.PRIVATE_MESSAGE_RECEIVED,\r\n\r\n // eslint-disable-next-line max-params\r\n (jid, txt, myJid, ts) => {\r\n const id = Strophe.getResourceFromJid(jid);\r\n\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.PRIVATE_MESSAGE_RECEIVED,\r\n id, txt, ts);\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.PRESENCE_STATUS,\r\n (jid, status) => {\r\n const id = Strophe.getResourceFromJid(jid);\r\n const participant = conference.getParticipantById(id);\r\n\r\n if (!participant || participant._status === status) {\r\n return;\r\n }\r\n participant._status = status;\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.USER_STATUS_CHANGED, id, status);\r\n });\r\n\r\n chatRoom.addListener(XMPPEvents.JSON_MESSAGE_RECEIVED,\r\n (from, payload) => {\r\n const id = Strophe.getResourceFromJid(from);\r\n const participant = conference.getParticipantById(id);\r\n\r\n if (participant) {\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,\r\n participant, payload);\r\n } else {\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,\r\n id, payload);\r\n }\r\n });\r\n\r\n chatRoom.addPresenceListener('startmuted', (data, from) => {\r\n let isModerator = false;\r\n\r\n if (conference.myUserId() === from && conference.isModerator()) {\r\n isModerator = true;\r\n } else {\r\n const participant = conference.getParticipantById(from);\r\n\r\n if (participant && participant.isModerator()) {\r\n isModerator = true;\r\n }\r\n }\r\n\r\n if (!isModerator) {\r\n return;\r\n }\r\n\r\n const startAudioMuted = data.attributes.audio === 'true';\r\n const startVideoMuted = data.attributes.video === 'true';\r\n\r\n let updated = false;\r\n\r\n if (startAudioMuted !== conference.startMutedPolicy.audio) {\r\n conference.startMutedPolicy.audio = startAudioMuted;\r\n updated = true;\r\n }\r\n\r\n if (startVideoMuted !== conference.startMutedPolicy.video) {\r\n conference.startMutedPolicy.video = startVideoMuted;\r\n updated = true;\r\n }\r\n\r\n if (updated) {\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,\r\n conference.startMutedPolicy\r\n );\r\n }\r\n });\r\n\r\n if (conference.statistics) {\r\n // FIXME ICE related events should end up in RTCEvents eventually\r\n chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,\r\n session => {\r\n conference.statistics.sendIceConnectionFailedEvent(\r\n session.peerconnection);\r\n });\r\n\r\n // FIXME XMPPEvents.ADD_ICE_CANDIDATE_FAILED is never emitted\r\n chatRoom.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED,\r\n (e, pc) => {\r\n conference.statistics.sendAddIceCandidateFailed(e, pc);\r\n });\r\n }\r\n\r\n // Breakout rooms.\r\n this.chatRoomForwarder.forward(XMPPEvents.BREAKOUT_ROOMS_MOVE_TO_ROOM,\r\n JitsiConferenceEvents.BREAKOUT_ROOMS_MOVE_TO_ROOM);\r\n this.chatRoomForwarder.forward(XMPPEvents.BREAKOUT_ROOMS_UPDATED,\r\n JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED);\r\n};\r\n\r\n/**\r\n * Setups event listeners related to conference.rtc\r\n */\r\nJitsiConferenceEventManager.prototype.setupRTCListeners = function() {\r\n const conference = this.conference;\r\n const rtc = conference.rtc;\r\n\r\n rtc.addListener(\r\n RTCEvents.REMOTE_TRACK_ADDED,\r\n conference.onRemoteTrackAdded.bind(conference));\r\n\r\n rtc.addListener(\r\n RTCEvents.REMOTE_TRACK_REMOVED,\r\n conference.onRemoteTrackRemoved.bind(conference));\r\n\r\n rtc.addListener(RTCEvents.DOMINANT_SPEAKER_CHANGED,\r\n (dominant, previous) => {\r\n if (conference.lastDominantSpeaker !== dominant && conference.room) {\r\n conference.lastDominantSpeaker = dominant;\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, dominant, previous);\r\n\r\n if (previous && previous.length) {\r\n const speakerList = previous.slice(0);\r\n\r\n // Add the dominant speaker to the top of the list (exclude self).\r\n if (conference.myUserId !== dominant) {\r\n speakerList.splice(0, 0, dominant);\r\n }\r\n\r\n // Trim the list to the top 5 speakers only.\r\n if (speakerList.length > SPEAKERS_AUDIO_LEVELS) {\r\n speakerList.splice(SPEAKERS_AUDIO_LEVELS, speakerList.length - SPEAKERS_AUDIO_LEVELS);\r\n }\r\n conference.statistics && conference.statistics.setSpeakerList(speakerList);\r\n }\r\n if (conference.statistics && conference.myUserId() === dominant) {\r\n // We are the new dominant speaker.\r\n conference.statistics.sendDominantSpeakerEvent(conference.room.roomjid);\r\n }\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, () => {\r\n const now = window.performance.now();\r\n const key = 'data.channel.opened';\r\n\r\n // TODO: Move all of the 'connectionTimes' logic to its own module.\r\n logger.log(`(TIME) ${key}:\\t`, now);\r\n conference.room.connectionTimes[key] = now;\r\n Statistics.sendAnalytics(\r\n createConnectionStageReachedEvent(key, { value: now }));\r\n\r\n conference.eventEmitter.emit(JitsiConferenceEvents.DATA_CHANNEL_OPENED);\r\n });\r\n\r\n rtc.addListener(RTCEvents.ENDPOINT_MESSAGE_RECEIVED,\r\n (from, payload) => {\r\n const participant = conference.getParticipantById(from);\r\n\r\n if (participant) {\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,\r\n participant, payload);\r\n } else {\r\n logger.warn(\r\n 'Ignored ENDPOINT_MESSAGE_RECEIVED for not existing '\r\n + `participant: ${from}`,\r\n payload);\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.ENDPOINT_STATS_RECEIVED,\r\n (from, payload) => {\r\n const participant = conference.getParticipantById(from);\r\n\r\n if (participant) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.ENDPOINT_STATS_RECEIVED, participant, payload);\r\n } else {\r\n logger.warn(`Ignoring ENDPOINT_STATS_RECEIVED for a non-existant participant: ${from}`);\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.LOCAL_UFRAG_CHANGED,\r\n (tpc, ufrag) => {\r\n if (!tpc.isP2P) {\r\n Statistics.sendLog(\r\n JSON.stringify({\r\n id: 'local_ufrag',\r\n value: ufrag\r\n }));\r\n }\r\n });\r\n rtc.addListener(RTCEvents.REMOTE_UFRAG_CHANGED,\r\n (tpc, ufrag) => {\r\n if (!tpc.isP2P) {\r\n Statistics.sendLog(\r\n JSON.stringify({\r\n id: 'remote_ufrag',\r\n value: ufrag\r\n }));\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.CREATE_ANSWER_FAILED,\r\n (e, tpc) => {\r\n conference.statistics.sendCreateAnswerFailed(e, tpc);\r\n if (!tpc.isP2P) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.OFFER_ANSWER_FAILED, e);\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.CREATE_OFFER_FAILED,\r\n (e, tpc) => {\r\n conference.statistics.sendCreateOfferFailed(e, tpc);\r\n if (!tpc.isP2P) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.OFFER_ANSWER_FAILED, e);\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.SET_LOCAL_DESCRIPTION_FAILED,\r\n (e, tpc) => {\r\n conference.statistics.sendSetLocalDescFailed(e, tpc);\r\n if (!tpc.isP2P) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.OFFER_ANSWER_FAILED, e);\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED,\r\n (e, tpc) => {\r\n conference.statistics.sendSetRemoteDescFailed(e, tpc);\r\n if (!tpc.isP2P) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.OFFER_ANSWER_FAILED, e);\r\n }\r\n });\r\n\r\n rtc.addListener(RTCEvents.LOCAL_TRACK_SSRC_UPDATED,\r\n (track, ssrc) => {\r\n // when starting screen sharing, the track is created and when\r\n // we do set local description and we process the ssrc we\r\n // will be notified for it and we will report it with the event\r\n // for screen sharing\r\n if (track.isVideoTrack() && track.videoType === VideoType.DESKTOP) {\r\n conference.statistics.sendScreenSharingEvent(true, ssrc);\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * Removes event listeners related to conference.xmpp\r\n */\r\nJitsiConferenceEventManager.prototype.removeXMPPListeners = function() {\r\n const conference = this.conference;\r\n\r\n Object.keys(this.xmppListeners).forEach(eventName => {\r\n conference.xmpp.removeListener(\r\n eventName,\r\n this.xmppListeners[eventName]);\r\n });\r\n this.xmppListeners = {};\r\n};\r\n\r\n\r\n/**\r\n * Setups event listeners related to conference.xmpp\r\n */\r\nJitsiConferenceEventManager.prototype.setupXMPPListeners = function() {\r\n const conference = this.conference;\r\n\r\n this._addConferenceXMPPListener(\r\n XMPPEvents.CALL_INCOMING,\r\n conference.onIncomingCall.bind(conference));\r\n this._addConferenceXMPPListener(\r\n XMPPEvents.CALL_ACCEPTED,\r\n conference.onCallAccepted.bind(conference));\r\n this._addConferenceXMPPListener(\r\n XMPPEvents.TRANSPORT_INFO,\r\n conference.onTransportInfo.bind(conference));\r\n this._addConferenceXMPPListener(\r\n XMPPEvents.CALL_ENDED,\r\n conference.onCallEnded.bind(conference));\r\n\r\n this._addConferenceXMPPListener(XMPPEvents.START_MUTED_FROM_FOCUS,\r\n (audioMuted, videoMuted) => {\r\n if (conference.options.config.ignoreStartMuted) {\r\n return;\r\n }\r\n\r\n conference.startAudioMuted = audioMuted;\r\n conference.startVideoMuted = videoMuted;\r\n\r\n // mute existing local tracks because this is initial mute from\r\n // Jicofo\r\n conference.getLocalTracks().forEach(track => {\r\n switch (track.getType()) {\r\n case MediaType.AUDIO:\r\n conference.startAudioMuted && track.mute();\r\n break;\r\n case MediaType.VIDEO:\r\n conference.startVideoMuted && track.mute();\r\n break;\r\n }\r\n });\r\n\r\n conference.eventEmitter.emit(JitsiConferenceEvents.STARTED_MUTED);\r\n });\r\n\r\n this._addConferenceXMPPListener(XMPPEvents.CONFERENCE_TIMESTAMP_RECEIVED,\r\n createdTimestamp => {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_CREATED_TIMESTAMP, createdTimestamp);\r\n });\r\n\r\n this._addConferenceXMPPListener(XMPPEvents.AV_MODERATION_CHANGED,\r\n (value, mediaType, actorJid) => {\r\n const actorParticipant = conference.getParticipants().find(p => p.getJid() === actorJid);\r\n\r\n conference.eventEmitter.emit(JitsiConferenceEvents.AV_MODERATION_CHANGED, {\r\n enabled: value,\r\n mediaType,\r\n actor: actorParticipant\r\n });\r\n });\r\n this._addConferenceXMPPListener(XMPPEvents.AV_MODERATION_PARTICIPANT_APPROVED,\r\n (mediaType, jid) => {\r\n const participant = conference.getParticipantById(Strophe.getResourceFromJid(jid));\r\n\r\n if (participant) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_APPROVED, {\r\n participant,\r\n mediaType\r\n });\r\n }\r\n });\r\n this._addConferenceXMPPListener(XMPPEvents.AV_MODERATION_PARTICIPANT_REJECTED,\r\n (mediaType, jid) => {\r\n const participant = conference.getParticipantById(Strophe.getResourceFromJid(jid));\r\n\r\n if (participant) {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.AV_MODERATION_PARTICIPANT_REJECTED, {\r\n participant,\r\n mediaType\r\n });\r\n }\r\n });\r\n this._addConferenceXMPPListener(XMPPEvents.AV_MODERATION_APPROVED,\r\n value => conference.eventEmitter.emit(JitsiConferenceEvents.AV_MODERATION_APPROVED, { mediaType: value }));\r\n this._addConferenceXMPPListener(XMPPEvents.AV_MODERATION_REJECTED,\r\n value => {\r\n conference.eventEmitter.emit(JitsiConferenceEvents.AV_MODERATION_REJECTED, { mediaType: value });\r\n });\r\n};\r\n\r\n/**\r\n * Add XMPP listener and save its reference for remove on leave conference.\r\n */\r\nJitsiConferenceEventManager.prototype._addConferenceXMPPListener = function(\r\n eventName, listener) {\r\n this.xmppListeners[eventName] = listener;\r\n this.conference.xmpp.addListener(eventName, listener);\r\n};\r\n\r\n/**\r\n * Setups event listeners related to conference.statistics\r\n */\r\nJitsiConferenceEventManager.prototype.setupStatisticsListeners = function() {\r\n const conference = this.conference;\r\n\r\n if (!conference.statistics) {\r\n return;\r\n }\r\n\r\n /* eslint-disable max-params */\r\n conference.statistics.addAudioLevelListener((tpc, ssrc, level, isLocal) => {\r\n conference.rtc.setAudioLevel(tpc, ssrc, level, isLocal);\r\n });\r\n\r\n /* eslint-enable max-params */\r\n\r\n // Forward the \"before stats disposed\" event\r\n conference.statistics.addBeforeDisposedListener(() => {\r\n conference.eventEmitter.emit(\r\n JitsiConferenceEvents.BEFORE_STATISTICS_DISPOSED);\r\n });\r\n\r\n // if we are in startSilent mode we will not be sending/receiving so nothing to detect\r\n if (!conference.options.config.startSilent) {\r\n conference.statistics.addByteSentStatsListener((tpc, stats) => {\r\n conference.getLocalTracks(MediaType.AUDIO).forEach(track => {\r\n const ssrc = tpc.getLocalSSRC(track);\r\n\r\n if (!ssrc || !stats.hasOwnProperty(ssrc)) {\r\n return;\r\n }\r\n\r\n track.onByteSentStatsReceived(tpc, stats[ssrc]);\r\n });\r\n });\r\n }\r\n};\r\n","export enum JitsiTrackEvents {\r\n /**\r\n * The media track was removed to the conference.\r\n */\r\n LOCAL_TRACK_STOPPED = 'track.stopped',\r\n\r\n /**\r\n * Audio levels of a this track was changed.\r\n * The first argument is a number with audio level value in range [0, 1].\r\n * The second argument is a TraceablePeerConnection which is the peer\r\n * connection which measured the audio level (one audio track can be added\r\n * to multiple peer connection at the same time). This argument is optional for\r\n * local tracks for which we can measure audio level without the peer\r\n * connection (the value will be undefined).\r\n *\r\n * NOTE The second argument should be treated as library internal and can be\r\n * removed at any time.\r\n */\r\n TRACK_AUDIO_LEVEL_CHANGED = 'track.audioLevelsChanged',\r\n\r\n /**\r\n * The audio output of the track was changed.\r\n */\r\n TRACK_AUDIO_OUTPUT_CHANGED = 'track.audioOutputChanged',\r\n\r\n /**\r\n * A media track mute status was changed.\r\n */\r\n TRACK_MUTE_CHANGED = 'track.trackMuteChanged',\r\n\r\n /**\r\n * The video type(\"camera\" or \"desktop\") of the track was changed.\r\n */\r\n TRACK_VIDEOTYPE_CHANGED = 'track.videoTypeChanged',\r\n\r\n /**\r\n * Indicates that the track is not receiving any data even though we expect it\r\n * to receive data (i.e. the stream is not stopped).\r\n */\r\n NO_DATA_FROM_SOURCE = 'track.no_data_from_source',\r\n\r\n /**\r\n * Indicates that the local audio track is not receiving any audio input from\r\n * the microphone that is currently selected.\r\n */\r\n NO_AUDIO_INPUT = 'track.no_audio_input',\r\n\r\n /**\r\n * Event fired whenever video track's streaming changes.\r\n * First argument is the sourceName of the track and the second is a string indicating if the connection is currently\r\n * - active - the connection is active.\r\n * - inactive - the connection is inactive, was intentionally interrupted by the bridge because of low BWE or because\r\n * of the endpoint falling out of last N.\r\n * - interrupted - a network problem occurred.\r\n * - restoring - the connection was inactive and is restoring now.\r\n *\r\n * The current status value can be obtained by calling JitsiRemoteTrack.getTrackStreamingStatus().\r\n */\r\n TRACK_STREAMING_STATUS_CHANGED = 'track.streaming_status_changed'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const LOCAL_TRACK_STOPPED = JitsiTrackEvents.LOCAL_TRACK_STOPPED;\r\nexport const TRACK_AUDIO_LEVEL_CHANGED = JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED;\r\nexport const TRACK_AUDIO_OUTPUT_CHANGED = JitsiTrackEvents.TRACK_AUDIO_OUTPUT_CHANGED;\r\nexport const TRACK_MUTE_CHANGED = JitsiTrackEvents.TRACK_MUTE_CHANGED;\r\nexport const TRACK_VIDEOTYPE_CHANGED = JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED;\r\nexport const NO_DATA_FROM_SOURCE = JitsiTrackEvents.NO_DATA_FROM_SOURCE;\r\nexport const NO_AUDIO_INPUT = JitsiTrackEvents.NO_AUDIO_INPUT;\r\nexport const TRACK_STREAMING_STATUS_CHANGED = JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED;\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport { createParticipantConnectionStatusEvent } from '../../service/statistics/AnalyticsEvents';\r\nimport browser from '../browser';\r\nimport Statistics from '../statistics/statistics';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Default value of 500 milliseconds for {@link ParticipantConnectionStatus.outOfLastNTimeout}.\r\n *\r\n * @type {number}\r\n */\r\nconst DEFAULT_NOT_IN_LAST_N_TIMEOUT = 500;\r\n\r\n/**\r\n * Default value of 2500 milliseconds for {@link ParticipantConnectionStatus.p2pRtcMuteTimeout}.\r\n */\r\nconst DEFAULT_P2P_RTC_MUTE_TIMEOUT = 2500;\r\n\r\n/**\r\n * Default value of 10000 milliseconds for {@link ParticipantConnectionStatus.rtcMuteTimeout}.\r\n *\r\n * @type {number}\r\n */\r\nconst DEFAULT_RTC_MUTE_TIMEOUT = 10000;\r\n\r\n/**\r\n * The time to wait a track to be restored. Track which was out of lastN\r\n * should be inactive and when entering lastN it becomes restoring and when\r\n * data is received from bridge it will become active, but if no data is\r\n * received for some time we set status of that participant connection to\r\n * interrupted.\r\n * @type {number}\r\n */\r\nconst DEFAULT_RESTORING_TIMEOUT = 10000;\r\n\r\n/**\r\n * Participant connection statuses.\r\n *\r\n * @type {{\r\n * ACTIVE: string,\r\n * INACTIVE: string,\r\n * INTERRUPTED: string,\r\n * RESTORING: string\r\n * }}\r\n */\r\nexport const ParticipantConnectionStatus = {\r\n /**\r\n * Status indicating that connection is currently active.\r\n */\r\n ACTIVE: 'active',\r\n\r\n /**\r\n * Status indicating that connection is currently inactive.\r\n * Inactive means the connection was stopped on purpose from the bridge,\r\n * like exiting lastN or adaptivity decided to drop video because of not\r\n * enough bandwidth.\r\n */\r\n INACTIVE: 'inactive',\r\n\r\n /**\r\n * Status indicating that connection is currently interrupted.\r\n */\r\n INTERRUPTED: 'interrupted',\r\n\r\n /**\r\n * Status indicating that connection is currently restoring.\r\n */\r\n RESTORING: 'restoring'\r\n};\r\n\r\n/**\r\n * Class is responsible for emitting\r\n * JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED events.\r\n */\r\nexport default class ParticipantConnectionStatusHandler {\r\n /* eslint-disable max-params*/\r\n /**\r\n * Calculates the new {@link ParticipantConnectionStatus} based on\r\n * the values given for some specific remote user. It is assumed that\r\n * the conference is currently in the JVB mode (in contrary to the P2P mode)\r\n * @param {boolean} isConnectionActiveByJvb true if the JVB did not get any\r\n * data from the user for the last 15 seconds.\r\n * @param {boolean} isInLastN indicates whether the user is in the last N\r\n * set. When set to false it means that JVB is not sending any video for\r\n * the user.\r\n * @param {boolean} isRestoringTimedout if true it means that the user has\r\n * been outside of last N too long to be considered\r\n * {@link ParticipantConnectionStatus.RESTORING}.\r\n * @param {boolean} isVideoMuted true if the user is video muted and we\r\n * should not expect to receive any video.\r\n * @param {boolean} isVideoTrackFrozen if the current browser support video\r\n * frozen detection then it will be set to true when the video track is\r\n * frozen. If the current browser does not support frozen detection the it's\r\n * always false.\r\n * @return {ParticipantConnectionStatus} the new connection status for\r\n * the user for whom the values above were provided.\r\n * @private\r\n */\r\n static _getNewStateForJvbMode(\r\n isConnectionActiveByJvb,\r\n isInLastN,\r\n isRestoringTimedout,\r\n isVideoMuted,\r\n isVideoTrackFrozen) {\r\n if (!isConnectionActiveByJvb) {\r\n // when there is a connection problem signaled from jvb\r\n // it means no media was flowing for at least 15secs, so both audio\r\n // and video are most likely interrupted\r\n return ParticipantConnectionStatus.INTERRUPTED;\r\n } else if (isVideoMuted) {\r\n // If the connection is active according to JVB and the user is\r\n // video muted there is no way for the connection to be inactive,\r\n // because the detection logic below only makes sense for video.\r\n return ParticipantConnectionStatus.ACTIVE;\r\n }\r\n\r\n // Logic when isVideoTrackFrozen is supported\r\n if (browser.supportsVideoMuteOnConnInterrupted()) {\r\n if (!isVideoTrackFrozen) {\r\n // If the video is playing we're good\r\n return ParticipantConnectionStatus.ACTIVE;\r\n } else if (isInLastN) {\r\n return isRestoringTimedout\r\n ? ParticipantConnectionStatus.INTERRUPTED\r\n : ParticipantConnectionStatus.RESTORING;\r\n }\r\n\r\n return ParticipantConnectionStatus.INACTIVE;\r\n }\r\n\r\n // Because this browser is incapable of detecting frozen video we must\r\n // rely on the lastN value\r\n return isInLastN\r\n ? ParticipantConnectionStatus.ACTIVE\r\n : ParticipantConnectionStatus.INACTIVE;\r\n }\r\n\r\n /* eslint-enable max-params*/\r\n\r\n /**\r\n * In P2P mode we don't care about any values coming from the JVB and\r\n * the connection status can be only active or interrupted.\r\n * @param {boolean} isVideoMuted the user if video muted\r\n * @param {boolean} isVideoTrackFrozen true if the video track for\r\n * the remote user is currently frozen. If the current browser does not\r\n * support video frozen detection then it's always false.\r\n * @return {ParticipantConnectionStatus}\r\n * @private\r\n */\r\n static _getNewStateForP2PMode(isVideoMuted, isVideoTrackFrozen) {\r\n if (!browser.supportsVideoMuteOnConnInterrupted()) {\r\n // There's no way to detect problems in P2P when there's no video\r\n // track frozen detection...\r\n return ParticipantConnectionStatus.ACTIVE;\r\n }\r\n\r\n return isVideoMuted || !isVideoTrackFrozen\r\n ? ParticipantConnectionStatus.ACTIVE\r\n : ParticipantConnectionStatus.INTERRUPTED;\r\n }\r\n\r\n /**\r\n * Creates new instance of ParticipantConnectionStatus.\r\n *\r\n * @constructor\r\n * @param {RTC} rtc the RTC service instance\r\n * @param {JitsiConference} conference parent conference instance\r\n * @param {Object} options\r\n * @param {number} [options.p2pRtcMuteTimeout=2500] custom value for\r\n * {@link ParticipantConnectionStatus.p2pRtcMuteTimeout}.\r\n * @param {number} [options.rtcMuteTimeout=2000] custom value for\r\n * {@link ParticipantConnectionStatus.rtcMuteTimeout}.\r\n * @param {number} [options.outOfLastNTimeout=500] custom value for\r\n * {@link ParticipantConnectionStatus.outOfLastNTimeout}.\r\n */\r\n constructor(rtc, conference, options) {\r\n this.rtc = rtc;\r\n this.conference = conference;\r\n\r\n /**\r\n * A map of the \"endpoint ID\"(which corresponds to the resource part\r\n * of MUC JID(nickname)) to the timeout callback IDs scheduled using\r\n * window.setTimeout.\r\n * @type {Object.}\r\n */\r\n this.trackTimers = {};\r\n\r\n /**\r\n * This map holds the endpoint connection status received from the JVB\r\n * (as it might be different than the one stored in JitsiParticipant).\r\n * Required for getting back in sync when remote video track is removed.\r\n * @type {Object.}\r\n */\r\n this.connStatusFromJvb = { };\r\n\r\n /**\r\n * If video track frozen detection through RTC mute event is supported,\r\n * we wait some time until video track is considered frozen. But because\r\n * when the user falls out of last N it is expected for the video to\r\n * freeze this timeout must be significantly reduced in \"out of last N\"\r\n * case.\r\n *\r\n * Basically this value is used instead of {@link rtcMuteTimeout} when\r\n * user is not in last N.\r\n * @type {number}\r\n */\r\n this.outOfLastNTimeout\r\n = typeof options.outOfLastNTimeout === 'number'\r\n ? options.outOfLastNTimeout : DEFAULT_NOT_IN_LAST_N_TIMEOUT;\r\n\r\n /**\r\n * How long we are going to wait for the corresponding signaling mute event after the RTC video track muted\r\n * event is fired on the Media stream, before the connection interrupted is fired. The default value is\r\n * {@link DEFAULT_P2P_RTC_MUTE_TIMEOUT}.\r\n *\r\n * @type {number} amount of time in milliseconds.\r\n */\r\n this.p2pRtcMuteTimeout = typeof options.p2pRtcMuteTimeout === 'number'\r\n ? options.p2pRtcMuteTimeout : DEFAULT_P2P_RTC_MUTE_TIMEOUT;\r\n\r\n /**\r\n * How long we're going to wait after the RTC video track muted event\r\n * for the corresponding signalling mute event, before the connection\r\n * interrupted is fired. The default value is\r\n * {@link DEFAULT_RTC_MUTE_TIMEOUT}.\r\n *\r\n * @type {number} amount of time in milliseconds\r\n */\r\n this.rtcMuteTimeout\r\n = typeof options.rtcMuteTimeout === 'number'\r\n ? options.rtcMuteTimeout : DEFAULT_RTC_MUTE_TIMEOUT;\r\n\r\n /**\r\n * This map holds a timestamp indicating when participant's video track\r\n * was RTC muted (it is assumed that each participant can have only 1\r\n * video track at a time). The purpose of storing the timestamp is to\r\n * avoid the transition to disconnected status in case of legitimate\r\n * video mute operation where the signalling video muted event can\r\n * arrive shortly after RTC muted event.\r\n *\r\n * The key is participant's ID which is the same as endpoint id in\r\n * the Colibri conference allocated on the JVB.\r\n *\r\n * The value is a timestamp measured in milliseconds obtained with\r\n * Date.now().\r\n *\r\n * FIXME merge this logic with NO_DATA_FROM_SOURCE event\r\n * implemented in JitsiLocalTrack by extending the event to\r\n * the remote track and allowing to set different timeout for\r\n * local and remote tracks.\r\n *\r\n * @type {Object.}\r\n */\r\n this.rtcMutedTimestamp = { };\r\n logger.info(`RtcMuteTimeout set to: ${this.rtcMuteTimeout}`);\r\n\r\n /**\r\n * This map holds the timestamps indicating when participant's video\r\n * entered lastN set. Participants entering lastN will have connection\r\n * status restoring and when we start receiving video will become\r\n * active, but if video is not received for certain time\r\n * {@link DEFAULT_RESTORING_TIMEOUT} that participant connection status\r\n * will become interrupted.\r\n *\r\n * @type {Map}\r\n */\r\n this.enteredLastNTimestamp = new Map();\r\n\r\n /**\r\n * A map of the \"endpoint ID\"(which corresponds to the resource part\r\n * of MUC JID(nickname)) to the restoring timeout callback IDs\r\n * scheduled using window.setTimeout.\r\n *\r\n * @type {Map}\r\n */\r\n this.restoringTimers = new Map();\r\n\r\n /**\r\n * A map that holds the current connection status (along with all the internal events that happen\r\n * while in that state).\r\n *\r\n * The goal is to send this information to the analytics backend for post-mortem analysis.\r\n */\r\n this.connectionStatusMap = new Map();\r\n }\r\n\r\n /**\r\n * Gets the video frozen timeout for given user.\r\n * @param {string} id endpoint/participant ID\r\n * @return {number} how long are we going to wait since RTC video muted\r\n * even, before a video track is considered frozen.\r\n * @private\r\n */\r\n _getVideoFrozenTimeout(id) {\r\n return this.rtc.isInLastN(id)\r\n ? this.rtcMuteTimeout\r\n : this.conference.isP2PActive() ? this.p2pRtcMuteTimeout : this.outOfLastNTimeout;\r\n }\r\n\r\n /**\r\n * Initializes ParticipantConnectionStatus and bind required event\r\n * listeners.\r\n */\r\n init() {\r\n\r\n this._onEndpointConnStatusChanged\r\n = this.onEndpointConnStatusChanged.bind(this);\r\n\r\n this.rtc.addListener(\r\n RTCEvents.ENDPOINT_CONN_STATUS_CHANGED,\r\n this._onEndpointConnStatusChanged);\r\n\r\n // Handles P2P status changes\r\n this._onP2PStatus = this.refreshConnectionStatusForAll.bind(this);\r\n this.conference.on(JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);\r\n\r\n // Used to send analytics events for the participant that left the call.\r\n this._onUserLeft = this.onUserLeft.bind(this);\r\n this.conference.on(JitsiConferenceEvents.USER_LEFT, this._onUserLeft);\r\n\r\n // On some browsers MediaStreamTrack trigger \"onmute\"/\"onunmute\"\r\n // events for video type tracks when they stop receiving data which is\r\n // often a sign that remote user is having connectivity issues\r\n if (browser.supportsVideoMuteOnConnInterrupted()) {\r\n\r\n this._onTrackRtcMuted = this.onTrackRtcMuted.bind(this);\r\n this.rtc.addListener(\r\n RTCEvents.REMOTE_TRACK_MUTE, this._onTrackRtcMuted);\r\n\r\n this._onTrackRtcUnmuted = this.onTrackRtcUnmuted.bind(this);\r\n this.rtc.addListener(\r\n RTCEvents.REMOTE_TRACK_UNMUTE, this._onTrackRtcUnmuted);\r\n\r\n // Track added/removed listeners are used to bind \"mute\"/\"unmute\"\r\n // event handlers\r\n this._onRemoteTrackAdded = this.onRemoteTrackAdded.bind(this);\r\n this.conference.on(\r\n JitsiConferenceEvents.TRACK_ADDED,\r\n this._onRemoteTrackAdded);\r\n\r\n this._onRemoteTrackRemoved = this.onRemoteTrackRemoved.bind(this);\r\n this.conference.on(\r\n JitsiConferenceEvents.TRACK_REMOVED,\r\n this._onRemoteTrackRemoved);\r\n\r\n // Listened which will be bound to JitsiRemoteTrack to listen for\r\n // signalling mute/unmute events.\r\n this._onSignallingMuteChanged\r\n = this.onSignallingMuteChanged.bind(this);\r\n\r\n // Used to send an analytics event when the video type changes.\r\n this._onTrackVideoTypeChanged\r\n = this.onTrackVideoTypeChanged.bind(this);\r\n }\r\n\r\n this._onLastNChanged = this._onLastNChanged.bind(this);\r\n this.conference.on(\r\n JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,\r\n this._onLastNChanged);\r\n\r\n this._onLastNValueChanged\r\n = this.refreshConnectionStatusForAll.bind(this);\r\n this.rtc.on(\r\n RTCEvents.LASTN_VALUE_CHANGED, this._onLastNValueChanged);\r\n }\r\n\r\n /**\r\n * Removes all event listeners and disposes of all resources held by this\r\n * instance.\r\n */\r\n dispose() {\r\n\r\n this.rtc.removeListener(\r\n RTCEvents.ENDPOINT_CONN_STATUS_CHANGED,\r\n this._onEndpointConnStatusChanged);\r\n\r\n if (browser.supportsVideoMuteOnConnInterrupted()) {\r\n this.rtc.removeListener(\r\n RTCEvents.REMOTE_TRACK_MUTE,\r\n this._onTrackRtcMuted);\r\n this.rtc.removeListener(\r\n RTCEvents.REMOTE_TRACK_UNMUTE,\r\n this._onTrackRtcUnmuted);\r\n\r\n this.conference.off(\r\n JitsiConferenceEvents.TRACK_ADDED,\r\n this._onRemoteTrackAdded);\r\n this.conference.off(\r\n JitsiConferenceEvents.TRACK_REMOVED,\r\n this._onRemoteTrackRemoved);\r\n }\r\n\r\n this.conference.off(\r\n JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,\r\n this._onLastNChanged);\r\n\r\n this.rtc.removeListener(\r\n RTCEvents.LASTN_VALUE_CHANGED, this._onLastNValueChanged);\r\n\r\n this.conference.off(\r\n JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);\r\n\r\n this.conference.off(\r\n JitsiConferenceEvents.USER_LEFT, this._onUserLeft);\r\n\r\n const participantIds = Object.keys(this.trackTimers);\r\n\r\n for (const participantId of participantIds) {\r\n this.clearTimeout(participantId);\r\n this.clearRtcMutedTimestamp(participantId);\r\n }\r\n\r\n for (const id in this.connectionStatusMap) {\r\n if (this.connectionStatusMap.hasOwnProperty(id)) {\r\n this.onUserLeft(id);\r\n }\r\n }\r\n\r\n // Clear RTC connection status cache\r\n this.connStatusFromJvb = {};\r\n }\r\n\r\n /**\r\n * Handles RTCEvents.ENDPOINT_CONN_STATUS_CHANGED triggered when we receive\r\n * notification over the data channel from the bridge about endpoint's\r\n * connection status update.\r\n * @param {string} endpointId - The endpoint ID(MUC nickname/resource JID).\r\n * @param {boolean} isActive - true if the connection is OK or false otherwise.\r\n */\r\n onEndpointConnStatusChanged(endpointId, isActive) {\r\n\r\n logger.debug(\r\n `Detector RTCEvents.ENDPOINT_CONN_STATUS_CHANGED(${Date.now()}): ${\r\n endpointId}: ${isActive}`);\r\n\r\n // Filter out events for the local JID for now\r\n if (endpointId !== this.conference.myUserId()) {\r\n // Store the status received over the data channels\r\n this.connStatusFromJvb[endpointId] = isActive;\r\n this.figureOutConnectionStatus(endpointId);\r\n }\r\n }\r\n\r\n /**\r\n * Changes connection status.\r\n * @param {JitsiParticipant} participant\r\n * @param newStatus\r\n */\r\n _changeConnectionStatus(participant, newStatus) {\r\n if (participant.getConnectionStatus() !== newStatus) {\r\n\r\n const endpointId = participant.getId();\r\n\r\n participant._setConnectionStatus(newStatus);\r\n\r\n logger.debug(\r\n `Emit endpoint conn status(${Date.now()}) ${endpointId}: ${\r\n newStatus}`);\r\n\r\n // Log the event on CallStats\r\n Statistics.sendLog(\r\n JSON.stringify({\r\n id: 'peer.conn.status',\r\n participant: endpointId,\r\n status: newStatus\r\n }));\r\n\r\n\r\n this.conference.eventEmitter.emit(\r\n JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,\r\n endpointId, newStatus);\r\n }\r\n }\r\n\r\n /**\r\n * Reset the postponed \"connection interrupted\" event which was previously\r\n * scheduled as a timeout on RTC 'onmute' event.\r\n *\r\n * @param {string} participantId - The participant for which the \"connection\r\n * interrupted\" timeout was scheduled.\r\n */\r\n clearTimeout(participantId) {\r\n if (this.trackTimers[participantId]) {\r\n window.clearTimeout(this.trackTimers[participantId]);\r\n this.trackTimers[participantId] = null;\r\n }\r\n }\r\n\r\n /**\r\n * Clears the timestamp of the RTC muted event for participant's video track\r\n * @param {string} participantId the id of the conference participant which\r\n * is the same as the Colibri endpoint ID of the video channel allocated for\r\n * the user on the videobridge.\r\n */\r\n clearRtcMutedTimestamp(participantId) {\r\n this.rtcMutedTimestamp[participantId] = null;\r\n }\r\n\r\n /**\r\n * Bind signalling mute event listeners for video {JitsiRemoteTrack} when\r\n * a new one is added to the conference.\r\n *\r\n * @param {JitsiTrack} remoteTrack - The {JitsiTrack} which is being added to\r\n * the conference.\r\n */\r\n onRemoteTrackAdded(remoteTrack) {\r\n if (!remoteTrack.isLocal()\r\n && remoteTrack.getType() === MediaType.VIDEO) {\r\n\r\n logger.debug(\r\n `Detector on remote track added for: ${\r\n remoteTrack.getParticipantId()}`);\r\n\r\n remoteTrack.on(\r\n JitsiTrackEvents.TRACK_MUTE_CHANGED,\r\n this._onSignallingMuteChanged);\r\n remoteTrack.on(\r\n JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,\r\n videoType => this._onTrackVideoTypeChanged(remoteTrack, videoType));\r\n }\r\n }\r\n\r\n /**\r\n * Removes all event listeners bound to the remote video track and clears\r\n * any related timeouts.\r\n *\r\n * @param {JitsiRemoteTrack} remoteTrack - The remote track which is being\r\n * removed from the conference.\r\n */\r\n onRemoteTrackRemoved(remoteTrack) {\r\n if (!remoteTrack.isLocal()\r\n && remoteTrack.getType() === MediaType.VIDEO) {\r\n\r\n const endpointId = remoteTrack.getParticipantId();\r\n\r\n logger.debug(`Detector on remote track removed: ${endpointId}`);\r\n\r\n remoteTrack.off(\r\n JitsiTrackEvents.TRACK_MUTE_CHANGED,\r\n this._onSignallingMuteChanged);\r\n\r\n this.clearTimeout(endpointId);\r\n this.clearRtcMutedTimestamp(endpointId);\r\n\r\n this.figureOutConnectionStatus(endpointId);\r\n }\r\n }\r\n\r\n /**\r\n * Checks if given participant's video is considered frozen.\r\n * @param {JitsiParticipant} participant - The participant.\r\n * @return {boolean} true if the video has frozen for given\r\n * participant or false when it's either not considered frozen\r\n * (yet) or if freeze detection is not supported by the current browser.\r\n *\r\n * FIXME merge this logic with NO_DATA_FROM_SOURCE event\r\n * implemented in JitsiLocalTrack by extending the event to\r\n * the remote track and allowing to set different timeout for\r\n * local and remote tracks.\r\n *\r\n */\r\n isVideoTrackFrozen(participant) {\r\n if (!browser.supportsVideoMuteOnConnInterrupted()) {\r\n return false;\r\n }\r\n\r\n const id = participant.getId();\r\n const hasAnyVideoRTCMuted = participant.hasAnyVideoTrackWebRTCMuted();\r\n const rtcMutedTimestamp = this.rtcMutedTimestamp[id];\r\n const timeout = this._getVideoFrozenTimeout(id);\r\n\r\n return hasAnyVideoRTCMuted\r\n && typeof rtcMutedTimestamp === 'number'\r\n && (Date.now() - rtcMutedTimestamp) >= timeout;\r\n }\r\n\r\n /**\r\n * Goes over every participant and updates connectivity status.\r\n * Should be called when a parameter which affects all of the participants\r\n * is changed (P2P for example).\r\n */\r\n refreshConnectionStatusForAll() {\r\n const participants = this.conference.getParticipants();\r\n\r\n for (const participant of participants) {\r\n this.figureOutConnectionStatus(participant.getId());\r\n }\r\n }\r\n\r\n /**\r\n * Figures out (and updates) the current connectivity status for\r\n * the participant identified by the given id.\r\n *\r\n * @param {string} id - The participant's id (MUC nickname or Colibri endpoint ID).\r\n */\r\n figureOutConnectionStatus(id) {\r\n const participant = this.conference.getParticipantById(id);\r\n\r\n if (!participant) {\r\n // Probably the participant is no longer in the conference\r\n // (at the time of writing this code, participant is\r\n // detached from the conference and TRACK_REMOVED events are\r\n // fired),\r\n // so we don't care, but let's print a log message for debugging purposes.\r\n logger.debug(`figure out conn status - no participant for: ${id}`);\r\n\r\n return;\r\n }\r\n\r\n const inP2PMode = this.conference.isP2PActive();\r\n const isRestoringTimedOut = this._isRestoringTimedout(id);\r\n const audioOnlyMode = this.conference.getLastN() === 0;\r\n\r\n // NOTE Overriding videoMuted to true for audioOnlyMode should disable\r\n // any detection based on video playback or the last N.\r\n const isVideoMuted = participant.isVideoMuted() || audioOnlyMode;\r\n const isVideoTrackFrozen = this.isVideoTrackFrozen(participant);\r\n const isInLastN = this.rtc.isInLastN(id);\r\n let isConnActiveByJvb = this.connStatusFromJvb[id];\r\n\r\n if (typeof isConnActiveByJvb !== 'boolean') {\r\n // If no status was received from the JVB it means that it's active\r\n // (the bridge does not send notification unless there is a problem)\r\n isConnActiveByJvb = true;\r\n }\r\n\r\n const newState\r\n = inP2PMode\r\n ? ParticipantConnectionStatusHandler._getNewStateForP2PMode(\r\n isVideoMuted,\r\n isVideoTrackFrozen)\r\n : ParticipantConnectionStatusHandler._getNewStateForJvbMode(\r\n isConnActiveByJvb,\r\n isInLastN,\r\n isRestoringTimedOut,\r\n isVideoMuted,\r\n isVideoTrackFrozen);\r\n\r\n // if the new state is not restoring clear timers and timestamps\r\n // that we use to track the restoring state\r\n if (newState !== ParticipantConnectionStatus.RESTORING) {\r\n this._clearRestoringTimer(id);\r\n }\r\n\r\n logger.debug(\r\n `Figure out conn status for ${id}, is video muted: ${\r\n isVideoMuted} is active(jvb): ${\r\n isConnActiveByJvb} video track frozen: ${\r\n isVideoTrackFrozen} p2p mode: ${\r\n inP2PMode} is in last N: ${\r\n isInLastN} currentStatus => newStatus: ${\r\n participant.getConnectionStatus()} => ${newState}`);\r\n\r\n const oldConnectionStatus = this.connectionStatusMap[id] || {};\r\n\r\n // Send an analytics event (guard on either the p2p flag or the connection status has changed\r\n // since the last time this code block run).\r\n if (!('p2p' in oldConnectionStatus)\r\n || !('connectionStatus' in oldConnectionStatus)\r\n || oldConnectionStatus.p2p !== inP2PMode\r\n || oldConnectionStatus.connectionStatus !== newState) {\r\n\r\n const nowMs = Date.now();\r\n\r\n this.maybeSendParticipantConnectionStatusEvent(id, nowMs);\r\n\r\n this.connectionStatusMap[id] = {\r\n ...oldConnectionStatus,\r\n connectionStatus: newState,\r\n p2p: inP2PMode,\r\n startedMs: nowMs\r\n };\r\n\r\n // sometimes (always?) we're late to hook the TRACK_VIDEOTYPE_CHANGED event and the\r\n // video type is not in oldConnectionStatus.\r\n if (!('videoType' in this.connectionStatusMap[id])) {\r\n const videoTracks = participant.getTracksByMediaType(MediaType.VIDEO);\r\n\r\n if (Array.isArray(videoTracks) && videoTracks.length !== 0) {\r\n this.connectionStatusMap[id].videoType = videoTracks[0].videoType;\r\n }\r\n }\r\n }\r\n this._changeConnectionStatus(participant, newState);\r\n }\r\n\r\n /**\r\n * Computes the duration of the current connection status for the participant with the specified id (i.e. 15 seconds\r\n * in the INTERRUPTED state) and sends a participant connection status event.\r\n * @param {string} id - The jid of the participant.\r\n * @param {Number} nowMs - The current time (in millis).\r\n * @returns {void}\r\n */\r\n maybeSendParticipantConnectionStatusEvent(id, nowMs) {\r\n const participantConnectionStatus = this.connectionStatusMap[id];\r\n\r\n if (participantConnectionStatus\r\n && 'startedMs' in participantConnectionStatus\r\n && 'videoType' in participantConnectionStatus\r\n && 'connectionStatus' in participantConnectionStatus\r\n && 'p2p' in participantConnectionStatus) {\r\n participantConnectionStatus.value = nowMs - participantConnectionStatus.startedMs;\r\n Statistics.sendAnalytics(\r\n createParticipantConnectionStatusEvent(participantConnectionStatus));\r\n }\r\n }\r\n\r\n /**\r\n * On change in Last N set check all leaving and entering participants to\r\n * change their corresponding statuses.\r\n *\r\n * @param {Array} leavingLastN - The array of ids leaving lastN.\r\n * @param {Array} enteringLastN - The array of ids entering lastN.\r\n * @private\r\n */\r\n _onLastNChanged(leavingLastN = [], enteringLastN = []) {\r\n const now = Date.now();\r\n\r\n logger.debug(`LastN endpoints changed leaving=${leavingLastN}, entering=${enteringLastN} at ${now}`);\r\n\r\n // If the browser doesn't fire the mute/onmute events when the remote peer stops/starts sending media,\r\n // calculate the connection status for all the endpoints since it won't get triggered automatically on\r\n // the endpoint that has started/stopped receiving media.\r\n if (!browser.supportsVideoMuteOnConnInterrupted()) {\r\n this.refreshConnectionStatusForAll();\r\n }\r\n\r\n for (const id of leavingLastN) {\r\n this.enteredLastNTimestamp.delete(id);\r\n this._clearRestoringTimer(id);\r\n browser.supportsVideoMuteOnConnInterrupted() && this.figureOutConnectionStatus(id);\r\n }\r\n for (const id of enteringLastN) {\r\n // store the timestamp this id is entering lastN\r\n this.enteredLastNTimestamp.set(id, now);\r\n browser.supportsVideoMuteOnConnInterrupted() && this.figureOutConnectionStatus(id);\r\n }\r\n }\r\n\r\n /**\r\n * Clears the restoring timer for participant's video track and the\r\n * timestamp for entering lastN.\r\n *\r\n * @param {string} participantId - The id of the conference participant which\r\n * is the same as the Colibri endpoint ID of the video channel allocated for\r\n * the user on the videobridge.\r\n */\r\n _clearRestoringTimer(participantId) {\r\n const rTimer = this.restoringTimers.get(participantId);\r\n\r\n if (rTimer) {\r\n clearTimeout(rTimer);\r\n this.restoringTimers.delete(participantId);\r\n }\r\n }\r\n\r\n /**\r\n * Checks whether a track had stayed enough in restoring state, compares\r\n * current time and the time the track entered in lastN. If it hasn't\r\n * timedout and there is no timer added, add new timer in order to give it\r\n * more time to become active or mark it as interrupted on next check.\r\n *\r\n * @param {string} participantId - The id of the conference participant which\r\n * is the same as the Colibri endpoint ID of the video channel allocated for\r\n * the user on the videobridge.\r\n * @returns {boolean} true if the track was in restoring state\r\n * more than the timeout ({@link DEFAULT_RESTORING_TIMEOUT}.) in order to\r\n * set its status to interrupted.\r\n * @private\r\n */\r\n _isRestoringTimedout(participantId) {\r\n const enteredLastNTimestamp\r\n = this.enteredLastNTimestamp.get(participantId);\r\n\r\n if (enteredLastNTimestamp\r\n && (Date.now() - enteredLastNTimestamp)\r\n >= DEFAULT_RESTORING_TIMEOUT) {\r\n return true;\r\n }\r\n\r\n // still haven't reached timeout, if there is no timer scheduled,\r\n // schedule one so we can track the restoring state and change it after\r\n // reaching the timeout\r\n const rTimer = this.restoringTimers.get(participantId);\r\n\r\n if (!rTimer) {\r\n this.restoringTimers.set(participantId, setTimeout(\r\n () => this.figureOutConnectionStatus(participantId),\r\n DEFAULT_RESTORING_TIMEOUT));\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Sends a last/final participant connection status event for the participant that left the conference.\r\n * @param {string} id - The id of the participant that left the conference.\r\n * @returns {void}\r\n */\r\n onUserLeft(id) {\r\n this.maybeSendParticipantConnectionStatusEvent(id, Date.now());\r\n delete this.connectionStatusMap[id];\r\n }\r\n\r\n /**\r\n * Handles RTC 'onmute' event for the video track.\r\n *\r\n * @param {JitsiRemoteTrack} track - The video track for which 'onmute' event\r\n * will be processed.\r\n */\r\n onTrackRtcMuted(track) {\r\n const participantId = track.getParticipantId();\r\n const participant = this.conference.getParticipantById(participantId);\r\n\r\n logger.debug(`Detector track RTC muted: ${participantId}`, Date.now());\r\n if (!participant) {\r\n logger.error(`No participant for id: ${participantId}`);\r\n\r\n return;\r\n }\r\n this.rtcMutedTimestamp[participantId] = Date.now();\r\n if (!participant.isVideoMuted()) {\r\n // If the user is not muted according to the signalling we'll give\r\n // it some time, before the connection interrupted event is\r\n // triggered.\r\n this.clearTimeout(participantId);\r\n\r\n // The timeout is reduced when user is not in the last N\r\n const timeout = this._getVideoFrozenTimeout(participantId);\r\n\r\n this.trackTimers[participantId] = window.setTimeout(() => {\r\n logger.debug(\r\n `Set RTC mute timeout for: ${participantId}\\\r\n of ${timeout} ms`);\r\n this.clearTimeout(participantId);\r\n this.figureOutConnectionStatus(participantId);\r\n }, timeout);\r\n }\r\n }\r\n\r\n /**\r\n * Handles RTC 'onunmute' event for the video track.\r\n *\r\n * @param {JitsiRemoteTrack} track - The video track for which 'onunmute'\r\n * event will be processed.\r\n */\r\n onTrackRtcUnmuted(track) {\r\n const participantId = track.getParticipantId();\r\n\r\n logger.debug(\r\n `Detector track RTC unmuted: ${participantId}`, Date.now());\r\n\r\n this.clearTimeout(participantId);\r\n this.clearRtcMutedTimestamp(participantId);\r\n\r\n this.figureOutConnectionStatus(participantId);\r\n }\r\n\r\n /**\r\n * Here the signalling \"mute\"/\"unmute\" events are processed.\r\n *\r\n * @param {JitsiRemoteTrack} track - The remote video track for which\r\n * the signalling mute/unmute event will be processed.\r\n */\r\n onSignallingMuteChanged(track) {\r\n const participantId = track.getParticipantId();\r\n\r\n logger.debug(\r\n `Detector on track signalling mute changed: ${participantId}`,\r\n track.isMuted());\r\n\r\n this.figureOutConnectionStatus(participantId);\r\n }\r\n\r\n /**\r\n * Sends a participant connection status event as a result of the video type\r\n * changing.\r\n * @param {JitsiRemoteTrack} track - The track.\r\n * @param {VideoType} type - The video type.\r\n * @returns {void}\r\n */\r\n onTrackVideoTypeChanged(track, type) {\r\n const id = track.getParticipantId();\r\n const nowMs = Date.now();\r\n\r\n this.maybeSendParticipantConnectionStatusEvent(id, nowMs);\r\n\r\n this.connectionStatusMap[id] = {\r\n ...this.connectionStatusMap[id] || {},\r\n videoType: type,\r\n startedMs: nowMs\r\n };\r\n }\r\n}\r\n","\r\nimport { Strophe } from 'strophe.js';\r\n\r\n\r\nimport * as JitsiConferenceEvents from './JitsiConferenceEvents';\r\nimport { ParticipantConnectionStatus }\r\n from './modules/connectivity/ParticipantConnectionStatus';\r\nimport { MediaType } from './service/RTC/MediaType';\r\n\r\n/**\r\n * Represents a participant in (i.e. a member of) a conference.\r\n */\r\nexport default class JitsiParticipant {\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n * Initializes a new JitsiParticipant instance.\r\n *\r\n * @constructor\r\n * @param jid the conference XMPP jid\r\n * @param conference\r\n * @param displayName\r\n * @param {Boolean} hidden - True if the new JitsiParticipant instance is to\r\n * represent a hidden participant; otherwise, false.\r\n * @param {string} statsID - optional participant statsID\r\n * @param {string} status - the initial status if any.\r\n * @param {object} identity - the xmpp identity\r\n * @param {boolean?} isReplacing - whether this is a participant replacing another into the meeting.\r\n * @param {boolean?} isReplaced - whether this is a participant to be kicked and replaced into the meeting.\r\n */\r\n constructor(jid, conference, displayName, hidden, statsID, status, identity, isReplacing, isReplaced) {\r\n this._jid = jid;\r\n this._id = Strophe.getResourceFromJid(jid);\r\n this._conference = conference;\r\n this._displayName = displayName;\r\n this._supportsDTMF = false;\r\n this._tracks = [];\r\n this._role = 'none';\r\n this._status = status;\r\n this._hidden = hidden;\r\n this._statsID = statsID;\r\n this._connectionStatus = ParticipantConnectionStatus.ACTIVE;\r\n this._properties = {};\r\n this._identity = identity;\r\n this._isReplacing = isReplacing;\r\n this._isReplaced = isReplaced;\r\n this._features = new Set();\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * @returns {JitsiConference} The conference that this participant belongs\r\n * to.\r\n */\r\n getConference() {\r\n return this._conference;\r\n }\r\n\r\n /**\r\n * Gets the value of a property of this participant.\r\n */\r\n getProperty(name) {\r\n return this._properties[name];\r\n }\r\n\r\n /**\r\n * Checks whether this JitsiParticipant has any video tracks which\r\n * are muted according to their underlying WebRTC MediaStreamTrack\r\n * muted status.\r\n * @return {boolean} true if this participant contains any\r\n * video JitsiTracks which are muted as defined in\r\n * {@link JitsiTrack.isWebRTCTrackMuted}.\r\n */\r\n hasAnyVideoTrackWebRTCMuted() {\r\n return (\r\n this.getTracks().some(\r\n jitsiTrack =>\r\n jitsiTrack.getType() === MediaType.VIDEO\r\n && jitsiTrack.isWebRTCTrackMuted()));\r\n }\r\n\r\n /**\r\n * Updates participant's connection status.\r\n * @param {string} state the current participant connection state.\r\n * {@link ParticipantConnectionStatus}.\r\n * @private\r\n */\r\n _setConnectionStatus(status) {\r\n this._connectionStatus = status;\r\n }\r\n\r\n /**\r\n * Return participant's connectivity status.\r\n *\r\n * @returns {string} the connection status\r\n * ParticipantConnectionStatus of the user.\r\n * {@link ParticipantConnectionStatus}.\r\n */\r\n getConnectionStatus() {\r\n return this._connectionStatus;\r\n }\r\n\r\n /**\r\n * Sets the value of a property of this participant, and fires an event if\r\n * the value has changed.\r\n * @name the name of the property.\r\n * @value the value to set.\r\n */\r\n setProperty(name, value) {\r\n const oldValue = this._properties[name];\r\n\r\n if (value !== oldValue) {\r\n this._properties[name] = value;\r\n this._conference.eventEmitter.emit(\r\n JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,\r\n this,\r\n name,\r\n oldValue,\r\n value);\r\n }\r\n }\r\n\r\n /**\r\n * @returns {Array.} The list of media tracks for this\r\n * participant.\r\n */\r\n getTracks() {\r\n return this._tracks.slice();\r\n }\r\n\r\n /**\r\n * @param {MediaType} mediaType\r\n * @returns {Array.} an array of media tracks for this\r\n * participant, for given media type.\r\n */\r\n getTracksByMediaType(mediaType) {\r\n return this.getTracks().filter(track => track.getType() === mediaType);\r\n }\r\n\r\n /**\r\n * @returns {String} The ID of this participant.\r\n */\r\n getId() {\r\n return this._id;\r\n }\r\n\r\n /**\r\n * @returns {String} The JID of this participant.\r\n */\r\n getJid() {\r\n return this._jid;\r\n }\r\n\r\n /**\r\n * @returns {String} The human-readable display name of this participant.\r\n */\r\n getDisplayName() {\r\n return this._displayName;\r\n }\r\n\r\n /**\r\n * @returns {String} The stats ID of this participant.\r\n */\r\n getStatsID() {\r\n return this._statsID;\r\n }\r\n\r\n /**\r\n * @returns {String} The status of the participant.\r\n */\r\n getStatus() {\r\n return this._status;\r\n }\r\n\r\n /**\r\n * @returns {Boolean} Whether this participant is a moderator or not.\r\n */\r\n isModerator() {\r\n return this._role === 'moderator';\r\n }\r\n\r\n /**\r\n * @returns {Boolean} Whether this participant is a hidden participant. Some\r\n * special system participants may want to join hidden (like for example the\r\n * recorder).\r\n */\r\n isHidden() {\r\n return this._hidden;\r\n }\r\n\r\n /**\r\n * @returns {Boolean} Whether this participant is a hidden participant. Some\r\n * special system participants may want to join hidden (like for example the\r\n * recorder).\r\n */\r\n isHiddenFromRecorder() {\r\n return Boolean(this._identity?.user?.['hidden-from-recorder']);\r\n }\r\n\r\n /**\r\n * @returns {Boolean} Whether this participant replaces another participant\r\n * from the meeting.\r\n */\r\n isReplacing() {\r\n return this._isReplacing;\r\n }\r\n\r\n /**\r\n * @returns {Boolean} Wheter this participants will be replaced by another\r\n * participant in the meeting.\r\n */\r\n isReplaced() {\r\n return this._isReplaced;\r\n }\r\n\r\n /**\r\n * @returns {Boolean} Whether this participant has muted their audio.\r\n */\r\n isAudioMuted() {\r\n return this._isMediaTypeMuted(MediaType.AUDIO);\r\n }\r\n\r\n /**\r\n * Determines whether all JitsiTracks which are of a specific MediaType and\r\n * which belong to this JitsiParticipant are muted.\r\n *\r\n * @param {MediaType} mediaType - The MediaType of the JitsiTracks to be\r\n * checked.\r\n * @private\r\n * @returns {Boolean} True if all JitsiTracks which are of the specified\r\n * mediaType and which belong to this JitsiParticipant are muted; otherwise,\r\n * false.\r\n */\r\n _isMediaTypeMuted(mediaType) {\r\n return this.getTracks().reduce(\r\n (muted, track) =>\r\n muted && (track.getType() !== mediaType || track.isMuted()),\r\n true);\r\n }\r\n\r\n /**\r\n * @returns {Boolean} Whether this participant has muted their video.\r\n */\r\n isVideoMuted() {\r\n return this._isMediaTypeMuted(MediaType.VIDEO);\r\n }\r\n\r\n /**\r\n * @returns {String} The role of this participant.\r\n */\r\n getRole() {\r\n return this._role;\r\n }\r\n\r\n /**\r\n * Sets a new participant role.\r\n * @param {String} newRole - the new role.\r\n */\r\n setRole(newRole) {\r\n this._role = newRole;\r\n }\r\n\r\n /**\r\n * Sets whether participant is replacing another based on jwt.\r\n * @param {String} newIsReplacing - whether is replacing.\r\n */\r\n setIsReplacing(newIsReplacing) {\r\n this._isReplacing = newIsReplacing;\r\n }\r\n\r\n /**\r\n * Sets whether participant is being replaced by another based on jwt.\r\n * @param {boolean} newIsReplaced - whether is being replaced.\r\n */\r\n setIsReplaced(newIsReplaced) {\r\n this._isReplaced = newIsReplaced;\r\n }\r\n\r\n /**\r\n *\r\n */\r\n supportsDTMF() {\r\n return this._supportsDTMF;\r\n }\r\n\r\n /**\r\n * Returns a set with the features for the participant.\r\n * @returns {Promise, Error>}\r\n */\r\n getFeatures() {\r\n return Promise.resolve(this._features);\r\n }\r\n\r\n /**\r\n * Checks current set features.\r\n * @param {String} feature - the feature to check.\r\n * @return {boolean} true if this participant contains the\r\n * feature.\r\n */\r\n hasFeature(feature) {\r\n return this._features.has(feature);\r\n }\r\n\r\n /**\r\n * Set new features.\r\n * @param {Set|undefined} newFeatures - Sets new features.\r\n */\r\n setFeatures(newFeatures) {\r\n this._features = newFeatures || new Set();\r\n }\r\n\r\n /**\r\n * Returns the bot type for the participant.\r\n *\r\n * @returns {string|undefined} - The bot type of the participant.\r\n */\r\n getBotType() {\r\n return this._botType;\r\n }\r\n\r\n /**\r\n * Sets the bot type for the participant.\r\n * @param {String} newBotType - The new bot type to set.\r\n */\r\n setBotType(newBotType) {\r\n this._botType = newBotType;\r\n }\r\n\r\n /**\r\n * Returns the connection jid for the participant.\r\n *\r\n * @returns {string|undefined} - The connection jid of the participant.\r\n */\r\n getConnectionJid() {\r\n return this._connectionJid;\r\n }\r\n\r\n /**\r\n * Sets the connection jid for the participant.\r\n * @param {String} newJid - The connection jid to set.\r\n */\r\n setConnectionJid(newJid) {\r\n this._connectionJid = newJid;\r\n }\r\n}\r\n","/**\r\n * The events for the connection.\r\n */\r\n\r\nexport enum JitsiConnectionEvents {\r\n /**\r\n * Indicates that the connection has been disconnected. The event provides\r\n * the following parameters to its listeners:\r\n *\r\n * @param msg {string} a message associated with the disconnect such as the\r\n * last (known) error message\r\n */\r\n CONNECTION_DISCONNECTED = 'connection.connectionDisconnected',\r\n\r\n /**\r\n * Indicates that the connection has been established. The event provides\r\n * the following parameters to its listeners:\r\n *\r\n * @param id {string} the ID of the local endpoint/participant/peer (within\r\n * the context of the established connection)\r\n */\r\n CONNECTION_ESTABLISHED = 'connection.connectionEstablished',\r\n\r\n /**\r\n * Indicates that the connection has been failed for some reason. The event\r\n * provides the following parameters to its listeners:\r\n *\r\n * @param errType {JitsiConnectionErrors} the type of error associated with\r\n * the failure\r\n * @param errReason {string} the error (message) associated with the failure\r\n * @param credentials {object} the credentials used to connect (if any)\r\n * @param errReasonDetails {object} an optional object with details about\r\n * the error, like shard moving, suspending. Used for analytics purposes.\r\n */\r\n CONNECTION_FAILED = 'connection.connectionFailed',\r\n\r\n /**\r\n * Indicates that the performed action cannot be executed because the\r\n * connection is not in the correct state(connected, disconnected, etc.)\r\n */\r\n WRONG_STATE = 'connection.wrongState',\r\n\r\n /**\r\n * Indicates that the display name is required over this connection and need to be supplied when\r\n * joining the room.\r\n * There are cases like lobby room where display name is required.\r\n */\r\n DISPLAY_NAME_REQUIRED = 'connection.display_name_required'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const CONNECTION_DISCONNECTED = JitsiConnectionEvents.CONNECTION_DISCONNECTED;\r\nexport const CONNECTION_ESTABLISHED = JitsiConnectionEvents.CONNECTION_ESTABLISHED;\r\nexport const CONNECTION_FAILED = JitsiConnectionEvents.CONNECTION_FAILED;\r\nexport const WRONG_STATE = JitsiConnectionEvents.WRONG_STATE;\r\nexport const DISPLAY_NAME_REQUIRED = JitsiConnectionEvents.DISPLAY_NAME_REQUIRED;\r\n","/**\r\n * The errors for the connection.\r\n */\r\n\r\nexport enum JitsiConnectionErrors {\r\n /**\r\n * Indicates that the connection was dropped with an error which was most likely\r\n * caused by some networking issues. The dropped term in this context means that\r\n * the connection was closed unexpectedly (not on user's request).\r\n *\r\n * One example is 'item-not-found' error thrown by Prosody when the BOSH session\r\n * times out after 60 seconds of inactivity. On the other hand 'item-not-found'\r\n * could also happen when BOSH request is sent to the server with the session-id\r\n * that is not know to the server. But this should not happen in lib-jitsi-meet\r\n * case as long as the service is configured correctly (there is no bug).\r\n */\r\n CONNECTION_DROPPED_ERROR = 'connection.droppedError',\r\n\r\n /**\r\n * Not specified errors.\r\n */\r\n OTHER_ERROR = 'connection.otherError',\r\n\r\n /**\r\n * Indicates that a password is required in order to join the conference.\r\n */\r\n PASSWORD_REQUIRED = 'connection.passwordRequired',\r\n\r\n /**\r\n * Indicates that the connection was dropped, because of too many 5xx HTTP\r\n * errors on BOSH requests.\r\n */\r\n SERVER_ERROR = 'connection.serverError'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const CONNECTION_DROPPED_ERROR = JitsiConnectionErrors.CONNECTION_DROPPED_ERROR;\r\nexport const OTHER_ERROR = JitsiConnectionErrors.OTHER_ERROR;\r\nexport const PASSWORD_REQUIRED = JitsiConnectionErrors.PASSWORD_REQUIRED;\r\nexport const SERVER_ERROR = JitsiConnectionErrors.SERVER_ERROR;\r\n","\r\n/**\r\n * Promise-like object which can be passed around for resolving it later. It\r\n * implements the \"thenable\" interface, so it can be used wherever a Promise\r\n * could be used.\r\n *\r\n * In addition a \"reject on timeout\" functionality is provided.\r\n */\r\nexport default class Deferred {\r\n /**\r\n * Instantiates a Deferred object.\r\n */\r\n constructor() {\r\n this.promise = new Promise((resolve, reject) => {\r\n this.resolve = (...args) => {\r\n this.clearRejectTimeout();\r\n resolve(...args);\r\n };\r\n this.reject = (...args) => {\r\n this.clearRejectTimeout();\r\n reject(...args);\r\n };\r\n });\r\n this.then = this.promise.then.bind(this.promise);\r\n this.catch = this.promise.catch.bind(this.promise);\r\n }\r\n\r\n /**\r\n * Clears the reject timeout.\r\n */\r\n clearRejectTimeout() {\r\n clearTimeout(this._timeout);\r\n }\r\n\r\n /**\r\n * Rejects the promise after the given timeout.\r\n */\r\n setRejectTimeout(ms) {\r\n this._timeout = setTimeout(() => {\r\n this.reject(new Error('timeout'));\r\n }, ms);\r\n }\r\n}\r\n","import EventEmitter from 'events';\r\n\r\n/**\r\n * The class implements basic event operations - add/remove listener.\r\n * NOTE: The purpose of the class is to be extended in order to add\r\n * this functionality to other classes.\r\n */\r\nexport default class Listenable {\r\n /**\r\n * Creates new instance.\r\n * @param {EventEmitter} eventEmitter\r\n * @constructor\r\n */\r\n constructor(eventEmitter = new EventEmitter()) {\r\n this.eventEmitter = eventEmitter;\r\n\r\n // aliases for addListener/removeListener\r\n this.addEventListener = this.on = this.addListener;\r\n this.removeEventListener = this.off = this.removeListener;\r\n }\r\n\r\n /**\r\n * Adds new listener.\r\n * @param {String} eventName the name of the event\r\n * @param {Function} listener the listener.\r\n * @returns {Function} - The unsubscribe function.\r\n */\r\n addListener(eventName, listener) {\r\n this.eventEmitter.addListener(eventName, listener);\r\n\r\n return () => this.removeEventListener(eventName, listener);\r\n }\r\n\r\n /**\r\n * Removes listener.\r\n * @param {String} eventName the name of the event that triggers the\r\n * listener\r\n * @param {Function} listener the listener.\r\n */\r\n removeListener(eventName, listener) {\r\n this.eventEmitter.removeListener(eventName, listener);\r\n }\r\n}\r\n","/* global RTCRtpScriptTransform */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n// Flag to set on senders / receivers to avoid setting up the encryption transform\r\n// more than once.\r\nconst kJitsiE2EE = Symbol('kJitsiE2EE');\r\n\r\n/**\r\n * Context encapsulating the cryptography bits required for E2EE.\r\n * This uses the WebRTC Insertable Streams API which is explained in\r\n * https://github.com/alvestrand/webrtc-media-streams/blob/master/explainer.md\r\n * that provides access to the encoded frames and allows them to be transformed.\r\n *\r\n * The encoded frame format is explained below in the _encodeFunction method.\r\n * High level design goals were:\r\n * - do not require changes to existing SFUs and retain (VP8) metadata.\r\n * - allow the SFU to rewrite SSRCs, timestamp, pictureId.\r\n * - allow for the key to be rotated frequently.\r\n */\r\nexport default class E2EEcontext {\r\n /**\r\n * Build a new E2EE context instance, which will be used in a given conference.\r\n * @param {boolean} [options.sharedKey] - whether there is a uniques key shared amoung all participants.\r\n */\r\n constructor({ sharedKey } = {}) {\r\n // Determine the URL for the worker script. Relative URLs are relative to\r\n // the entry point, not the script that launches the worker.\r\n let baseUrl = '';\r\n const ljm = document.querySelector('script[src*=\"lib-jitsi-meet\"]');\r\n\r\n if (ljm) {\r\n const idx = ljm.src.lastIndexOf('/');\r\n\r\n baseUrl = `${ljm.src.substring(0, idx)}/`;\r\n }\r\n\r\n let workerUrl = `${baseUrl}lib-jitsi-meet.e2ee-worker.js`;\r\n\r\n // If there is no baseUrl then we create the worker in a normal way\r\n // as you cant load scripts inside blobs from relative paths.\r\n // See: https://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers-loadingscripts\r\n if (baseUrl && baseUrl !== '/') {\r\n // Initialize the E2EE worker. In order to avoid CORS issues, start the worker and have it\r\n // synchronously load the JS.\r\n const workerBlob\r\n = new Blob([ `importScripts(\"${workerUrl}\");` ], { type: 'application/javascript' });\r\n\r\n workerUrl = window.URL.createObjectURL(workerBlob);\r\n }\r\n\r\n this._worker = new Worker(workerUrl, { name: 'E2EE Worker' });\r\n\r\n this._worker.onerror = e => logger.error(e);\r\n\r\n this._worker.postMessage({\r\n operation: 'initialize',\r\n sharedKey\r\n });\r\n }\r\n\r\n /**\r\n * Cleans up all state associated with the given participant. This is needed when a\r\n * participant leaves the current conference.\r\n *\r\n * @param {string} participantId - The participant that just left.\r\n */\r\n cleanup(participantId) {\r\n this._worker.postMessage({\r\n operation: 'cleanup',\r\n participantId\r\n });\r\n }\r\n\r\n /**\r\n * Cleans up all state associated with all participants in the conference. This is needed when disabling e2ee.\r\n *\r\n */\r\n cleanupAll() {\r\n this._worker.postMessage({\r\n operation: 'cleanupAll'\r\n });\r\n }\r\n\r\n /**\r\n * Handles the given {@code RTCRtpReceiver} by creating a {@code TransformStream} which will inject\r\n * a frame decoder.\r\n *\r\n * @param {RTCRtpReceiver} receiver - The receiver which will get the decoding function injected.\r\n * @param {string} kind - The kind of track this receiver belongs to.\r\n * @param {string} participantId - The participant id that this receiver belongs to.\r\n */\r\n handleReceiver(receiver, kind, participantId) {\r\n if (receiver[kJitsiE2EE]) {\r\n return;\r\n }\r\n receiver[kJitsiE2EE] = true;\r\n\r\n if (window.RTCRtpScriptTransform) {\r\n const options = {\r\n operation: 'decode',\r\n participantId\r\n };\r\n\r\n receiver.transform = new RTCRtpScriptTransform(this._worker, options);\r\n } else {\r\n const receiverStreams = receiver.createEncodedStreams();\r\n\r\n this._worker.postMessage({\r\n operation: 'decode',\r\n readableStream: receiverStreams.readable,\r\n writableStream: receiverStreams.writable,\r\n participantId\r\n }, [ receiverStreams.readable, receiverStreams.writable ]);\r\n }\r\n }\r\n\r\n /**\r\n * Handles the given {@code RTCRtpSender} by creating a {@code TransformStream} which will inject\r\n * a frame encoder.\r\n *\r\n * @param {RTCRtpSender} sender - The sender which will get the encoding function injected.\r\n * @param {string} kind - The kind of track this sender belongs to.\r\n * @param {string} participantId - The participant id that this sender belongs to.\r\n */\r\n handleSender(sender, kind, participantId) {\r\n if (sender[kJitsiE2EE]) {\r\n return;\r\n }\r\n sender[kJitsiE2EE] = true;\r\n\r\n if (window.RTCRtpScriptTransform) {\r\n const options = {\r\n operation: 'encode',\r\n participantId\r\n };\r\n\r\n sender.transform = new RTCRtpScriptTransform(this._worker, options);\r\n } else {\r\n const senderStreams = sender.createEncodedStreams();\r\n\r\n this._worker.postMessage({\r\n operation: 'encode',\r\n readableStream: senderStreams.readable,\r\n writableStream: senderStreams.writable,\r\n participantId\r\n }, [ senderStreams.readable, senderStreams.writable ]);\r\n }\r\n }\r\n\r\n /**\r\n * Set the E2EE key for the specified participant.\r\n *\r\n * @param {string} participantId - the ID of the participant who's key we are setting.\r\n * @param {Uint8Array | boolean} key - they key for the given participant.\r\n * @param {Number} keyIndex - the key index.\r\n */\r\n setKey(participantId, key, keyIndex) {\r\n this._worker.postMessage({\r\n operation: 'setKey',\r\n key,\r\n keyIndex,\r\n participantId\r\n });\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport browser from '../browser';\r\nimport Deferred from '../util/Deferred';\r\nimport Listenable from '../util/Listenable';\r\n\r\nimport E2EEContext from './E2EEContext';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Abstract class that integrates {@link E2EEContext} with a key management system.\r\n */\r\nexport class KeyHandler extends Listenable {\r\n /**\r\n * Build a new KeyHandler instance, which will be used in a given conference.\r\n * @param {JitsiConference} conference - the current conference.\r\n * @param {object} options - the options passed to {E2EEContext}, see implemention.\r\n */\r\n constructor(conference, options = {}) {\r\n super();\r\n\r\n this.conference = conference;\r\n this.e2eeCtx = new E2EEContext(options);\r\n\r\n this.enabled = false;\r\n this._enabling = undefined;\r\n\r\n // Conference media events in order to attach the encryptor / decryptor.\r\n // FIXME add events to TraceablePeerConnection which will allow to see when there's new receiver or sender\r\n // added instead of shenanigans around conference track events and track muted.\r\n //\r\n\r\n this.conference.on(\r\n JitsiConferenceEvents._MEDIA_SESSION_STARTED,\r\n this._onMediaSessionStarted.bind(this));\r\n this.conference.on(\r\n JitsiConferenceEvents.TRACK_ADDED,\r\n track => track.isLocal() && this._onLocalTrackAdded(track));\r\n this.conference.rtc.on(\r\n RTCEvents.REMOTE_TRACK_ADDED,\r\n (track, tpc) => this._setupReceiverE2EEForTrack(tpc, track));\r\n this.conference.on(\r\n JitsiConferenceEvents.TRACK_MUTE_CHANGED,\r\n this._trackMuteChanged.bind(this));\r\n }\r\n\r\n /**\r\n * Indicates whether E2EE is currently enabled or not.\r\n *\r\n * @returns {boolean}\r\n */\r\n isEnabled() {\r\n return this.enabled;\r\n }\r\n\r\n /**\r\n * Enables / disables End-To-End encryption.\r\n *\r\n * @param {boolean} enabled - whether E2EE should be enabled or not.\r\n * @returns {void}\r\n */\r\n async setEnabled(enabled) {\r\n if (enabled === this.enabled) {\r\n return;\r\n }\r\n\r\n this._enabling && await this._enabling;\r\n\r\n this._enabling = new Deferred();\r\n\r\n this.enabled = enabled;\r\n\r\n if (!enabled) {\r\n this.e2eeCtx.cleanupAll();\r\n }\r\n\r\n this._setEnabled && await this._setEnabled(enabled);\r\n\r\n this.conference.setLocalParticipantProperty('e2ee.enabled', enabled);\r\n\r\n this.conference._restartMediaSessions();\r\n\r\n this._enabling.resolve();\r\n }\r\n\r\n /**\r\n * Sets the key for End-to-End encryption.\r\n *\r\n * @returns {void}\r\n */\r\n setEncryptionKey() {\r\n throw new Error('Not implemented by subclass');\r\n }\r\n\r\n /**\r\n * Setup E2EE on the new track that has been added to the conference, apply it on all the open peerconnections.\r\n * @param {JitsiLocalTrack} track - the new track that's being added to the conference.\r\n * @private\r\n */\r\n _onLocalTrackAdded(track) {\r\n for (const session of this.conference.getMediaSessions()) {\r\n this._setupSenderE2EEForTrack(session, track);\r\n }\r\n }\r\n\r\n /**\r\n * Setups E2E encryption for the new session.\r\n * @param {JingleSessionPC} session - the new media session.\r\n * @private\r\n */\r\n _onMediaSessionStarted(session) {\r\n const localTracks = this.conference.getLocalTracks();\r\n\r\n for (const track of localTracks) {\r\n this._setupSenderE2EEForTrack(session, track);\r\n }\r\n }\r\n\r\n /**\r\n * Setup E2EE for the receiving side.\r\n *\r\n * @private\r\n */\r\n _setupReceiverE2EEForTrack(tpc, track) {\r\n if (!this.enabled) {\r\n return;\r\n }\r\n\r\n const receiver = tpc.findReceiverForTrack(track.track);\r\n\r\n if (receiver) {\r\n this.e2eeCtx.handleReceiver(receiver, track.getType(), track.getParticipantId());\r\n } else {\r\n logger.warn(`Could not handle E2EE for ${track}: receiver not found in: ${tpc}`);\r\n }\r\n }\r\n\r\n /**\r\n * Setup E2EE for the sending side.\r\n *\r\n * @param {JingleSessionPC} session - the session which sends the media produced by the track.\r\n * @param {JitsiLocalTrack} track - the local track for which e2e encoder will be configured.\r\n * @private\r\n */\r\n _setupSenderE2EEForTrack(session, track) {\r\n if (!this.enabled) {\r\n return;\r\n }\r\n\r\n const pc = session.peerconnection;\r\n const sender = pc && pc.findSenderForTrack(track.track);\r\n\r\n if (sender) {\r\n this.e2eeCtx.handleSender(sender, track.getType(), track.getParticipantId());\r\n } else {\r\n logger.warn(`Could not handle E2EE for ${track}: sender not found in ${pc}`);\r\n }\r\n }\r\n\r\n /**\r\n * Setup E2EE on the sender that is created for the unmuted local video track.\r\n * @param {JitsiLocalTrack} track - the track for which muted status has changed.\r\n * @private\r\n */\r\n _trackMuteChanged(track) {\r\n if (browser.doesVideoMuteByStreamRemove() && track.isLocal() && track.isVideoTrack() && !track.isMuted()) {\r\n for (const session of this.conference.getMediaSessions()) {\r\n this._setupSenderE2EEForTrack(session, track);\r\n }\r\n }\r\n }\r\n}\r\n","import { KeyHandler } from './KeyHandler';\r\n\r\n/**\r\n * This module integrates {@link E2EEContext} with {external} in order to set the keys for encryption.\r\n */\r\nexport class ExternallyManagedKeyHandler extends KeyHandler {\r\n /**\r\n * Build a new ExternallyManagedKeyHandler instance, which will be used in a given conference.\r\n * @param conference - the current conference.\r\n */\r\n constructor(conference) {\r\n super(conference, { sharedKey: true });\r\n }\r\n\r\n /**\r\n * Sets the key and index for End-to-End encryption.\r\n *\r\n * @param {CryptoKey} [keyInfo.encryptionKey] - encryption key.\r\n * @param {Number} [keyInfo.index] - the index of the encryption key.\r\n * @returns {void}\r\n */\r\n setKey(keyInfo) {\r\n this.e2eeCtx.setKey(undefined, { encryptionKey: keyInfo.encryptionKey }, keyInfo.index);\r\n }\r\n}\r\n","import uuid from './dist/index.js';\nexport const v1 = uuid.v1;\nexport const v3 = uuid.v3;\nexport const v4 = uuid.v4;\nexport const v5 = uuid.v5;\n","/* global Olm */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport base64js from 'base64-js';\r\nimport isEqual from 'lodash.isequal';\r\nimport { v4 as uuidv4 } from 'uuid';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport Deferred from '../util/Deferred';\r\nimport Listenable from '../util/Listenable';\r\nimport { FEATURE_E2EE, JITSI_MEET_MUC_TYPE } from '../xmpp/xmpp';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\nconst REQ_TIMEOUT = 5 * 1000;\r\nconst OLM_MESSAGE_TYPE = 'olm';\r\nconst OLM_MESSAGE_TYPES = {\r\n ERROR: 'error',\r\n KEY_INFO: 'key-info',\r\n KEY_INFO_ACK: 'key-info-ack',\r\n SESSION_ACK: 'session-ack',\r\n SESSION_INIT: 'session-init'\r\n};\r\n\r\nconst kOlmData = Symbol('OlmData');\r\n\r\nconst OlmAdapterEvents = {\r\n OLM_ID_KEY_READY: 'olm.id_key_ready',\r\n PARTICIPANT_E2EE_CHANNEL_READY: 'olm.participant_e2ee_channel_ready',\r\n PARTICIPANT_KEY_UPDATED: 'olm.partitipant_key_updated'\r\n};\r\n\r\n/**\r\n * This class implements an End-to-End Encrypted communication channel between every two peers\r\n * in the conference. This channel uses libolm to achieve E2EE.\r\n *\r\n * The created channel is then used to exchange the secret key that each participant will use\r\n * to encrypt the actual media (see {@link E2EEContext}).\r\n *\r\n * A simple JSON message based protocol is implemented, which follows a request - response model:\r\n * - session-init: Initiates an olm session establishment procedure. This message will be sent\r\n * by the participant who just joined, to everyone else.\r\n * - session-ack: Completes the olm session etablishment. This messsage may contain ancilliary\r\n * encrypted data, more specifically the sender's current key.\r\n * - key-info: Includes the sender's most up to date key information.\r\n * - key-info-ack: Acknowledges the reception of a key-info request. In addition, it may contain\r\n * the sender's key information, if available.\r\n * - error: Indicates a request processing error has occurred.\r\n *\r\n * These requessts and responses are transport independent. Currently they are sent using XMPP\r\n * MUC private messages.\r\n */\r\nexport class OlmAdapter extends Listenable {\r\n /**\r\n * Creates an adapter instance for the given conference.\r\n */\r\n constructor(conference) {\r\n super();\r\n\r\n this._conf = conference;\r\n this._init = new Deferred();\r\n this._key = undefined;\r\n this._keyIndex = -1;\r\n this._reqs = new Map();\r\n this._sessionInitialization = undefined;\r\n\r\n if (OlmAdapter.isSupported()) {\r\n this._bootstrapOlm();\r\n\r\n this._conf.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, this._onEndpointMessageReceived.bind(this));\r\n this._conf.on(JitsiConferenceEvents.CONFERENCE_LEFT, this._onConferenceLeft.bind(this));\r\n this._conf.on(JitsiConferenceEvents.USER_LEFT, this._onParticipantLeft.bind(this));\r\n this._conf.on(JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,\r\n this._onParticipantPropertyChanged.bind(this));\r\n } else {\r\n this._init.reject(new Error('Olm not supported'));\r\n }\r\n }\r\n\r\n /**\r\n * Starts new olm sessions with every other participant that has the participantId \"smaller\" the localParticipantId.\r\n */\r\n async initSessions() {\r\n if (this._sessionInitialization) {\r\n throw new Error('OlmAdapter initSessions called multiple times');\r\n } else {\r\n this._sessionInitialization = new Deferred();\r\n\r\n await this._init;\r\n\r\n const promises = [];\r\n const localParticipantId = this._conf.myUserId();\r\n\r\n for (const participant of this._conf.getParticipants()) {\r\n if (participant.hasFeature(FEATURE_E2EE) && localParticipantId < participant.getId()) {\r\n promises.push(this._sendSessionInit(participant));\r\n }\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n\r\n // TODO: retry failed ones.\r\n\r\n this._sessionInitialization.resolve();\r\n this._sessionInitialization = undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Indicates if olm is supported on the current platform.\r\n *\r\n * @returns {boolean}\r\n */\r\n static isSupported() {\r\n return typeof window.Olm !== 'undefined';\r\n }\r\n\r\n /**\r\n * Updates the current participant key and distributes it to all participants in the conference\r\n * by sending a key-info message.\r\n *\r\n * @param {Uint8Array|boolean} key - The new key.\r\n * @retrns {Promise}\r\n */\r\n async updateKey(key) {\r\n // Store it locally for new sessions.\r\n this._key = key;\r\n this._keyIndex++;\r\n\r\n // Broadcast it.\r\n const promises = [];\r\n\r\n for (const participant of this._conf.getParticipants()) {\r\n const pId = participant.getId();\r\n const olmData = this._getParticipantOlmData(participant);\r\n\r\n // TODO: skip those who don't support E2EE.\r\n if (!olmData.session) {\r\n logger.warn(`Tried to send key to participant ${pId} but we have no session`);\r\n\r\n // eslint-disable-next-line no-continue\r\n continue;\r\n }\r\n\r\n const uuid = uuidv4();\r\n const data = {\r\n [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,\r\n olm: {\r\n type: OLM_MESSAGE_TYPES.KEY_INFO,\r\n data: {\r\n ciphertext: this._encryptKeyInfo(olmData.session),\r\n uuid\r\n }\r\n }\r\n };\r\n const d = new Deferred();\r\n\r\n d.setRejectTimeout(REQ_TIMEOUT);\r\n d.catch(() => {\r\n this._reqs.delete(uuid);\r\n });\r\n this._reqs.set(uuid, d);\r\n promises.push(d);\r\n\r\n this._sendMessage(data, pId);\r\n }\r\n\r\n await Promise.allSettled(promises);\r\n\r\n // TODO: retry failed ones?\r\n\r\n return this._keyIndex;\r\n }\r\n\r\n /**\r\n * Updates the current participant key.\r\n * @param {Uint8Array|boolean} key - The new key.\r\n * @returns {number}\r\n */\r\n updateCurrentKey(key) {\r\n this._key = key;\r\n\r\n return this._keyIndex;\r\n }\r\n\r\n /**\r\n * Frees the olmData session for the given participant.\r\n *\r\n */\r\n clearParticipantSession(participant) {\r\n const olmData = this._getParticipantOlmData(participant);\r\n\r\n if (olmData.session) {\r\n olmData.session.free();\r\n olmData.session = undefined;\r\n }\r\n }\r\n\r\n\r\n /**\r\n * Frees the olmData sessions for all participants.\r\n *\r\n */\r\n clearAllParticipantsSessions() {\r\n for (const participant of this._conf.getParticipants()) {\r\n this.clearParticipantSession(participant);\r\n }\r\n }\r\n\r\n /**\r\n * Internal helper to bootstrap the olm library.\r\n *\r\n * @returns {Promise}\r\n * @private\r\n */\r\n async _bootstrapOlm() {\r\n logger.debug('Initializing Olm...');\r\n\r\n try {\r\n await Olm.init();\r\n\r\n this._olmAccount = new Olm.Account();\r\n this._olmAccount.create();\r\n\r\n const idKeys = JSON.parse(this._olmAccount.identity_keys());\r\n\r\n this._idKey = idKeys.curve25519;\r\n\r\n logger.debug(`Olm ${Olm.get_library_version().join('.')} initialized`);\r\n this._init.resolve();\r\n this._onIdKeyReady(this._idKey);\r\n } catch (e) {\r\n logger.error('Failed to initialize Olm', e);\r\n this._init.reject(e);\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Publishes our own Olmn id key in presence.\r\n * @private\r\n */\r\n _onIdKeyReady(idKey) {\r\n logger.debug(`Olm id key ready: ${idKey}`);\r\n\r\n // Publish it in presence.\r\n this._conf.setLocalParticipantProperty('e2ee.idKey', idKey);\r\n }\r\n\r\n /**\r\n * Event posted when the E2EE signalling channel has been established with the given participant.\r\n * @private\r\n */\r\n _onParticipantE2EEChannelReady(id) {\r\n logger.debug(`E2EE channel with participant ${id} is ready`);\r\n }\r\n\r\n /**\r\n * Internal helper for encrypting the current key information for a given participant.\r\n *\r\n * @param {Olm.Session} session - Participant's session.\r\n * @returns {string} - The encrypted text with the key information.\r\n * @private\r\n */\r\n _encryptKeyInfo(session) {\r\n const keyInfo = {};\r\n\r\n if (this._key !== undefined) {\r\n keyInfo.key = this._key ? base64js.fromByteArray(this._key) : false;\r\n keyInfo.keyIndex = this._keyIndex;\r\n }\r\n\r\n return session.encrypt(JSON.stringify(keyInfo));\r\n }\r\n\r\n /**\r\n * Internal helper for getting the olm related data associated with a participant.\r\n *\r\n * @param {JitsiParticipant} participant - Participant whose data wants to be extracted.\r\n * @returns {Object}\r\n * @private\r\n */\r\n _getParticipantOlmData(participant) {\r\n participant[kOlmData] = participant[kOlmData] || {};\r\n\r\n return participant[kOlmData];\r\n }\r\n\r\n /**\r\n * Handles leaving the conference, cleaning up olm sessions.\r\n *\r\n * @private\r\n */\r\n async _onConferenceLeft() {\r\n logger.debug('Conference left');\r\n\r\n await this._init;\r\n\r\n for (const participant of this._conf.getParticipants()) {\r\n this._onParticipantLeft(participant.getId(), participant);\r\n }\r\n\r\n if (this._olmAccount) {\r\n this._olmAccount.free();\r\n this._olmAccount = undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Main message handler. Handles 1-to-1 messages received from other participants\r\n * and send the appropriate replies.\r\n *\r\n * @private\r\n */\r\n async _onEndpointMessageReceived(participant, payload) {\r\n if (payload[JITSI_MEET_MUC_TYPE] !== OLM_MESSAGE_TYPE) {\r\n return;\r\n }\r\n\r\n if (!payload.olm) {\r\n logger.warn('Incorrectly formatted message');\r\n\r\n return;\r\n }\r\n\r\n await this._init;\r\n\r\n const msg = payload.olm;\r\n const pId = participant.getId();\r\n const olmData = this._getParticipantOlmData(participant);\r\n\r\n switch (msg.type) {\r\n case OLM_MESSAGE_TYPES.SESSION_INIT: {\r\n if (olmData.session) {\r\n logger.warn(`Participant ${pId} already has a session`);\r\n\r\n this._sendError(participant, 'Session already established');\r\n } else {\r\n // Create a session for communicating with this participant.\r\n\r\n const session = new Olm.Session();\r\n\r\n session.create_outbound(this._olmAccount, msg.data.idKey, msg.data.otKey);\r\n olmData.session = session;\r\n\r\n // Send ACK\r\n const ack = {\r\n [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,\r\n olm: {\r\n type: OLM_MESSAGE_TYPES.SESSION_ACK,\r\n data: {\r\n ciphertext: this._encryptKeyInfo(session),\r\n uuid: msg.data.uuid\r\n }\r\n }\r\n };\r\n\r\n this._sendMessage(ack, pId);\r\n this._onParticipantE2EEChannelReady(pId);\r\n }\r\n break;\r\n }\r\n case OLM_MESSAGE_TYPES.SESSION_ACK: {\r\n if (olmData.session) {\r\n logger.warn(`Participant ${pId} already has a session`);\r\n\r\n this._sendError(participant, 'No session found');\r\n } else if (msg.data.uuid === olmData.pendingSessionUuid) {\r\n const { ciphertext } = msg.data;\r\n const d = this._reqs.get(msg.data.uuid);\r\n const session = new Olm.Session();\r\n\r\n session.create_inbound(this._olmAccount, ciphertext.body);\r\n\r\n // Remove OT keys that have been used to setup this session.\r\n this._olmAccount.remove_one_time_keys(session);\r\n\r\n // Decrypt first message.\r\n const data = session.decrypt(ciphertext.type, ciphertext.body);\r\n\r\n olmData.session = session;\r\n olmData.pendingSessionUuid = undefined;\r\n\r\n this._onParticipantE2EEChannelReady(pId);\r\n\r\n this._reqs.delete(msg.data.uuid);\r\n d.resolve();\r\n\r\n const json = safeJsonParse(data);\r\n\r\n if (json.key) {\r\n const key = base64js.toByteArray(json.key);\r\n const keyIndex = json.keyIndex;\r\n\r\n olmData.lastKey = key;\r\n this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_KEY_UPDATED, pId, key, keyIndex);\r\n }\r\n } else {\r\n logger.warn('Received ACK with the wrong UUID');\r\n\r\n this._sendError(participant, 'Invalid UUID');\r\n }\r\n break;\r\n }\r\n case OLM_MESSAGE_TYPES.ERROR: {\r\n logger.error(msg.data.error);\r\n\r\n break;\r\n }\r\n case OLM_MESSAGE_TYPES.KEY_INFO: {\r\n if (olmData.session) {\r\n const { ciphertext } = msg.data;\r\n const data = olmData.session.decrypt(ciphertext.type, ciphertext.body);\r\n const json = safeJsonParse(data);\r\n\r\n if (json.key !== undefined && json.keyIndex !== undefined) {\r\n const key = json.key ? base64js.toByteArray(json.key) : false;\r\n const keyIndex = json.keyIndex;\r\n\r\n if (!isEqual(olmData.lastKey, key)) {\r\n olmData.lastKey = key;\r\n this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_KEY_UPDATED, pId, key, keyIndex);\r\n }\r\n\r\n // Send ACK.\r\n const ack = {\r\n [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,\r\n olm: {\r\n type: OLM_MESSAGE_TYPES.KEY_INFO_ACK,\r\n data: {\r\n ciphertext: this._encryptKeyInfo(olmData.session),\r\n uuid: msg.data.uuid\r\n }\r\n }\r\n };\r\n\r\n this._sendMessage(ack, pId);\r\n }\r\n } else {\r\n logger.debug(`Received key info message from ${pId} but we have no session for them!`);\r\n\r\n this._sendError(participant, 'No session found while processing key-info');\r\n }\r\n break;\r\n }\r\n case OLM_MESSAGE_TYPES.KEY_INFO_ACK: {\r\n if (olmData.session) {\r\n const { ciphertext } = msg.data;\r\n const data = olmData.session.decrypt(ciphertext.type, ciphertext.body);\r\n const json = safeJsonParse(data);\r\n\r\n if (json.key !== undefined && json.keyIndex !== undefined) {\r\n const key = json.key ? base64js.toByteArray(json.key) : false;\r\n const keyIndex = json.keyIndex;\r\n\r\n if (!isEqual(olmData.lastKey, key)) {\r\n olmData.lastKey = key;\r\n this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_KEY_UPDATED, pId, key, keyIndex);\r\n }\r\n }\r\n\r\n const d = this._reqs.get(msg.data.uuid);\r\n\r\n this._reqs.delete(msg.data.uuid);\r\n d.resolve();\r\n } else {\r\n logger.debug(`Received key info ack message from ${pId} but we have no session for them!`);\r\n\r\n this._sendError(participant, 'No session found while processing key-info-ack');\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Handles a participant leaving. When a participant leaves their olm session is destroyed.\r\n *\r\n * @private\r\n */\r\n _onParticipantLeft(id, participant) {\r\n logger.debug(`Participant ${id} left`);\r\n\r\n this.clearParticipantSession(participant);\r\n }\r\n\r\n /**\r\n * Handles an update in a participant's presence property.\r\n *\r\n * @param {JitsiParticipant} participant - The participant.\r\n * @param {string} name - The name of the property that changed.\r\n * @param {*} oldValue - The property's previous value.\r\n * @param {*} newValue - The property's new value.\r\n * @private\r\n */\r\n async _onParticipantPropertyChanged(participant, name, oldValue, newValue) {\r\n switch (name) {\r\n case 'e2ee.enabled':\r\n if (newValue && this._conf.isE2EEEnabled()) {\r\n const localParticipantId = this._conf.myUserId();\r\n const participantId = participant.getId();\r\n const participantFeatures = await participant.getFeatures();\r\n\r\n if (participantFeatures.has(FEATURE_E2EE) && localParticipantId < participantId) {\r\n if (this._sessionInitialization) {\r\n await this._sessionInitialization;\r\n }\r\n await this._sendSessionInit(participant);\r\n\r\n const olmData = this._getParticipantOlmData(participant);\r\n const uuid = uuidv4();\r\n const data = {\r\n [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,\r\n olm: {\r\n type: OLM_MESSAGE_TYPES.KEY_INFO,\r\n data: {\r\n ciphertext: this._encryptKeyInfo(olmData.session),\r\n uuid\r\n }\r\n }\r\n };\r\n\r\n this._sendMessage(data, participantId);\r\n }\r\n }\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Builds and sends an error message to the target participant.\r\n *\r\n * @param {JitsiParticipant} participant - The target participant.\r\n * @param {string} error - The error message.\r\n * @returns {void}\r\n */\r\n _sendError(participant, error) {\r\n const pId = participant.getId();\r\n const err = {\r\n [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,\r\n olm: {\r\n type: OLM_MESSAGE_TYPES.ERROR,\r\n data: {\r\n error\r\n }\r\n }\r\n };\r\n\r\n this._sendMessage(err, pId);\r\n }\r\n\r\n /**\r\n * Internal helper to send the given object to the given participant ID.\r\n * This function merely exists so the transport can be easily swapped.\r\n * Currently messages are transmitted via XMPP MUC private messages.\r\n *\r\n * @param {object} data - The data that will be sent to the target participant.\r\n * @param {string} participantId - ID of the target participant.\r\n */\r\n _sendMessage(data, participantId) {\r\n this._conf.sendMessage(data, participantId);\r\n }\r\n\r\n /**\r\n * Builds and sends the session-init request to the target participant.\r\n *\r\n * @param {JitsiParticipant} participant - Participant to whom we'll send the request.\r\n * @returns {Promise} - The promise will be resolved when the session-ack is received.\r\n * @private\r\n */\r\n _sendSessionInit(participant) {\r\n const pId = participant.getId();\r\n const olmData = this._getParticipantOlmData(participant);\r\n\r\n if (olmData.session) {\r\n logger.warn(`Tried to send session-init to ${pId} but we already have a session`);\r\n\r\n return Promise.reject();\r\n }\r\n\r\n if (olmData.pendingSessionUuid !== undefined) {\r\n logger.warn(`Tried to send session-init to ${pId} but we already have a pending session`);\r\n\r\n return Promise.reject();\r\n }\r\n\r\n // Generate a One Time Key.\r\n this._olmAccount.generate_one_time_keys(1);\r\n\r\n const otKeys = JSON.parse(this._olmAccount.one_time_keys());\r\n const otKey = Object.values(otKeys.curve25519)[0];\r\n\r\n if (!otKey) {\r\n return Promise.reject(new Error('No one-time-keys generated'));\r\n }\r\n\r\n // Mark the OT keys (one really) as published so they are not reused.\r\n this._olmAccount.mark_keys_as_published();\r\n\r\n const uuid = uuidv4();\r\n const init = {\r\n [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,\r\n olm: {\r\n type: OLM_MESSAGE_TYPES.SESSION_INIT,\r\n data: {\r\n idKey: this._idKey,\r\n otKey,\r\n uuid\r\n }\r\n }\r\n };\r\n\r\n const d = new Deferred();\r\n\r\n d.setRejectTimeout(REQ_TIMEOUT);\r\n d.catch(() => {\r\n this._reqs.delete(uuid);\r\n olmData.pendingSessionUuid = undefined;\r\n });\r\n this._reqs.set(uuid, d);\r\n\r\n this._sendMessage(init, pId);\r\n\r\n // Store the UUID for matching with the ACK.\r\n olmData.pendingSessionUuid = uuid;\r\n\r\n return d;\r\n }\r\n}\r\n\r\n/**\r\n * Helper to ensure JSON parsing always returns an object.\r\n *\r\n * @param {string} data - The data that needs to be parsed.\r\n * @returns {object} - Parsed data or empty object in case of failure.\r\n */\r\nfunction safeJsonParse(data) {\r\n try {\r\n return JSON.parse(data);\r\n } catch (e) {\r\n return {};\r\n }\r\n}\r\n\r\nOlmAdapter.events = OlmAdapterEvents;\r\n","import { getLogger } from '@jitsi/logger';\r\nimport debounce from 'lodash.debounce';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\n\r\nimport { KeyHandler } from './KeyHandler';\r\nimport { OlmAdapter } from './OlmAdapter';\r\nimport { importKey, ratchet } from './crypto-utils';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n// Period which we'll wait before updating / rotating our keys when a participant\r\n// joins or leaves.\r\nconst DEBOUNCE_PERIOD = 5000;\r\n\r\n/**\r\n * This module integrates {@link E2EEContext} with {@link OlmAdapter} in order to distribute the keys for encryption.\r\n */\r\nexport class ManagedKeyHandler extends KeyHandler {\r\n /**\r\n * Build a new AutomaticKeyHandler instance, which will be used in a given conference.\r\n */\r\n constructor(conference) {\r\n super(conference);\r\n\r\n this._key = undefined;\r\n this._conferenceJoined = false;\r\n\r\n this._olmAdapter = new OlmAdapter(conference);\r\n\r\n this._rotateKey = debounce(this._rotateKeyImpl, DEBOUNCE_PERIOD);\r\n this._ratchetKey = debounce(this._ratchetKeyImpl, DEBOUNCE_PERIOD);\r\n\r\n // Olm signalling events.\r\n this._olmAdapter.on(\r\n OlmAdapter.events.PARTICIPANT_KEY_UPDATED,\r\n this._onParticipantKeyUpdated.bind(this));\r\n\r\n this.conference.on(\r\n JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,\r\n this._onParticipantPropertyChanged.bind(this));\r\n this.conference.on(\r\n JitsiConferenceEvents.USER_JOINED,\r\n this._onParticipantJoined.bind(this));\r\n this.conference.on(\r\n JitsiConferenceEvents.USER_LEFT,\r\n this._onParticipantLeft.bind(this));\r\n this.conference.on(\r\n JitsiConferenceEvents.CONFERENCE_JOINED,\r\n () => {\r\n this._conferenceJoined = true;\r\n });\r\n }\r\n\r\n /**\r\n * When E2EE is enabled it initializes sessions and sets the key.\r\n * Cleans up the sessions when disabled.\r\n *\r\n * @param {boolean} enabled - whether E2EE should be enabled or not.\r\n * @returns {void}\r\n */\r\n async _setEnabled(enabled) {\r\n if (enabled) {\r\n await this._olmAdapter.initSessions();\r\n } else {\r\n this._olmAdapter.clearAllParticipantsSessions();\r\n }\r\n\r\n // Generate a random key in case we are enabling.\r\n this._key = enabled ? this._generateKey() : false;\r\n\r\n // Send it to others using the E2EE olm channel.\r\n const index = await this._olmAdapter.updateKey(this._key);\r\n\r\n // Set our key so we begin encrypting.\r\n this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);\r\n }\r\n\r\n /**\r\n * Handles an update in a participant's presence property.\r\n *\r\n * @param {JitsiParticipant} participant - The participant.\r\n * @param {string} name - The name of the property that changed.\r\n * @param {*} oldValue - The property's previous value.\r\n * @param {*} newValue - The property's new value.\r\n * @private\r\n */\r\n async _onParticipantPropertyChanged(participant, name, oldValue, newValue) {\r\n switch (name) {\r\n case 'e2ee.idKey':\r\n logger.debug(`Participant ${participant.getId()} updated their id key: ${newValue}`);\r\n break;\r\n case 'e2ee.enabled':\r\n if (!newValue && this.enabled) {\r\n this._olmAdapter.clearParticipantSession(participant);\r\n }\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Advances (using ratcheting) the current key when a new participant joins the conference.\r\n * @private\r\n */\r\n _onParticipantJoined() {\r\n if (this._conferenceJoined && this.enabled) {\r\n this._ratchetKey();\r\n }\r\n }\r\n\r\n /**\r\n * Rotates the current key when a participant leaves the conference.\r\n * @private\r\n */\r\n _onParticipantLeft(id) {\r\n this.e2eeCtx.cleanup(id);\r\n\r\n if (this.enabled) {\r\n this._rotateKey();\r\n }\r\n }\r\n\r\n /**\r\n * Rotates the local key. Rotating the key implies creating a new one, then distributing it\r\n * to all participants and once they all received it, start using it.\r\n *\r\n * @private\r\n */\r\n async _rotateKeyImpl() {\r\n logger.debug('Rotating key');\r\n\r\n this._key = this._generateKey();\r\n const index = await this._olmAdapter.updateKey(this._key);\r\n\r\n this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);\r\n }\r\n\r\n /**\r\n * Advances the current key by using ratcheting.\r\n *\r\n * @private\r\n */\r\n async _ratchetKeyImpl() {\r\n logger.debug('Ratchetting key');\r\n\r\n const material = await importKey(this._key);\r\n const newKey = await ratchet(material);\r\n\r\n this._key = new Uint8Array(newKey);\r\n\r\n const index = this._olmAdapter.updateCurrentKey(this._key);\r\n\r\n this.e2eeCtx.setKey(this.conference.myUserId(), this._key, index);\r\n }\r\n\r\n /**\r\n * Handles an update in a participant's key.\r\n *\r\n * @param {string} id - The participant ID.\r\n * @param {Uint8Array | boolean} key - The new key for the participant.\r\n * @param {Number} index - The new key's index.\r\n * @private\r\n */\r\n _onParticipantKeyUpdated(id, key, index) {\r\n logger.debug(`Participant ${id} updated their key`);\r\n\r\n this.e2eeCtx.setKey(id, key, index);\r\n }\r\n\r\n /**\r\n * Generates a new 256 bit random key.\r\n *\r\n * @returns {Uint8Array}\r\n * @private\r\n */\r\n _generateKey() {\r\n return window.crypto.getRandomValues(new Uint8Array(32));\r\n }\r\n}\r\n","/**\r\n * Derives a set of keys from the master key.\r\n * @param {CryptoKey} material - master key to derive from\r\n *\r\n * See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1\r\n */\r\nexport async function deriveKeys(material) {\r\n const info = new ArrayBuffer();\r\n const textEncoder = new TextEncoder();\r\n\r\n // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#HKDF\r\n // https://developer.mozilla.org/en-US/docs/Web/API/HkdfParams\r\n const encryptionKey = await crypto.subtle.deriveKey({\r\n name: 'HKDF',\r\n salt: textEncoder.encode('JFrameEncryptionKey'),\r\n hash: 'SHA-256',\r\n info\r\n }, material, {\r\n name: 'AES-GCM',\r\n length: 128\r\n }, false, [ 'encrypt', 'decrypt' ]);\r\n\r\n return {\r\n material,\r\n encryptionKey\r\n };\r\n}\r\n\r\n/**\r\n * Ratchets a key. See\r\n * https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.5.1\r\n * @param {CryptoKey} material - base key material\r\n * @returns {ArrayBuffer} - ratcheted key material\r\n */\r\nexport async function ratchet(material) {\r\n const textEncoder = new TextEncoder();\r\n\r\n // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits\r\n return crypto.subtle.deriveBits({\r\n name: 'HKDF',\r\n salt: textEncoder.encode('JFrameRatchetKey'),\r\n hash: 'SHA-256',\r\n info: new ArrayBuffer()\r\n }, material, 256);\r\n}\r\n\r\n/**\r\n * Converts a raw key into a WebCrypto key object with default options\r\n * suitable for our usage.\r\n * @param {ArrayBuffer} keyBytes - raw key\r\n * @param {Array} keyUsages - key usages, see importKey documentation\r\n * @returns {CryptoKey} - the WebCrypto key.\r\n */\r\nexport async function importKey(keyBytes) {\r\n // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey\r\n return crypto.subtle.importKey('raw', keyBytes, 'HKDF', false, [ 'deriveBits', 'deriveKey' ]);\r\n}\r\n","import browser from '../browser';\r\n\r\nimport { ExternallyManagedKeyHandler } from './ExternallyManagedKeyHandler';\r\nimport { ManagedKeyHandler } from './ManagedKeyHandler';\r\nimport { OlmAdapter } from './OlmAdapter';\r\n\r\n/**\r\n * This module integrates {@link KeyHandler} with {@link JitsiConference} in order to enable E2E encryption.\r\n */\r\nexport class E2EEncryption {\r\n /**\r\n * A constructor.\r\n * @param {JitsiConference} conference - The conference instance for which E2E encryption is to be enabled.\r\n */\r\n constructor(conference) {\r\n const { e2ee = {} } = conference.options.config;\r\n\r\n this._externallyManaged = e2ee.externallyManagedKey;\r\n\r\n if (this._externallyManaged) {\r\n this._keyHandler = new ExternallyManagedKeyHandler(conference);\r\n } else {\r\n this._keyHandler = new ManagedKeyHandler(conference);\r\n }\r\n }\r\n\r\n /**\r\n * Indicates if E2EE is supported in the current platform.\r\n *\r\n * @param {object} config - Global configuration.\r\n * @returns {boolean}\r\n */\r\n static isSupported(config) {\r\n const { e2ee = {} } = config;\r\n\r\n if (!e2ee.externallyManagedKey && !OlmAdapter.isSupported()) {\r\n return false;\r\n }\r\n\r\n return !(config.testing && config.testing.disableE2EE)\r\n && (browser.supportsInsertableStreams()\r\n || (config.enableEncodedTransformSupport && browser.supportsEncodedTransform()));\r\n }\r\n\r\n /**\r\n * Indicates whether E2EE is currently enabled or not.\r\n *\r\n * @returns {boolean}\r\n */\r\n isEnabled() {\r\n return this._keyHandler.isEnabled();\r\n }\r\n\r\n /**\r\n * Enables / disables End-To-End encryption.\r\n *\r\n * @param {boolean} enabled - whether E2EE should be enabled or not.\r\n * @returns {void}\r\n */\r\n async setEnabled(enabled) {\r\n await this._keyHandler.setEnabled(enabled);\r\n }\r\n\r\n /**\r\n * Sets the key and index for End-to-End encryption.\r\n *\r\n * @param {CryptoKey} [keyInfo.encryptionKey] - encryption key.\r\n * @param {Number} [keyInfo.index] - the index of the encryption key.\r\n * @returns {void}\r\n */\r\n setEncryptionKey(keyInfo) {\r\n this._keyHandler.setKey(keyInfo);\r\n }\r\n}\r\n","/* global $ */\r\n\r\nimport { b64_sha1, Strophe } from 'strophe.js'; // eslint-disable-line camelcase\r\n\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\nimport Listenable from '../util/Listenable';\r\n\r\n/**\r\n * The property\r\n */\r\nconst IDENTITY_PROPERTIES = [ 'category', 'type', 'lang', 'name' ];\r\nconst IDENTITY_PROPERTIES_FOR_COMPARE = [ 'category', 'type', 'lang' ];\r\nconst HASH = 'sha-1';\r\n\r\n/**\r\n *\r\n * @param a\r\n * @param b\r\n */\r\nfunction compareIdentities(a, b) {\r\n let res = 0;\r\n\r\n IDENTITY_PROPERTIES_FOR_COMPARE.some(key =>\r\n (res = ((a[key] > b[key]) && 1) || ((a[key] < b[key]) && -1)) !== 0\r\n );\r\n\r\n return res;\r\n}\r\n\r\n/**\r\n * Produces a sha-1 from provided identity and features values.\r\n *\r\n * @param {Array} identities - The identity objects.\r\n * @param {Array} features - The features.\r\n * @returns {string}\r\n */\r\nfunction generateSha(identities, features) {\r\n const sortedIdentities = identities.sort(compareIdentities).reduce(\r\n (accumulatedValue, identity) => `${\r\n IDENTITY_PROPERTIES.reduce(\r\n (tmp, key, idx) =>\r\n tmp\r\n + (idx === 0 ? '' : '/')\r\n + (identity[key] ? identity[key] : ''),\r\n '')\r\n }<`, '');\r\n const sortedFeatures = features.sort().reduce(\r\n (tmp, feature) => `${tmp + feature}<`, '');\r\n\r\n return b64_sha1(sortedIdentities + sortedFeatures);\r\n}\r\n\r\n/**\r\n * Parses the disco-info node and returns the sets of features and identities.\r\n * @param {String} node The node with results to parse.\r\n * @returns {{features: Set, identities: Set}}\r\n */\r\nexport function parseDiscoInfo(node) {\r\n const features = new Set();\r\n const identities = new Set();\r\n\r\n $(node).find('>query>feature')\r\n .each((_, el) => features.add(el.getAttribute('var')));\r\n $(node).find('>query>identity')\r\n .each((_, el) => identities.add({\r\n type: el.getAttribute('type'),\r\n name: el.getAttribute('name'),\r\n category: el.getAttribute('category')\r\n }));\r\n\r\n return {\r\n features,\r\n identities\r\n };\r\n}\r\n\r\n/**\r\n * Implements xep-0115 ( http://xmpp.org/extensions/xep-0115.html )\r\n */\r\nexport default class Caps extends Listenable {\r\n /**\r\n * Constructs new Caps instance.\r\n * @param {Strophe.Connection} connection the strophe connection object\r\n * @param {String} node the value of the node attribute of the \"c\" xml node\r\n * that will be sent to the other participants\r\n */\r\n constructor(connection = {}, node = 'http://jitsi.org/jitsimeet') {\r\n super();\r\n this.node = node;\r\n this.disco = connection.disco;\r\n if (!this.disco) {\r\n throw new Error(\r\n 'Missing strophe-plugins '\r\n + '(disco plugin is required)!');\r\n }\r\n\r\n this.version = '';\r\n this.rooms = new Set();\r\n\r\n // We keep track of features added outside the library and we publish them\r\n // in the presence of the participant for simplicity, avoiding the disco info request-response.\r\n this.externalFeatures = new Set();\r\n\r\n const emuc = connection.emuc;\r\n\r\n emuc.addListener(XMPPEvents.EMUC_ROOM_ADDED,\r\n room => this._addChatRoom(room));\r\n emuc.addListener(XMPPEvents.EMUC_ROOM_REMOVED,\r\n room => this._removeChatRoom(room));\r\n Object.keys(emuc.rooms).forEach(jid => {\r\n this._addChatRoom(emuc.rooms[jid]);\r\n });\r\n\r\n Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps');\r\n this.disco.addFeature(Strophe.NS.CAPS);\r\n }\r\n\r\n /**\r\n * Adds new feature to the list of supported features for the local\r\n * participant\r\n * @param {String} feature the name of the feature.\r\n * @param {boolean} submit if true - new presence with updated \"c\" node\r\n * will be sent.\r\n * @param {boolean} external whether this feature was added externally to the library.\r\n * We put features used directly by the clients (is jibri, remote-control enabled etc.) in the presence\r\n * to avoid additional disco-info queries by those clients.\r\n */\r\n addFeature(feature, submit = false, external = false) {\r\n this.disco.addFeature(feature);\r\n this._generateVersion();\r\n\r\n if (external && !this.externalFeatures.has(feature)) {\r\n this.externalFeatures.add(feature);\r\n this.rooms.forEach(room => this._updateRoomWithExternalFeatures(room));\r\n }\r\n\r\n if (submit) {\r\n this.submit();\r\n }\r\n }\r\n\r\n /**\r\n * Removes a feature from the list of supported features for the local\r\n * participant\r\n * @param {String} feature the name of the feature.\r\n * @param {boolean} submit if true - new presence with updated \"c\" node\r\n * will be sent.\r\n * @param {boolean} external whether this feature was added externally to the library.\r\n */\r\n removeFeature(feature, submit = false, external = false) {\r\n this.disco.removeFeature(feature);\r\n this._generateVersion();\r\n\r\n if (external && this.externalFeatures.has(feature)) {\r\n this.externalFeatures.delete(feature);\r\n this.rooms.forEach(room => this._updateRoomWithExternalFeatures(room));\r\n }\r\n\r\n if (submit) {\r\n this.submit();\r\n }\r\n }\r\n\r\n /**\r\n * Sends new presence stanza for every room from the list of rooms.\r\n */\r\n submit() {\r\n this.rooms.forEach(room => room.sendPresence());\r\n }\r\n\r\n /**\r\n * Updates the presences in the room based on the current values in externalFeatures.\r\n * @param {ChatRoom} room the room to update.\r\n * @private\r\n */\r\n _updateRoomWithExternalFeatures(room) {\r\n if (this.externalFeatures.size === 0) {\r\n room.removeFromPresence('features');\r\n } else {\r\n const children = [];\r\n\r\n this.externalFeatures.forEach(f => {\r\n children.push({\r\n 'tagName': 'feature',\r\n attributes: { 'var': f }\r\n });\r\n });\r\n\r\n room.addOrReplaceInPresence('features', { children });\r\n }\r\n }\r\n\r\n /**\r\n * Returns a set with the features for a host.\r\n * @param {String} jid the jid of the host\r\n * @param {int} timeout the timeout in ms for reply from the host.\r\n * @returns {Promise, Error>}\r\n */\r\n getFeaturesAndIdentities(jid, node, timeout = 5000) {\r\n return this._getDiscoInfo(jid, node, timeout);\r\n }\r\n\r\n /**\r\n * Returns a set with the features and identities for a host.\r\n * @param {String} jid the jid of the host\r\n * @param {String|null} node the node to query\r\n * @param {int} timeout the timeout in ms for reply from the host.\r\n * @returns {Promise}\r\n * @private\r\n */\r\n _getDiscoInfo(jid, node, timeout) {\r\n return new Promise((resolve, reject) =>\r\n this.disco.info(jid, node, response => {\r\n resolve(parseDiscoInfo(response));\r\n }, reject, timeout)\r\n );\r\n }\r\n\r\n /**\r\n * Adds ChatRoom instance to the list of rooms. Adds listeners to the room\r\n * and adds \"c\" element to the presences of the room.\r\n * @param {ChatRoom} room the room.\r\n */\r\n _addChatRoom(room) {\r\n this.rooms.add(room);\r\n this._fixChatRoomPresenceMap(room);\r\n\r\n this._updateRoomWithExternalFeatures(room);\r\n }\r\n\r\n /**\r\n * Removes ChatRoom instance from the list of rooms. Removes listeners\r\n * added from the Caps class.\r\n * @param {ChatRoom} room the room.\r\n */\r\n _removeChatRoom(room) {\r\n this.rooms.delete(room);\r\n }\r\n\r\n /**\r\n * Creates/updates the \"c\" xml node into the presence of the passed room.\r\n * @param {ChatRoom} room the room.\r\n */\r\n _fixChatRoomPresenceMap(room) {\r\n room.addOrReplaceInPresence('c', {\r\n attributes: {\r\n xmlns: Strophe.NS.CAPS,\r\n hash: HASH,\r\n node: this.node,\r\n ver: this.version\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Handles this.version changes.\r\n */\r\n _notifyVersionChanged() {\r\n // update the version for all rooms\r\n this.rooms.forEach(room => this._fixChatRoomPresenceMap(room));\r\n }\r\n\r\n /**\r\n * Generates the value for the \"ver\" attribute.\r\n */\r\n _generateVersion() {\r\n this.version\r\n = generateSha(this.disco._identities, this.disco._features);\r\n\r\n this._notifyVersionChanged();\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport Listenable from '../util/Listenable';\r\n\r\nexport const NETWORK_INFO_EVENT = 'NETWORK_INFO_CHANGED';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Module provides information about the current status of the internet\r\n * connection. Lib-jitsi-meet doesn't have any logic for detecting internet\r\n * online/offline, but rather it relies on the information supplied by the app\r\n * that uses it. By default the online state is assumed and the lib acts as if\r\n * it was connected. See {@link JitsiMeetJS.setNetworkInfo}.\r\n */\r\nexport class NetworkInfo extends Listenable {\r\n /**\r\n * Creates new {@link NetworkInfo} instance.\r\n */\r\n constructor() {\r\n super();\r\n this._current = {\r\n isOnline: true\r\n };\r\n }\r\n\r\n /**\r\n * Updates the network info state.\r\n * @param {boolean} isOnline - {@code true} if internet is online or {@code false} otherwise.\r\n */\r\n updateNetworkInfo({ isOnline }) {\r\n logger.debug('updateNetworkInfo', { isOnline });\r\n this._current = {\r\n isOnline: isOnline === true\r\n };\r\n this.eventEmitter.emit(NETWORK_INFO_EVENT, this._current);\r\n }\r\n\r\n /**\r\n * Returns the online/offline internet status. By default the value is {@code true} and changes only if\r\n * the lib's user wires the state through {@link JitsiMeetJS.setNetworkInfo} like the jitsi-meet does. Because of\r\n * that any logic should still assume that the internet may be offline and should handle the failure gracefully.\r\n * It's only a good hint in the other way around: to pause internet operations until it comes back online.\r\n * @returns {boolean}\r\n */\r\n isOnline() {\r\n return this._current.isOnline === true;\r\n }\r\n}\r\n\r\nconst networkInfo = new NetworkInfo();\r\n\r\nexport default networkInfo;\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport {\r\n default as NetworkInfo,\r\n NETWORK_INFO_EVENT\r\n} from '../connectivity/NetworkInfo';\r\nimport { getJitterDelay } from '../util/Retry';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The class contains the logic for triggering connection resume via XEP-0198 stream management.\r\n * It does two things, the first one is it tracks the internet online/offline status and it makes sure that\r\n * the reconnect is attempted only while online. The seconds thing is that it tracks the retry attempts and extends\r\n * the retry interval using the full jitter pattern.\r\n */\r\nexport default class ResumeTask {\r\n /**\r\n * Initializes new {@code RetryTask}.\r\n * @param {Strophe.Connection} stropheConnection - The Strophe connection instance.\r\n */\r\n constructor(stropheConnection) {\r\n this._stropheConn = stropheConnection;\r\n\r\n /**\r\n * The counter increased before each resume retry attempt, used to calculate exponential backoff.\r\n * @type {number}\r\n * @private\r\n */\r\n this._resumeRetryN = 0;\r\n\r\n this._retryDelay = undefined;\r\n }\r\n\r\n /**\r\n * @returns {number|undefined} - How much the app will wait before trying to resume the XMPP connection. When\r\n * 'undefined' it means that no resume task was not scheduled.\r\n */\r\n get retryDelay() {\r\n return this._retryDelay;\r\n }\r\n\r\n /**\r\n * Called by {@link XmppConnection} when the connection drops and it's a signal it wants to schedule a reconnect.\r\n *\r\n * @returns {void}\r\n */\r\n schedule() {\r\n this._cancelResume();\r\n\r\n this._resumeRetryN += 1;\r\n\r\n this._networkOnlineListener\r\n = NetworkInfo.addEventListener(\r\n NETWORK_INFO_EVENT,\r\n ({ isOnline }) => {\r\n if (isOnline) {\r\n this._scheduleResume();\r\n } else {\r\n this._cancelResume();\r\n }\r\n });\r\n\r\n NetworkInfo.isOnline() && this._scheduleResume();\r\n }\r\n\r\n /**\r\n * Schedules a delayed timeout which will execute the resume action.\r\n * @private\r\n * @returns {void}\r\n */\r\n _scheduleResume() {\r\n if (this._resumeTimeout) {\r\n\r\n // NO-OP\r\n return;\r\n }\r\n\r\n // The retry delay will be:\r\n // 1st retry: 1.5s - 3s\r\n // 2nd retry: 3s - 9s\r\n // 3rd and next retry: 4.5s - 27s\r\n this._resumeRetryN = Math.min(3, this._resumeRetryN);\r\n this._retryDelay = getJitterDelay(\r\n /* retry */ this._resumeRetryN,\r\n /* minDelay */ this._resumeRetryN * 1500,\r\n 3);\r\n\r\n logger.info(`Will try to resume the XMPP connection in ${this.retryDelay}ms`);\r\n\r\n this._resumeTimeout = setTimeout(() => this._resumeConnection(), this.retryDelay);\r\n }\r\n\r\n /**\r\n * Cancels the delayed resume task.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _cancelResume() {\r\n if (this._resumeTimeout) {\r\n logger.info('Canceling connection resume task');\r\n clearTimeout(this._resumeTimeout);\r\n this._resumeTimeout = undefined;\r\n this._retryDelay = undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Resumes the XMPP connection using the stream management plugin.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _resumeConnection() {\r\n const { streamManagement } = this._stropheConn;\r\n const resumeToken = streamManagement.getResumeToken();\r\n\r\n // Things may have changed since when the task was scheduled\r\n if (!resumeToken) {\r\n return;\r\n }\r\n\r\n logger.info('Trying to resume the XMPP connection');\r\n\r\n const url = new URL(this._stropheConn.service);\r\n let { search } = url;\r\n const pattern = /(previd=)([\\w-]+)/;\r\n const oldToken = search.match(pattern);\r\n\r\n // Replace previd if the previd value has changed.\r\n if (oldToken && oldToken.indexOf(resumeToken) === -1) {\r\n search = search.replace(pattern, `$1${resumeToken}`);\r\n\r\n // Append previd if it doesn't exist.\r\n } else if (!oldToken) {\r\n search += search.indexOf('?') === -1 ? `?previd=${resumeToken}` : `&previd=${resumeToken}`;\r\n }\r\n\r\n url.search = search;\r\n\r\n this._stropheConn.service = url.toString();\r\n\r\n streamManagement.resume();\r\n }\r\n\r\n /**\r\n * Cancels the retry task. It's called by {@link XmppConnection} when it's no longer interested in reconnecting for\r\n * example when the disconnect method is called.\r\n *\r\n * @returns {void}\r\n */\r\n cancel() {\r\n this._cancelResume();\r\n this._resumeRetryN = 0;\r\n if (this._networkOnlineListener) {\r\n this._networkOnlineListener();\r\n this._networkOnlineListener = null;\r\n }\r\n }\r\n}\r\n","/**\r\n* Gets next timeout using the full jitter pattern.\r\n*\r\n* NOTE that there are no checks for argument correctness, so either do the math or use defaults.\r\n*\r\n* @param {number} retry - The retry number.\r\n* @param {number} minDelay - The minimal delay in milliseconds.\r\n* @param {number} base - The exponent base.\r\n* @returns {number} - The amount of waiting before trying another time given in milliseconds.\r\n* @private\r\n*/\r\nexport function getJitterDelay(retry, minDelay = 500, base = 2) {\r\n return Math.floor((Math.random() * ((Math.pow(base, retry) * 1000) - minDelay)) + minDelay);\r\n}\r\n","/**\r\n * Attaches to the {@link Strophe.Connection.rawInput} which is called whenever any data is received from the server.\r\n */\r\nexport default class LastRequestTracker {\r\n /**\r\n * Initializes new instance.\r\n */\r\n constructor() {\r\n this._lastSuccess = null;\r\n this._lastFailedMessage = null;\r\n }\r\n\r\n /**\r\n * Starts tracking requests on the given connection.\r\n *\r\n * @param {XmppConnection} xmppConnection - The XMPP connection which manages the given {@code stropheConnection}.\r\n * @param {Object} stropheConnection - Strophe connection instance.\r\n */\r\n startTracking(xmppConnection, stropheConnection) {\r\n const originalRawInput = stropheConnection.rawInput;\r\n\r\n stropheConnection.rawInput = (...args) => {\r\n const rawMessage = args[0];\r\n\r\n if (rawMessage.includes('failure')) {\r\n this._lastFailedMessage = rawMessage;\r\n }\r\n\r\n // It's okay to use rawInput callback only once the connection has been established, otherwise it will\r\n // treat 'item-not-found' or other connection error on websocket reconnect as successful stanza received.\r\n if (xmppConnection.connected) {\r\n this._lastSuccess = Date.now();\r\n }\r\n originalRawInput.apply(stropheConnection, args);\r\n };\r\n }\r\n\r\n /**\r\n * Returns the last raw failed incoming message on the xmpp connection.\r\n *\r\n * @returns {string|null}\r\n */\r\n getLastFailedMessage() {\r\n return this._lastFailedMessage;\r\n }\r\n\r\n /**\r\n * Returns how many milliseconds have passed since the last successful BOSH request.\r\n *\r\n * @returns {number|null}\r\n */\r\n getTimeSinceLastSuccess() {\r\n return this._lastSuccess\r\n ? Date.now() - this._lastSuccess\r\n : null;\r\n }\r\n}\r\n","import Listenable from '../util/Listenable';\r\n\r\n/**\r\n * Creates ConnectionPlugin class that extends the passed class.\r\n * @param {Class} base the definition of the class that will be extended by\r\n * ConnectionPlugin\r\n */\r\nfunction getConnectionPluginDefinition(base = class {}) {\r\n /**\r\n * Base class for strophe connection plugins.\r\n */\r\n return class extends base {\r\n /**\r\n *\r\n */\r\n constructor(...args) {\r\n super(...args);\r\n this.connection = null;\r\n }\r\n\r\n /**\r\n *\r\n * @param connection\r\n */\r\n init(connection) {\r\n this.connection = connection;\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * ConnectionPlugin class.\r\n */\r\nexport default getConnectionPluginDefinition();\r\n\r\n/**\r\n * ConnectionPlugin class that extends Listenable.\r\n */\r\nexport const ConnectionPluginListenable\r\n = getConnectionPluginDefinition(Listenable);\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { $iq, Strophe } from 'strophe.js';\r\n\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\n\r\nimport ConnectionPlugin from './ConnectionPlugin';\r\n\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Default ping every 10 sec\r\n */\r\nconst PING_DEFAULT_INTERVAL = 10000;\r\n\r\n/**\r\n * Default ping timeout error after 5 sec of waiting.\r\n */\r\nconst PING_DEFAULT_TIMEOUT = 5000;\r\n\r\n/**\r\n * Default value for how many ping failures will be tolerated before the WebSocket connection is killed.\r\n * The worst case scenario in case of ping timing out without a response is (25 seconds at the time of this writing):\r\n * PING_THRESHOLD * PING_INTERVAL + PING_TIMEOUT\r\n */\r\nconst PING_DEFAULT_THRESHOLD = 2;\r\n\r\n/**\r\n * XEP-0199 ping plugin.\r\n *\r\n * Registers \"urn:xmpp:ping\" namespace under Strophe.NS.PING.\r\n */\r\nexport default class PingConnectionPlugin extends ConnectionPlugin {\r\n /**\r\n * Constructs new object\r\n * @param {Object} options\r\n * @param {Function} options.onPingThresholdExceeded - Callback called when ping fails too many times (controlled\r\n * by the {@link PING_THRESHOLD} constant).\r\n * @param {Function} options._getTimeSinceLastServerResponse - A function to obtain the last seen\r\n * response from the server.\r\n * @param {Object} options.pingOptions - The ping options if any.\r\n * @constructor\r\n */\r\n constructor({ getTimeSinceLastServerResponse, onPingThresholdExceeded, pingOptions = {} }) {\r\n super();\r\n this.failedPings = 0;\r\n this._onPingThresholdExceeded = onPingThresholdExceeded;\r\n this._getTimeSinceLastServerResponse = getTimeSinceLastServerResponse;\r\n\r\n this.pingInterval = typeof pingOptions.interval === 'number' ? pingOptions.interval : PING_DEFAULT_INTERVAL;\r\n this.pingTimeout = typeof pingOptions.timeout === 'number' ? pingOptions.timeout : PING_DEFAULT_TIMEOUT;\r\n this.pingThreshold = typeof pingOptions.threshold === 'number'\r\n ? pingOptions.threshold : PING_DEFAULT_THRESHOLD;\r\n\r\n // The number of timestamps of send pings to keep.\r\n // The current value is 2 minutes.\r\n this.pingTimestampsToKeep = Math.round(120000 / this.pingInterval);\r\n this.pingExecIntervals = new Array(this.pingTimestampsToKeep);\r\n }\r\n\r\n /**\r\n * Initializes the plugin. Method called by Strophe.\r\n * @param connection Strophe connection instance.\r\n */\r\n init(connection) {\r\n super.init(connection);\r\n Strophe.addNamespace('PING', 'urn:xmpp:ping');\r\n }\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n * Sends \"ping\" to given jid\r\n * @param jid the JID to which ping request will be sent.\r\n * @param success callback called on success.\r\n * @param error callback called on error.\r\n * @param timeout ms how long are we going to wait for the response. On\r\n * timeout error callback is called with undefined error argument.\r\n */\r\n ping(jid, success, error, timeout) {\r\n this._addPingExecutionTimestamp();\r\n\r\n const iq = $iq({\r\n type: 'get',\r\n to: jid\r\n });\r\n\r\n iq.c('ping', { xmlns: Strophe.NS.PING });\r\n this.connection.sendIQ2(iq, { timeout })\r\n .then(success, error);\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Starts to send ping in given interval to specified remote JID.\r\n * This plugin supports only one such task and stopInterval\r\n * must be called before starting a new one.\r\n * @param remoteJid remote JID to which ping requests will be sent to.\r\n */\r\n startInterval(remoteJid) {\r\n clearInterval(this.intervalId);\r\n this.intervalId = window.setInterval(() => {\r\n\r\n // when there were some server responses in the interval since the last time we checked (_lastServerCheck)\r\n // let's skip the ping\r\n\r\n const now = Date.now();\r\n\r\n if (this._getTimeSinceLastServerResponse() < now - this._lastServerCheck) {\r\n // do this just to keep in sync the intervals so we can detect suspended device\r\n this._addPingExecutionTimestamp();\r\n\r\n this._lastServerCheck = now;\r\n this.failedPings = 0;\r\n\r\n return;\r\n }\r\n\r\n this.ping(remoteJid, () => {\r\n // server response is measured on raw input and ping response time is measured after all the xmpp\r\n // processing is done in js, so there can be some misalignment when we do the check above.\r\n // That's why we store the last time we got the response\r\n this._lastServerCheck = this._getTimeSinceLastServerResponse() + Date.now();\r\n\r\n this.failedPings = 0;\r\n }, error => {\r\n this.failedPings += 1;\r\n const errmsg = `Ping ${error ? 'error' : 'timeout'}`;\r\n\r\n if (this.failedPings >= this.pingThreshold) {\r\n GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));\r\n logger.error(errmsg, error);\r\n this._onPingThresholdExceeded && this._onPingThresholdExceeded();\r\n } else {\r\n logger.warn(errmsg, error);\r\n }\r\n }, this.pingTimeout);\r\n }, this.pingInterval);\r\n logger.info(`XMPP pings will be sent every ${this.pingInterval} ms`);\r\n }\r\n\r\n /**\r\n * Stops current \"ping\" interval task.\r\n */\r\n stopInterval() {\r\n if (this.intervalId) {\r\n window.clearInterval(this.intervalId);\r\n this.intervalId = null;\r\n this.failedPings = 0;\r\n logger.info('Ping interval cleared');\r\n }\r\n }\r\n\r\n /**\r\n * Adds the current time to the array of send ping timestamps.\r\n * @private\r\n */\r\n _addPingExecutionTimestamp() {\r\n this.pingExecIntervals.push(new Date().getTime());\r\n\r\n // keep array length to PING_TIMESTAMPS_TO_KEEP\r\n if (this.pingExecIntervals.length > this.pingTimestampsToKeep) {\r\n this.pingExecIntervals.shift();\r\n }\r\n }\r\n\r\n /**\r\n * Returns the maximum time between the recent sent pings, if there is a\r\n * big value it means the computer was inactive for some time(suspended).\r\n * Checks the maximum gap between sending pings, considering and the\r\n * current time. Trying to detect computer inactivity (sleep).\r\n *\r\n * @returns {int} the time ping was suspended, if it was not 0 is returned.\r\n */\r\n getPingSuspendTime() {\r\n const pingIntervals = this.pingExecIntervals.slice();\r\n\r\n // we need current time, as if ping was sent now\r\n // if computer sleeps we will get correct interval after next\r\n // scheduled ping, bet we sometimes need that interval before waiting\r\n // for the next ping, on closing the connection on error.\r\n pingIntervals.push(new Date().getTime());\r\n\r\n let maxInterval = 0;\r\n let previousTS = pingIntervals[0];\r\n\r\n pingIntervals.forEach(e => {\r\n const currentInterval = e - previousTS;\r\n\r\n if (currentInterval > maxInterval) {\r\n maxInterval = currentInterval;\r\n }\r\n\r\n previousTS = e;\r\n });\r\n\r\n // remove the interval between the ping sent\r\n // this way in normal execution there is no suspend and the return\r\n // will be 0 or close to 0.\r\n maxInterval -= this.pingInterval;\r\n\r\n // make sure we do not return less than 0\r\n return Math.max(maxInterval, 0);\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { $pres, Strophe } from 'strophe.js';\r\nimport 'strophejs-plugin-stream-management';\r\n\r\nimport Listenable from '../util/Listenable';\r\n\r\nimport ResumeTask from './ResumeTask';\r\nimport LastSuccessTracker from './StropheLastSuccess';\r\nimport PingConnectionPlugin from './strophe.ping';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The lib-jitsi-meet layer for {@link Strophe.Connection}.\r\n */\r\nexport default class XmppConnection extends Listenable {\r\n /**\r\n * The list of {@link XmppConnection} events.\r\n *\r\n * @returns {Object}\r\n */\r\n static get Events() {\r\n return {\r\n CONN_STATUS_CHANGED: 'CONN_STATUS_CHANGED',\r\n CONN_SHARD_CHANGED: 'CONN_SHARD_CHANGED'\r\n };\r\n }\r\n\r\n /**\r\n * The list of Xmpp connection statuses.\r\n *\r\n * @returns {Strophe.Status}\r\n */\r\n static get Status() {\r\n return Strophe.Status;\r\n }\r\n\r\n /**\r\n * Initializes new connection instance.\r\n *\r\n * @param {Object} options\r\n * @param {String} options.serviceUrl - The BOSH or WebSocket service URL.\r\n * @param {String} options.shard - The BOSH or WebSocket is connecting to this shard.\r\n * Useful for detecting when shard changes.\r\n * @param {String} [options.enableWebsocketResume=true] - True/false to control the stream resumption functionality.\r\n * It will enable automatically by default if supported by the XMPP server.\r\n * @param {Number} [options.websocketKeepAlive=60000] - The websocket keep alive interval.\r\n * It's the interval + a up to a minute of jitter. Pass -1 to disable.\r\n * The keep alive is HTTP GET request to {@link options.serviceUrl} or to {@link options.websocketKeepAliveUrl}.\r\n * @param {Number} [options.websocketKeepAliveUrl] - The websocket keep alive url to use if any,\r\n * if missing the serviceUrl url will be used.\r\n * @param {Object} [options.xmppPing] - The xmpp ping settings.\r\n */\r\n constructor({ enableWebsocketResume, websocketKeepAlive, websocketKeepAliveUrl, serviceUrl, shard, xmppPing }) {\r\n super();\r\n this._options = {\r\n enableWebsocketResume: typeof enableWebsocketResume === 'undefined' ? true : enableWebsocketResume,\r\n pingOptions: xmppPing,\r\n shard,\r\n websocketKeepAlive: typeof websocketKeepAlive === 'undefined' ? 60 * 1000 : Number(websocketKeepAlive),\r\n websocketKeepAliveUrl\r\n };\r\n\r\n this._stropheConn = new Strophe.Connection(serviceUrl);\r\n this._usesWebsocket = serviceUrl.startsWith('ws:') || serviceUrl.startsWith('wss:');\r\n\r\n // The default maxRetries is 5, which is too long.\r\n this._stropheConn.maxRetries = 3;\r\n\r\n this._rawInputTracker = new LastSuccessTracker();\r\n this._rawInputTracker.startTracking(this, this._stropheConn);\r\n\r\n this._resumeTask = new ResumeTask(this._stropheConn);\r\n\r\n /**\r\n * @typedef DeferredSendIQ Object\r\n * @property {Element} iq - The IQ to send.\r\n * @property {function} resolve - The resolve method of the deferred Promise.\r\n * @property {function} reject - The reject method of the deferred Promise.\r\n * @property {number} timeout - The ID of the timeout task that needs to be cleared, before sending the IQ.\r\n */\r\n /**\r\n * Deferred IQs to be sent upon reconnect.\r\n * @type {Array}\r\n * @private\r\n */\r\n this._deferredIQs = [];\r\n\r\n // Ping plugin is mandatory for the Websocket mode to work correctly. It's used to detect when the connection\r\n // is broken (WebSocket/TCP connection not closed gracefully).\r\n this.addConnectionPlugin(\r\n 'ping',\r\n new PingConnectionPlugin({\r\n getTimeSinceLastServerResponse: () => this.getTimeSinceLastSuccess(),\r\n onPingThresholdExceeded: () => this._onPingErrorThresholdExceeded(),\r\n pingOptions: xmppPing\r\n }));\r\n\r\n // tracks whether this is the initial connection or a reconnect\r\n this._oneSuccessfulConnect = false;\r\n }\r\n\r\n /**\r\n * A getter for the connected state.\r\n *\r\n * @returns {boolean}\r\n */\r\n get connected() {\r\n const websocket = this._stropheConn && this._stropheConn._proto && this._stropheConn._proto.socket;\r\n\r\n return (this._status === Strophe.Status.CONNECTED || this._status === Strophe.Status.ATTACHED)\r\n && (!this.isUsingWebSocket || (websocket && websocket.readyState === WebSocket.OPEN));\r\n }\r\n\r\n /**\r\n * Retrieves the feature discovery plugin instance.\r\n *\r\n * @returns {Strophe.Connection.disco}\r\n */\r\n get disco() {\r\n return this._stropheConn.disco;\r\n }\r\n\r\n /**\r\n * A getter for the disconnecting state.\r\n *\r\n * @returns {boolean}\r\n */\r\n get disconnecting() {\r\n return this._stropheConn.disconnecting === true;\r\n }\r\n\r\n /**\r\n * A getter for the domain.\r\n *\r\n * @returns {string|null}\r\n */\r\n get domain() {\r\n return this._stropheConn.domain;\r\n }\r\n\r\n /**\r\n * Tells if Websocket is used as the transport for the current XMPP connection. Returns true for Websocket or false\r\n * for BOSH.\r\n * @returns {boolean}\r\n */\r\n get isUsingWebSocket() {\r\n return this._usesWebsocket;\r\n }\r\n\r\n /**\r\n * A getter for the JID.\r\n *\r\n * @returns {string|null}\r\n */\r\n get jid() {\r\n return this._stropheConn.jid;\r\n }\r\n\r\n /**\r\n * Returns headers for the last BOSH response received.\r\n *\r\n * @returns {string}\r\n */\r\n get lastResponseHeaders() {\r\n return this._stropheConn._proto && this._stropheConn._proto.lastResponseHeaders;\r\n }\r\n\r\n /**\r\n * A getter for the logger plugin instance.\r\n *\r\n * @returns {*}\r\n */\r\n get logger() {\r\n return this._stropheConn.logger;\r\n }\r\n\r\n /**\r\n * A getter for the connection options.\r\n *\r\n * @returns {*}\r\n */\r\n get options() {\r\n return this._stropheConn.options;\r\n }\r\n\r\n /**\r\n * A getter for the domain to be used for ping.\r\n */\r\n get pingDomain() {\r\n return this._options.pingOptions?.domain || this.domain;\r\n }\r\n\r\n /**\r\n * A getter for the service URL.\r\n *\r\n * @returns {string}\r\n */\r\n get service() {\r\n return this._stropheConn.service;\r\n }\r\n\r\n /**\r\n * Sets new value for shard.\r\n * @param value the new shard value.\r\n */\r\n set shard(value) {\r\n this._options.shard = value;\r\n\r\n // shard setting changed so let's schedule a new keep-alive check if connected\r\n if (this._oneSuccessfulConnect) {\r\n this._maybeStartWSKeepAlive();\r\n }\r\n }\r\n\r\n /**\r\n * Returns the current connection status.\r\n *\r\n * @returns {Strophe.Status}\r\n */\r\n get status() {\r\n return this._status;\r\n }\r\n\r\n /**\r\n * Adds a connection plugin to this instance.\r\n *\r\n * @param {string} name - The name of the plugin or rather a key under which it will be stored on this connection\r\n * instance.\r\n * @param {ConnectionPluginListenable} plugin - The plugin to add.\r\n */\r\n addConnectionPlugin(name, plugin) {\r\n this[name] = plugin;\r\n plugin.init(this);\r\n }\r\n\r\n /**\r\n * See {@link Strophe.Connection.addHandler}\r\n *\r\n * @returns {void}\r\n */\r\n addHandler(...args) {\r\n this._stropheConn.addHandler(...args);\r\n }\r\n\r\n /* eslint-disable max-params */\r\n /**\r\n * Wraps {@link Strophe.Connection.attach} method in order to intercept the connection status updates.\r\n * See {@link Strophe.Connection.attach} for the params description.\r\n *\r\n * @returns {void}\r\n */\r\n attach(jid, sid, rid, callback, ...args) {\r\n this._stropheConn.attach(jid, sid, rid, this._stropheConnectionCb.bind(this, callback), ...args);\r\n }\r\n\r\n /**\r\n * Wraps Strophe.Connection.connect method in order to intercept the connection status updates.\r\n * See {@link Strophe.Connection.connect} for the params description.\r\n *\r\n * @returns {void}\r\n */\r\n connect(jid, pass, callback, ...args) {\r\n this._stropheConn.connect(jid, pass, this._stropheConnectionCb.bind(this, callback), ...args);\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Handles {@link Strophe.Status} updates for the current connection.\r\n *\r\n * @param {function} targetCallback - The callback passed by the {@link XmppConnection} consumer to one of\r\n * the connect methods.\r\n * @param {Strophe.Status} status - The new connection status.\r\n * @param {*} args - The rest of the arguments passed by Strophe.\r\n * @private\r\n */\r\n _stropheConnectionCb(targetCallback, status, ...args) {\r\n this._status = status;\r\n\r\n let blockCallback = false;\r\n\r\n if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {\r\n this._maybeEnableStreamResume();\r\n\r\n // after connecting - immediately check whether shard changed,\r\n // we need this only when using websockets as bosh checks headers from every response\r\n if (this._usesWebsocket && this._oneSuccessfulConnect) {\r\n this._keepAliveAndCheckShard();\r\n }\r\n this._oneSuccessfulConnect = true;\r\n\r\n this._maybeStartWSKeepAlive();\r\n this._processDeferredIQs();\r\n this._resumeTask.cancel();\r\n this.ping.startInterval(this._options.pingOptions?.domain || this.domain);\r\n } else if (status === Strophe.Status.DISCONNECTED) {\r\n this.ping.stopInterval();\r\n\r\n // FIXME add RECONNECTING state instead of blocking the DISCONNECTED update\r\n blockCallback = this._tryResumingConnection();\r\n if (!blockCallback) {\r\n clearTimeout(this._wsKeepAlive);\r\n }\r\n }\r\n\r\n if (!blockCallback) {\r\n targetCallback(status, ...args);\r\n this.eventEmitter.emit(XmppConnection.Events.CONN_STATUS_CHANGED, status);\r\n }\r\n }\r\n\r\n /**\r\n * Clears the list of IQs and rejects deferred Promises with an error.\r\n *\r\n * @private\r\n */\r\n _clearDeferredIQs() {\r\n for (const deferred of this._deferredIQs) {\r\n deferred.reject(new Error('disconnect'));\r\n }\r\n this._deferredIQs = [];\r\n }\r\n\r\n /**\r\n * The method is meant to be used for testing. It's a shortcut for closing the WebSocket.\r\n *\r\n * @returns {void}\r\n */\r\n closeWebsocket() {\r\n if (this._stropheConn && this._stropheConn._proto) {\r\n this._stropheConn._proto._closeSocket();\r\n this._stropheConn._proto._onClose(null);\r\n }\r\n }\r\n\r\n /**\r\n * See {@link Strophe.Connection.disconnect}.\r\n *\r\n * @returns {void}\r\n */\r\n disconnect(...args) {\r\n this._resumeTask.cancel();\r\n clearTimeout(this._wsKeepAlive);\r\n this._clearDeferredIQs();\r\n this._stropheConn.disconnect(...args);\r\n }\r\n\r\n /**\r\n * See {@link Strophe.Connection.flush}.\r\n *\r\n * @returns {void}\r\n */\r\n flush(...args) {\r\n this._stropheConn.flush(...args);\r\n }\r\n\r\n /**\r\n * See {@link LastRequestTracker.getTimeSinceLastSuccess}.\r\n *\r\n * @returns {number|null}\r\n */\r\n getTimeSinceLastSuccess() {\r\n return this._rawInputTracker.getTimeSinceLastSuccess();\r\n }\r\n\r\n /**\r\n * See {@link LastRequestTracker.getLastFailedMessage}.\r\n *\r\n * @returns {string|null}\r\n */\r\n getLastFailedMessage() {\r\n return this._rawInputTracker.getLastFailedMessage();\r\n }\r\n\r\n /**\r\n * Requests a resume token from the server if enabled and all requirements are met.\r\n *\r\n * @private\r\n */\r\n _maybeEnableStreamResume() {\r\n if (!this._options.enableWebsocketResume) {\r\n\r\n return;\r\n }\r\n\r\n const { streamManagement } = this._stropheConn;\r\n\r\n if (!this.isUsingWebSocket) {\r\n logger.warn('Stream resume enabled, but WebSockets are not enabled');\r\n } else if (!streamManagement) {\r\n logger.warn('Stream resume enabled, but Strophe streamManagement plugin is not installed');\r\n } else if (!streamManagement.isSupported()) {\r\n logger.warn('Stream resume enabled, but XEP-0198 is not supported by the server');\r\n } else if (!streamManagement.getResumeToken()) {\r\n logger.info('Enabling XEP-0198 stream management');\r\n streamManagement.enable(/* resume */ true);\r\n }\r\n }\r\n\r\n /**\r\n * Starts the Websocket keep alive if enabled.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _maybeStartWSKeepAlive() {\r\n const { websocketKeepAlive } = this._options;\r\n\r\n if (this._usesWebsocket && websocketKeepAlive > 0) {\r\n this._wsKeepAlive || logger.info(`WebSocket keep alive interval: ${websocketKeepAlive}ms`);\r\n clearTimeout(this._wsKeepAlive);\r\n\r\n const intervalWithJitter = /* base */ websocketKeepAlive + /* jitter */ (Math.random() * 60 * 1000);\r\n\r\n logger.debug(`Scheduling next WebSocket keep-alive in ${intervalWithJitter}ms`);\r\n\r\n this._wsKeepAlive = setTimeout(\r\n () => this._keepAliveAndCheckShard()\r\n .then(() => this._maybeStartWSKeepAlive()),\r\n intervalWithJitter);\r\n }\r\n }\r\n\r\n /**\r\n * Do a http GET to the shard and if shard change will throw an event.\r\n *\r\n * @private\r\n * @returns {Promise}\r\n */\r\n _keepAliveAndCheckShard() {\r\n const { shard, websocketKeepAliveUrl } = this._options;\r\n const url = websocketKeepAliveUrl ? websocketKeepAliveUrl\r\n : this.service.replace('wss://', 'https://').replace('ws://', 'http://');\r\n\r\n return fetch(url)\r\n .then(response => {\r\n\r\n // skips header checking if there is no info in options\r\n if (!shard) {\r\n return;\r\n }\r\n\r\n const responseShard = response.headers.get('x-jitsi-shard');\r\n\r\n if (responseShard !== shard) {\r\n logger.error(\r\n `Detected that shard changed from ${shard} to ${responseShard}`);\r\n this.eventEmitter.emit(XmppConnection.Events.CONN_SHARD_CHANGED);\r\n }\r\n })\r\n .catch(error => {\r\n logger.error(`Websocket Keep alive failed for url: ${url}`, { error });\r\n });\r\n }\r\n\r\n /**\r\n * Goes over the list of {@link DeferredSendIQ} tasks and sends them.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _processDeferredIQs() {\r\n for (const deferred of this._deferredIQs) {\r\n if (deferred.iq) {\r\n clearTimeout(deferred.timeout);\r\n\r\n const timeLeft = Date.now() - deferred.start;\r\n\r\n this.sendIQ(\r\n deferred.iq,\r\n result => deferred.resolve(result),\r\n error => deferred.reject(error),\r\n timeLeft);\r\n }\r\n }\r\n\r\n this._deferredIQs = [];\r\n }\r\n\r\n /**\r\n * Send a stanza. This function is called to push data onto the send queue to go out over the wire.\r\n *\r\n * @param {Element|Strophe.Builder} stanza - The stanza to send.\r\n * @returns {void}\r\n */\r\n send(stanza) {\r\n if (!this.connected) {\r\n throw new Error('Not connected');\r\n }\r\n this._stropheConn.send(stanza);\r\n }\r\n\r\n /**\r\n * Helper function to send IQ stanzas.\r\n *\r\n * @param {Element} elem - The stanza to send.\r\n * @param {Function} callback - The callback function for a successful request.\r\n * @param {Function} errback - The callback function for a failed or timed out request. On timeout, the stanza will\r\n * be null.\r\n * @param {number} timeout - The time specified in milliseconds for a timeout to occur.\r\n * @returns {number} - The id used to send the IQ.\r\n */\r\n sendIQ(elem, callback, errback, timeout) {\r\n if (!this.connected) {\r\n errback('Not connected');\r\n\r\n return;\r\n }\r\n\r\n return this._stropheConn.sendIQ(elem, callback, errback, timeout);\r\n }\r\n\r\n /**\r\n * Sends an IQ immediately if connected or puts it on the send queue otherwise(in contrary to other send methods\r\n * which would fail immediately if disconnected).\r\n *\r\n * @param {Element} iq - The IQ to send.\r\n * @param {number} timeout - How long to wait for the response. The time when the connection is reconnecting is\r\n * included, which means that the IQ may never be sent and still fail with a timeout.\r\n */\r\n sendIQ2(iq, { timeout }) {\r\n return new Promise((resolve, reject) => {\r\n if (this.connected) {\r\n this.sendIQ(\r\n iq,\r\n result => resolve(result),\r\n error => reject(error),\r\n timeout);\r\n } else {\r\n const deferred = {\r\n iq,\r\n resolve,\r\n reject,\r\n start: Date.now(),\r\n timeout: setTimeout(() => {\r\n // clears the IQ on timeout and invalidates the deferred task\r\n deferred.iq = undefined;\r\n\r\n // Strophe calls with undefined on timeout\r\n reject(undefined);\r\n }, timeout)\r\n };\r\n\r\n this._deferredIQs.push(deferred);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Called by the ping plugin when ping fails too many times.\r\n *\r\n * @returns {void}\r\n */\r\n _onPingErrorThresholdExceeded() {\r\n if (this.isUsingWebSocket) {\r\n logger.warn('Ping error threshold exceeded - killing the WebSocket');\r\n this.closeWebsocket();\r\n }\r\n }\r\n\r\n /**\r\n * Helper function to send presence stanzas. The main benefit is for sending presence stanzas for which you expect\r\n * a responding presence stanza with the same id (for example when leaving a chat room).\r\n *\r\n * @param {Element} elem - The stanza to send.\r\n * @param {Function} callback - The callback function for a successful request.\r\n * @param {Function} errback - The callback function for a failed or timed out request. On timeout, the stanza will\r\n * be null.\r\n * @param {number} timeout - The time specified in milliseconds for a timeout to occur.\r\n * @returns {number} - The id used to send the presence.\r\n */\r\n sendPresence(elem, callback, errback, timeout) {\r\n if (!this.connected) {\r\n errback('Not connected');\r\n\r\n return;\r\n }\r\n this._stropheConn.sendPresence(elem, callback, errback, timeout);\r\n }\r\n\r\n /**\r\n * The method gracefully closes the BOSH connection by using 'navigator.sendBeacon'.\r\n *\r\n * @returns {boolean} - true if the beacon was sent.\r\n */\r\n sendUnavailableBeacon() {\r\n if (!navigator.sendBeacon || this._stropheConn.disconnecting || !this._stropheConn.connected) {\r\n return false;\r\n }\r\n\r\n this._stropheConn._changeConnectStatus(Strophe.Status.DISCONNECTING);\r\n this._stropheConn.disconnecting = true;\r\n\r\n const body = this._stropheConn._proto._buildBody()\r\n .attrs({\r\n type: 'terminate'\r\n });\r\n const pres = $pres({\r\n xmlns: Strophe.NS.CLIENT,\r\n type: 'unavailable'\r\n });\r\n\r\n body.cnode(pres.tree());\r\n\r\n const res = navigator.sendBeacon(\r\n this.service.indexOf('https://') === -1 ? `https:${this.service}` : this.service,\r\n Strophe.serialize(body.tree()));\r\n\r\n logger.info(`Successfully send unavailable beacon ${res}`);\r\n\r\n this._stropheConn._proto._abortAllRequests();\r\n this._stropheConn._doDisconnect();\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Tries to use stream management plugin to resume dropped XMPP connection. The streamManagement plugin clears\r\n * the resume token if any connection error occurs which would put it in unrecoverable state, so as long as\r\n * the token is present it means the connection can be resumed.\r\n *\r\n * @private\r\n * @returns {boolean}\r\n */\r\n _tryResumingConnection() {\r\n const { streamManagement } = this._stropheConn;\r\n const resumeToken = streamManagement && streamManagement.getResumeToken();\r\n\r\n if (resumeToken) {\r\n this._resumeTask.schedule();\r\n\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n}\r\n","export enum JitsiTranscriptionStatus {\r\n /**\r\n * The transcription is on.\r\n */\r\n ON = 'on',\r\n\r\n /**\r\n * The transcription is off.\r\n */\r\n OFF = 'off'\r\n}\r\n\r\n// exported for backward compatibility\r\nexport const ON = JitsiTranscriptionStatus.ON;\r\nexport const OFF = JitsiTranscriptionStatus.OFF;\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { $msg } from 'strophe.js';\r\n\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The AVModeration logic.\r\n */\r\nexport default class AVModeration {\r\n\r\n /**\r\n * Constructs AV moderation room.\r\n *\r\n * @param {ChatRoom} room the main room.\r\n */\r\n constructor(room) {\r\n this._xmpp = room.xmpp;\r\n\r\n this._mainRoom = room;\r\n\r\n this._moderationEnabledByType = {\r\n [MediaType.AUDIO]: false,\r\n [MediaType.VIDEO]: false\r\n };\r\n\r\n this._whitelistAudio = [];\r\n this._whitelistVideo = [];\r\n\r\n this._onMessage = this._onMessage.bind(this);\r\n this._xmpp.addListener(XMPPEvents.AV_MODERATION_RECEIVED, this._onMessage);\r\n }\r\n\r\n /**\r\n * Stops listening for events.\r\n */\r\n dispose() {\r\n this._xmpp.removeListener(XMPPEvents.AV_MODERATION_RECEIVED, this._onMessage);\r\n }\r\n\r\n /**\r\n * Whether AV moderation is supported on backend.\r\n *\r\n * @returns {boolean} whether AV moderation is supported on backend.\r\n */\r\n isSupported() {\r\n return Boolean(this._xmpp.avModerationComponentAddress);\r\n }\r\n\r\n /**\r\n * Enables or disables AV Moderation by sending a msg with command to the component.\r\n */\r\n enable(state, mediaType) {\r\n if (!this.isSupported() || !this._mainRoom.isModerator()) {\r\n logger.error(`Cannot enable:${state} AV moderation supported:${this.isSupported()},\r\n moderator:${this._mainRoom.isModerator()}`);\r\n\r\n return;\r\n }\r\n\r\n if (state === this._moderationEnabledByType[mediaType]) {\r\n logger.warn(`Moderation already in state:${state} for mediaType:${mediaType}`);\r\n\r\n return;\r\n }\r\n\r\n // send the enable/disable message\r\n const msg = $msg({ to: this._xmpp.avModerationComponentAddress });\r\n\r\n msg.c('av_moderation', {\r\n enable: state,\r\n mediaType\r\n }).up();\r\n\r\n this._xmpp.connection.send(msg);\r\n }\r\n\r\n /**\r\n * Approves that a participant can unmute by sending a msg with its jid to the component.\r\n */\r\n approve(mediaType, jid) {\r\n if (!this.isSupported() || !this._mainRoom.isModerator()) {\r\n logger.error(`Cannot approve in AV moderation supported:${this.isSupported()},\r\n moderator:${this._mainRoom.isModerator()}`);\r\n\r\n return;\r\n }\r\n\r\n // send a message to whitelist the jid and approve it to unmute\r\n const msg = $msg({ to: this._xmpp.avModerationComponentAddress });\r\n\r\n msg.c('av_moderation', {\r\n mediaType,\r\n jidToWhitelist: jid }).up();\r\n\r\n this._xmpp.connection.send(msg);\r\n }\r\n\r\n /**\r\n * Rejects that a participant can unmute by sending a msg with its jid to the component.\r\n */\r\n reject(mediaType, jid) {\r\n if (!this.isSupported() || !this._mainRoom.isModerator()) {\r\n logger.error(`Cannot reject in AV moderation supported:${this.isSupported()},\r\n moderator:${this._mainRoom.isModerator()}`);\r\n\r\n return;\r\n }\r\n\r\n // send a message to remove from whitelist the jid and reject it to unmute\r\n const msg = $msg({ to: this._xmpp.avModerationComponentAddress });\r\n\r\n msg.c('av_moderation', {\r\n mediaType,\r\n jidToBlacklist: jid\r\n }).up();\r\n\r\n this._xmpp.connection.send(msg);\r\n }\r\n\r\n /**\r\n * Receives av_moderation parsed messages as json.\r\n * @param obj the parsed json content of the message to process.\r\n * @private\r\n */\r\n _onMessage(obj) {\r\n const { removed, mediaType: media, enabled, approved, actor, whitelists: newWhitelists } = obj;\r\n\r\n if (newWhitelists) {\r\n const oldList = media === MediaType.AUDIO\r\n ? this._whitelistAudio\r\n : this._whitelistVideo;\r\n const newList = Array.isArray(newWhitelists[media]) ? newWhitelists[media] : [];\r\n\r\n if (removed) {\r\n oldList.filter(x => !newList.includes(x))\r\n .forEach(jid => this._xmpp.eventEmitter\r\n .emit(XMPPEvents.AV_MODERATION_PARTICIPANT_REJECTED, media, jid));\r\n } else {\r\n newList.filter(x => !oldList.includes(x))\r\n .forEach(jid => this._xmpp.eventEmitter\r\n .emit(XMPPEvents.AV_MODERATION_PARTICIPANT_APPROVED, media, jid));\r\n }\r\n\r\n if (media === MediaType.AUDIO) {\r\n this._whitelistAudio = newList;\r\n } else {\r\n this._whitelistVideo = newList;\r\n }\r\n } else if (enabled !== undefined && this._moderationEnabledByType[media] !== enabled) {\r\n this._moderationEnabledByType[media] = enabled;\r\n\r\n this._xmpp.eventEmitter.emit(XMPPEvents.AV_MODERATION_CHANGED, enabled, media, actor);\r\n } else if (removed) {\r\n this._xmpp.eventEmitter.emit(XMPPEvents.AV_MODERATION_REJECTED, media);\r\n } else if (approved) {\r\n this._xmpp.eventEmitter.emit(XMPPEvents.AV_MODERATION_APPROVED, media);\r\n }\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { $msg, Strophe } from 'strophe.js';\r\n\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\n\r\nconst FEATURE_KEY = 'features/breakout-rooms';\r\nconst BREAKOUT_ROOM_ACTIONS = {\r\n ADD: `${FEATURE_KEY}/add`,\r\n REMOVE: `${FEATURE_KEY}/remove`,\r\n MOVE_TO_ROOM: `${FEATURE_KEY}/move-to-room`\r\n};\r\nconst BREAKOUT_ROOM_EVENTS = {\r\n MOVE_TO_ROOM: `${FEATURE_KEY}/move-to-room`,\r\n UPDATE: `${FEATURE_KEY}/update`\r\n};\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Helper class for handling breakout rooms.\r\n */\r\nexport default class BreakoutRooms {\r\n\r\n /**\r\n * Constructs lobby room.\r\n *\r\n * @param {ChatRoom} room the room we are in.\r\n */\r\n constructor(room) {\r\n this.room = room;\r\n\r\n this._handleMessages = this._handleMessages.bind(this);\r\n this.room.xmpp.addListener(XMPPEvents.BREAKOUT_ROOMS_EVENT, this._handleMessages);\r\n\r\n this._rooms = {};\r\n }\r\n\r\n /**\r\n * Stops listening for events.\r\n */\r\n dispose() {\r\n this.room.xmpp.removeListener(XMPPEvents.BREAKOUT_ROOMS_EVENT, this._handleMessages);\r\n }\r\n\r\n /**\r\n * Creates a breakout room with the given subject.\r\n *\r\n * @param {string} subject - A subject for the breakout room.\r\n */\r\n createBreakoutRoom(subject) {\r\n if (!this.isSupported() || !this.room.isModerator()) {\r\n logger.error(`Cannot create breakout room - supported:${this.isSupported()},\r\n moderator:${this.room.isModerator()}`);\r\n\r\n return;\r\n }\r\n\r\n const message = {\r\n type: BREAKOUT_ROOM_ACTIONS.ADD,\r\n subject\r\n };\r\n\r\n this._sendMessage(message);\r\n }\r\n\r\n /**\r\n * Removes a breakout room.\r\n *\r\n * @param {string} breakoutRoomJid - JID of the room to be removed.\r\n */\r\n removeBreakoutRoom(breakoutRoomJid) {\r\n if (!this.isSupported() || !this.room.isModerator()) {\r\n logger.error(`Cannot remove breakout room - supported:${this.isSupported()},\r\n moderator:${this.room.isModerator()}`);\r\n\r\n return;\r\n }\r\n\r\n const message = {\r\n type: BREAKOUT_ROOM_ACTIONS.REMOVE,\r\n breakoutRoomJid\r\n };\r\n\r\n this._sendMessage(message);\r\n }\r\n\r\n /**\r\n * Sends the given participant to the given room.\r\n *\r\n * @param {string} participantJid - JID of the participant to be sent to a room.\r\n * @param {string} roomJid - JID of the target room.\r\n */\r\n sendParticipantToRoom(participantJid, roomJid) {\r\n if (!this.isSupported() || !this.room.isModerator()) {\r\n logger.error(`Cannot send participant to room - supported:${this.isSupported()},\r\n moderator:${this.room.isModerator()}`);\r\n\r\n return;\r\n }\r\n\r\n const message = {\r\n type: BREAKOUT_ROOM_ACTIONS.MOVE_TO_ROOM,\r\n participantJid,\r\n roomJid\r\n };\r\n\r\n this._sendMessage(message);\r\n }\r\n\r\n /**\r\n * Whether Breakout Rooms support is enabled in the backend or not.\r\n */\r\n isSupported() {\r\n return Boolean(this.getComponentAddress());\r\n }\r\n\r\n /**\r\n * Gets the address of the Breakout Rooms XMPP component.\r\n *\r\n * @returns The address of the component.\r\n */\r\n getComponentAddress() {\r\n return this.room.xmpp.breakoutRoomsComponentAddress;\r\n }\r\n\r\n /**\r\n * Stores if the current room is a breakout room.\r\n *\r\n * @param {boolean} isBreakoutRoom - Whether this room is a breakout room.\r\n */\r\n _setIsBreakoutRoom(isBreakoutRoom) {\r\n this._isBreakoutRoom = isBreakoutRoom;\r\n }\r\n\r\n /**\r\n * Checks whether this room is a breakout room.\r\n *\r\n * @returns True if the room is a breakout room, false otherwise.\r\n */\r\n isBreakoutRoom() {\r\n if (typeof this._isBreakoutRoom !== 'undefined') {\r\n return this._isBreakoutRoom;\r\n }\r\n\r\n // Use heuristic, helpful for checking in the MUC_JOINED event.\r\n return Strophe.getDomainFromJid(this.room.myroomjid) === this.getComponentAddress();\r\n }\r\n\r\n /**\r\n * Sets the main room JID associated with this breakout room. Only applies when\r\n * in a breakout room.\r\n *\r\n * @param {string} jid - The main room JID.\r\n */\r\n _setMainRoomJid(jid) {\r\n this._mainRoomJid = jid;\r\n }\r\n\r\n /**\r\n * Gets the main room's JID associated with this breakout room.\r\n *\r\n * @returns The main room JID.\r\n */\r\n getMainRoomJid() {\r\n return this._mainRoomJid;\r\n }\r\n\r\n /**\r\n * Handles a message for managing breakout rooms.\r\n *\r\n * @param {object} payload - Arbitrary data.\r\n */\r\n _handleMessages(payload) {\r\n switch (payload.event) {\r\n case BREAKOUT_ROOM_EVENTS.MOVE_TO_ROOM:\r\n this.room.eventEmitter.emit(XMPPEvents.BREAKOUT_ROOMS_MOVE_TO_ROOM, payload.roomJid);\r\n break;\r\n case BREAKOUT_ROOM_EVENTS.UPDATE: {\r\n this._rooms = payload.rooms;\r\n this.room.eventEmitter.emit(XMPPEvents.BREAKOUT_ROOMS_UPDATED, payload);\r\n break;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Helper to send a breakout rooms message to the component.\r\n *\r\n * @param {Object} message - Command that needs to be sent.\r\n */\r\n _sendMessage(message) {\r\n const msg = $msg({ to: this.getComponentAddress() });\r\n\r\n msg.c('breakout_rooms', message).up();\r\n\r\n this.room.xmpp.connection.send(msg);\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { $msg, Strophe } from 'strophe.js';\r\n\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The command type for updating a lobby participant's e-mail address.\r\n *\r\n * @type {string}\r\n */\r\nconst EMAIL_COMMAND = 'email';\r\n\r\n/**\r\n * The Lobby room implementation. Setting a room to members only, joining the lobby room\r\n * approving or denying access to participants from the lobby room.\r\n */\r\nexport default class Lobby {\r\n\r\n /**\r\n * Constructs lobby room.\r\n *\r\n * @param {ChatRoom} room the main room.\r\n */\r\n constructor(room) {\r\n this.xmpp = room.xmpp;\r\n this.mainRoom = room;\r\n\r\n const maybeJoinLobbyRoom = this._maybeJoinLobbyRoom.bind(this);\r\n\r\n this.mainRoom.addEventListener(\r\n XMPPEvents.LOCAL_ROLE_CHANGED,\r\n maybeJoinLobbyRoom);\r\n\r\n this.mainRoom.addEventListener(\r\n XMPPEvents.MUC_MEMBERS_ONLY_CHANGED,\r\n maybeJoinLobbyRoom);\r\n\r\n this.mainRoom.addEventListener(\r\n XMPPEvents.ROOM_CONNECT_MEMBERS_ONLY_ERROR,\r\n jid => {\r\n this.lobbyRoomJid = jid;\r\n });\r\n }\r\n\r\n /**\r\n * Whether lobby is supported on backend.\r\n *\r\n * @returns {boolean} whether lobby is supported on backend.\r\n */\r\n isSupported() {\r\n return this.xmpp.lobbySupported;\r\n }\r\n\r\n /**\r\n * Enables lobby by setting the main room to be members only and joins the lobby chat room.\r\n *\r\n * @returns {Promise}\r\n */\r\n enable() {\r\n if (!this.isSupported()) {\r\n return Promise.reject(new Error('Lobby not supported!'));\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.mainRoom.setMembersOnly(true, resolve, reject);\r\n });\r\n }\r\n\r\n /**\r\n * Disable lobby by setting the main room to be non members only and levaes the lobby chat room if joined.\r\n *\r\n * @returns {void}\r\n */\r\n disable() {\r\n if (!this.isSupported() || !this.mainRoom.isModerator()\r\n || !this.lobbyRoom || !this.mainRoom.membersOnlyEnabled) {\r\n return;\r\n }\r\n\r\n this.mainRoom.setMembersOnly(false);\r\n }\r\n\r\n /**\r\n * Broadcast a message to all participants in the lobby room\r\n * @param {Object} message The message to send\r\n *\r\n * @returns {void}\r\n */\r\n sendMessage(message) {\r\n if (this.lobbyRoom) {\r\n this.lobbyRoom.sendMessage(JSON.stringify(message), 'json-message');\r\n }\r\n }\r\n\r\n /**\r\n * Sends a private message to a participant in a lobby room.\r\n * @param {string} id The message to send\r\n * @param {Object} message The message to send\r\n *\r\n * @returns {void}\r\n */\r\n sendPrivateMessage(id, message) {\r\n if (this.lobbyRoom) {\r\n this.lobbyRoom.sendPrivateMessage(id, JSON.stringify(message), 'json-message');\r\n }\r\n }\r\n\r\n /**\r\n * Gets the local id for a participant in a lobby room.\r\n * This is used for lobby room private chat messages.\r\n *\r\n * @returns {string}\r\n */\r\n getLocalId() {\r\n if (this.lobbyRoom) {\r\n return Strophe.getResourceFromJid(this.lobbyRoom.myroomjid);\r\n }\r\n }\r\n\r\n /**\r\n * Adds a message listener to the lobby room.\r\n * @param {Function} listener The listener function,\r\n * called when a new message is received in the lobby room.\r\n *\r\n * @returns {Function} Handler returned to be able to remove it later.\r\n */\r\n addMessageListener(listener) {\r\n if (this.lobbyRoom) {\r\n const handler = (participantId, message) => {\r\n listener(message, Strophe.getResourceFromJid(participantId));\r\n };\r\n\r\n this.lobbyRoom.on(XMPPEvents.JSON_MESSAGE_RECEIVED, handler);\r\n\r\n return handler;\r\n }\r\n }\r\n\r\n /**\r\n * Remove a message handler from the lobby room.\r\n * @param {Function} handler The handler function to remove.\r\n *\r\n * @returns {void}\r\n */\r\n removeMessageHandler(handler) {\r\n if (this.lobbyRoom) {\r\n this.lobbyRoom.off(XMPPEvents.JSON_MESSAGE_RECEIVED, handler);\r\n }\r\n }\r\n\r\n /**\r\n * Leaves the lobby room.\r\n *\r\n * @returns {Promise}\r\n */\r\n leave() {\r\n if (this.lobbyRoom) {\r\n return this.lobbyRoom.leave()\r\n .then(() => {\r\n this.lobbyRoom = undefined;\r\n logger.info('Lobby room left!');\r\n })\r\n .catch(() => {}); // eslint-disable-line no-empty-function\r\n }\r\n\r\n return Promise.reject(\r\n new Error('The lobby has already been left'));\r\n }\r\n\r\n /**\r\n * We had received a jid for the lobby room.\r\n *\r\n * @param jid the lobby room jid to join.\r\n */\r\n setLobbyRoomJid(jid) {\r\n this.lobbyRoomJid = jid;\r\n }\r\n\r\n /**\r\n * Checks the state of mainRoom, lobbyRoom and current user role to decide whether to join lobby room.\r\n * @private\r\n */\r\n _maybeJoinLobbyRoom() {\r\n if (!this.isSupported()) {\r\n return;\r\n }\r\n\r\n const isModerator = this.mainRoom.joined && this.mainRoom.isModerator();\r\n\r\n if (isModerator && this.mainRoom.membersOnlyEnabled && !this.lobbyRoom) {\r\n // join the lobby\r\n this.join()\r\n .then(() => logger.info('Joined lobby room'))\r\n .catch(e => logger.error('Failed joining lobby', e));\r\n }\r\n }\r\n\r\n /**\r\n * Joins a lobby room setting display name and eventually avatar(using the email provided).\r\n *\r\n * @param {string} username is required.\r\n * @param {string} email is optional.\r\n * @returns {Promise} resolves once we join the room.\r\n */\r\n join(displayName, email) {\r\n const isModerator = this.mainRoom.joined && this.mainRoom.isModerator();\r\n\r\n if (!this.lobbyRoomJid) {\r\n return Promise.reject(new Error('Missing lobbyRoomJid, cannot join lobby room.'));\r\n }\r\n\r\n const roomName = Strophe.getNodeFromJid(this.lobbyRoomJid);\r\n const customDomain = Strophe.getDomainFromJid(this.lobbyRoomJid);\r\n\r\n this.lobbyRoom = this.xmpp.createRoom(\r\n roomName, {\r\n customDomain,\r\n disableDiscoInfo: true,\r\n disableFocus: true,\r\n enableLobby: false\r\n }\r\n );\r\n\r\n if (displayName) {\r\n // remove previously set nickname\r\n this.lobbyRoom.addOrReplaceInPresence('nick', {\r\n attributes: { xmlns: 'http://jabber.org/protocol/nick' },\r\n value: displayName\r\n });\r\n }\r\n\r\n if (isModerator) {\r\n this.lobbyRoom.addPresenceListener(EMAIL_COMMAND, (node, from) => {\r\n this.mainRoom.eventEmitter.emit(XMPPEvents.MUC_LOBBY_MEMBER_UPDATED, from, { email: node.value });\r\n });\r\n this.lobbyRoom.addEventListener(\r\n XMPPEvents.MUC_MEMBER_JOINED,\r\n // eslint-disable-next-line max-params\r\n (from, nick, role, isHiddenDomain, statsID, status, identity, botType, jid) => {\r\n // we need to ignore joins on lobby for participants that are already in the main room\r\n if (Object.values(this.mainRoom.members).find(m => m.jid === jid)) {\r\n return;\r\n }\r\n\r\n // Check if the user is a member if any breakout room.\r\n for (const room of Object.values(this.mainRoom.getBreakoutRooms()._rooms)) {\r\n if (Object.values(room.participants).find(p => p.jid === jid)) {\r\n return;\r\n }\r\n }\r\n\r\n // we emit the new event on the main room so we can propagate\r\n // events to the conference\r\n this.mainRoom.eventEmitter.emit(\r\n XMPPEvents.MUC_LOBBY_MEMBER_JOINED,\r\n Strophe.getResourceFromJid(from),\r\n nick,\r\n identity ? identity.avatar : undefined\r\n );\r\n });\r\n this.lobbyRoom.addEventListener(\r\n XMPPEvents.MUC_MEMBER_LEFT, from => {\r\n // we emit the new event on the main room so we can propagate\r\n // events to the conference\r\n this.mainRoom.eventEmitter.emit(\r\n XMPPEvents.MUC_LOBBY_MEMBER_LEFT,\r\n Strophe.getResourceFromJid(from)\r\n );\r\n });\r\n this.lobbyRoom.addEventListener(\r\n XMPPEvents.MUC_DESTROYED,\r\n () => {\r\n // let's make sure we emit that all lobby users had left\r\n Object.keys(this.lobbyRoom.members)\r\n .forEach(j => this.mainRoom.eventEmitter.emit(\r\n XMPPEvents.MUC_LOBBY_MEMBER_LEFT, Strophe.getResourceFromJid(j)));\r\n\r\n this.lobbyRoom.clean();\r\n\r\n this.lobbyRoom = undefined;\r\n logger.info('Lobby room left(destroyed)!');\r\n });\r\n } else {\r\n // this should only be handled by those waiting in lobby\r\n this.lobbyRoom.addEventListener(XMPPEvents.KICKED, isSelfPresence => {\r\n if (isSelfPresence) {\r\n this.mainRoom.eventEmitter.emit(XMPPEvents.MUC_DENIED_ACCESS);\r\n\r\n this.lobbyRoom.clean();\r\n\r\n return;\r\n }\r\n });\r\n\r\n // As there is still reference of the main room\r\n // the invite will be detected and addressed to its eventEmitter, even though we are not in it\r\n // the invite message should be received directly to the xmpp conn in general\r\n this.mainRoom.addEventListener(\r\n XMPPEvents.INVITE_MESSAGE_RECEIVED,\r\n (roomJid, from, txt, invitePassword) => {\r\n logger.debug(`Received approval to join ${roomJid} ${from} ${txt}`);\r\n if (roomJid === this.mainRoom.roomjid) {\r\n // we are now allowed, so let's join\r\n this.mainRoom.join(invitePassword);\r\n }\r\n });\r\n this.lobbyRoom.addEventListener(\r\n XMPPEvents.MUC_DESTROYED,\r\n (reason, jid) => {\r\n // we are receiving the jid of the main room\r\n // means we are invited to join, maybe lobby was disabled\r\n if (jid) {\r\n this.mainRoom.join();\r\n\r\n return;\r\n }\r\n\r\n this.lobbyRoom.clean();\r\n\r\n this.mainRoom.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);\r\n });\r\n\r\n // If participant retries joining shared password while waiting in the lobby\r\n // and succeeds make sure we leave lobby\r\n this.mainRoom.addEventListener(\r\n XMPPEvents.MUC_JOINED,\r\n () => {\r\n this.leave();\r\n });\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.lobbyRoom.addEventListener(XMPPEvents.MUC_JOINED, () => {\r\n resolve();\r\n\r\n // send our email, as we do not handle this on initial presence we need a second one\r\n if (email && !isModerator) {\r\n this.lobbyRoom.addOrReplaceInPresence(EMAIL_COMMAND, { value: email })\r\n && this.lobbyRoom.sendPresence();\r\n }\r\n });\r\n this.lobbyRoom.addEventListener(XMPPEvents.ROOM_JOIN_ERROR, reject);\r\n this.lobbyRoom.addEventListener(XMPPEvents.ROOM_CONNECT_NOT_ALLOWED_ERROR, reject);\r\n this.lobbyRoom.addEventListener(XMPPEvents.ROOM_CONNECT_ERROR, reject);\r\n\r\n this.lobbyRoom.join();\r\n });\r\n\r\n }\r\n\r\n /**\r\n * Should be possible only for moderators.\r\n * @param id\r\n */\r\n denyAccess(id) {\r\n if (!this.isSupported() || !this.mainRoom.isModerator()) {\r\n return;\r\n }\r\n\r\n const jid = Object.keys(this.lobbyRoom.members)\r\n .find(j => Strophe.getResourceFromJid(j) === id);\r\n\r\n if (jid) {\r\n this.lobbyRoom.kick(jid);\r\n } else {\r\n logger.error(`Not found member for ${id} in lobby room.`);\r\n }\r\n }\r\n\r\n /**\r\n * Should be possible only for moderators.\r\n * @param id\r\n */\r\n approveAccess(id) {\r\n if (!this.isSupported() || !this.mainRoom.isModerator()) {\r\n return;\r\n }\r\n\r\n // Get the main room JID. If we are in a breakout room we'll use the main\r\n // room's lobby.\r\n let mainRoomJid = this.mainRoom.roomjid;\r\n\r\n if (this.mainRoom.getBreakoutRooms().isBreakoutRoom()) {\r\n mainRoomJid = this.mainRoom.getBreakoutRooms().getMainRoomJid();\r\n }\r\n\r\n const memberRoomJid = Object.keys(this.lobbyRoom.members)\r\n .find(j => Strophe.getResourceFromJid(j) === id);\r\n\r\n if (memberRoomJid) {\r\n const jid = this.lobbyRoom.members[memberRoomJid].jid;\r\n const msgToSend\r\n = $msg({ to: mainRoomJid })\r\n .c('x', { xmlns: 'http://jabber.org/protocol/muc#user' })\r\n .c('invite', { to: jid });\r\n\r\n this.xmpp.connection.sendIQ(msgToSend,\r\n () => { }, // eslint-disable-line no-empty-function\r\n e => {\r\n logger.error(`Error sending invite for ${jid}`, e);\r\n });\r\n } else {\r\n logger.error(`Not found member for ${memberRoomJid} in lobby room.`);\r\n }\r\n }\r\n}\r\n","import { jitsiLocalStorage } from '@jitsi/js-utils';\r\nimport { getLogger } from '@jitsi/logger';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\nimport UsernameGenerator from '../util/UsernameGenerator';\r\n\r\nlet _callStatsUserName;\r\n\r\nlet _machineId;\r\n\r\n/**\r\n *\r\n */\r\nexport default {\r\n\r\n /**\r\n * The storage used to store the settings.\r\n */\r\n _storage: jitsiLocalStorage,\r\n\r\n /**\r\n * Initializes the Settings class.\r\n *\r\n * @param {Storage|undefined} externalStorage - Object that implements the Storage interface. This object will be\r\n * used for storing data instead of jitsiLocalStorage if specified.\r\n */\r\n init(externalStorage) {\r\n this._storage = externalStorage || jitsiLocalStorage;\r\n },\r\n\r\n /**\r\n * Returns fake username for callstats\r\n * @returns {string} fake username for callstats\r\n */\r\n get callStatsUserName() {\r\n if (!_callStatsUserName) {\r\n _callStatsUserName = this._storage.getItem('callStatsUserName');\r\n if (!_callStatsUserName) {\r\n _callStatsUserName = generateCallStatsUserName();\r\n this._storage.setItem('callStatsUserName', _callStatsUserName);\r\n }\r\n }\r\n\r\n return _callStatsUserName;\r\n },\r\n\r\n /**\r\n * Returns current machine id.\r\n * @returns {string} machine id\r\n */\r\n get machineId() {\r\n if (!_machineId) {\r\n const amDid = this._storage.getItem('billingId');\r\n\r\n _machineId = amDid || this._storage.getItem('jitsiMeetId');\r\n\r\n if (amDid) {\r\n this._storage.setItem('jitsiMeetId', amDid);\r\n } else if (!_machineId) {\r\n _machineId = generateJitsiMeetId();\r\n this._storage.setItem('jitsiMeetId', _machineId);\r\n }\r\n }\r\n\r\n return _machineId;\r\n },\r\n\r\n /**\r\n * Returns current session id.\r\n * @returns {string} current session id\r\n */\r\n get sessionId() {\r\n // We may update sessionId in localStorage from another JitsiConference\r\n // instance and that's why we should always re-read it.\r\n return this._storage.getItem('sessionId');\r\n },\r\n\r\n /**\r\n * Save current session id.\r\n * @param {string} sessionId session id\r\n */\r\n set sessionId(sessionId) {\r\n if (sessionId) {\r\n this._storage.setItem('sessionId', sessionId);\r\n } else {\r\n this._storage.removeItem('sessionId');\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Generate fake username for callstats.\r\n * @returns {string} fake random username\r\n */\r\nfunction generateCallStatsUserName() {\r\n const username = UsernameGenerator.generateUsername();\r\n\r\n logger.log('generated callstats uid', username);\r\n\r\n return username;\r\n}\r\n\r\n/**\r\n * Generate unique id.\r\n * @returns {string} random unique id\r\n */\r\nfunction generateJitsiMeetId() {\r\n const jitsiMeetId = generateUniqueId();\r\n\r\n logger.log('generated id', jitsiMeetId);\r\n\r\n return jitsiMeetId;\r\n}\r\n\r\n/**\r\n *\r\n */\r\nfunction generateUniqueId() {\r\n return _p8() + _p8() + _p8() + _p8();\r\n}\r\n\r\n/**\r\n *\r\n */\r\nfunction _p8() {\r\n return `${Math.random().toString(16)}000000000`.substr(2, 8);\r\n}\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $iq, Strophe } from 'strophe.js';\r\n\r\nimport Settings from '../settings/Settings';\r\n\r\nconst AuthenticationEvents\r\n = require('../../service/authentication/AuthenticationEvents');\r\nconst { XMPPEvents } = require('../../service/xmpp/XMPPEvents');\r\nconst GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n *\r\n * @param step\r\n */\r\nfunction createExpBackoffTimer(step) {\r\n let count = 1;\r\n\r\n return function(reset) {\r\n // Reset call\r\n if (reset) {\r\n count = 1;\r\n\r\n return;\r\n }\r\n\r\n // Calculate next timeout\r\n const timeout = Math.pow(2, count - 1);\r\n\r\n count += 1;\r\n\r\n return timeout * step;\r\n };\r\n}\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n *\r\n * @param roomName\r\n * @param xmpp\r\n * @param emitter\r\n * @param options\r\n */\r\nexport default function Moderator(roomName, xmpp, emitter, options) {\r\n this.roomName = roomName;\r\n this.xmppService = xmpp;\r\n this.getNextTimeout = createExpBackoffTimer(1000);\r\n this.getNextErrorTimeout = createExpBackoffTimer(1000);\r\n\r\n // External authentication stuff\r\n this.externalAuthEnabled = false;\r\n this.options = options;\r\n\r\n // Whether SIP gateway (jigasi) support is enabled. This is set\r\n // based on conference properties received in presence.\r\n this.sipGatewayEnabled = false;\r\n\r\n this.eventEmitter = emitter;\r\n\r\n this.connection = this.xmppService.connection;\r\n\r\n // FIXME: Message listener that talks to POPUP window\r\n /**\r\n *\r\n * @param event\r\n */\r\n function listener(event) {\r\n if (event.data && event.data.sessionId) {\r\n if (event.origin !== window.location.origin) {\r\n logger.warn(\r\n `Ignoring sessionId from different origin: ${\r\n event.origin}`);\r\n\r\n return;\r\n }\r\n Settings.sessionId = event.data.sessionId;\r\n\r\n // After popup is closed we will authenticate\r\n }\r\n }\r\n\r\n // Register\r\n if (window.addEventListener) {\r\n window.addEventListener('message', listener, false);\r\n } else {\r\n window.attachEvent('onmessage', listener);\r\n }\r\n}\r\n\r\n/* eslint-enable max-params */\r\n\r\nModerator.prototype.isExternalAuthEnabled = function() {\r\n return this.externalAuthEnabled;\r\n};\r\n\r\nModerator.prototype.isSipGatewayEnabled = function() {\r\n return this.sipGatewayEnabled;\r\n};\r\n\r\nModerator.prototype.onMucMemberLeft = function(jid) {\r\n const resource = Strophe.getResourceFromJid(jid);\r\n\r\n if (resource === 'focus') {\r\n logger.info(\r\n 'Focus has left the room - leaving conference');\r\n this.eventEmitter.emit(XMPPEvents.FOCUS_LEFT);\r\n }\r\n};\r\n\r\nModerator.prototype.setFocusUserJid = function(focusJid) {\r\n if (!this.focusUserJid) {\r\n this.focusUserJid = focusJid;\r\n logger.info(`Focus jid set to: ${this.focusUserJid}`);\r\n }\r\n};\r\n\r\nModerator.prototype.getFocusUserJid = function() {\r\n return this.focusUserJid;\r\n};\r\n\r\nModerator.prototype.getFocusComponent = function() {\r\n // Get focus component address\r\n let focusComponent = this.options.connection.hosts.focus;\r\n\r\n // If not specified use default: 'focus.domain'\r\n\r\n if (!focusComponent) {\r\n focusComponent = `focus.${this.options.connection.hosts.domain}`;\r\n }\r\n\r\n return focusComponent;\r\n};\r\n\r\nModerator.prototype.createConferenceIq = function() {\r\n // Generate create conference IQ\r\n const elem = $iq({ to: this.getFocusComponent(),\r\n type: 'set' });\r\n\r\n // Session Id used for authentication\r\n const { sessionId } = Settings;\r\n const machineUID = Settings.machineId;\r\n const config = this.options.conference;\r\n\r\n logger.info(`Session ID: ${sessionId} machine UID: ${machineUID}`);\r\n\r\n elem.c('conference', {\r\n xmlns: 'http://jitsi.org/protocol/focus',\r\n room: this.roomName,\r\n 'machine-uid': machineUID\r\n });\r\n\r\n if (sessionId) {\r\n elem.attrs({ 'session-id': sessionId });\r\n }\r\n\r\n elem.c(\r\n 'property', {\r\n name: 'disableRtx',\r\n value: Boolean(config.disableRtx)\r\n }).up();\r\n\r\n if (config.audioPacketDelay !== undefined) {\r\n elem.c(\r\n 'property', {\r\n name: 'audioPacketDelay',\r\n value: config.audioPacketDelay\r\n }).up();\r\n }\r\n if (config.startBitrate) {\r\n elem.c(\r\n 'property', {\r\n name: 'startBitrate',\r\n value: config.startBitrate\r\n }).up();\r\n }\r\n if (config.minBitrate) {\r\n elem.c(\r\n 'property', {\r\n name: 'minBitrate',\r\n value: config.minBitrate\r\n }).up();\r\n }\r\n\r\n if (this.options.conference.startAudioMuted !== undefined) {\r\n elem.c(\r\n 'property', {\r\n name: 'startAudioMuted',\r\n value: this.options.conference.startAudioMuted\r\n }).up();\r\n }\r\n if (this.options.conference.startVideoMuted !== undefined) {\r\n elem.c(\r\n 'property', {\r\n name: 'startVideoMuted',\r\n value: this.options.conference.startVideoMuted\r\n }).up();\r\n }\r\n\r\n // this flag determines whether the bridge will include this call in its\r\n // rtcstats reporting or not. If the site admin hasn't set the flag in\r\n // config.js, then the client defaults to false (see\r\n // react/features/rtcstats/functions.js in jitsi-meet). The server-side\r\n // components default to true to match the pre-existing behavior so we only\r\n // signal if false.\r\n const rtcstatsEnabled = this.options.conference?.analytics?.rtcstatsEnabled ?? false;\r\n\r\n if (!rtcstatsEnabled) {\r\n elem.c(\r\n 'property', {\r\n name: 'rtcstatsEnabled',\r\n value: rtcstatsEnabled\r\n }).up();\r\n }\r\n\r\n const { callStatsID, callStatsSecret, disableThirdPartyRequests, enableCallStats } = this.options.conference;\r\n const callstatsDisabled = !callStatsID || !callStatsSecret || !enableCallStats\r\n\r\n // Even though AppID and AppSecret may be specified, the integration\r\n // of callstats.io may be disabled because of globally-disallowed\r\n // requests to any third parties.\r\n || disableThirdPartyRequests === true;\r\n\r\n // since the default is true across all the server-side components, only signal if false.\r\n if (callstatsDisabled) {\r\n elem.c(\r\n 'property', {\r\n name: 'callstatsEnabled',\r\n value: !callstatsDisabled\r\n }).up();\r\n }\r\n elem.up();\r\n\r\n return elem;\r\n};\r\n\r\n\r\nModerator.prototype.parseSessionId = function(resultIq) {\r\n // eslint-disable-next-line newline-per-chained-call\r\n const sessionId = $(resultIq).find('conference').attr('session-id');\r\n\r\n if (sessionId) {\r\n logger.info(`Received sessionId: ${sessionId}`);\r\n Settings.sessionId = sessionId;\r\n }\r\n};\r\n\r\nModerator.prototype.parseConfigOptions = function(resultIq) {\r\n // eslint-disable-next-line newline-per-chained-call\r\n this.setFocusUserJid($(resultIq).find('conference').attr('focusjid'));\r\n\r\n const authenticationEnabled\r\n = $(resultIq).find(\r\n '>conference>property'\r\n + '[name=\\'authentication\\'][value=\\'true\\']').length > 0;\r\n\r\n logger.info(`Authentication enabled: ${authenticationEnabled}`);\r\n\r\n this.externalAuthEnabled = $(resultIq).find(\r\n '>conference>property'\r\n + '[name=\\'externalAuth\\'][value=\\'true\\']').length > 0;\r\n\r\n logger.info(\r\n `External authentication enabled: ${this.externalAuthEnabled}`);\r\n\r\n if (!this.externalAuthEnabled) {\r\n // We expect to receive sessionId in 'internal' authentication mode\r\n this.parseSessionId(resultIq);\r\n }\r\n\r\n // eslint-disable-next-line newline-per-chained-call\r\n const authIdentity = $(resultIq).find('>conference').attr('identity');\r\n\r\n this.eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,\r\n authenticationEnabled, authIdentity);\r\n\r\n // Check if jicofo has jigasi support enabled.\r\n if ($(resultIq).find(\r\n '>conference>property'\r\n + '[name=\\'sipGatewayEnabled\\'][value=\\'true\\']').length) {\r\n this.sipGatewayEnabled = true;\r\n }\r\n\r\n logger.info(`Sip gateway enabled: ${this.sipGatewayEnabled}`);\r\n};\r\n\r\n// FIXME We need to show the fact that we're waiting for the focus to the user\r\n// (or that the focus is not available)\r\n/**\r\n * Allocates the conference focus.\r\n *\r\n * @param {Function} callback - the function to be called back upon the\r\n * successful allocation of the conference focus\r\n * @returns {Promise} - Resolved when Jicofo allows to join the room. It's never\r\n * rejected and it'll keep on pinging Jicofo forever.\r\n */\r\nModerator.prototype.allocateConferenceFocus = function() {\r\n return new Promise(resolve => {\r\n // Try to use focus user JID from the config\r\n this.setFocusUserJid(this.options.connection.focusUserJid);\r\n\r\n // Send create conference IQ\r\n this.connection.sendIQ(\r\n this.createConferenceIq(),\r\n result => this._allocateConferenceFocusSuccess(result, resolve),\r\n error => this._allocateConferenceFocusError(error, resolve));\r\n\r\n // XXX We're pressed for time here because we're beginning a complex\r\n // and/or lengthy conference-establishment process which supposedly\r\n // involves multiple RTTs. We don't have the time to wait for Strophe to\r\n // decide to send our IQ.\r\n this.connection.flush();\r\n });\r\n};\r\n\r\n/**\r\n * Invoked by {@link #allocateConferenceFocus} upon its request receiving an\r\n * error result.\r\n *\r\n * @param error - the error result of the request that\r\n * {@link #allocateConferenceFocus} sent\r\n * @param {Function} callback - the function to be called back upon the\r\n * successful allocation of the conference focus\r\n */\r\nModerator.prototype._allocateConferenceFocusError = function(error, callback) {\r\n // If the session is invalid, remove and try again without session ID to get\r\n // a new one\r\n const invalidSession\r\n = $(error).find('>error>session-invalid').length\r\n || $(error).find('>error>not-acceptable').length;\r\n\r\n if (invalidSession) {\r\n logger.info('Session expired! - removing');\r\n Settings.sessionId = undefined;\r\n }\r\n if ($(error).find('>error>graceful-shutdown').length) {\r\n this.eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);\r\n\r\n return;\r\n }\r\n\r\n // Check for error returned by the reservation system\r\n const reservationErr = $(error).find('>error>reservation-error');\r\n\r\n if (reservationErr.length) {\r\n // Trigger error event\r\n const errorCode = reservationErr.attr('error-code');\r\n const errorTextNode = $(error).find('>error>text');\r\n let errorMsg;\r\n\r\n if (errorTextNode) {\r\n errorMsg = errorTextNode.text();\r\n }\r\n this.eventEmitter.emit(\r\n XMPPEvents.RESERVATION_ERROR,\r\n errorCode,\r\n errorMsg);\r\n\r\n return;\r\n }\r\n\r\n // Not authorized to create new room\r\n if ($(error).find('>error>not-authorized').length) {\r\n logger.warn('Unauthorized to start the conference', error);\r\n const toDomain = Strophe.getDomainFromJid(error.getAttribute('to'));\r\n\r\n if (toDomain !== this.options.connection.hosts.anonymousdomain) {\r\n // FIXME \"is external\" should come either from the focus or\r\n // config.js\r\n this.externalAuthEnabled = true;\r\n }\r\n this.eventEmitter.emit(XMPPEvents.AUTHENTICATION_REQUIRED);\r\n\r\n return;\r\n }\r\n const waitMs = this.getNextErrorTimeout();\r\n const errmsg = `Focus error, retry after ${waitMs}`;\r\n\r\n GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));\r\n logger.error(errmsg, error);\r\n\r\n // Show message\r\n const focusComponent = this.getFocusComponent();\r\n const retrySec = waitMs / 1000;\r\n\r\n // FIXME: message is duplicated ? Do not show in case of session invalid\r\n // which means just a retry\r\n\r\n if (!invalidSession) {\r\n this.eventEmitter.emit(\r\n XMPPEvents.FOCUS_DISCONNECTED,\r\n focusComponent,\r\n retrySec);\r\n }\r\n\r\n // Reset response timeout\r\n this.getNextTimeout(true);\r\n window.setTimeout(\r\n () => this.allocateConferenceFocus().then(callback),\r\n waitMs);\r\n};\r\n\r\n/**\r\n * Invoked by {@link #allocateConferenceFocus} upon its request receiving a\r\n * success (i.e. non-error) result.\r\n *\r\n * @param result - the success (i.e. non-error) result of the request that\r\n * {@link #allocateConferenceFocus} sent\r\n * @param {Function} callback - the function to be called back upon the\r\n * successful allocation of the conference focus\r\n */\r\nModerator.prototype._allocateConferenceFocusSuccess = function(\r\n result,\r\n callback) {\r\n // Setup config options\r\n this.parseConfigOptions(result);\r\n\r\n // Reset the error timeout (because we haven't failed here).\r\n this.getNextErrorTimeout(true);\r\n\r\n // eslint-disable-next-line newline-per-chained-call\r\n if ($(result).find('conference').attr('ready') === 'true') {\r\n // Reset the non-error timeout (because we've succeeded here).\r\n this.getNextTimeout(true);\r\n\r\n // Exec callback\r\n callback();\r\n } else {\r\n const waitMs = this.getNextTimeout();\r\n\r\n logger.info(`Waiting for the focus... ${waitMs}`);\r\n window.setTimeout(\r\n () => this.allocateConferenceFocus().then(callback),\r\n waitMs);\r\n }\r\n};\r\n\r\nModerator.prototype.authenticate = function() {\r\n return new Promise((resolve, reject) => {\r\n this.connection.sendIQ(\r\n this.createConferenceIq(),\r\n result => {\r\n this.parseSessionId(result);\r\n resolve();\r\n },\r\n errorIq => reject({\r\n error: $(errorIq).find('iq>error :first')\r\n .prop('tagName'),\r\n message: $(errorIq).find('iq>error>text')\r\n .text()\r\n })\r\n );\r\n });\r\n};\r\n\r\nModerator.prototype.getLoginUrl = function(urlCallback, failureCallback) {\r\n this._getLoginUrl(/* popup */ false, urlCallback, failureCallback);\r\n};\r\n\r\n/**\r\n *\r\n * @param {boolean} popup false for {@link Moderator#getLoginUrl} or true for\r\n * {@link Moderator#getPopupLoginUrl}\r\n * @param urlCb\r\n * @param failureCb\r\n */\r\nModerator.prototype._getLoginUrl = function(popup, urlCb, failureCb) {\r\n const iq = $iq({ to: this.getFocusComponent(),\r\n type: 'get' });\r\n const attrs = {\r\n xmlns: 'http://jitsi.org/protocol/focus',\r\n room: this.roomName,\r\n 'machine-uid': Settings.machineId\r\n };\r\n let str = 'auth url'; // for logger\r\n\r\n if (popup) {\r\n attrs.popup = true;\r\n str = `POPUP ${str}`;\r\n }\r\n iq.c('login-url', attrs);\r\n\r\n /**\r\n * Implements a failure callback which reports an error message and an error\r\n * through (1) GlobalOnErrorHandler, (2) logger, and (3) failureCb.\r\n *\r\n * @param {string} errmsg the error messsage to report\r\n * @param {*} error the error to report (in addition to errmsg)\r\n */\r\n function reportError(errmsg, err) {\r\n GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));\r\n logger.error(errmsg, err);\r\n failureCb(err);\r\n }\r\n this.connection.sendIQ(\r\n iq,\r\n result => {\r\n // eslint-disable-next-line newline-per-chained-call\r\n let url = $(result).find('login-url').attr('url');\r\n\r\n url = decodeURIComponent(url);\r\n if (url) {\r\n logger.info(`Got ${str}: ${url}`);\r\n urlCb(url);\r\n } else {\r\n reportError(`Failed to get ${str} from the focus`, result);\r\n }\r\n },\r\n reportError.bind(undefined, `Get ${str} error`)\r\n );\r\n};\r\n\r\nModerator.prototype.getPopupLoginUrl = function(urlCallback, failureCallback) {\r\n this._getLoginUrl(/* popup */ true, urlCallback, failureCallback);\r\n};\r\n\r\nModerator.prototype.logout = function(callback) {\r\n const iq = $iq({ to: this.getFocusComponent(),\r\n type: 'set' });\r\n const { sessionId } = Settings;\r\n\r\n if (!sessionId) {\r\n callback();\r\n\r\n return;\r\n }\r\n iq.c('logout', {\r\n xmlns: 'http://jitsi.org/protocol/focus',\r\n 'session-id': sessionId\r\n });\r\n this.connection.sendIQ(\r\n iq,\r\n result => {\r\n // eslint-disable-next-line newline-per-chained-call\r\n let logoutUrl = $(result).find('logout').attr('logout-url');\r\n\r\n if (logoutUrl) {\r\n logoutUrl = decodeURIComponent(logoutUrl);\r\n }\r\n logger.info(`Log out OK, url: ${logoutUrl}`, result);\r\n Settings.sessionId = undefined;\r\n callback(logoutUrl);\r\n },\r\n error => {\r\n const errmsg = 'Logout error';\r\n\r\n GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));\r\n logger.error(errmsg, error);\r\n }\r\n );\r\n};\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport isEqual from 'lodash.isequal';\r\nimport { $iq, $msg, $pres, Strophe } from 'strophe.js';\r\n\r\nimport * as JitsiTranscriptionStatus from '../../JitsiTranscriptionStatus';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\nimport Listenable from '../util/Listenable';\r\n\r\nimport AVModeration from './AVModeration';\r\nimport BreakoutRooms from './BreakoutRooms';\r\nimport Lobby from './Lobby';\r\nimport XmppConnection from './XmppConnection';\r\nimport Moderator from './moderator';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\nexport const parser = {\r\n packet2JSON(xmlElement, nodes) {\r\n for (const child of Array.from(xmlElement.children)) {\r\n const node = {\r\n attributes: {},\r\n children: [],\r\n tagName: child.tagName\r\n };\r\n\r\n for (const attr of Array.from(child.attributes)) {\r\n node.attributes[attr.name] = attr.value;\r\n }\r\n const text = Strophe.getText(child);\r\n\r\n if (text) {\r\n // Using Strophe.getText will do work for traversing all direct\r\n // child text nodes but returns an escaped value, which is not\r\n // desirable at this point.\r\n node.value = Strophe.xmlunescape(text);\r\n }\r\n nodes.push(node);\r\n this.packet2JSON(child, node.children);\r\n }\r\n },\r\n json2packet(nodes, packet) {\r\n for (let i = 0; i < nodes.length; i++) {\r\n const node = nodes[i];\r\n\r\n if (node) {\r\n packet.c(node.tagName, node.attributes);\r\n if (node.value) {\r\n packet.t(node.value);\r\n }\r\n if (node.children) {\r\n this.json2packet(node.children, packet);\r\n }\r\n packet.up();\r\n }\r\n }\r\n\r\n // packet.up();\r\n }\r\n};\r\n\r\n/**\r\n * Returns array of JS objects from the presence JSON associated with the passed\r\n / nodeName\r\n * @param pres the presence JSON\r\n * @param nodeName the name of the node (videomuted, audiomuted, etc)\r\n */\r\nexport function filterNodeFromPresenceJSON(pres, nodeName) {\r\n const res = [];\r\n\r\n for (let i = 0; i < pres.length; i++) {\r\n if (pres[i].tagName === nodeName) {\r\n res.push(pres[i]);\r\n }\r\n }\r\n\r\n return res;\r\n}\r\n\r\n// XXX As ChatRoom constructs XMPP stanzas and Strophe is build around the idea\r\n// of chaining function calls, allow long function call chains.\r\n/* eslint-disable newline-per-chained-call */\r\n\r\n/**\r\n * Array of affiliations that are allowed in members only room.\r\n * @type {string[]}\r\n */\r\nconst MEMBERS_AFFILIATIONS = [ 'owner', 'admin', 'member' ];\r\n\r\n/**\r\n *\r\n */\r\nexport default class ChatRoom extends Listenable {\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n *\r\n * @param {XmppConnection} connection - The XMPP connection instance.\r\n * @param jid\r\n * @param password\r\n * @param XMPP\r\n * @param options\r\n * @param {boolean} options.disableFocus - when set to {@code false} will\r\n * not invite Jicofo into the room.\r\n * @param {boolean} options.disableDiscoInfo - when set to {@code false} will skip disco info.\r\n * This is intended to be used only for lobby rooms.\r\n * @param {boolean} options.enableLobby - when set to {@code false} will skip creating lobby room.\r\n * @param {boolean} options.hiddenFromRecorderFeatureEnabled - when set to {@code true} we will check identity tag\r\n * for node presence.\r\n */\r\n constructor(connection, jid, password, XMPP, options) {\r\n super();\r\n this.xmpp = XMPP;\r\n this.connection = connection;\r\n this.roomjid = Strophe.getBareJidFromJid(jid);\r\n this.myroomjid = jid;\r\n this.password = password;\r\n this.replaceParticipant = false;\r\n logger.info(`Joined MUC as ${this.myroomjid}`);\r\n this.members = {};\r\n this.presMap = {};\r\n this.presHandlers = {};\r\n this._removeConnListeners = [];\r\n this.joined = false;\r\n this.inProgressEmitted = false;\r\n this.role = null;\r\n this.focusMucJid = null;\r\n this.noBridgeAvailable = false;\r\n this.options = options || {};\r\n this.moderator\r\n = new Moderator(this.roomjid, this.xmpp, this.eventEmitter, {\r\n connection: this.xmpp.options,\r\n conference: this.options\r\n });\r\n if (typeof this.options.enableLobby === 'undefined' || this.options.enableLobby) {\r\n this.lobby = new Lobby(this);\r\n }\r\n this.avModeration = new AVModeration(this);\r\n this.breakoutRooms = new BreakoutRooms(this);\r\n this.initPresenceMap(options);\r\n this.lastPresences = {};\r\n this.phoneNumber = null;\r\n this.phonePin = null;\r\n this.connectionTimes = {};\r\n this.participantPropertyListener = null;\r\n\r\n this.locked = false;\r\n this.transcriptionStatus = JitsiTranscriptionStatus.OFF;\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n *\r\n */\r\n initPresenceMap(options = {}) {\r\n this.presMap.to = this.myroomjid;\r\n this.presMap.xns = 'http://jabber.org/protocol/muc';\r\n this.presMap.nodes = [];\r\n\r\n if (options.statsId) {\r\n this.presMap.nodes.push({\r\n 'tagName': 'stats-id',\r\n 'value': options.statsId\r\n });\r\n }\r\n\r\n this.presenceUpdateTime = Date.now();\r\n }\r\n\r\n /**\r\n * Joins the chat room.\r\n * @param {string} password - Password to unlock room on joining.\r\n * @returns {Promise} - resolved when join completes. At the time of this\r\n * writing it's never rejected.\r\n */\r\n join(password, replaceParticipant) {\r\n this.password = password;\r\n this.replaceParticipant = replaceParticipant;\r\n\r\n return new Promise(resolve => {\r\n this.options.disableFocus\r\n && logger.info(`Conference focus disabled for ${this.roomjid}`);\r\n\r\n const preJoin\r\n = this.options.disableFocus\r\n ? Promise.resolve()\r\n : this.moderator.allocateConferenceFocus();\r\n\r\n preJoin.then(() => {\r\n this.sendPresence(true);\r\n this._removeConnListeners.push(\r\n this.connection.addEventListener(\r\n XmppConnection.Events.CONN_STATUS_CHANGED,\r\n this.onConnStatusChanged.bind(this))\r\n );\r\n resolve();\r\n });\r\n });\r\n }\r\n\r\n /**\r\n *\r\n * @param fromJoin - Whether this is initial presence to join the room.\r\n */\r\n sendPresence(fromJoin) {\r\n const to = this.presMap.to;\r\n\r\n if (!this.connection || !this.connection.connected || !to || (!this.joined && !fromJoin)) {\r\n // Too early to send presence - not initialized\r\n return;\r\n }\r\n\r\n const pres = $pres({ to });\r\n\r\n // xep-0045 defines: \"including in the initial presence stanza an empty\r\n // element qualified by the 'http://jabber.org/protocol/muc'\r\n // namespace\" and subsequent presences should not include that or it can\r\n // be considered as joining, and server can send us the message history\r\n // for the room on every presence\r\n if (fromJoin) {\r\n if (this.replaceParticipant) {\r\n pres.c('flip_device').up();\r\n }\r\n\r\n pres.c('x', { xmlns: this.presMap.xns });\r\n\r\n if (this.password) {\r\n pres.c('password').t(this.password).up();\r\n }\r\n if (this.options.billingId) {\r\n pres.c('billingid').t(this.options.billingId).up();\r\n }\r\n\r\n pres.up();\r\n }\r\n\r\n parser.json2packet(this.presMap.nodes, pres);\r\n\r\n // we store time we last synced presence state\r\n this.presenceSyncTime = Date.now();\r\n\r\n this.connection.send(pres);\r\n if (fromJoin) {\r\n // XXX We're pressed for time here because we're beginning a complex\r\n // and/or lengthy conference-establishment process which supposedly\r\n // involves multiple RTTs. We don't have the time to wait for\r\n // Strophe to decide to send our IQ.\r\n this.connection.flush();\r\n }\r\n }\r\n\r\n /**\r\n * Sends the presence unavailable, signaling the server\r\n * we want to leave the room.\r\n */\r\n doLeave() {\r\n logger.log('do leave', this.myroomjid);\r\n const pres = $pres({ to: this.myroomjid,\r\n type: 'unavailable' });\r\n\r\n this.presMap.length = 0;\r\n\r\n // XXX Strophe is asynchronously sending by default. Unfortunately, that\r\n // means that there may not be enough time to send the unavailable\r\n // presence. Switching Strophe to synchronous sending is not much of an\r\n // option because it may lead to a noticeable delay in navigating away\r\n // from the current location. As a compromise, we will try to increase\r\n // the chances of sending the unavailable presence within the short time\r\n // span that we have upon unloading by invoking flush() on the\r\n // connection. We flush() once before sending/queuing the unavailable\r\n // presence in order to attemtp to have the unavailable presence at the\r\n // top of the send queue. We flush() once more after sending/queuing the\r\n // unavailable presence in order to attempt to have it sent as soon as\r\n // possible.\r\n // FIXME do not use Strophe.Connection in the ChatRoom directly\r\n !this.connection.isUsingWebSocket && this.connection.flush();\r\n this.connection.send(pres);\r\n this.connection.flush();\r\n }\r\n\r\n /**\r\n *\r\n */\r\n discoRoomInfo() {\r\n // https://xmpp.org/extensions/xep-0045.html#disco-roominfo\r\n\r\n const getInfo\r\n = $iq({\r\n type: 'get',\r\n to: this.roomjid\r\n })\r\n .c('query', { xmlns: Strophe.NS.DISCO_INFO });\r\n\r\n this.connection.sendIQ(getInfo, result => {\r\n const locked\r\n = $(result).find('>query>feature[var=\"muc_passwordprotected\"]')\r\n .length\r\n === 1;\r\n\r\n if (locked !== this.locked) {\r\n this.eventEmitter.emit(XMPPEvents.MUC_LOCK_CHANGED, locked);\r\n this.locked = locked;\r\n }\r\n\r\n const meetingIdValEl\r\n = $(result).find('>query>x[type=\"result\"]>field[var=\"muc#roominfo_meetingId\"]>value');\r\n\r\n if (meetingIdValEl.length) {\r\n this.setMeetingId(meetingIdValEl.text());\r\n } else {\r\n logger.warn('No meeting ID from backend');\r\n }\r\n\r\n const membersOnly = $(result).find('>query>feature[var=\"muc_membersonly\"]').length === 1;\r\n\r\n const lobbyRoomField\r\n = $(result).find('>query>x[type=\"result\"]>field[var=\"muc#roominfo_lobbyroom\"]>value');\r\n\r\n if (this.lobby) {\r\n this.lobby.setLobbyRoomJid(lobbyRoomField && lobbyRoomField.length ? lobbyRoomField.text() : undefined);\r\n }\r\n\r\n const isBreakoutField\r\n = $(result).find('>query>x[type=\"result\"]>field[var=\"muc#roominfo_isbreakout\"]>value');\r\n const isBreakoutRoom = Boolean(isBreakoutField?.text());\r\n\r\n this.breakoutRooms._setIsBreakoutRoom(isBreakoutRoom);\r\n\r\n const breakoutMainRoomField\r\n = $(result).find('>query>x[type=\"result\"]>field[var=\"muc#roominfo_breakout_main_room\"]>value');\r\n\r\n if (breakoutMainRoomField?.length) {\r\n this.breakoutRooms._setMainRoomJid(breakoutMainRoomField.text());\r\n }\r\n\r\n if (membersOnly !== this.membersOnlyEnabled) {\r\n this.membersOnlyEnabled = membersOnly;\r\n this.eventEmitter.emit(XMPPEvents.MUC_MEMBERS_ONLY_CHANGED, membersOnly);\r\n }\r\n\r\n }, error => {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error('Error getting room info: ', error);\r\n });\r\n }\r\n\r\n /**\r\n * Sets the meeting unique Id (received from the backend).\r\n *\r\n * @param {string} meetingId - The new meetings id.\r\n * @returns {void}\r\n */\r\n setMeetingId(meetingId) {\r\n if (this.meetingId !== meetingId) {\r\n if (this.meetingId) {\r\n logger.warn(`Meeting Id changed from:${this.meetingId} to:${meetingId}`);\r\n }\r\n this.meetingId = meetingId;\r\n this.eventEmitter.emit(XMPPEvents.MEETING_ID_SET, meetingId);\r\n }\r\n }\r\n\r\n /**\r\n *\r\n */\r\n createNonAnonymousRoom() {\r\n // http://xmpp.org/extensions/xep-0045.html#createroom-reserved\r\n\r\n if (this.options.disableDiscoInfo) {\r\n return;\r\n }\r\n\r\n const getForm = $iq({ type: 'get',\r\n to: this.roomjid })\r\n .c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' })\r\n .c('x', { xmlns: 'jabber:x:data',\r\n type: 'submit' });\r\n\r\n this.connection.sendIQ(getForm, form => {\r\n if (!$(form).find(\r\n '>query>x[xmlns=\"jabber:x:data\"]'\r\n + '>field[var=\"muc#roomconfig_whois\"]').length) {\r\n const errmsg = 'non-anonymous rooms not supported';\r\n\r\n GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));\r\n logger.error(errmsg);\r\n\r\n return;\r\n }\r\n\r\n const formSubmit = $iq({ to: this.roomjid,\r\n type: 'set' })\r\n .c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' });\r\n\r\n formSubmit.c('x', { xmlns: 'jabber:x:data',\r\n type: 'submit' });\r\n\r\n formSubmit.c('field', { 'var': 'FORM_TYPE' })\r\n .c('value')\r\n .t('http://jabber.org/protocol/muc#roomconfig').up().up();\r\n\r\n formSubmit.c('field', { 'var': 'muc#roomconfig_whois' })\r\n .c('value').t('anyone').up().up();\r\n\r\n this.connection.sendIQ(formSubmit);\r\n\r\n }, error => {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error('Error getting room configuration form: ', error);\r\n });\r\n }\r\n\r\n /**\r\n * Handles Xmpp Connection status updates.\r\n *\r\n * @param {Strophe.Status} status - The Strophe connection status.\r\n */\r\n onConnStatusChanged(status) {\r\n // Send cached presence when the XMPP connection is re-established.\r\n if (status === XmppConnection.Status.CONNECTED) {\r\n this.sendPresence();\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param pres\r\n */\r\n onPresence(pres) {\r\n const from = pres.getAttribute('from');\r\n const member = {};\r\n const statusEl = pres.getElementsByTagName('status')[0];\r\n\r\n if (statusEl) {\r\n member.status = statusEl.textContent || '';\r\n }\r\n let hasStatusUpdate = false;\r\n let hasVersionUpdate = false;\r\n const xElement\r\n = pres.getElementsByTagNameNS(\r\n 'http://jabber.org/protocol/muc#user', 'x')[0];\r\n const mucUserItem\r\n = xElement && xElement.getElementsByTagName('item')[0];\r\n\r\n member.isReplaceParticipant\r\n = pres.getElementsByTagName('flip_device').length;\r\n\r\n member.affiliation\r\n = mucUserItem && mucUserItem.getAttribute('affiliation');\r\n member.role = mucUserItem && mucUserItem.getAttribute('role');\r\n\r\n // Focus recognition\r\n const jid = mucUserItem && mucUserItem.getAttribute('jid');\r\n\r\n member.jid = jid;\r\n member.isFocus\r\n = jid && jid.indexOf(`${this.moderator.getFocusUserJid()}/`) === 0;\r\n member.isHiddenDomain\r\n = jid && jid.indexOf('@') > 0\r\n && this.options.hiddenDomain\r\n === jid.substring(jid.indexOf('@') + 1, jid.indexOf('/'));\r\n\r\n this.eventEmitter.emit(XMPPEvents.PRESENCE_RECEIVED, {\r\n fromHiddenDomain: member.isHiddenDomain,\r\n presence: pres\r\n });\r\n\r\n const xEl = pres.querySelector('x');\r\n\r\n if (xEl) {\r\n xEl.remove();\r\n }\r\n\r\n const nodes = [];\r\n\r\n parser.packet2JSON(pres, nodes);\r\n this.lastPresences[from] = nodes;\r\n\r\n // process nodes to extract data needed for MUC_JOINED and\r\n // MUC_MEMBER_JOINED events\r\n const extractIdentityInformation = node => {\r\n const identity = {};\r\n const userInfo = node.children.find(c => c.tagName === 'user');\r\n\r\n if (userInfo) {\r\n identity.user = {};\r\n const tags = [ 'id', 'name', 'avatar' ];\r\n\r\n if (this.options.hiddenFromRecorderFeatureEnabled) {\r\n tags.push('hidden-from-recorder');\r\n }\r\n\r\n for (const tag of tags) {\r\n const child\r\n = userInfo.children.find(c => c.tagName === tag);\r\n\r\n if (child) {\r\n identity.user[tag] = child.value;\r\n }\r\n }\r\n }\r\n const groupInfo = node.children.find(c => c.tagName === 'group');\r\n\r\n if (groupInfo) {\r\n identity.group = groupInfo.value;\r\n }\r\n\r\n return identity;\r\n };\r\n\r\n for (let i = 0; i < nodes.length; i++) {\r\n const node = nodes[i];\r\n\r\n switch (node.tagName) {\r\n case 'bot': {\r\n const { attributes } = node;\r\n\r\n if (!attributes) {\r\n break;\r\n }\r\n const { type } = attributes;\r\n\r\n member.botType = type;\r\n break;\r\n }\r\n case 'nick':\r\n member.nick = node.value;\r\n break;\r\n case 'userId':\r\n member.id = node.value;\r\n break;\r\n case 'stats-id':\r\n member.statsID = node.value;\r\n break;\r\n case 'identity':\r\n member.identity = extractIdentityInformation(node);\r\n break;\r\n case 'features': {\r\n member.features = this._extractFeatures(node);\r\n break;\r\n }\r\n case 'stat': {\r\n const { attributes } = node;\r\n\r\n if (!attributes) {\r\n break;\r\n }\r\n const { name } = attributes;\r\n\r\n if (name === 'version') {\r\n member.version = attributes.value;\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (!this.joined && !this.inProgressEmitted) {\r\n const now = this.connectionTimes['muc.join.started'] = window.performance.now();\r\n\r\n logger.log('(TIME) MUC join started:\\t', now);\r\n\r\n this.eventEmitter.emit(XMPPEvents.MUC_JOIN_IN_PROGRESS);\r\n this.inProgressEmitted = true;\r\n }\r\n\r\n if (from === this.myroomjid) {\r\n const newRole\r\n = member.affiliation === 'owner' ? member.role : 'none';\r\n\r\n if (this.role !== newRole) {\r\n this.role = newRole;\r\n this.eventEmitter.emit(\r\n XMPPEvents.LOCAL_ROLE_CHANGED,\r\n this.role);\r\n }\r\n if (!this.joined) {\r\n this.joined = true;\r\n const now = this.connectionTimes['muc.joined']\r\n = window.performance.now();\r\n\r\n logger.log('(TIME) MUC joined:\\t', now);\r\n\r\n // set correct initial state of locked\r\n if (this.password) {\r\n this.locked = true;\r\n }\r\n\r\n // Re-send presence in case any presence updates were added,\r\n // but blocked from sending, during the join process.\r\n // send the presence only if there was a modification after we had synced it\r\n if (this.presenceUpdateTime >= this.presenceSyncTime) {\r\n this.sendPresence();\r\n }\r\n\r\n this.eventEmitter.emit(XMPPEvents.MUC_JOINED);\r\n\r\n // Now let's check the disco-info to retrieve the\r\n // meeting Id if any\r\n !this.options.disableDiscoInfo && this.discoRoomInfo();\r\n }\r\n } else if (jid === undefined) {\r\n logger.info('Ignoring member with undefined JID');\r\n } else if (this.members[from] === undefined) {\r\n // new participant\r\n this.members[from] = member;\r\n logger.log('entered', from, member);\r\n hasStatusUpdate = member.status !== undefined;\r\n hasVersionUpdate = member.version !== undefined;\r\n if (member.isFocus) {\r\n this._initFocus(from, member.features);\r\n } else {\r\n // identity is being added to member joined, so external\r\n // services can be notified for that (currently identity is\r\n // not used inside library)\r\n this.eventEmitter.emit(\r\n XMPPEvents.MUC_MEMBER_JOINED,\r\n from,\r\n member.nick,\r\n member.role,\r\n member.isHiddenDomain,\r\n member.statsID,\r\n member.status,\r\n member.identity,\r\n member.botType,\r\n member.jid,\r\n member.features,\r\n member.isReplaceParticipant);\r\n\r\n // we are reporting the status with the join\r\n // so we do not want a second event about status update\r\n hasStatusUpdate = false;\r\n }\r\n } else {\r\n // Presence update for existing participant\r\n // Watch role change:\r\n const memberOfThis = this.members[from];\r\n\r\n if (memberOfThis.role !== member.role) {\r\n memberOfThis.role = member.role;\r\n this.eventEmitter.emit(\r\n XMPPEvents.MUC_ROLE_CHANGED, from, member.role);\r\n }\r\n\r\n // affiliation changed\r\n if (memberOfThis.affiliation !== member.affiliation) {\r\n memberOfThis.affiliation = member.affiliation;\r\n }\r\n\r\n // fire event that botType had changed\r\n if (memberOfThis.botType !== member.botType) {\r\n memberOfThis.botType = member.botType;\r\n this.eventEmitter.emit(\r\n XMPPEvents.MUC_MEMBER_BOT_TYPE_CHANGED,\r\n from,\r\n member.botType);\r\n }\r\n\r\n if (member.isFocus) {\r\n // From time to time first few presences of the focus are not\r\n // containing it's jid. That way we can mark later the focus\r\n // member instead of not marking it at all and not starting the\r\n // conference.\r\n // FIXME: Maybe there is a better way to handle this issue. It\r\n // seems there is some period of time in prosody that the\r\n // configuration form is received but not applied. And if any\r\n // participant joins during that period of time the first\r\n // presence from the focus won't contain\r\n // .\r\n // By default we are disabling the waiting for form submission in order to use the room\r\n // and we had enabled by default that jids are public in the room ,\r\n // so this case should not happen, if public jid is turned off we will receive the jid\r\n // when we become moderator in the room\r\n memberOfThis.isFocus = true;\r\n this._initFocus(from, member.features);\r\n }\r\n\r\n // store the new display name\r\n if (member.displayName) {\r\n memberOfThis.displayName = member.displayName;\r\n }\r\n\r\n // update stored status message to be able to detect changes\r\n if (memberOfThis.status !== member.status) {\r\n hasStatusUpdate = true;\r\n memberOfThis.status = member.status;\r\n }\r\n\r\n if (memberOfThis.version !== member.version) {\r\n hasVersionUpdate = true;\r\n memberOfThis.version = member.version;\r\n }\r\n\r\n if (!isEqual(memberOfThis.features, member.features)) {\r\n memberOfThis.features = member.features;\r\n this.eventEmitter.emit(XMPPEvents.PARTICIPANT_FEATURES_CHANGED, from, member.features);\r\n }\r\n }\r\n\r\n // after we had fired member or room joined events, lets fire events\r\n // for the rest info we got in presence\r\n for (let i = 0; i < nodes.length; i++) {\r\n const node = nodes[i];\r\n\r\n switch (node.tagName) {\r\n case 'nick':\r\n if (!member.isFocus) {\r\n const displayName\r\n = this.xmpp.options.displayJids\r\n ? Strophe.getResourceFromJid(from)\r\n : member.nick;\r\n\r\n this.eventEmitter.emit(\r\n XMPPEvents.DISPLAY_NAME_CHANGED,\r\n from,\r\n displayName);\r\n }\r\n break;\r\n case 'bridgeNotAvailable':\r\n if (member.isFocus && !this.noBridgeAvailable) {\r\n this.noBridgeAvailable = true;\r\n this.eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);\r\n }\r\n break;\r\n case 'conference-properties':\r\n if (member.isFocus) {\r\n const properties = {};\r\n\r\n for (let j = 0; j < node.children.length; j++) {\r\n const { attributes } = node.children[j];\r\n\r\n if (attributes && attributes.key) {\r\n properties[attributes.key] = attributes.value;\r\n }\r\n }\r\n\r\n this.eventEmitter.emit(XMPPEvents.CONFERENCE_PROPERTIES_CHANGED, properties);\r\n\r\n // Log if Jicofo supports restart by terminate only once. This conference property does not change\r\n // during the call.\r\n if (typeof this.restartByTerminateSupported === 'undefined') {\r\n this.restartByTerminateSupported = properties['support-terminate-restart'] === 'true';\r\n logger.info(`Jicofo supports restart by terminate: ${this.supportsRestartByTerminate()}`);\r\n }\r\n }\r\n break;\r\n case 'transcription-status': {\r\n const { attributes } = node;\r\n\r\n if (!attributes) {\r\n break;\r\n }\r\n\r\n const { status } = attributes;\r\n\r\n if (status && status !== this.transcriptionStatus) {\r\n this.transcriptionStatus = status;\r\n this.eventEmitter.emit(\r\n XMPPEvents.TRANSCRIPTION_STATUS_CHANGED,\r\n status\r\n );\r\n }\r\n\r\n\r\n break;\r\n }\r\n case 'call-control': {\r\n const att = node.attributes;\r\n\r\n if (!att) {\r\n break;\r\n }\r\n this.phoneNumber = att.phone || null;\r\n this.phonePin = att.pin || null;\r\n this.eventEmitter.emit(XMPPEvents.PHONE_NUMBER_CHANGED);\r\n break;\r\n }\r\n default:\r\n this.processNode(node, from);\r\n }\r\n }\r\n\r\n // Trigger status message update if necessary\r\n if (hasStatusUpdate) {\r\n this.eventEmitter.emit(\r\n XMPPEvents.PRESENCE_STATUS,\r\n from,\r\n member.status);\r\n }\r\n\r\n if (hasVersionUpdate) {\r\n logger.info(`Received version for ${jid}: ${member.version}`);\r\n }\r\n }\r\n\r\n /**\r\n * Extracts the features from the presence.\r\n * @param node the node to process.\r\n * @return features the Set of features where extracted data is added.\r\n * @private\r\n */\r\n _extractFeatures(node) {\r\n const features = new Set();\r\n\r\n for (let j = 0; j < node.children.length; j++) {\r\n const { attributes } = node.children[j];\r\n\r\n if (attributes && attributes.var) {\r\n features.add(attributes.var);\r\n }\r\n }\r\n\r\n return features;\r\n }\r\n\r\n /**\r\n * Initialize some properties when the focus participant is verified.\r\n * @param from jid of the focus\r\n * @param features the features reported in jicofo presence\r\n */\r\n _initFocus(from, features) {\r\n this.focusMucJid = from;\r\n this.focusFeatures = features;\r\n }\r\n\r\n /**\r\n * Sets the special listener to be used for \"command\"s whose name starts\r\n * with \"jitsi_participant_\".\r\n */\r\n setParticipantPropertyListener(listener) {\r\n this.participantPropertyListener = listener;\r\n }\r\n\r\n /**\r\n * Checks if Jicofo supports restarting Jingle session after 'session-terminate'.\r\n * @returns {boolean}\r\n */\r\n supportsRestartByTerminate() {\r\n return this.restartByTerminateSupported;\r\n }\r\n\r\n /**\r\n *\r\n * @param node\r\n * @param from\r\n */\r\n processNode(node, from) {\r\n // make sure we catch all errors coming from any handler\r\n // otherwise we can remove the presence handler from strophe\r\n try {\r\n let tagHandlers = this.presHandlers[node.tagName];\r\n\r\n if (node.tagName.startsWith('jitsi_participant_')) {\r\n tagHandlers = [ this.participantPropertyListener ];\r\n }\r\n\r\n if (tagHandlers) {\r\n tagHandlers.forEach(handler => {\r\n handler(node, Strophe.getResourceFromJid(from), from);\r\n });\r\n }\r\n } catch (e) {\r\n GlobalOnErrorHandler.callErrorHandler(e);\r\n logger.error(`Error processing:${node.tagName} node.`, e);\r\n }\r\n }\r\n\r\n /**\r\n * Send text message to the other participants in the conference\r\n * @param message\r\n * @param elementName\r\n */\r\n sendMessage(message, elementName) {\r\n const msg = $msg({ to: this.roomjid,\r\n type: 'groupchat' });\r\n\r\n // We are adding the message in a packet extension. If this element\r\n // is different from 'body', we add a custom namespace.\r\n // e.g. for 'json-message' extension of message stanza.\r\n if (elementName === 'body') {\r\n msg.c(elementName, {}, message);\r\n } else {\r\n msg.c(elementName, { xmlns: 'http://jitsi.org/jitmeet' }, message);\r\n }\r\n\r\n this.connection.send(msg);\r\n this.eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, message);\r\n }\r\n\r\n /* eslint-disable max-params */\r\n /**\r\n * Send private text message to another participant of the conference\r\n * @param id id/muc resource of the receiver\r\n * @param message\r\n * @param elementName\r\n */\r\n sendPrivateMessage(id, message, elementName) {\r\n const msg = $msg({ to: `${this.roomjid}/${id}`,\r\n type: 'chat' });\r\n\r\n // We are adding the message in packet. If this element is different\r\n // from 'body', we add our custom namespace for the same.\r\n // e.g. for 'json-message' message extension.\r\n if (elementName === 'body') {\r\n msg.c(elementName, message).up();\r\n } else {\r\n msg.c(elementName, { xmlns: 'http://jitsi.org/jitmeet' }, message)\r\n .up();\r\n }\r\n\r\n this.connection.send(msg);\r\n this.eventEmitter.emit(\r\n XMPPEvents.SENDING_PRIVATE_CHAT_MESSAGE, message);\r\n }\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n *\r\n * @param subject\r\n */\r\n setSubject(subject) {\r\n const msg = $msg({ to: this.roomjid,\r\n type: 'groupchat' });\r\n\r\n msg.c('subject', subject);\r\n this.connection.send(msg);\r\n }\r\n\r\n /**\r\n * Called when participant leaves.\r\n * @param jid the jid of the participant that leaves\r\n * @param skipEvents optional params to skip any events, including check\r\n * whether this is the focus that left\r\n */\r\n onParticipantLeft(jid, skipEvents) {\r\n delete this.lastPresences[jid];\r\n\r\n if (skipEvents) {\r\n return;\r\n }\r\n\r\n this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);\r\n\r\n this.moderator.onMucMemberLeft(jid);\r\n }\r\n\r\n /**\r\n *\r\n * @param pres\r\n * @param from\r\n */\r\n onPresenceUnavailable(pres, from) {\r\n // ignore presence\r\n if ($(pres).find('>ignore[xmlns=\"http://jitsi.org/jitmeet/\"]').length) {\r\n return true;\r\n }\r\n\r\n // room destroyed ?\r\n const destroySelect = $(pres).find('>x[xmlns=\"http://jabber.org/protocol/muc#user\"]>destroy');\r\n\r\n if (destroySelect.length) {\r\n let reason;\r\n const reasonSelect\r\n = $(pres).find(\r\n '>x[xmlns=\"http://jabber.org/protocol/muc#user\"]'\r\n + '>destroy>reason');\r\n\r\n if (reasonSelect.length) {\r\n reason = reasonSelect.text();\r\n }\r\n\r\n this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason, destroySelect.attr('jid'));\r\n this.connection.emuc.doLeave(this.roomjid);\r\n\r\n return true;\r\n }\r\n\r\n // Status code 110 indicates that this notification is \"self-presence\".\r\n const isSelfPresence\r\n = $(pres)\r\n .find(\r\n '>x[xmlns=\"http://jabber.org/protocol/muc#user\"]>'\r\n + 'status[code=\"110\"]')\r\n .length;\r\n const isKick\r\n = $(pres)\r\n .find(\r\n '>x[xmlns=\"http://jabber.org/protocol/muc#user\"]'\r\n + '>status[code=\"307\"]')\r\n .length;\r\n const membersKeys = Object.keys(this.members);\r\n const isReplaceParticipant = $(pres).find('flip_device').length;\r\n\r\n if (isKick) {\r\n const actorSelect\r\n = $(pres)\r\n .find('>x[xmlns=\"http://jabber.org/protocol/muc#user\"]>item>actor');\r\n let actorNick;\r\n\r\n if (actorSelect.length) {\r\n actorNick = actorSelect.attr('nick');\r\n }\r\n\r\n let reason;\r\n const reasonSelect\r\n = $(pres).find(\r\n '>x[xmlns=\"http://jabber.org/protocol/muc#user\"]'\r\n + '>item>reason');\r\n\r\n if (reasonSelect.length) {\r\n reason = reasonSelect.text();\r\n }\r\n\r\n // we first fire the kicked so we can show the participant\r\n // who kicked, before notifying that participant left\r\n // we fire kicked for us and for any participant kicked\r\n this.eventEmitter.emit(\r\n XMPPEvents.KICKED,\r\n isSelfPresence,\r\n actorNick,\r\n Strophe.getResourceFromJid(from),\r\n reason,\r\n isReplaceParticipant);\r\n }\r\n\r\n if (isSelfPresence) {\r\n // If the status code is 110 this means we're leaving and we would\r\n // like to remove everyone else from our view, so we trigger the\r\n // event.\r\n membersKeys.forEach(jid => {\r\n const member = this.members[jid];\r\n\r\n delete this.members[jid];\r\n this.onParticipantLeft(jid, member.isFocus);\r\n });\r\n this.connection.emuc.doLeave(this.roomjid);\r\n\r\n // we fire muc_left only if this is not a kick,\r\n // kick has both statuses 110 and 307.\r\n if (!isKick) {\r\n this.eventEmitter.emit(XMPPEvents.MUC_LEFT);\r\n }\r\n } else {\r\n delete this.members[from];\r\n this.onParticipantLeft(from, false);\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param msg\r\n * @param from\r\n */\r\n onMessage(msg, from) {\r\n const type = msg.getAttribute('type');\r\n\r\n if (type === 'error') {\r\n const settingsErrorMsg = $(msg).find('>settings-error>text').text();\r\n\r\n if (settingsErrorMsg.length) {\r\n this.eventEmitter.emit(XMPPEvents.SETTINGS_ERROR_RECEIVED, settingsErrorMsg);\r\n\r\n return true;\r\n }\r\n const errorMsg = $(msg).find('>error>text').text();\r\n\r\n this.eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED, errorMsg);\r\n\r\n return true;\r\n }\r\n\r\n const txt = $(msg).find('>body').text();\r\n const subject = $(msg).find('>subject');\r\n\r\n if (subject.length) {\r\n const subjectText = subject.text();\r\n\r\n if (subjectText || subjectText === '') {\r\n this.eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);\r\n logger.log(`Subject is changed to ${subjectText}`);\r\n }\r\n }\r\n\r\n // xep-0203 delay\r\n let stamp = $(msg).find('>delay').attr('stamp');\r\n\r\n if (!stamp) {\r\n // or xep-0091 delay, UTC timestamp\r\n stamp = $(msg).find('>[xmlns=\"jabber:x:delay\"]').attr('stamp');\r\n\r\n if (stamp) {\r\n // the format is CCYYMMDDThh:mm:ss\r\n const dateParts\r\n = stamp.match(/(\\d{4})(\\d{2})(\\d{2}T\\d{2}:\\d{2}:\\d{2})/);\r\n\r\n stamp = `${dateParts[1]}-${dateParts[2]}-${dateParts[3]}Z`;\r\n }\r\n }\r\n\r\n if (from === this.roomjid) {\r\n let invite;\r\n\r\n if ($(msg).find('>x[xmlns=\"http://jabber.org/protocol/muc#user\"]>status[code=\"104\"]').length) {\r\n this.discoRoomInfo();\r\n } else if ((invite = $(msg).find('>x[xmlns=\"http://jabber.org/protocol/muc#user\"]>invite'))\r\n && invite.length) {\r\n const passwordSelect = $(msg).find('>x[xmlns=\"http://jabber.org/protocol/muc#user\"]>password');\r\n let password;\r\n\r\n if (passwordSelect && passwordSelect.length) {\r\n password = passwordSelect.text();\r\n }\r\n\r\n this.eventEmitter.emit(XMPPEvents.INVITE_MESSAGE_RECEIVED,\r\n from, invite.attr('from'), txt, password);\r\n }\r\n }\r\n\r\n const jsonMessage = $(msg).find('>json-message').text();\r\n\r\n if (jsonMessage) {\r\n const parsedJson = this.xmpp.tryParseJSONAndVerify(jsonMessage);\r\n\r\n // We emit this event if the message is a valid json, and is not\r\n // delivered after a delay, i.e. stamp is undefined.\r\n // e.g. - subtitles should not be displayed if delayed.\r\n if (parsedJson && stamp === undefined) {\r\n this.eventEmitter.emit(XMPPEvents.JSON_MESSAGE_RECEIVED,\r\n from, parsedJson);\r\n\r\n return;\r\n }\r\n }\r\n\r\n if (txt) {\r\n if (type === 'chat') {\r\n this.eventEmitter.emit(XMPPEvents.PRIVATE_MESSAGE_RECEIVED,\r\n from, txt, this.myroomjid, stamp);\r\n } else if (type === 'groupchat') {\r\n this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,\r\n from, txt, this.myroomjid, stamp);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param pres\r\n * @param from\r\n */\r\n onPresenceError(pres, from) {\r\n if ($(pres)\r\n .find(\r\n '>error[type=\"auth\"]'\r\n + '>not-authorized['\r\n + 'xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"]')\r\n .length) {\r\n logger.log('on password required', from);\r\n this.eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED);\r\n } else if ($(pres)\r\n .find(\r\n '>error[type=\"cancel\"]'\r\n + '>not-allowed['\r\n + 'xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"]')\r\n .length) {\r\n const toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));\r\n\r\n if (toDomain === this.xmpp.options.hosts.anonymousdomain) {\r\n // enter the room by replying with 'not-authorized'. This would\r\n // result in reconnection from authorized domain.\r\n // We're either missing Jicofo/Prosody config for anonymous\r\n // domains or something is wrong.\r\n this.eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR);\r\n\r\n } else {\r\n logger.warn('onPresError ', pres);\r\n this.eventEmitter.emit(\r\n XMPPEvents.ROOM_CONNECT_NOT_ALLOWED_ERROR);\r\n }\r\n } else if ($(pres).find('>error>service-unavailable').length) {\r\n logger.warn('Maximum users limit for the room has been reached',\r\n pres);\r\n this.eventEmitter.emit(XMPPEvents.ROOM_MAX_USERS_ERROR);\r\n } else if ($(pres)\r\n .find(\r\n '>error[type=\"auth\"]'\r\n + '>registration-required['\r\n + 'xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"]').length) {\r\n\r\n // let's extract the lobby jid from the custom field\r\n const lobbyRoomNode = $(pres).find('>error[type=\"auth\"]>lobbyroom');\r\n let lobbyRoomJid;\r\n\r\n if (lobbyRoomNode.length) {\r\n lobbyRoomJid = lobbyRoomNode.text();\r\n } else {\r\n // let's fallback to old location of lobbyroom node, TODO: to be removed in the future once\r\n // everything is updated\r\n const lobbyRoomOldNode = $(pres).find('>lobbyroom');\r\n\r\n if (lobbyRoomOldNode.length) {\r\n lobbyRoomJid = lobbyRoomOldNode.text();\r\n }\r\n }\r\n\r\n this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_MEMBERS_ONLY_ERROR, lobbyRoomJid);\r\n } else {\r\n logger.warn('onPresError ', pres);\r\n this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR);\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param jid\r\n * @param affiliation\r\n */\r\n setAffiliation(jid, affiliation) {\r\n const grantIQ = $iq({\r\n to: this.roomjid,\r\n type: 'set'\r\n })\r\n .c('query', { xmlns: 'http://jabber.org/protocol/muc#admin' })\r\n .c('item', {\r\n affiliation,\r\n jid: Strophe.getBareJidFromJid(jid)\r\n })\r\n .c('reason').t(`Your affiliation has been changed to '${affiliation}'.`)\r\n .up().up().up();\r\n\r\n this.connection.sendIQ(\r\n grantIQ,\r\n result => logger.log('Set affiliation of participant with jid: ', jid, 'to', affiliation, result),\r\n error => logger.log('Set affiliation of participant error: ', error));\r\n }\r\n\r\n /**\r\n *\r\n * @param jid\r\n * @param reason\r\n */\r\n kick(jid, reason = 'You have been kicked.') {\r\n const kickIQ = $iq({ to: this.roomjid,\r\n type: 'set' })\r\n .c('query', { xmlns: 'http://jabber.org/protocol/muc#admin' })\r\n .c('item', { nick: Strophe.getResourceFromJid(jid),\r\n role: 'none' })\r\n .c('reason').t(reason).up().up().up();\r\n\r\n this.connection.sendIQ(\r\n kickIQ,\r\n result => logger.log('Kick participant with jid: ', jid, result),\r\n error => logger.log('Kick participant error: ', error));\r\n }\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n *\r\n * @param key\r\n * @param onSuccess\r\n * @param onError\r\n * @param onNotSupported\r\n */\r\n lockRoom(key, onSuccess, onError, onNotSupported) {\r\n // http://xmpp.org/extensions/xep-0045.html#roomconfig\r\n this.connection.sendIQ(\r\n $iq({\r\n to: this.roomjid,\r\n type: 'get'\r\n })\r\n .c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' }),\r\n res => {\r\n if ($(res)\r\n .find(\r\n '>query>x[xmlns=\"jabber:x:data\"]'\r\n + '>field[var=\"muc#roomconfig_roomsecret\"]')\r\n .length) {\r\n const formsubmit\r\n = $iq({\r\n to: this.roomjid,\r\n type: 'set'\r\n })\r\n .c('query', {\r\n xmlns: 'http://jabber.org/protocol/muc#owner'\r\n });\r\n\r\n formsubmit.c('x', {\r\n xmlns: 'jabber:x:data',\r\n type: 'submit'\r\n });\r\n formsubmit\r\n .c('field', { 'var': 'FORM_TYPE' })\r\n .c('value')\r\n .t('http://jabber.org/protocol/muc#roomconfig')\r\n .up()\r\n .up();\r\n formsubmit\r\n .c('field', { 'var': 'muc#roomconfig_roomsecret' })\r\n .c('value')\r\n .t(key)\r\n .up()\r\n .up();\r\n formsubmit\r\n .c('field',\r\n { 'var': 'muc#roomconfig_passwordprotectedroom' })\r\n .c('value')\r\n .t(key === null || key.length === 0 ? '0' : '1')\r\n .up()\r\n .up();\r\n\r\n // if members only enabled\r\n if (this.membersOnlyEnabled) {\r\n formsubmit\r\n .c('field', { 'var': 'muc#roomconfig_membersonly' })\r\n .c('value')\r\n .t('true')\r\n .up()\r\n .up();\r\n }\r\n\r\n // Fixes a bug in prosody 0.9.+\r\n // https://prosody.im/issues/issue/373\r\n formsubmit\r\n .c('field', { 'var': 'muc#roomconfig_whois' })\r\n .c('value')\r\n .t('anyone')\r\n .up()\r\n .up();\r\n\r\n this.connection.sendIQ(\r\n formsubmit,\r\n () => {\r\n\r\n // we set the password in chat room so we can use it\r\n // later when dialing out\r\n this.password = key;\r\n onSuccess();\r\n },\r\n onError);\r\n } else {\r\n onNotSupported();\r\n }\r\n },\r\n onError);\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Turns off or on the members only config for the main room.\r\n *\r\n * @param {boolean} enabled - Whether to turn it on or off.\r\n * @param onSuccess - optional callback.\r\n * @param onError - optional callback.\r\n */\r\n setMembersOnly(enabled, onSuccess, onError) {\r\n if (enabled && Object.values(this.members).filter(m => !m.isFocus).length) {\r\n // first grant membership to all that are in the room\r\n // currently there is a bug in prosody where it handles only the first item\r\n // that's why we will send iq per member\r\n Object.values(this.members).forEach(m => {\r\n if (m.jid && !MEMBERS_AFFILIATIONS.includes(m.affiliation)) {\r\n this.xmpp.connection.sendIQ(\r\n $iq({\r\n to: this.roomjid,\r\n type: 'set' })\r\n .c('query', {\r\n xmlns: 'http://jabber.org/protocol/muc#admin' })\r\n .c('item', {\r\n 'affiliation': 'member',\r\n 'jid': Strophe.getBareJidFromJid(m.jid)\r\n }).up().up());\r\n }\r\n });\r\n }\r\n\r\n const errorCallback = onError ? onError : () => {}; // eslint-disable-line no-empty-function\r\n\r\n this.xmpp.connection.sendIQ(\r\n $iq({\r\n to: this.roomjid,\r\n type: 'get'\r\n }).c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' }),\r\n res => {\r\n if ($(res).find('>query>x[xmlns=\"jabber:x:data\"]>field[var=\"muc#roomconfig_membersonly\"]').length) {\r\n const formToSubmit\r\n = $iq({\r\n to: this.roomjid,\r\n type: 'set'\r\n }).c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' });\r\n\r\n formToSubmit.c('x', {\r\n xmlns: 'jabber:x:data',\r\n type: 'submit'\r\n });\r\n formToSubmit\r\n .c('field', { 'var': 'FORM_TYPE' })\r\n .c('value')\r\n .t('http://jabber.org/protocol/muc#roomconfig')\r\n .up()\r\n .up();\r\n formToSubmit\r\n .c('field', { 'var': 'muc#roomconfig_membersonly' })\r\n .c('value')\r\n .t(enabled ? 'true' : 'false')\r\n .up()\r\n .up();\r\n\r\n // if room is locked from other participant or we are locking it\r\n if (this.locked) {\r\n formToSubmit\r\n .c('field',\r\n { 'var': 'muc#roomconfig_passwordprotectedroom' })\r\n .c('value')\r\n .t('1')\r\n .up()\r\n .up();\r\n }\r\n\r\n this.xmpp.connection.sendIQ(formToSubmit, onSuccess, errorCallback);\r\n } else {\r\n errorCallback(new Error('Setting members only room not supported!'));\r\n }\r\n },\r\n errorCallback);\r\n }\r\n\r\n /**\r\n * Adds the key to the presence map, overriding any previous value.\r\n * This method is used by jibri.\r\n *\r\n * @param key The key to add or replace.\r\n * @param values The new values.\r\n * @returns {boolean|null} true if the operation succeeded or false when no add or replce was\r\n * performed as the value was already there.\r\n * @deprecated Use 'addOrReplaceInPresence' instead. TODO: remove it from here and jibri.\r\n */\r\n addToPresence(key, values) {\r\n return this.addOrReplaceInPresence(key, values);\r\n }\r\n\r\n /**\r\n * Adds the key to the presence map, overriding any previous value.\r\n * @param key The key to add or replace.\r\n * @param values The new values.\r\n * @returns {boolean|null} true if the operation succeeded or false when no add or replace was\r\n * performed as the value was already there.\r\n */\r\n addOrReplaceInPresence(key, values) {\r\n values.tagName = key;\r\n\r\n const matchingNodes = this.presMap.nodes.filter(node => key === node.tagName);\r\n\r\n // if we have found just one, let's check is it the same\r\n if (matchingNodes.length === 1 && isEqual(matchingNodes[0], values)) {\r\n return false;\r\n }\r\n\r\n this.removeFromPresence(key);\r\n this.presMap.nodes.push(values);\r\n this.presenceUpdateTime = Date.now();\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Retrieves a value from the presence map.\r\n *\r\n * @param {string} key - The key to find the value for.\r\n * @returns {Object?}\r\n */\r\n getFromPresence(key) {\r\n return this.presMap.nodes.find(node => key === node.tagName);\r\n }\r\n\r\n /**\r\n * Removes a key from the presence map.\r\n * @param key\r\n */\r\n removeFromPresence(key) {\r\n const nodes = this.presMap.nodes.filter(node => key !== node.tagName);\r\n\r\n this.presMap.nodes = nodes;\r\n this.presenceUpdateTime = Date.now();\r\n }\r\n\r\n /**\r\n *\r\n * @param name\r\n * @param handler\r\n */\r\n addPresenceListener(name, handler) {\r\n if (typeof handler !== 'function') {\r\n throw new Error('\"handler\" is not a function');\r\n }\r\n let tagHandlers = this.presHandlers[name];\r\n\r\n if (!tagHandlers) {\r\n this.presHandlers[name] = tagHandlers = [];\r\n }\r\n if (tagHandlers.indexOf(handler) === -1) {\r\n tagHandlers.push(handler);\r\n } else {\r\n logger.warn(\r\n `Trying to add the same handler more than once for: ${name}`);\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param name\r\n * @param handler\r\n */\r\n removePresenceListener(name, handler) {\r\n const tagHandlers = this.presHandlers[name];\r\n const handlerIdx = tagHandlers ? tagHandlers.indexOf(handler) : -1;\r\n\r\n // eslint-disable-next-line no-negated-condition\r\n if (handlerIdx !== -1) {\r\n tagHandlers.splice(handlerIdx, 1);\r\n } else {\r\n logger.warn(`Handler for: ${name} was not registered`);\r\n }\r\n }\r\n\r\n /**\r\n * Checks if the user identified by given mucJid is the conference\r\n * focus.\r\n * @param mucJid the full MUC address of the user to be checked.\r\n * @returns {boolean|null} true if MUC user is the conference focus\r\n * or false if is not. When given mucJid does not exist in\r\n * the MUC then null is returned.\r\n */\r\n isFocus(mucJid) {\r\n const member = this.members[mucJid];\r\n\r\n if (member) {\r\n return member.isFocus;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n *\r\n */\r\n isModerator() {\r\n return this.role === 'moderator';\r\n }\r\n\r\n /**\r\n *\r\n * @param peerJid\r\n */\r\n getMemberRole(peerJid) {\r\n if (this.members[peerJid]) {\r\n return this.members[peerJid].role;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n *\r\n * @param mute\r\n */\r\n addAudioInfoToPresence(mute) {\r\n const audioMutedTagName = 'audiomuted';\r\n\r\n // we skip adding it as muted is default value\r\n if (mute && !this.getFromPresence(audioMutedTagName)) {\r\n return false;\r\n }\r\n\r\n return this.addOrReplaceInPresence(\r\n audioMutedTagName,\r\n {\r\n value: mute.toString()\r\n });\r\n }\r\n\r\n /**\r\n *\r\n * @param mute\r\n */\r\n addVideoInfoToPresence(mute) {\r\n const videoMutedTagName = 'videomuted';\r\n\r\n // we skip adding it as muted is default value\r\n if (mute && !this.getFromPresence(videoMutedTagName)) {\r\n return false;\r\n }\r\n\r\n return this.addOrReplaceInPresence(\r\n videoMutedTagName,\r\n {\r\n value: mute.toString()\r\n });\r\n }\r\n\r\n /**\r\n * Obtains the info about given media advertised in the MUC presence of\r\n * the participant identified by the given endpoint JID.\r\n * @param {string} endpointId the endpoint ID mapped to the participant\r\n * which corresponds to MUC nickname.\r\n * @param {MediaType} mediaType the type of the media for which presence\r\n * info will be obtained.\r\n * @return {PeerMediaInfo} presenceInfo an object with media presence\r\n * info or null either if there is no presence available or if\r\n * the media type given is invalid.\r\n */\r\n getMediaPresenceInfo(endpointId, mediaType) {\r\n // Will figure out current muted status by looking up owner's presence\r\n const pres = this.lastPresences[`${this.roomjid}/${endpointId}`];\r\n\r\n if (!pres) {\r\n // No presence available\r\n return null;\r\n }\r\n const data = {\r\n muted: true, // muted by default\r\n videoType: mediaType === MediaType.VIDEO ? VideoType.CAMERA : undefined // 'camera' by default\r\n };\r\n let mutedNode = null;\r\n\r\n if (mediaType === MediaType.AUDIO) {\r\n mutedNode = filterNodeFromPresenceJSON(pres, 'audiomuted');\r\n } else if (mediaType === MediaType.VIDEO) {\r\n mutedNode = filterNodeFromPresenceJSON(pres, 'videomuted');\r\n const codecTypeNode = filterNodeFromPresenceJSON(pres, 'jitsi_participant_codecType');\r\n const videoTypeNode = filterNodeFromPresenceJSON(pres, 'videoType');\r\n\r\n if (videoTypeNode.length > 0) {\r\n data.videoType = videoTypeNode[0].value;\r\n }\r\n if (codecTypeNode.length > 0) {\r\n data.codecType = codecTypeNode[0].value;\r\n }\r\n } else {\r\n logger.error(`Unsupported media type: ${mediaType}`);\r\n\r\n return null;\r\n }\r\n\r\n if (mutedNode.length > 0) {\r\n data.muted = mutedNode[0].value === 'true';\r\n }\r\n\r\n return data;\r\n }\r\n\r\n /**\r\n * Returns the last presence advertised by a MUC member.\r\n * @param {string} mucNick\r\n * @returns {*}\r\n */\r\n getLastPresence(mucNick) {\r\n return this.lastPresences[`${this.roomjid}/${mucNick}`];\r\n }\r\n\r\n /**\r\n * Returns true if the SIP calls are supported and false otherwise\r\n */\r\n isSIPCallingSupported() {\r\n if (this.moderator) {\r\n return this.moderator.isSipGatewayEnabled();\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Dials a number.\r\n * @param number the number\r\n */\r\n dial(number) {\r\n return this.connection.rayo.dial(number, 'fromnumber',\r\n Strophe.getBareJidFromJid(this.myroomjid), this.password,\r\n this.focusMucJid);\r\n }\r\n\r\n /**\r\n * Hangup an existing call\r\n */\r\n hangup() {\r\n return this.connection.rayo.hangup();\r\n }\r\n\r\n /**\r\n *\r\n * @returns {Lobby}\r\n */\r\n getLobby() {\r\n return this.lobby;\r\n }\r\n\r\n /**\r\n * @returns {AVModeration}\r\n */\r\n getAVModeration() {\r\n return this.avModeration;\r\n }\r\n\r\n /**\r\n * @returns {BreakoutRooms}\r\n */\r\n getBreakoutRooms() {\r\n return this.breakoutRooms;\r\n }\r\n\r\n /**\r\n * Returns the phone number for joining the conference.\r\n */\r\n getPhoneNumber() {\r\n return this.phoneNumber;\r\n }\r\n\r\n /**\r\n * Returns the pin for joining the conference with phone.\r\n */\r\n getPhonePin() {\r\n return this.phonePin;\r\n }\r\n\r\n /**\r\n * Returns the meeting unique ID if any came from backend.\r\n *\r\n * @returns {string} - The meeting ID.\r\n */\r\n getMeetingId() {\r\n return this.meetingId;\r\n }\r\n\r\n /**\r\n * Mutes remote participant.\r\n * @param jid of the participant\r\n * @param mute\r\n * @param mediaType\r\n */\r\n muteParticipant(jid, mute, mediaType) {\r\n logger.info('set mute', mute, jid);\r\n const iqToFocus = $iq(\r\n { to: this.focusMucJid,\r\n type: 'set' })\r\n .c('mute', {\r\n xmlns: `http://jitsi.org/jitmeet/${mediaType}`,\r\n jid\r\n })\r\n .t(mute.toString())\r\n .up();\r\n\r\n this.connection.sendIQ(\r\n iqToFocus,\r\n result => logger.log('set mute', result),\r\n error => logger.log('set mute error', error));\r\n }\r\n\r\n /**\r\n * TODO: Document\r\n * @param iq\r\n */\r\n onMute(iq) {\r\n const from = iq.getAttribute('from');\r\n\r\n if (from !== this.focusMucJid) {\r\n logger.warn('Ignored mute from non focus peer');\r\n\r\n return;\r\n }\r\n const mute = $(iq).find('mute');\r\n\r\n if (mute.length && mute.text() === 'true') {\r\n this.eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS, mute.attr('actor'));\r\n } else {\r\n // XXX Why do we support anything but muting? Why do we encode the\r\n // value in the text of the element? Why do we use a separate XML\r\n // namespace?\r\n logger.warn('Ignoring a mute request which does not explicitly '\r\n + 'specify a positive mute command.');\r\n }\r\n }\r\n\r\n /**\r\n * TODO: Document\r\n * @param iq\r\n */\r\n onMuteVideo(iq) {\r\n const from = iq.getAttribute('from');\r\n\r\n if (from !== this.focusMucJid) {\r\n logger.warn('Ignored mute from non focus peer');\r\n\r\n return;\r\n }\r\n const mute = $(iq).find('mute');\r\n\r\n if (mute.length && mute.text() === 'true') {\r\n this.eventEmitter.emit(XMPPEvents.VIDEO_MUTED_BY_FOCUS, mute.attr('actor'));\r\n } else {\r\n // XXX Why do we support anything but muting? Why do we encode the\r\n // value in the text of the element? Why do we use a separate XML\r\n // namespace?\r\n logger.warn('Ignoring a mute request which does not explicitly '\r\n + 'specify a positive mute command.');\r\n }\r\n }\r\n\r\n /**\r\n * Clean any listeners or resources, executed on leaving.\r\n */\r\n clean() {\r\n this._removeConnListeners.forEach(remove => remove());\r\n this._removeConnListeners = [];\r\n\r\n this.joined = false;\r\n this.inProgressEmitted = false;\r\n }\r\n\r\n /**\r\n * Leaves the room. Closes the jingle session.\r\n * @returns {Promise} which is resolved if XMPPEvents.MUC_LEFT is received\r\n * less than 5s after sending presence unavailable. Otherwise the promise is\r\n * rejected.\r\n */\r\n leave() {\r\n this.avModeration.dispose();\r\n this.breakoutRooms.dispose();\r\n\r\n const promises = [];\r\n\r\n this.lobby?.lobbyRoom && promises.push(this.lobby.leave());\r\n\r\n promises.push(new Promise((resolve, reject) => {\r\n let timeout = -1;\r\n\r\n const onMucLeft = (doReject = false) => {\r\n this.eventEmitter.removeListener(XMPPEvents.MUC_LEFT, onMucLeft);\r\n clearTimeout(timeout);\r\n if (doReject) {\r\n // The timeout expired. Make sure we clean the EMUC state.\r\n this.connection.emuc.doLeave(this.roomjid);\r\n reject(new Error('The timeout for the confirmation about leaving the room expired.'));\r\n } else {\r\n resolve();\r\n }\r\n };\r\n\r\n timeout = setTimeout(() => onMucLeft(true), 5000);\r\n\r\n this.clean();\r\n this.eventEmitter.on(XMPPEvents.MUC_LEFT, onMucLeft);\r\n this.doLeave();\r\n }));\r\n\r\n return Promise.allSettled(promises);\r\n }\r\n}\r\n\r\n/* eslint-enable newline-per-chained-call */\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { Strophe } from 'strophe.js';\r\n\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\n\r\nimport ChatRoom from './ChatRoom';\r\nimport { ConnectionPluginListenable } from './ConnectionPlugin';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * MUC connection plugin.\r\n */\r\nexport default class MucConnectionPlugin extends ConnectionPluginListenable {\r\n /**\r\n *\r\n * @param xmpp\r\n */\r\n constructor(xmpp) {\r\n super();\r\n this.xmpp = xmpp;\r\n this.rooms = {};\r\n }\r\n\r\n /**\r\n *\r\n * @param connection\r\n */\r\n init(connection) {\r\n super.init(connection);\r\n\r\n // add handlers (just once)\r\n this.connection.addHandler(this.onPresence.bind(this), null,\r\n 'presence', null, null, null, null);\r\n this.connection.addHandler(this.onPresenceUnavailable.bind(this),\r\n null, 'presence', 'unavailable', null);\r\n this.connection.addHandler(this.onPresenceError.bind(this), null,\r\n 'presence', 'error', null);\r\n this.connection.addHandler(this.onMessage.bind(this), null,\r\n 'message', null, null);\r\n this.connection.addHandler(this.onMute.bind(this),\r\n 'http://jitsi.org/jitmeet/audio', 'iq', 'set', null, null);\r\n this.connection.addHandler(this.onMuteVideo.bind(this),\r\n 'http://jitsi.org/jitmeet/video', 'iq', 'set', null, null);\r\n }\r\n\r\n /**\r\n *\r\n * @param jid\r\n * @param password\r\n * @param options\r\n */\r\n createRoom(jid, password, options) {\r\n const roomJid = Strophe.getBareJidFromJid(jid);\r\n\r\n if (this.isRoomCreated(roomJid)) {\r\n const errmsg = 'You are already in the room!';\r\n\r\n logger.error(errmsg);\r\n throw new Error(errmsg);\r\n }\r\n this.rooms[roomJid] = new ChatRoom(this.connection, jid,\r\n password, this.xmpp, options);\r\n this.eventEmitter.emit(\r\n XMPPEvents.EMUC_ROOM_ADDED, this.rooms[roomJid]);\r\n\r\n return this.rooms[roomJid];\r\n }\r\n\r\n /**\r\n * Check if a room with the passed JID is already created.\r\n *\r\n * @param {string} roomJid - The JID of the room.\r\n * @returns {boolean}\r\n */\r\n isRoomCreated(roomJid) {\r\n return roomJid in this.rooms;\r\n }\r\n\r\n /**\r\n *\r\n * @param jid\r\n */\r\n doLeave(jid) {\r\n this.eventEmitter.emit(\r\n XMPPEvents.EMUC_ROOM_REMOVED, this.rooms[jid]);\r\n delete this.rooms[jid];\r\n }\r\n\r\n /**\r\n *\r\n * @param pres\r\n */\r\n onPresence(pres) {\r\n const from = pres.getAttribute('from');\r\n\r\n // What is this for? A workaround for something?\r\n if (pres.getAttribute('type')) {\r\n return true;\r\n }\r\n\r\n const room = this.rooms[Strophe.getBareJidFromJid(from)];\r\n\r\n if (!room) {\r\n return true;\r\n }\r\n\r\n // Parse status.\r\n if ($(pres).find('>x[xmlns=\"http://jabber.org/protocol/muc#user\"]'\r\n + '>status[code=\"201\"]').length) {\r\n room.createNonAnonymousRoom();\r\n }\r\n\r\n room.onPresence(pres);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n *\r\n * @param pres\r\n */\r\n onPresenceUnavailable(pres) {\r\n const from = pres.getAttribute('from');\r\n const room = this.rooms[Strophe.getBareJidFromJid(from)];\r\n\r\n if (!room) {\r\n return true;\r\n }\r\n\r\n room.onPresenceUnavailable(pres, from);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n *\r\n * @param pres\r\n */\r\n onPresenceError(pres) {\r\n const from = pres.getAttribute('from');\r\n const room = this.rooms[Strophe.getBareJidFromJid(from)];\r\n\r\n if (!room) {\r\n return true;\r\n }\r\n\r\n room.onPresenceError(pres, from);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n *\r\n * @param msg\r\n */\r\n onMessage(msg) {\r\n // FIXME: this is a hack. but jingle on muc makes nickchanges hard\r\n const from = msg.getAttribute('from');\r\n const room = this.rooms[Strophe.getBareJidFromJid(from)];\r\n\r\n if (!room) {\r\n return true;\r\n }\r\n\r\n room.onMessage(msg, from);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * TODO: Document\r\n * @param iq\r\n */\r\n onMute(iq) {\r\n const from = iq.getAttribute('from');\r\n const room = this.rooms[Strophe.getBareJidFromJid(from)];\r\n\r\n // Returning false would result in the listener being deregistered by Strophe\r\n if (!room) {\r\n return true;\r\n }\r\n\r\n room.onMute(iq);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * TODO: Document\r\n * @param iq\r\n */\r\n onMuteVideo(iq) {\r\n const from = iq.getAttribute('from');\r\n const room = this.rooms[Strophe.getBareJidFromJid(from)];\r\n\r\n // Returning false would result in the listener being deregistered by Strophe\r\n if (!room) {\r\n return true;\r\n }\r\n\r\n room.onMuteVideo(iq);\r\n\r\n return true;\r\n }\r\n}\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $build } from 'strophe.js';\r\n\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Creates a \"source\" XML element for the source described in compact JSON format in [sourceCompactJson].\r\n * @param {*} owner the endpoint ID of the owner of the source.\r\n * @param {*} sourceCompactJson the compact JSON representation of the source.\r\n * @returns the created \"source\" XML element.\r\n */\r\nfunction _createSourceExtension(owner, sourceCompactJson) {\r\n const node = $build('source', {\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',\r\n ssrc: sourceCompactJson.s,\r\n name: FeatureFlags.isSourceNameSignalingEnabled() ? sourceCompactJson.n : undefined\r\n });\r\n\r\n if (sourceCompactJson.m) {\r\n node.c('parameter', {\r\n name: 'msid',\r\n value: sourceCompactJson.m\r\n }).up();\r\n }\r\n node.c('ssrc-info', {\r\n xmlns: 'http://jitsi.org/jitmeet',\r\n owner\r\n }).up();\r\n\r\n return node.node;\r\n}\r\n\r\n/**\r\n * Creates an \"ssrc-group\" XML element for the SSRC group described in compact JSON format in [ssrcGroupCompactJson].\r\n * @param {*} ssrcGroupCompactJson the compact JSON representation of the SSRC group.\r\n * @returns the created \"ssrc-group\" element.\r\n */\r\nfunction _createSsrcGroupExtension(ssrcGroupCompactJson) {\r\n const node = $build('ssrc-group', {\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',\r\n semantics: _getSemantics(ssrcGroupCompactJson[0])\r\n });\r\n\r\n for (let i = 1; i < ssrcGroupCompactJson.length; i++) {\r\n node.c('source', {\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0',\r\n ssrc: ssrcGroupCompactJson[i]\r\n }).up();\r\n }\r\n\r\n return node.node;\r\n}\r\n\r\n/**\r\n * Finds in a Jingle IQ the RTP description element with the given media type. If one does not exists, create it (as\r\n * well as the required \"content\" parent element) and adds it to the IQ.\r\n * @param {*} iq\r\n * @param {*} mediaType The media type, \"audio\" or \"video\".\r\n * @returns the RTP description element with the given media type.\r\n */\r\nfunction _getOrCreateRtpDescription(iq, mediaType) {\r\n const jingle = $(iq).find('jingle')[0];\r\n let content = $(jingle).find(`content[name=\"${mediaType}\"]`);\r\n let description;\r\n\r\n if (content.length) {\r\n content = content[0];\r\n } else {\r\n // I'm not suree if \"creator\" and \"senders\" are required.\r\n content = $build('content', {\r\n name: mediaType\r\n }).node;\r\n jingle.appendChild(content);\r\n }\r\n\r\n description = $(content).find('description');\r\n\r\n if (description.length) {\r\n description = description[0];\r\n } else {\r\n description = $build('description', {\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:1',\r\n media: mediaType\r\n }).node;\r\n content.appendChild(description);\r\n }\r\n\r\n return description;\r\n}\r\n\r\n/**\r\n * Converts the short string representing SSRC group semantics in compact JSON format to the standard representation\r\n * (i.e. convert \"f\" to \"FID\" and \"s\" to \"SIM\").\r\n * @param {*} str the compact JSON format representation of an SSRC group's semantics.\r\n * @returns the SSRC group semantics corresponding to [str].\r\n */\r\nfunction _getSemantics(str) {\r\n if (str === 'f') {\r\n return 'FID';\r\n } else if (str === 's') {\r\n return 'SIM';\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Reads a JSON-encoded message (from a \"json-message\" element) and extracts source descriptions. Adds the extracted\r\n * source descriptions to the given Jingle IQ in the standard Jingle format.\r\n *\r\n * Encoding sources in this compact JSON format instead of standard Jingle was introduced in order to reduce the\r\n * network traffic and load on the XMPP server. The format is described in Jicofo [TODO: insert link].\r\n *\r\n * @param {*} iq the IQ to which source descriptions will be added.\r\n * @param {*} jsonMessageXml The XML node for the \"json-message\" element.\r\n * @returns {Map} The audio and video ssrcs extracted from the JSON-encoded message with remote\r\n * endpoint id as the key.\r\n */\r\nexport function expandSourcesFromJson(iq, jsonMessageXml) {\r\n let json;\r\n\r\n try {\r\n json = JSON.parse(jsonMessageXml.textContent);\r\n } catch (error) {\r\n logger.error(`json-message XML contained invalid JSON, ignoring: ${jsonMessageXml.textContent}`);\r\n\r\n return null;\r\n }\r\n\r\n if (!json?.sources) {\r\n // It might be a message of a different type, no need to log.\r\n return null;\r\n }\r\n\r\n // This is where we'll add \"source\" and \"ssrc-group\" elements. Create them elements if they don't exist.\r\n const audioRtpDescription = _getOrCreateRtpDescription(iq, MediaType.AUDIO);\r\n const videoRtpDescription = _getOrCreateRtpDescription(iq, MediaType.VIDEO);\r\n const ssrcMap = new Map();\r\n\r\n for (const owner in json.sources) {\r\n if (json.sources.hasOwnProperty(owner)) {\r\n const ssrcs = [];\r\n const ownerSources = json.sources[owner];\r\n\r\n // The video sources, video ssrc-groups, audio sources and audio ssrc-groups are encoded in that order in\r\n // the elements of the array.\r\n const videoSources = ownerSources?.length && ownerSources[0];\r\n const videoSsrcGroups = ownerSources?.length > 1 && ownerSources[1];\r\n const audioSources = ownerSources?.length > 2 && ownerSources[2];\r\n const audioSsrcGroups = ownerSources?.length > 3 && ownerSources[3];\r\n\r\n if (videoSources?.length) {\r\n for (let i = 0; i < videoSources.length; i++) {\r\n videoRtpDescription.appendChild(_createSourceExtension(owner, videoSources[i]));\r\n ssrcs.push(videoSources[i]?.s);\r\n }\r\n }\r\n\r\n if (videoSsrcGroups?.length) {\r\n for (let i = 0; i < videoSsrcGroups.length; i++) {\r\n videoRtpDescription.appendChild(_createSsrcGroupExtension(videoSsrcGroups[i]));\r\n }\r\n }\r\n if (audioSources?.length) {\r\n for (let i = 0; i < audioSources.length; i++) {\r\n audioRtpDescription.appendChild(_createSourceExtension(owner, audioSources[i]));\r\n ssrcs.push(audioSources[i]?.s);\r\n }\r\n }\r\n\r\n if (audioSsrcGroups?.length) {\r\n for (let i = 0; i < audioSsrcGroups.length; i++) {\r\n audioRtpDescription.appendChild(_createSsrcGroupExtension(audioSsrcGroups[i]));\r\n }\r\n }\r\n ssrcMap.set(owner, ssrcs);\r\n }\r\n }\r\n\r\n return ssrcMap;\r\n}\r\n","/**\r\n * Enumeration of the media direction types.\r\n */\r\nexport enum MediaDirection {\r\n /**\r\n * Media is send and receive is suspended.\r\n */\r\n INACTIVE = 'inactive',\r\n\r\n /**\r\n * Media is only received from remote peer.\r\n */\r\n RECVONLY = 'recvonly',\r\n\r\n /**\r\n * Media is only sent to the remote peer.\r\n */\r\n SENDONLY = 'sendonly',\r\n\r\n /**\r\n * Media is sent and received.\r\n */\r\n SENDRECV = 'sendrecv'\r\n};\r\n","\r\nimport JitsiTrackError from '../../JitsiTrackError';\r\nimport * as JitsiTrackErrors from '../../JitsiTrackErrors';\r\nimport browser from '../browser';\r\n\r\nconst logger = require('@jitsi/logger').getLogger(__filename);\r\n\r\n/**\r\n * The default frame rate for Screen Sharing.\r\n */\r\nexport const SS_DEFAULT_FRAME_RATE = 5;\r\n\r\n/**\r\n * Handles obtaining a stream from a screen capture on different browsers.\r\n */\r\nconst ScreenObtainer = {\r\n /**\r\n * If not null it means that the initialization process is still in\r\n * progress. It is used to make desktop stream request wait and continue\r\n * after it's done.\r\n * {@type Promise|null}\r\n */\r\n\r\n obtainStream: null,\r\n\r\n /**\r\n * Initializes the function used to obtain a screen capture\r\n * (this.obtainStream).\r\n *\r\n * @param {object} options\r\n */\r\n init(options = {}) {\r\n this.options = options;\r\n this.obtainStream = this._createObtainStreamMethod();\r\n\r\n if (!this.obtainStream) {\r\n logger.info('Desktop sharing disabled');\r\n }\r\n },\r\n\r\n /**\r\n * Returns a method which will be used to obtain the screen sharing stream\r\n * (based on the browser type).\r\n *\r\n * @returns {Function}\r\n * @private\r\n */\r\n _createObtainStreamMethod() {\r\n if (browser.isNWJS()) {\r\n return (onSuccess, onFailure) => {\r\n window.JitsiMeetNW.obtainDesktopStream(\r\n onSuccess,\r\n (error, constraints) => {\r\n let jitsiError;\r\n\r\n // FIXME:\r\n // This is very very dirty fix for recognising that the\r\n // user have clicked the cancel button from the Desktop\r\n // sharing pick window. The proper solution would be to\r\n // detect this in the NWJS application by checking the\r\n // streamId === \"\". Even better solution would be to\r\n // stop calling GUM from the NWJS app and just pass the\r\n // streamId to lib-jitsi-meet. This way the desktop\r\n // sharing implementation for NWJS and chrome extension\r\n // will be the same and lib-jitsi-meet will be able to\r\n // control the constraints, check the streamId, etc.\r\n //\r\n // I cannot find documentation about \"InvalidStateError\"\r\n // but this is what we are receiving from GUM when the\r\n // streamId for the desktop sharing is \"\".\r\n\r\n if (error && error.name === 'InvalidStateError') {\r\n jitsiError = new JitsiTrackError(\r\n JitsiTrackErrors.SCREENSHARING_USER_CANCELED\r\n );\r\n } else {\r\n jitsiError = new JitsiTrackError(\r\n error, constraints, [ 'desktop' ]);\r\n }\r\n (typeof onFailure === 'function')\r\n && onFailure(jitsiError);\r\n });\r\n };\r\n } else if (browser.isElectron()) {\r\n return this.obtainScreenOnElectron;\r\n } else if (browser.isReactNative() && browser.supportsGetDisplayMedia()) {\r\n return this.obtainScreenFromGetDisplayMediaRN;\r\n } else if (browser.supportsGetDisplayMedia()) {\r\n return this.obtainScreenFromGetDisplayMedia;\r\n }\r\n logger.log('Screen sharing not supported on ', browser.getName());\r\n\r\n return null;\r\n },\r\n\r\n /**\r\n * Gets the appropriate constraints for audio sharing.\r\n *\r\n * @returns {Object|boolean}\r\n */\r\n _getAudioConstraints() {\r\n const { audioQuality } = this.options;\r\n const audio = audioQuality?.stereo ? {\r\n autoGainControl: false,\r\n channelCount: 2,\r\n echoCancellation: false,\r\n noiseSuppression: false\r\n } : true;\r\n\r\n return audio;\r\n },\r\n\r\n /**\r\n * Checks whether obtaining a screen capture is supported in the current\r\n * environment.\r\n * @returns {boolean}\r\n */\r\n isSupported() {\r\n return this.obtainStream !== null;\r\n },\r\n\r\n /**\r\n * Obtains a screen capture stream on Electron.\r\n *\r\n * @param onSuccess - Success callback.\r\n * @param onFailure - Failure callback.\r\n */\r\n obtainScreenOnElectron(onSuccess, onFailure) {\r\n if (window.JitsiMeetScreenObtainer && window.JitsiMeetScreenObtainer.openDesktopPicker) {\r\n const { desktopSharingFrameRate, desktopSharingSources } = this.options;\r\n\r\n window.JitsiMeetScreenObtainer.openDesktopPicker(\r\n {\r\n desktopSharingSources: desktopSharingSources || [ 'screen', 'window' ]\r\n },\r\n (streamId, streamType, screenShareAudio = false) => {\r\n if (streamId) {\r\n let audioConstraints = false;\r\n\r\n if (screenShareAudio) {\r\n audioConstraints = {};\r\n const optionalConstraints = this._getAudioConstraints();\r\n\r\n if (typeof optionalConstraints !== 'boolean') {\r\n audioConstraints = {\r\n optional: optionalConstraints\r\n };\r\n }\r\n\r\n // Audio screen sharing for electron only works for screen type devices.\r\n // i.e. when the user shares the whole desktop.\r\n // Note. The documentation specifies that chromeMediaSourceId should not be present\r\n // which, in the case a users has multiple monitors, leads to them being shared all\r\n // at once. However we tested with chromeMediaSourceId present and it seems to be\r\n // working properly.\r\n if (streamType === 'screen') {\r\n audioConstraints.mandatory = {\r\n chromeMediaSource: 'desktop'\r\n };\r\n }\r\n }\r\n\r\n const constraints = {\r\n audio: audioConstraints,\r\n video: {\r\n mandatory: {\r\n chromeMediaSource: 'desktop',\r\n chromeMediaSourceId: streamId,\r\n minFrameRate: desktopSharingFrameRate?.min ?? SS_DEFAULT_FRAME_RATE,\r\n maxFrameRate: desktopSharingFrameRate?.max ?? SS_DEFAULT_FRAME_RATE,\r\n maxWidth: window.screen.width,\r\n maxHeight: window.screen.height\r\n }\r\n }\r\n };\r\n\r\n // We have to use the old API on Electron to get a desktop stream.\r\n navigator.mediaDevices.getUserMedia(constraints)\r\n .then(stream => onSuccess({\r\n stream,\r\n sourceId: streamId,\r\n sourceType: streamType\r\n }), onFailure);\r\n } else {\r\n // As noted in Chrome Desktop Capture API:\r\n // If user didn't select any source (i.e. canceled the prompt)\r\n // then the callback is called with an empty streamId.\r\n onFailure(new JitsiTrackError(JitsiTrackErrors.SCREENSHARING_USER_CANCELED));\r\n }\r\n },\r\n err => onFailure(new JitsiTrackError(\r\n JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_ERROR,\r\n err\r\n ))\r\n );\r\n } else {\r\n onFailure(new JitsiTrackError(JitsiTrackErrors.ELECTRON_DESKTOP_PICKER_NOT_FOUND));\r\n }\r\n },\r\n\r\n /**\r\n * Obtains a screen capture stream using getDisplayMedia.\r\n *\r\n * @param callback - The success callback.\r\n * @param errorCallback - The error callback.\r\n */\r\n obtainScreenFromGetDisplayMedia(callback, errorCallback) {\r\n let getDisplayMedia;\r\n\r\n if (navigator.getDisplayMedia) {\r\n getDisplayMedia = navigator.getDisplayMedia.bind(navigator);\r\n } else {\r\n // eslint-disable-next-line max-len\r\n getDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);\r\n }\r\n\r\n const { desktopSharingFrameRate } = this.options;\r\n const setScreenSharingResolutionConstraints = browser.isChromiumBased()\r\n && this.options?.testing?.setScreenSharingResolutionConstraints;\r\n let video = {};\r\n\r\n if (typeof desktopSharingFrameRate === 'object') {\r\n video.frameRate = desktopSharingFrameRate;\r\n }\r\n if (setScreenSharingResolutionConstraints) {\r\n // Set bogus resolution constraints to work around\r\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1056311\r\n video.height = 99999;\r\n video.width = 99999;\r\n }\r\n\r\n const audio = this._getAudioConstraints();\r\n\r\n // At the time of this writing 'min' constraint for fps is not supported by getDisplayMedia.\r\n video.frameRate && delete video.frameRate.min;\r\n\r\n if (Object.keys(video).length === 0) {\r\n video = true;\r\n }\r\n\r\n const constraints = {\r\n video,\r\n audio,\r\n cursor: 'always'\r\n };\r\n\r\n logger.info('Using getDisplayMedia for screen sharing', constraints);\r\n\r\n getDisplayMedia(constraints)\r\n .then(stream => {\r\n callback({\r\n stream,\r\n sourceId: stream.id\r\n });\r\n })\r\n .catch(error => {\r\n const errorDetails = {\r\n errorName: error && error.name,\r\n errorMsg: error && error.message,\r\n errorStack: error && error.stack\r\n };\r\n\r\n logger.error('getDisplayMedia error', constraints, errorDetails);\r\n\r\n if (errorDetails.errorMsg && errorDetails.errorMsg.indexOf('denied by system') !== -1) {\r\n // On Chrome this is the only thing different between error returned when user cancels\r\n // and when no permission was given on the OS level.\r\n errorCallback(new JitsiTrackError(JitsiTrackErrors.PERMISSION_DENIED));\r\n\r\n return;\r\n }\r\n\r\n errorCallback(new JitsiTrackError(JitsiTrackErrors.SCREENSHARING_USER_CANCELED));\r\n });\r\n },\r\n\r\n /**\r\n * Obtains a screen capture stream using getDisplayMedia.\r\n *\r\n * @param callback - The success callback.\r\n * @param errorCallback - The error callback.\r\n */\r\n obtainScreenFromGetDisplayMediaRN(callback, errorCallback) {\r\n logger.info('Using getDisplayMedia for screen sharing');\r\n\r\n navigator.mediaDevices.getDisplayMedia({ video: true })\r\n .then(stream => {\r\n callback({\r\n stream,\r\n sourceId: stream.id });\r\n })\r\n .catch(() => {\r\n errorCallback(new JitsiTrackError(JitsiTrackErrors\r\n .SCREENSHARING_USER_CANCELED));\r\n });\r\n },\r\n\r\n /**\r\n * Sets the max frame rate to be used for a desktop track capture.\r\n *\r\n * @param {number} maxFps capture frame rate to be used for desktop tracks.\r\n * @returns {void}\r\n */\r\n setDesktopSharingFrameRate(maxFps) {\r\n logger.info(`Setting the desktop capture rate to ${maxFps}`);\r\n\r\n this.options.desktopSharingFrameRate = {\r\n min: SS_DEFAULT_FRAME_RATE,\r\n max: maxFps\r\n };\r\n }\r\n};\r\n\r\nexport default ScreenObtainer;\r\n","import { getLogger } from '@jitsi/logger';\r\nconst logger = getLogger(__filename);\r\n\r\nimport CodecMimeType from '../../service/RTC/CodecMimeType';\r\nimport { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport browser from '../browser';\r\nimport RandomUtil from '../util/RandomUtil';\r\n\r\nconst SDPUtil = {\r\n filterSpecialChars(text) {\r\n // XXX Neither one of the falsy values (e.g. null, undefined, false,\r\n // \"\", etc.) \"contain\" special chars.\r\n // eslint-disable-next-line no-useless-escape\r\n return text ? text.replace(/[\\\\\\/\\{,\\}\\+]/g, '') : text;\r\n },\r\n iceparams(mediadesc, sessiondesc) {\r\n let data = null;\r\n let pwd, ufrag;\r\n\r\n if ((ufrag = SDPUtil.findLine(mediadesc, 'a=ice-ufrag:', sessiondesc))\r\n && (pwd\r\n = SDPUtil.findLine(\r\n mediadesc,\r\n 'a=ice-pwd:',\r\n sessiondesc))) {\r\n data = {\r\n ufrag: SDPUtil.parseICEUfrag(ufrag),\r\n pwd: SDPUtil.parseICEPwd(pwd)\r\n };\r\n }\r\n\r\n return data;\r\n },\r\n parseICEUfrag(line) {\r\n return line.substring(12);\r\n },\r\n buildICEUfrag(frag) {\r\n return `a=ice-ufrag:${frag}`;\r\n },\r\n parseICEPwd(line) {\r\n return line.substring(10);\r\n },\r\n buildICEPwd(pwd) {\r\n return `a=ice-pwd:${pwd}`;\r\n },\r\n parseMID(line) {\r\n return line.substring(6);\r\n },\r\n\r\n /**\r\n * Finds the MSID attribute in the given array of SSRC attribute lines and returns the value.\r\n *\r\n * @param {string[]} ssrcLines - an array of lines similar to 'a:213123 msid:stream-id track-id'.\r\n * @returns {undefined|string}\r\n */\r\n parseMSIDAttribute(ssrcLines) {\r\n const msidLine = ssrcLines.find(line => line.indexOf(' msid:') > 0);\r\n\r\n if (!msidLine) {\r\n return undefined;\r\n }\r\n\r\n const v = msidLine.substring(msidLine.indexOf(' msid:') + 6 /* the length of ' msid:' */);\r\n\r\n return SDPUtil.filterSpecialChars(v);\r\n },\r\n parseMLine(line) {\r\n const data = {};\r\n const parts = line.substring(2).split(' ');\r\n\r\n data.media = parts.shift();\r\n data.port = parts.shift();\r\n data.proto = parts.shift();\r\n if (parts[parts.length - 1] === '') { // trailing whitespace\r\n parts.pop();\r\n }\r\n data.fmt = parts;\r\n\r\n return data;\r\n },\r\n buildMLine(mline) {\r\n return (\r\n `m=${mline.media} ${mline.port} ${mline.proto} ${\r\n mline.fmt.join(' ')}`);\r\n },\r\n parseRTPMap(line) {\r\n const data = {};\r\n let parts = line.substring(9).split(' ');\r\n\r\n data.id = parts.shift();\r\n parts = parts[0].split('/');\r\n data.name = parts.shift();\r\n data.clockrate = parts.shift();\r\n data.channels = parts.length ? parts.shift() : '1';\r\n\r\n return data;\r\n },\r\n\r\n /**\r\n * Parses SDP line \"a=sctpmap:...\" and extracts SCTP port from it.\r\n * @param line eg. \"a=sctpmap:5000 webrtc-datachannel\"\r\n * @returns [SCTP port number, protocol, streams]\r\n */\r\n parseSCTPMap(line) {\r\n const parts = line.substring(10).split(' ');\r\n const sctpPort = parts[0];\r\n const protocol = parts[1];\r\n\r\n // Stream count is optional\r\n const streamCount = parts.length > 2 ? parts[2] : null;\r\n\r\n\r\n return [ sctpPort, protocol, streamCount ];// SCTP port\r\n },\r\n parseSCTPPort(line) {\r\n return line.substring(12);\r\n },\r\n buildRTPMap(el) {\r\n let line\r\n = `a=rtpmap:${el.getAttribute('id')} ${el.getAttribute('name')}/${\r\n el.getAttribute('clockrate')}`;\r\n\r\n if (el.getAttribute('channels')\r\n && el.getAttribute('channels') !== '1') {\r\n line += `/${el.getAttribute('channels')}`;\r\n }\r\n\r\n return line;\r\n },\r\n parseCrypto(line) {\r\n const data = {};\r\n const parts = line.substring(9).split(' ');\r\n\r\n data.tag = parts.shift();\r\n data['crypto-suite'] = parts.shift();\r\n data['key-params'] = parts.shift();\r\n if (parts.length) {\r\n data['session-params'] = parts.join(' ');\r\n }\r\n\r\n return data;\r\n },\r\n parseFingerprint(line) { // RFC 4572\r\n const data = {};\r\n const parts = line.substring(14).split(' ');\r\n\r\n data.hash = parts.shift();\r\n data.fingerprint = parts.shift();\r\n\r\n // TODO assert that fingerprint satisfies 2UHEX *(\":\" 2UHEX) ?\r\n return data;\r\n },\r\n parseFmtp(line) {\r\n const data = [];\r\n let parts = line.split(' ');\r\n\r\n parts.shift();\r\n parts = parts.join(' ').split(';');\r\n for (let i = 0; i < parts.length; i++) {\r\n let key = parts[i].split('=')[0];\r\n\r\n while (key.length && key[0] === ' ') {\r\n key = key.substring(1);\r\n }\r\n const value = parts[i].split('=')[1];\r\n\r\n if (key && value) {\r\n data.push({ name: key,\r\n value });\r\n } else if (key) {\r\n // rfc 4733 (DTMF) style stuff\r\n data.push({ name: '',\r\n value: key });\r\n }\r\n }\r\n\r\n return data;\r\n },\r\n parseICECandidate(line) {\r\n const candidate = {};\r\n const elems = line.split(' ');\r\n\r\n candidate.foundation = elems[0].substring(12);\r\n candidate.component = elems[1];\r\n candidate.protocol = elems[2].toLowerCase();\r\n candidate.priority = elems[3];\r\n candidate.ip = elems[4];\r\n candidate.port = elems[5];\r\n\r\n // elems[6] => \"typ\"\r\n candidate.type = elems[7];\r\n candidate.generation = 0; // default value, may be overwritten below\r\n for (let i = 8; i < elems.length; i += 2) {\r\n switch (elems[i]) {\r\n case 'raddr':\r\n candidate['rel-addr'] = elems[i + 1];\r\n break;\r\n case 'rport':\r\n candidate['rel-port'] = elems[i + 1];\r\n break;\r\n case 'generation':\r\n candidate.generation = elems[i + 1];\r\n break;\r\n case 'tcptype':\r\n candidate.tcptype = elems[i + 1];\r\n break;\r\n default: // TODO\r\n logger.debug(\r\n `parseICECandidate not translating \"${\r\n elems[i]}\" = \"${elems[i + 1]}\"`);\r\n }\r\n }\r\n candidate.network = '1';\r\n\r\n // not applicable to SDP -- FIXME: should be unique, not just random\r\n // eslint-disable-next-line newline-per-chained-call\r\n candidate.id = Math.random().toString(36).substr(2, 10);\r\n\r\n return candidate;\r\n },\r\n buildICECandidate(cand) {\r\n let line = [\r\n `a=candidate:${cand.foundation}`,\r\n cand.component,\r\n cand.protocol,\r\n cand.priority,\r\n cand.ip,\r\n cand.port,\r\n 'typ',\r\n cand.type\r\n ].join(' ');\r\n\r\n line += ' ';\r\n switch (cand.type) {\r\n case 'srflx':\r\n case 'prflx':\r\n case 'relay':\r\n if (cand.hasOwnAttribute('rel-addr')\r\n && cand.hasOwnAttribute('rel-port')) {\r\n line += 'raddr';\r\n line += ' ';\r\n line += cand['rel-addr'];\r\n line += ' ';\r\n line += 'rport';\r\n line += ' ';\r\n line += cand['rel-port'];\r\n line += ' ';\r\n }\r\n break;\r\n }\r\n if (cand.hasOwnAttribute('tcptype')) {\r\n line += 'tcptype';\r\n line += ' ';\r\n line += cand.tcptype;\r\n line += ' ';\r\n }\r\n line += 'generation';\r\n line += ' ';\r\n line += cand.hasOwnAttribute('generation') ? cand.generation : '0';\r\n\r\n return line;\r\n },\r\n parseSSRC(desc) {\r\n // proprietary mapping of a=ssrc lines\r\n // TODO: see \"Jingle RTP Source Description\" by Juberti and P. Thatcher\r\n // on google docs and parse according to that\r\n const data = new Map();\r\n const lines = desc.split('\\r\\n');\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n if (lines[i].substring(0, 7) === 'a=ssrc:') {\r\n // FIXME: Use regex to smartly find the ssrc.\r\n const ssrc = lines[i].split('a=ssrc:')[1].split(' ')[0];\r\n\r\n if (!data.get(ssrc)) {\r\n data.set(ssrc, []);\r\n }\r\n\r\n data.get(ssrc).push(lines[i]);\r\n }\r\n }\r\n\r\n return data;\r\n },\r\n\r\n /**\r\n * Gets the source name out of the name attribute \"a=ssrc:254321 name:name1\".\r\n *\r\n * @param {string[]} ssrcLines\r\n * @returns {string | undefined}\r\n */\r\n parseSourceNameLine(ssrcLines) {\r\n const sourceNameLine = ssrcLines.find(ssrcSdpLine => ssrcSdpLine.indexOf(' name:') > 0);\r\n\r\n // Everything past the \"name:\" part\r\n return sourceNameLine?.substring(sourceNameLine.indexOf(' name:') + 6);\r\n },\r\n parseRTCPFB(line) {\r\n const parts = line.substr(10).split(' ');\r\n const data = {};\r\n\r\n data.pt = parts.shift();\r\n data.type = parts.shift();\r\n data.params = parts;\r\n\r\n return data;\r\n },\r\n parseExtmap(line) {\r\n const parts = line.substr(9).split(' ');\r\n const data = {};\r\n\r\n data.value = parts.shift();\r\n if (data.value.indexOf('/') === -1) {\r\n data.direction = 'both';\r\n } else {\r\n data.direction = data.value.substr(data.value.indexOf('/') + 1);\r\n data.value = data.value.substr(0, data.value.indexOf('/'));\r\n }\r\n data.uri = parts.shift();\r\n data.params = parts;\r\n\r\n return data;\r\n },\r\n findLine(haystack, needle, sessionpart) {\r\n let lines = haystack.split('\\r\\n');\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n if (lines[i].substring(0, needle.length) === needle) {\r\n return lines[i];\r\n }\r\n }\r\n if (!sessionpart) {\r\n return false;\r\n }\r\n\r\n // search session part\r\n lines = sessionpart.split('\\r\\n');\r\n for (let j = 0; j < lines.length; j++) {\r\n if (lines[j].substring(0, needle.length) === needle) {\r\n return lines[j];\r\n }\r\n }\r\n\r\n return false;\r\n },\r\n findLines(haystack, needle, sessionpart) {\r\n let lines = haystack.split('\\r\\n');\r\n const needles = [];\r\n\r\n for (let i = 0; i < lines.length; i++) {\r\n if (lines[i].substring(0, needle.length) === needle) {\r\n needles.push(lines[i]);\r\n }\r\n }\r\n if (needles.length || !sessionpart) {\r\n return needles;\r\n }\r\n\r\n // search session part\r\n lines = sessionpart.split('\\r\\n');\r\n for (let j = 0; j < lines.length; j++) {\r\n if (lines[j].substring(0, needle.length) === needle) {\r\n needles.push(lines[j]);\r\n }\r\n }\r\n\r\n return needles;\r\n },\r\n candidateToJingle(line) {\r\n // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host\r\n // generation 0\r\n // \r\n if (line.indexOf('candidate:') === 0) {\r\n // eslint-disable-next-line no-param-reassign\r\n line = `a=${line}`;\r\n } else if (line.substring(0, 12) !== 'a=candidate:') {\r\n logger.warn(\r\n 'parseCandidate called with a line that is not a candidate'\r\n + ' line');\r\n logger.warn(line);\r\n\r\n return null;\r\n }\r\n if (line.substring(line.length - 2) === '\\r\\n') { // chomp it\r\n // eslint-disable-next-line no-param-reassign\r\n line = line.substring(0, line.length - 2);\r\n }\r\n const candidate = {};\r\n const elems = line.split(' ');\r\n\r\n if (elems[6] !== 'typ') {\r\n logger.warn('did not find typ in the right place');\r\n logger.warn(line);\r\n\r\n return null;\r\n }\r\n candidate.foundation = elems[0].substring(12);\r\n candidate.component = elems[1];\r\n candidate.protocol = elems[2].toLowerCase();\r\n candidate.priority = elems[3];\r\n candidate.ip = elems[4];\r\n candidate.port = elems[5];\r\n\r\n // elems[6] => \"typ\"\r\n candidate.type = elems[7];\r\n\r\n candidate.generation = '0'; // default, may be overwritten below\r\n for (let i = 8; i < elems.length; i += 2) {\r\n switch (elems[i]) {\r\n case 'raddr':\r\n candidate['rel-addr'] = elems[i + 1];\r\n break;\r\n case 'rport':\r\n candidate['rel-port'] = elems[i + 1];\r\n break;\r\n case 'generation':\r\n candidate.generation = elems[i + 1];\r\n break;\r\n case 'tcptype':\r\n candidate.tcptype = elems[i + 1];\r\n break;\r\n default: // TODO\r\n logger.debug(`not translating \"${elems[i]}\" = \"${elems[i + 1]}\"`);\r\n }\r\n }\r\n candidate.network = '1';\r\n\r\n // not applicable to SDP -- FIXME: should be unique, not just random\r\n // eslint-disable-next-line newline-per-chained-call\r\n candidate.id = Math.random().toString(36).substr(2, 10);\r\n\r\n return candidate;\r\n },\r\n candidateFromJingle(cand) {\r\n let line = 'a=candidate:';\r\n\r\n line += cand.getAttribute('foundation');\r\n line += ' ';\r\n line += cand.getAttribute('component');\r\n line += ' ';\r\n\r\n let protocol = cand.getAttribute('protocol');\r\n\r\n // use tcp candidates for FF\r\n\r\n if (browser.isFirefox() && protocol.toLowerCase() === 'ssltcp') {\r\n protocol = 'tcp';\r\n }\r\n\r\n line += protocol; // .toUpperCase(); // chrome M23 doesn't like this\r\n line += ' ';\r\n line += cand.getAttribute('priority');\r\n line += ' ';\r\n line += cand.getAttribute('ip');\r\n line += ' ';\r\n line += cand.getAttribute('port');\r\n line += ' ';\r\n line += 'typ';\r\n line += ` ${cand.getAttribute('type')}`;\r\n line += ' ';\r\n switch (cand.getAttribute('type')) {\r\n case 'srflx':\r\n case 'prflx':\r\n case 'relay':\r\n if (cand.getAttribute('rel-addr')\r\n && cand.getAttribute('rel-port')) {\r\n line += 'raddr';\r\n line += ' ';\r\n line += cand.getAttribute('rel-addr');\r\n line += ' ';\r\n line += 'rport';\r\n line += ' ';\r\n line += cand.getAttribute('rel-port');\r\n line += ' ';\r\n }\r\n break;\r\n }\r\n if (protocol.toLowerCase() === 'tcp') {\r\n line += 'tcptype';\r\n line += ' ';\r\n line += cand.getAttribute('tcptype');\r\n line += ' ';\r\n }\r\n line += 'generation';\r\n line += ' ';\r\n line += cand.getAttribute('generation') || '0';\r\n\r\n return `${line}\\r\\n`;\r\n },\r\n\r\n /**\r\n * Parse the 'most' primary video ssrc from the given m line\r\n * @param {object} mLine object as parsed from transform.parse\r\n * @return {number} the primary video ssrc from the given m line\r\n */\r\n parsePrimaryVideoSsrc(videoMLine) {\r\n const numSsrcs = videoMLine.ssrcs\r\n .map(ssrcInfo => ssrcInfo.id)\r\n .filter((ssrc, index, array) => array.indexOf(ssrc) === index)\r\n .length;\r\n const numGroups\r\n = (videoMLine.ssrcGroups && videoMLine.ssrcGroups.length) || 0;\r\n\r\n if (numSsrcs > 1 && numGroups === 0) {\r\n // Ambiguous, can't figure out the primary\r\n return;\r\n }\r\n let primarySsrc = null;\r\n\r\n if (numSsrcs === 1) {\r\n primarySsrc = videoMLine.ssrcs[0].id;\r\n } else if (numSsrcs === 2) {\r\n // Can figure it out if there's an FID group\r\n const fidGroup\r\n = videoMLine.ssrcGroups.find(\r\n group => group.semantics === 'FID');\r\n\r\n if (fidGroup) {\r\n primarySsrc = fidGroup.ssrcs.split(' ')[0];\r\n }\r\n } else if (numSsrcs >= 3) {\r\n // Can figure it out if there's a sim group\r\n const simGroup\r\n = videoMLine.ssrcGroups.find(\r\n group => group.semantics === 'SIM');\r\n\r\n if (simGroup) {\r\n primarySsrc = simGroup.ssrcs.split(' ')[0];\r\n }\r\n }\r\n\r\n return primarySsrc;\r\n },\r\n\r\n /**\r\n * Generate an ssrc\r\n * @returns {number} an ssrc\r\n */\r\n generateSsrc() {\r\n return RandomUtil.randomInt(1, 0xffffffff);\r\n },\r\n\r\n /**\r\n * Get an attribute for the given ssrc with the given attributeName\r\n * from the given mline\r\n * @param {object} mLine an mLine object as parsed from transform.parse\r\n * @param {number} ssrc the ssrc for which an attribute is desired\r\n * @param {string} attributeName the name of the desired attribute\r\n * @returns {string} the value corresponding to the given ssrc\r\n * and attributeName\r\n */\r\n getSsrcAttribute(mLine, ssrc, attributeName) {\r\n for (let i = 0; i < mLine.ssrcs.length; ++i) {\r\n const ssrcLine = mLine.ssrcs[i];\r\n\r\n if (ssrcLine.id === ssrc\r\n && ssrcLine.attribute === attributeName) {\r\n return ssrcLine.value;\r\n }\r\n }\r\n },\r\n\r\n /**\r\n * Parses the ssrcs from the group sdp line and\r\n * returns them as a list of numbers\r\n * @param {object} the ssrcGroup object as parsed from\r\n * sdp-transform\r\n * @returns {list} a list of the ssrcs in the group\r\n * parsed as numbers\r\n */\r\n parseGroupSsrcs(ssrcGroup) {\r\n return ssrcGroup\r\n .ssrcs\r\n .split(' ')\r\n .map(ssrcStr => parseInt(ssrcStr, 10));\r\n },\r\n\r\n /**\r\n * Get the mline of the given type from the given sdp\r\n * @param {object} sdp sdp as parsed from transform.parse\r\n * @param {string} type the type of the desired mline (e.g. \"video\")\r\n * @returns {object} a media object\r\n */\r\n getMedia(sdp, type) {\r\n return sdp.media.find(m => m.type === type);\r\n },\r\n\r\n /**\r\n * Extracts the ICE username fragment from an SDP string.\r\n * @param {string} sdp the SDP in raw text format\r\n */\r\n getUfrag(sdp) {\r\n const ufragLines\r\n = sdp.split('\\n').filter(line => line.startsWith('a=ice-ufrag:'));\r\n\r\n if (ufragLines.length > 0) {\r\n return ufragLines[0].substr('a=ice-ufrag:'.length);\r\n }\r\n },\r\n\r\n /**\r\n * Sets the given codecName as the preferred codec by moving it to the beginning\r\n * of the payload types list (modifies the given mline in place). All instances\r\n * of the codec are moved up.\r\n * @param {object} mLine the mline object from an sdp as parsed by transform.parse\r\n * @param {string} codecName the name of the preferred codec\r\n */\r\n preferCodec(mline, codecName) {\r\n if (!mline || !codecName) {\r\n return;\r\n }\r\n\r\n const matchingPayloadTypes = mline.rtp\r\n .filter(rtp => rtp.codec && rtp.codec.toLowerCase() === codecName.toLowerCase())\r\n .map(rtp => rtp.payload);\r\n\r\n if (matchingPayloadTypes) {\r\n // Call toString() on payloads to get around an issue within SDPTransform that sets\r\n // payloads as a number, instead of a string, when there is only one payload.\r\n const payloadTypes\r\n = mline.payloads\r\n .toString()\r\n .split(' ')\r\n .map(p => parseInt(p, 10));\r\n\r\n for (const pt of matchingPayloadTypes.reverse()) {\r\n const payloadIndex = payloadTypes.indexOf(pt);\r\n\r\n payloadTypes.splice(payloadIndex, 1);\r\n payloadTypes.unshift(pt);\r\n }\r\n mline.payloads = payloadTypes.join(' ');\r\n }\r\n },\r\n\r\n /**\r\n * Strips the given codec from the given mline. All related RTX payload\r\n * types are also stripped. If the resulting mline would have no codecs,\r\n * it's disabled.\r\n *\r\n * @param {object} mLine the mline object from an sdp as parsed by transform.parse.\r\n * @param {string} codecName the name of the codec which will be stripped.\r\n * @param {boolean} highProfile determines if only the high profile H264 codec needs to be\r\n * stripped from the sdp when the passed codecName is H264.\r\n */\r\n stripCodec(mLine, codecName, highProfile = false) {\r\n if (!mLine || !codecName) {\r\n return;\r\n }\r\n\r\n const h264Pts = [];\r\n let removePts = [];\r\n const stripH264HighCodec = codecName.toLowerCase() === CodecMimeType.H264 && highProfile;\r\n\r\n for (const rtp of mLine.rtp) {\r\n if (rtp.codec\r\n && rtp.codec.toLowerCase() === codecName.toLowerCase()) {\r\n if (stripH264HighCodec) {\r\n h264Pts.push(rtp.payload);\r\n } else {\r\n removePts.push(rtp.payload);\r\n }\r\n }\r\n }\r\n\r\n // high profile H264 codecs have 64 as the first two bytes of the profile-level-id.\r\n if (stripH264HighCodec) {\r\n removePts = mLine.fmtp\r\n .filter(item => h264Pts.indexOf(item.payload) > -1 && item.config.includes('profile-level-id=64'))\r\n .map(item => item.payload);\r\n }\r\n\r\n if (removePts.length > 0) {\r\n // We also need to remove the payload types that are related to RTX\r\n // for the codecs we want to disable.\r\n const rtxApts = removePts.map(item => `apt=${item}`);\r\n const rtxPts = mLine.fmtp.filter(\r\n item => rtxApts.indexOf(item.config) !== -1);\r\n\r\n removePts.push(...rtxPts.map(item => item.payload));\r\n\r\n // Call toString() on payloads to get around an issue within\r\n // SDPTransform that sets payloads as a number, instead of a string,\r\n // when there is only one payload.\r\n const allPts = mLine.payloads\r\n .toString()\r\n .split(' ')\r\n .map(Number);\r\n const keepPts = allPts.filter(pt => removePts.indexOf(pt) === -1);\r\n\r\n if (keepPts.length === 0) {\r\n // There are no other codecs, disable the stream.\r\n mLine.port = 0;\r\n mLine.direction = MediaDirection.INACTIVE;\r\n mLine.payloads = '*';\r\n } else {\r\n mLine.payloads = keepPts.join(' ');\r\n }\r\n\r\n mLine.rtp = mLine.rtp.filter(\r\n item => keepPts.indexOf(item.payload) !== -1);\r\n mLine.fmtp = mLine.fmtp.filter(\r\n item => keepPts.indexOf(item.payload) !== -1);\r\n if (mLine.rtcpFb) {\r\n mLine.rtcpFb = mLine.rtcpFb.filter(\r\n item => keepPts.indexOf(item.payload) !== -1);\r\n }\r\n }\r\n }\r\n};\r\n\r\nexport default SDPUtil;\r\n","/* global $ */\r\n\r\nimport clonedeep from 'lodash.clonedeep';\r\nimport transform from 'sdp-transform';\r\n\r\nimport { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport browser from '../browser';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\n\r\nimport SDPUtil from './SDPUtil';\r\n\r\n/**\r\n *\r\n * @param sdp\r\n */\r\nexport default function SDP(sdp) {\r\n const media = sdp.split('\\r\\nm=');\r\n\r\n for (let i = 1, length = media.length; i < length; i++) {\r\n let mediaI = `m=${media[i]}`;\r\n\r\n if (i !== length - 1) {\r\n mediaI += '\\r\\n';\r\n }\r\n media[i] = mediaI;\r\n }\r\n const session = `${media.shift()}\\r\\n`;\r\n\r\n this.media = media;\r\n this.raw = session + media.join('');\r\n this.session = session;\r\n}\r\n\r\n/**\r\n * A flag will make {@link transportToJingle} and {@link jingle2media} replace\r\n * ICE candidates IPs with invalid value of '1.1.1.1' which will cause ICE\r\n * failure. The flag is used in the automated testing.\r\n * @type {boolean}\r\n */\r\nSDP.prototype.failICE = false;\r\n\r\n/**\r\n * Whether or not to remove TCP ice candidates when translating from/to jingle.\r\n * @type {boolean}\r\n */\r\nSDP.prototype.removeTcpCandidates = false;\r\n\r\n/**\r\n * Whether or not to remove UDP ice candidates when translating from/to jingle.\r\n * @type {boolean}\r\n */\r\nSDP.prototype.removeUdpCandidates = false;\r\n\r\n/**\r\n * Adds a new m-line to the description so that a new local source can then be attached to the transceiver that gets\r\n * added after a reneogtiation cycle.\r\n *\r\n * @param {Mediatype} mediaType media type of the new source that is being added.\r\n */\r\nSDP.prototype.addMlineForNewLocalSource = function(mediaType) {\r\n const mid = this.media.length;\r\n const sdp = transform.parse(this.raw);\r\n const mline = clonedeep(sdp.media.find(m => m.type === mediaType));\r\n\r\n // Edit media direction, mid and remove the existing ssrc lines in the m-line.\r\n mline.mid = mid;\r\n mline.direction = MediaDirection.RECVONLY;\r\n\r\n // Remove the ssrcs and source groups.\r\n mline.msid = undefined;\r\n mline.ssrcs = undefined;\r\n mline.ssrcGroups = undefined;\r\n\r\n sdp.media = sdp.media.concat(mline);\r\n\r\n // We regenerate the BUNDLE group (since we added a new m-line)\r\n sdp.groups.forEach(group => {\r\n if (group.type === 'BUNDLE') {\r\n const mids = group.mids.split(' ');\r\n\r\n mids.push(mid);\r\n group.mids = mids.join(' ');\r\n }\r\n });\r\n this.raw = transform.write(sdp);\r\n};\r\n\r\n/**\r\n * Returns map of MediaChannel mapped per channel idx.\r\n */\r\nSDP.prototype.getMediaSsrcMap = function() {\r\n const mediaSSRCs = {};\r\n\r\n for (let mediaindex = 0; mediaindex < this.media.length; mediaindex++) {\r\n const mid\r\n = SDPUtil.parseMID(\r\n SDPUtil.findLine(this.media[mediaindex], 'a=mid:'));\r\n const media = {\r\n mediaindex,\r\n mid,\r\n ssrcs: {},\r\n ssrcGroups: []\r\n };\r\n\r\n mediaSSRCs[mediaindex] = media;\r\n\r\n SDPUtil.findLines(this.media[mediaindex], 'a=ssrc:').forEach(line => {\r\n const linessrc = line.substring(7).split(' ')[0];\r\n\r\n // allocate new ChannelSsrc\r\n\r\n if (!media.ssrcs[linessrc]) {\r\n media.ssrcs[linessrc] = {\r\n ssrc: linessrc,\r\n lines: []\r\n };\r\n }\r\n media.ssrcs[linessrc].lines.push(line);\r\n });\r\n SDPUtil.findLines(this.media[mediaindex], 'a=ssrc-group:').forEach(line => {\r\n const idx = line.indexOf(' ');\r\n const semantics = line.substr(0, idx).substr(13);\r\n const ssrcs = line.substr(14 + semantics.length).split(' ');\r\n\r\n if (ssrcs.length) {\r\n media.ssrcGroups.push({\r\n semantics,\r\n ssrcs\r\n });\r\n }\r\n });\r\n }\r\n\r\n return mediaSSRCs;\r\n};\r\n\r\n/**\r\n * Returns true if this SDP contains given SSRC.\r\n * @param ssrc the ssrc to check.\r\n * @returns {boolean} true if this SDP contains given SSRC.\r\n */\r\nSDP.prototype.containsSSRC = function(ssrc) {\r\n // FIXME this code is really strange - improve it if you can\r\n const medias = this.getMediaSsrcMap();\r\n let result = false;\r\n\r\n Object.keys(medias).forEach(mediaindex => {\r\n if (result) {\r\n return;\r\n }\r\n if (medias[mediaindex].ssrcs[ssrc]) {\r\n result = true;\r\n }\r\n });\r\n\r\n return result;\r\n};\r\n\r\n// add content's to a jingle element\r\nSDP.prototype.toJingle = function(elem, thecreator) {\r\n // https://xmpp.org/extensions/xep-0338.html\r\n SDPUtil.findLines(this.session, 'a=group:').forEach(line => {\r\n const parts = line.split(' ');\r\n const semantics = parts.shift().substr(8);\r\n\r\n elem.c('group', { xmlns: 'urn:xmpp:jingle:apps:grouping:0',\r\n semantics });\r\n for (let j = 0; j < parts.length; j++) {\r\n elem.c('content', { name: parts[j] }).up();\r\n }\r\n elem.up();\r\n });\r\n\r\n for (let i = 0; i < this.media.length; i++) {\r\n const mline = SDPUtil.parseMLine(this.media[i].split('\\r\\n')[0]);\r\n\r\n if (!(mline.media === 'audio'\r\n || mline.media === 'video'\r\n || mline.media === 'application')) {\r\n continue; // eslint-disable-line no-continue\r\n }\r\n\r\n let ssrc;\r\n const assrcline = SDPUtil.findLine(this.media[i], 'a=ssrc:');\r\n\r\n if (assrcline) {\r\n ssrc = assrcline.substring(7).split(' ')[0]; // take the first\r\n } else {\r\n ssrc = false;\r\n }\r\n\r\n elem.c('content', { creator: thecreator,\r\n name: mline.media });\r\n const amidline = SDPUtil.findLine(this.media[i], 'a=mid:');\r\n\r\n if (amidline) {\r\n // prefer identifier from a=mid if present\r\n const mid = SDPUtil.parseMID(amidline);\r\n\r\n elem.attrs({ name: mid });\r\n }\r\n\r\n if (mline.media === 'audio' || mline.media === 'video') {\r\n elem.c('description',\r\n { xmlns: 'urn:xmpp:jingle:apps:rtp:1',\r\n media: mline.media });\r\n if (ssrc) {\r\n elem.attrs({ ssrc });\r\n }\r\n for (let j = 0; j < mline.fmt.length; j++) {\r\n const rtpmap\r\n = SDPUtil.findLine(\r\n this.media[i],\r\n `a=rtpmap:${mline.fmt[j]}`);\r\n\r\n elem.c('payload-type', SDPUtil.parseRTPMap(rtpmap));\r\n\r\n // put any 'a=fmtp:' + mline.fmt[j] lines into \r\n const afmtpline\r\n = SDPUtil.findLine(\r\n this.media[i],\r\n `a=fmtp:${mline.fmt[j]}`);\r\n\r\n if (afmtpline) {\r\n const fmtpParameters = SDPUtil.parseFmtp(afmtpline);\r\n\r\n // eslint-disable-next-line max-depth\r\n for (let k = 0; k < fmtpParameters.length; k++) {\r\n elem.c('parameter', fmtpParameters[k]).up();\r\n }\r\n }\r\n\r\n // XEP-0293 -- map a=rtcp-fb\r\n this.rtcpFbToJingle(i, elem, mline.fmt[j]);\r\n\r\n elem.up();\r\n }\r\n\r\n if (ssrc) {\r\n const ssrcMap = SDPUtil.parseSSRC(this.media[i]);\r\n\r\n for (const [ availableSsrc, ssrcParameters ] of ssrcMap) {\r\n const sourceName = SDPUtil.parseSourceNameLine(ssrcParameters);\r\n\r\n elem.c('source', {\r\n ssrc: availableSsrc,\r\n name: FeatureFlags.isSourceNameSignalingEnabled() ? sourceName : undefined,\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'\r\n });\r\n\r\n const msid = SDPUtil.parseMSIDAttribute(ssrcParameters);\r\n\r\n // eslint-disable-next-line max-depth\r\n if (msid) {\r\n elem.c('parameter');\r\n elem.attrs({ name: 'msid' });\r\n elem.attrs({ value: msid });\r\n elem.up();\r\n }\r\n\r\n elem.up();\r\n }\r\n\r\n // XEP-0339 handle ssrc-group attributes\r\n const ssrcGroupLines\r\n = SDPUtil.findLines(this.media[i], 'a=ssrc-group:');\r\n\r\n ssrcGroupLines.forEach(line => {\r\n const idx = line.indexOf(' ');\r\n const semantics = line.substr(0, idx).substr(13);\r\n const ssrcs = line.substr(14 + semantics.length).split(' ');\r\n\r\n if (ssrcs.length) {\r\n elem.c('ssrc-group', { semantics,\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });\r\n ssrcs.forEach(s => elem.c('source', { ssrc: s }).up());\r\n elem.up();\r\n }\r\n });\r\n }\r\n\r\n const ridLines = SDPUtil.findLines(this.media[i], 'a=rid:');\r\n\r\n if (ridLines.length && browser.usesRidsForSimulcast()) {\r\n // Map a line which looks like \"a=rid:2 send\" to just\r\n // the rid (\"2\")\r\n const rids = ridLines\r\n .map(ridLine => ridLine.split(':')[1])\r\n .map(ridInfo => ridInfo.split(' ')[0]);\r\n\r\n rids.forEach(rid => {\r\n elem.c('source', {\r\n rid,\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'\r\n });\r\n elem.up();\r\n });\r\n const unifiedSimulcast\r\n = SDPUtil.findLine(this.media[i], 'a=simulcast:');\r\n\r\n if (unifiedSimulcast) {\r\n elem.c('rid-group', {\r\n semantics: 'SIM',\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'\r\n });\r\n rids.forEach(rid => {\r\n elem.c('source', { rid }).up();\r\n });\r\n elem.up();\r\n }\r\n }\r\n\r\n if (SDPUtil.findLine(this.media[i], 'a=rtcp-mux')) {\r\n elem.c('rtcp-mux').up();\r\n }\r\n\r\n // XEP-0293 -- map a=rtcp-fb:*\r\n this.rtcpFbToJingle(i, elem, '*');\r\n\r\n // XEP-0294\r\n const extmapLines = SDPUtil.findLines(this.media[i], 'a=extmap:');\r\n\r\n for (let j = 0; j < extmapLines.length; j++) {\r\n const extmap = SDPUtil.parseExtmap(extmapLines[j]);\r\n\r\n elem.c('rtp-hdrext', {\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',\r\n uri: extmap.uri,\r\n id: extmap.value\r\n });\r\n\r\n // eslint-disable-next-line max-depth\r\n if (extmap.hasOwnProperty('direction')) {\r\n\r\n // eslint-disable-next-line max-depth\r\n switch (extmap.direction) {\r\n case MediaDirection.SENDONLY:\r\n elem.attrs({ senders: 'responder' });\r\n break;\r\n case MediaDirection.RECVONLY:\r\n elem.attrs({ senders: 'initiator' });\r\n break;\r\n case MediaDirection.SENDRECV:\r\n elem.attrs({ senders: 'both' });\r\n break;\r\n case MediaDirection.INACTIVE:\r\n elem.attrs({ senders: 'none' });\r\n break;\r\n }\r\n }\r\n\r\n // TODO: handle params\r\n elem.up();\r\n }\r\n elem.up(); // end of description\r\n }\r\n\r\n // map ice-ufrag/pwd, dtls fingerprint, candidates\r\n this.transportToJingle(i, elem);\r\n\r\n const m = this.media[i];\r\n\r\n if (SDPUtil.findLine(m, `a=${MediaDirection.SENDRECV}`, this.session)) {\r\n elem.attrs({ senders: 'both' });\r\n } else if (SDPUtil.findLine(m, `a=${MediaDirection.SENDONLY}`, this.session)) {\r\n elem.attrs({ senders: 'initiator' });\r\n } else if (SDPUtil.findLine(m, `a=${MediaDirection.RECVONLY}`, this.session)) {\r\n elem.attrs({ senders: 'responder' });\r\n } else if (SDPUtil.findLine(m, `a=${MediaDirection.INACTIVE}`, this.session)) {\r\n elem.attrs({ senders: 'none' });\r\n }\r\n\r\n // Reject an m-line only when port is 0 and a=bundle-only is not present in the section.\r\n // The port is automatically set to 0 when bundle-only is used.\r\n if (mline.port === '0' && !SDPUtil.findLine(m, 'a=bundle-only', this.session)) {\r\n // estos hack to reject an m-line\r\n elem.attrs({ senders: 'rejected' });\r\n }\r\n elem.up(); // end of content\r\n }\r\n elem.up();\r\n\r\n return elem;\r\n};\r\n\r\nSDP.prototype.transportToJingle = function(mediaindex, elem) {\r\n elem.c('transport');\r\n\r\n // XEP-0343 DTLS/SCTP\r\n const sctpport\r\n = SDPUtil.findLine(this.media[mediaindex], 'a=sctp-port:', this.session);\r\n const sctpmap\r\n = SDPUtil.findLine(this.media[mediaindex], 'a=sctpmap:', this.session);\r\n\r\n if (sctpport) {\r\n const sctpAttrs = SDPUtil.parseSCTPPort(sctpport);\r\n\r\n elem.c('sctpmap', {\r\n xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',\r\n number: sctpAttrs, /* SCTP port */\r\n protocol: 'webrtc-datachannel' /* protocol */\r\n });\r\n\r\n // The parser currently requires streams to be present\r\n elem.attrs({ streams: 0 });\r\n elem.up();\r\n } else if (sctpmap) {\r\n const sctpAttrs = SDPUtil.parseSCTPMap(sctpmap);\r\n\r\n elem.c('sctpmap', {\r\n xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',\r\n number: sctpAttrs[0], /* SCTP port */\r\n protocol: sctpAttrs[1] /* protocol */\r\n });\r\n\r\n // Optional stream count attribute\r\n if (sctpAttrs.length > 2) {\r\n elem.attrs({ streams: sctpAttrs[2] });\r\n } else {\r\n elem.attrs({ streams: 0 });\r\n }\r\n elem.up();\r\n }\r\n\r\n // XEP-0320\r\n const fingerprints\r\n = SDPUtil.findLines(\r\n this.media[mediaindex],\r\n 'a=fingerprint:',\r\n this.session);\r\n\r\n fingerprints.forEach(line => {\r\n const fingerprint = SDPUtil.parseFingerprint(line);\r\n\r\n fingerprint.xmlns = 'urn:xmpp:jingle:apps:dtls:0';\r\n elem.c('fingerprint').t(fingerprint.fingerprint);\r\n delete fingerprint.fingerprint;\r\n\r\n const setupLine\r\n = SDPUtil.findLine(\r\n this.media[mediaindex],\r\n 'a=setup:',\r\n this.session);\r\n\r\n if (setupLine) {\r\n fingerprint.setup = setupLine.substr(8);\r\n }\r\n elem.attrs(fingerprint);\r\n elem.up(); // end of fingerprint\r\n });\r\n const iceParameters = SDPUtil.iceparams(this.media[mediaindex], this.session);\r\n\r\n if (iceParameters) {\r\n iceParameters.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';\r\n elem.attrs(iceParameters);\r\n\r\n // XEP-0176\r\n const candidateLines\r\n = SDPUtil.findLines(\r\n this.media[mediaindex],\r\n 'a=candidate:',\r\n this.session);\r\n\r\n candidateLines.forEach(line => { // add any a=candidate lines\r\n const candidate = SDPUtil.candidateToJingle(line);\r\n\r\n if (this.failICE) {\r\n candidate.ip = '1.1.1.1';\r\n }\r\n const protocol\r\n = candidate && typeof candidate.protocol === 'string'\r\n ? candidate.protocol.toLowerCase()\r\n : '';\r\n\r\n if ((this.removeTcpCandidates\r\n && (protocol === 'tcp' || protocol === 'ssltcp'))\r\n || (this.removeUdpCandidates && protocol === 'udp')) {\r\n return;\r\n }\r\n elem.c('candidate', candidate).up();\r\n });\r\n }\r\n elem.up(); // end of transport\r\n};\r\n\r\n// XEP-0293\r\nSDP.prototype.rtcpFbToJingle = function(mediaindex, elem, payloadtype) {\r\n const lines\r\n = SDPUtil.findLines(\r\n this.media[mediaindex],\r\n `a=rtcp-fb:${payloadtype}`);\r\n\r\n lines.forEach(line => {\r\n const feedback = SDPUtil.parseRTCPFB(line);\r\n\r\n if (feedback.type === 'trr-int') {\r\n elem.c('rtcp-fb-trr-int', {\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0',\r\n value: feedback.params[0]\r\n });\r\n elem.up();\r\n } else {\r\n elem.c('rtcp-fb', {\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0',\r\n type: feedback.type\r\n });\r\n if (feedback.params.length > 0) {\r\n elem.attrs({ 'subtype': feedback.params[0] });\r\n }\r\n elem.up();\r\n }\r\n });\r\n};\r\n\r\nSDP.prototype.rtcpFbFromJingle = function(elem, payloadtype) { // XEP-0293\r\n let sdp = '';\r\n const feedbackElementTrrInt\r\n = elem.find(\r\n '>rtcp-fb-trr-int[xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\"]');\r\n\r\n if (feedbackElementTrrInt.length) {\r\n sdp += 'a=rtcp-fb:* trr-int ';\r\n if (feedbackElementTrrInt.attr('value')) {\r\n sdp += feedbackElementTrrInt.attr('value');\r\n } else {\r\n sdp += '0';\r\n }\r\n sdp += '\\r\\n';\r\n }\r\n\r\n const feedbackElements = elem.find('>rtcp-fb[xmlns=\"urn:xmpp:jingle:apps:rtp:rtcp-fb:0\"]');\r\n\r\n feedbackElements.each((_, fb) => {\r\n sdp += `a=rtcp-fb:${payloadtype} ${fb.getAttribute('type')}`;\r\n if (fb.hasAttribute('subtype')) {\r\n sdp += ` ${fb.getAttribute('subtype')}`;\r\n }\r\n sdp += '\\r\\n';\r\n });\r\n\r\n return sdp;\r\n};\r\n\r\n// construct an SDP from a jingle stanza\r\nSDP.prototype.fromJingle = function(jingle) {\r\n const sessionId = Date.now();\r\n\r\n // Use a unique session id for every TPC.\r\n this.raw = 'v=0\\r\\n'\r\n + `o=- ${sessionId} 2 IN IP4 0.0.0.0\\r\\n`\r\n + 's=-\\r\\n'\r\n + 't=0 0\\r\\n';\r\n\r\n // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04\r\n // #section-8\r\n const groups\r\n = $(jingle).find('>group[xmlns=\"urn:xmpp:jingle:apps:grouping:0\"]');\r\n\r\n if (groups.length) {\r\n groups.each((idx, group) => {\r\n const contents\r\n = $(group)\r\n .find('>content')\r\n .map((_, content) => content.getAttribute('name'))\r\n .get();\r\n\r\n if (contents.length > 0) {\r\n this.raw\r\n += `a=group:${\r\n group.getAttribute('semantics')\r\n || group.getAttribute('type')} ${\r\n contents.join(' ')}\\r\\n`;\r\n }\r\n });\r\n }\r\n\r\n this.session = this.raw;\r\n jingle.find('>content').each((_, content) => {\r\n const m = this.jingle2media($(content));\r\n\r\n this.media.push(m);\r\n });\r\n\r\n // reconstruct msid-semantic -- apparently not necessary\r\n /*\r\n var msid = SDPUtil.parseSSRC(this.raw);\r\n if (msid.hasOwnProperty('mslabel')) {\r\n this.session += \"a=msid-semantic: WMS \" + msid.mslabel + \"\\r\\n\";\r\n }\r\n */\r\n\r\n this.raw = this.session + this.media.join('');\r\n};\r\n\r\n// translate a jingle content element into an an SDP media part\r\nSDP.prototype.jingle2media = function(content) {\r\n const desc = content.find('>description');\r\n const transport = content.find('>transport[xmlns=\"urn:xmpp:jingle:transports:ice-udp:1\"]');\r\n let sdp = '';\r\n const sctp = transport.find(\r\n '>sctpmap[xmlns=\"urn:xmpp:jingle:transports:dtls-sctp:1\"]');\r\n\r\n const media = { media: desc.attr('media') };\r\n\r\n media.port = '9';\r\n if (content.attr('senders') === 'rejected') {\r\n // estos hack to reject an m-line.\r\n media.port = '0';\r\n }\r\n if (transport.find('>fingerprint[xmlns=\"urn:xmpp:jingle:apps:dtls:0\"]').length) {\r\n media.proto = sctp.length ? 'UDP/DTLS/SCTP' : 'UDP/TLS/RTP/SAVPF';\r\n } else {\r\n media.proto = 'UDP/TLS/RTP/SAVPF';\r\n }\r\n if (sctp.length) {\r\n sdp += `m=application ${media.port} UDP/DTLS/SCTP webrtc-datachannel\\r\\n`;\r\n sdp += `a=sctp-port:${sctp.attr('number')}\\r\\n`;\r\n sdp += 'a=max-message-size:262144\\r\\n';\r\n } else {\r\n media.fmt\r\n = desc\r\n .find('>payload-type')\r\n .map((_, payloadType) => payloadType.getAttribute('id'))\r\n .get();\r\n sdp += `${SDPUtil.buildMLine(media)}\\r\\n`;\r\n }\r\n\r\n sdp += 'c=IN IP4 0.0.0.0\\r\\n';\r\n if (!sctp.length) {\r\n sdp += 'a=rtcp:1 IN IP4 0.0.0.0\\r\\n';\r\n }\r\n\r\n // XEP-0176 ICE parameters\r\n if (transport.length) {\r\n if (transport.attr('ufrag')) {\r\n sdp += `${SDPUtil.buildICEUfrag(transport.attr('ufrag'))}\\r\\n`;\r\n }\r\n if (transport.attr('pwd')) {\r\n sdp += `${SDPUtil.buildICEPwd(transport.attr('pwd'))}\\r\\n`;\r\n }\r\n transport.find('>fingerprint[xmlns=\"urn:xmpp:jingle:apps:dtls:0\"]').each((_, fingerprint) => {\r\n sdp += `a=fingerprint:${fingerprint.getAttribute('hash')}`;\r\n sdp += ` ${$(fingerprint).text()}`;\r\n sdp += '\\r\\n';\r\n if (fingerprint.hasAttribute('setup')) {\r\n sdp += `a=setup:${fingerprint.getAttribute('setup')}\\r\\n`;\r\n }\r\n });\r\n }\r\n\r\n // XEP-0176 ICE candidates\r\n transport.find('>candidate')\r\n .each((_, candidate) => {\r\n let protocol = candidate.getAttribute('protocol');\r\n\r\n protocol\r\n = typeof protocol === 'string' ? protocol.toLowerCase() : '';\r\n\r\n if ((this.removeTcpCandidates\r\n && (protocol === 'tcp' || protocol === 'ssltcp'))\r\n || (this.removeUdpCandidates && protocol === 'udp')) {\r\n return;\r\n } else if (this.failICE) {\r\n candidate.setAttribute('ip', '1.1.1.1');\r\n }\r\n\r\n sdp += SDPUtil.candidateFromJingle(candidate);\r\n });\r\n\r\n switch (content.attr('senders')) {\r\n case 'initiator':\r\n sdp += `a=${MediaDirection.SENDONLY}\\r\\n`;\r\n break;\r\n case 'responder':\r\n sdp += `a=${MediaDirection.RECVONLY}\\r\\n`;\r\n break;\r\n case 'none':\r\n sdp += `a=${MediaDirection.INACTIVE}\\r\\n`;\r\n break;\r\n case 'both':\r\n sdp += `a=${MediaDirection.SENDRECV}\\r\\n`;\r\n break;\r\n }\r\n sdp += `a=mid:${content.attr('name')}\\r\\n`;\r\n\r\n // \r\n // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec\r\n // though\r\n // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html\r\n if (desc.find('>rtcp-mux').length) {\r\n sdp += 'a=rtcp-mux\\r\\n';\r\n }\r\n\r\n desc.find('>payload-type').each((_, payloadType) => {\r\n sdp += `${SDPUtil.buildRTPMap(payloadType)}\\r\\n`;\r\n if ($(payloadType).find('>parameter').length) {\r\n sdp += `a=fmtp:${payloadType.getAttribute('id')} `;\r\n sdp\r\n += $(payloadType)\r\n .find('>parameter')\r\n .map((__, parameter) => {\r\n const name = parameter.getAttribute('name');\r\n\r\n return (\r\n (name ? `${name}=` : '')\r\n + parameter.getAttribute('value'));\r\n })\r\n .get()\r\n .join('; ');\r\n sdp += '\\r\\n';\r\n }\r\n\r\n // xep-0293\r\n sdp += this.rtcpFbFromJingle($(payloadType), payloadType.getAttribute('id'));\r\n });\r\n\r\n // xep-0293\r\n sdp += this.rtcpFbFromJingle(desc, '*');\r\n\r\n // xep-0294\r\n desc\r\n .find('>rtp-hdrext[xmlns=\"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0\"]')\r\n .each((_, hdrExt) => {\r\n sdp\r\n += `a=extmap:${hdrExt.getAttribute('id')} ${\r\n hdrExt.getAttribute('uri')}\\r\\n`;\r\n });\r\n\r\n // XEP-0339 handle ssrc-group attributes\r\n desc\r\n .find('>ssrc-group[xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\"]')\r\n .each((_, ssrcGroup) => {\r\n const semantics = ssrcGroup.getAttribute('semantics');\r\n const ssrcs\r\n = $(ssrcGroup)\r\n .find('>source')\r\n .map((__, source) => source.getAttribute('ssrc'))\r\n .get();\r\n\r\n if (ssrcs.length) {\r\n sdp += `a=ssrc-group:${semantics} ${ssrcs.join(' ')}\\r\\n`;\r\n }\r\n });\r\n\r\n // XEP-0339 handle source attributes\r\n let userSources = '';\r\n let nonUserSources = '';\r\n\r\n desc\r\n .find('>source[xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\"]')\r\n .each((_, source) => {\r\n const ssrc = source.getAttribute('ssrc');\r\n let isUserSource = true;\r\n let sourceStr = '';\r\n\r\n $(source)\r\n .find('>parameter')\r\n .each((__, parameter) => {\r\n const name = parameter.getAttribute('name');\r\n let value = parameter.getAttribute('value');\r\n\r\n value = SDPUtil.filterSpecialChars(value);\r\n sourceStr += `a=ssrc:${ssrc} ${name}`;\r\n\r\n if (value && value.length) {\r\n sourceStr += `:${value}`;\r\n }\r\n\r\n sourceStr += '\\r\\n';\r\n\r\n if (value?.includes('mixedmslabel')) {\r\n isUserSource = false;\r\n }\r\n });\r\n\r\n if (isUserSource) {\r\n userSources += sourceStr;\r\n } else {\r\n nonUserSources += sourceStr;\r\n }\r\n });\r\n\r\n // The sdp-interop package is relying the mixedmslabel m line to be the first one in order to set the direction\r\n // to sendrecv.\r\n sdp += nonUserSources + userSources;\r\n\r\n return sdp;\r\n};\r\n","import FeatureFlags from '../flags/FeatureFlags';\r\n\r\nimport SDPUtil from './SDPUtil';\r\n\r\n// this could be useful in Array.prototype.\r\n/**\r\n *\r\n * @param array1\r\n * @param array2\r\n */\r\nfunction arrayEquals(array1, array2) {\r\n // if the other array is a falsy value, return\r\n if (!array2) {\r\n return false;\r\n }\r\n\r\n // compare lengths - can save a lot of time\r\n if (array1.length !== array2.length) {\r\n return false;\r\n }\r\n\r\n for (let i = 0, l = array1.length; i < l; i++) {\r\n // Check if we have nested arrays\r\n if (array1[i] instanceof Array && array2[i] instanceof Array) {\r\n // recurse into the nested arrays\r\n if (!array1[i].equals(array2[i])) {\r\n return false;\r\n }\r\n } else if (array1[i] !== array2[i]) {\r\n // Warning - two different object instances will never be\r\n // equal: {x:20} != {x:20}\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n *\r\n * @param mySDP\r\n * @param otherSDP\r\n */\r\nexport default function SDPDiffer(mySDP, otherSDP) {\r\n this.mySDP = mySDP;\r\n this.otherSDP = otherSDP;\r\n if (!mySDP) {\r\n throw new Error('\"mySDP\" is undefined!');\r\n } else if (!otherSDP) {\r\n throw new Error('\"otherSDP\" is undefined!');\r\n }\r\n}\r\n\r\n/**\r\n * Returns map of MediaChannel that contains media contained in\r\n * 'mySDP', but not contained in 'otherSdp'. Mapped by channel idx.\r\n */\r\nSDPDiffer.prototype.getNewMedia = function() {\r\n\r\n const myMedias = this.mySDP.getMediaSsrcMap();\r\n const othersMedias = this.otherSDP.getMediaSsrcMap();\r\n const newMedia = {};\r\n\r\n Object.keys(othersMedias).forEach(othersMediaIdx => {\r\n const myMedia = myMedias[othersMediaIdx];\r\n const othersMedia = othersMedias[othersMediaIdx];\r\n\r\n if (!myMedia && othersMedia) {\r\n // Add whole channel\r\n newMedia[othersMediaIdx] = othersMedia;\r\n\r\n return;\r\n }\r\n\r\n // Look for new ssrcs across the channel\r\n Object.keys(othersMedia.ssrcs).forEach(ssrc => {\r\n if (Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {\r\n // Allocate channel if we've found ssrc that doesn't exist in\r\n // our channel\r\n if (!newMedia[othersMediaIdx]) {\r\n newMedia[othersMediaIdx] = {\r\n mediaindex: othersMedia.mediaindex,\r\n mid: othersMedia.mid,\r\n ssrcs: {},\r\n ssrcGroups: []\r\n };\r\n }\r\n newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];\r\n } else if (othersMedia.ssrcs[ssrc].lines\r\n && myMedia.ssrcs[ssrc].lines) {\r\n // we want to detect just changes in adding/removing msid\r\n const myContainMsid = myMedia.ssrcs[ssrc].lines.find(\r\n line => line.indexOf('msid') !== -1) !== undefined;\r\n const newContainMsid = othersMedia.ssrcs[ssrc].lines.find(\r\n line => line.indexOf('msid') !== -1) !== undefined;\r\n\r\n if (myContainMsid !== newContainMsid) {\r\n if (!newMedia[othersMediaIdx]) {\r\n newMedia[othersMediaIdx] = {\r\n mediaindex: othersMedia.mediaindex,\r\n mid: othersMedia.mid,\r\n ssrcs: {},\r\n ssrcGroups: []\r\n };\r\n }\r\n newMedia[othersMediaIdx].ssrcs[ssrc]\r\n = othersMedia.ssrcs[ssrc];\r\n }\r\n }\r\n });\r\n\r\n // Look for new ssrc groups across the channels\r\n othersMedia.ssrcGroups.forEach(otherSsrcGroup => {\r\n\r\n // try to match the other ssrc-group with an ssrc-group of ours\r\n let matched = false;\r\n\r\n for (let i = 0; i < myMedia.ssrcGroups.length; i++) {\r\n const mySsrcGroup = myMedia.ssrcGroups[i];\r\n\r\n if (otherSsrcGroup.semantics === mySsrcGroup.semantics\r\n && arrayEquals(otherSsrcGroup.ssrcs, mySsrcGroup.ssrcs)) {\r\n\r\n matched = true;\r\n break;\r\n }\r\n }\r\n\r\n if (!matched) {\r\n // Allocate channel if we've found an ssrc-group that doesn't\r\n // exist in our channel\r\n\r\n if (!newMedia[othersMediaIdx]) {\r\n newMedia[othersMediaIdx] = {\r\n mediaindex: othersMedia.mediaindex,\r\n mid: othersMedia.mid,\r\n ssrcs: {},\r\n ssrcGroups: []\r\n };\r\n }\r\n newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);\r\n }\r\n });\r\n });\r\n\r\n return newMedia;\r\n};\r\n\r\n/**\r\n * TODO: document!\r\n */\r\nSDPDiffer.prototype.toJingle = function(modify) {\r\n const sdpMediaSsrcs = this.getNewMedia();\r\n\r\n let modified = false;\r\n\r\n Object.keys(sdpMediaSsrcs).forEach(mediaindex => {\r\n modified = true;\r\n const media = sdpMediaSsrcs[mediaindex];\r\n\r\n modify.c('content', { name: media.mid });\r\n\r\n modify.c('description',\r\n { xmlns: 'urn:xmpp:jingle:apps:rtp:1',\r\n media: media.mid });\r\n\r\n // FIXME: not completely sure this operates on blocks and / or handles\r\n // different ssrcs correctly\r\n // generate sources from lines\r\n Object.keys(media.ssrcs).forEach(ssrcNum => {\r\n const mediaSsrc = media.ssrcs[ssrcNum];\r\n const ssrcLines = mediaSsrc.lines;\r\n const sourceName = SDPUtil.parseSourceNameLine(ssrcLines);\r\n\r\n modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });\r\n modify.attrs({\r\n name: FeatureFlags.isSourceNameSignalingEnabled() ? sourceName : undefined,\r\n ssrc: mediaSsrc.ssrc\r\n });\r\n\r\n // Only MSID attribute is sent\r\n const msid = SDPUtil.parseMSIDAttribute(ssrcLines);\r\n\r\n if (msid) {\r\n modify.c('parameter');\r\n modify.attrs({ name: 'msid' });\r\n modify.attrs({ value: msid });\r\n modify.up();\r\n }\r\n\r\n modify.up(); // end of source\r\n });\r\n\r\n // generate source groups from lines\r\n media.ssrcGroups.forEach(ssrcGroup => {\r\n if (ssrcGroup.ssrcs.length) {\r\n\r\n modify.c('ssrc-group', {\r\n semantics: ssrcGroup.semantics,\r\n xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'\r\n });\r\n\r\n ssrcGroup.ssrcs.forEach(ssrc => {\r\n modify.c('source', { ssrc })\r\n .up(); // end of source\r\n });\r\n modify.up(); // end of ssrc-group\r\n }\r\n });\r\n\r\n modify.up(); // end of description\r\n modify.up(); // end of content\r\n });\r\n\r\n return modified;\r\n};\r\n","/**\n * Creates a continuation function with some arguments already applied.\n *\n * Useful as a shorthand when combined with other control flow functions. Any\n * arguments passed to the returned function are added to the arguments\n * originally passed to apply.\n *\n * @name apply\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {Function} fn - The function you want to eventually apply all\n * arguments to. Invokes with (arguments...).\n * @param {...*} arguments... - Any number of arguments to automatically apply\n * when the continuation is called.\n * @returns {Function} the partially-applied function\n * @example\n *\n * // using apply\n * async.parallel([\n * async.apply(fs.writeFile, 'testfile1', 'test1'),\n * async.apply(fs.writeFile, 'testfile2', 'test2')\n * ]);\n *\n *\n * // the same process without using apply\n * async.parallel([\n * function(callback) {\n * fs.writeFile('testfile1', 'test1', callback);\n * },\n * function(callback) {\n * fs.writeFile('testfile2', 'test2', callback);\n * }\n * ]);\n *\n * // It's possible to pass any number of additional arguments when calling the\n * // continuation:\n *\n * node> var fn = async.apply(sys.puts, 'one');\n * node> fn('two', 'three');\n * one\n * two\n * three\n */\nfunction apply(fn, ...args) {\n return (...callArgs) => fn(...args,...callArgs);\n}\n\nfunction initialParams (fn) {\n return function (...args/*, callback*/) {\n var callback = args.pop();\n return fn.call(this, args, callback);\n };\n}\n\n/* istanbul ignore file */\n\nvar hasQueueMicrotask = typeof queueMicrotask === 'function' && queueMicrotask;\nvar hasSetImmediate = typeof setImmediate === 'function' && setImmediate;\nvar hasNextTick = typeof process === 'object' && typeof process.nextTick === 'function';\n\nfunction fallback(fn) {\n setTimeout(fn, 0);\n}\n\nfunction wrap(defer) {\n return (fn, ...args) => defer(() => fn(...args));\n}\n\nvar _defer;\n\nif (hasQueueMicrotask) {\n _defer = queueMicrotask;\n} else if (hasSetImmediate) {\n _defer = setImmediate;\n} else if (hasNextTick) {\n _defer = process.nextTick;\n} else {\n _defer = fallback;\n}\n\nvar setImmediate$1 = wrap(_defer);\n\n/**\n * Take a sync function and make it async, passing its return value to a\n * callback. This is useful for plugging sync functions into a waterfall,\n * series, or other async functions. Any arguments passed to the generated\n * function will be passed to the wrapped function (except for the final\n * callback argument). Errors thrown will be passed to the callback.\n *\n * If the function passed to `asyncify` returns a Promise, that promises's\n * resolved/rejected state will be used to call the callback, rather than simply\n * the synchronous return value.\n *\n * This also means you can asyncify ES2017 `async` functions.\n *\n * @name asyncify\n * @static\n * @memberOf module:Utils\n * @method\n * @alias wrapSync\n * @category Util\n * @param {Function} func - The synchronous function, or Promise-returning\n * function to convert to an {@link AsyncFunction}.\n * @returns {AsyncFunction} An asynchronous wrapper of the `func`. To be\n * invoked with `(args..., callback)`.\n * @example\n *\n * // passing a regular synchronous function\n * async.waterfall([\n * async.apply(fs.readFile, filename, \"utf8\"),\n * async.asyncify(JSON.parse),\n * function (data, next) {\n * // data is the result of parsing the text.\n * // If there was a parsing error, it would have been caught.\n * }\n * ], callback);\n *\n * // passing a function returning a promise\n * async.waterfall([\n * async.apply(fs.readFile, filename, \"utf8\"),\n * async.asyncify(function (contents) {\n * return db.model.create(contents);\n * }),\n * function (model, next) {\n * // `model` is the instantiated model object.\n * // If there was an error, this function would be skipped.\n * }\n * ], callback);\n *\n * // es2017 example, though `asyncify` is not needed if your JS environment\n * // supports async functions out of the box\n * var q = async.queue(async.asyncify(async function(file) {\n * var intermediateStep = await processFile(file);\n * return await somePromise(intermediateStep)\n * }));\n *\n * q.push(files);\n */\nfunction asyncify(func) {\n if (isAsync(func)) {\n return function (...args/*, callback*/) {\n const callback = args.pop();\n const promise = func.apply(this, args);\n return handlePromise(promise, callback)\n }\n }\n\n return initialParams(function (args, callback) {\n var result;\n try {\n result = func.apply(this, args);\n } catch (e) {\n return callback(e);\n }\n // if result is Promise object\n if (result && typeof result.then === 'function') {\n return handlePromise(result, callback)\n } else {\n callback(null, result);\n }\n });\n}\n\nfunction handlePromise(promise, callback) {\n return promise.then(value => {\n invokeCallback(callback, null, value);\n }, err => {\n invokeCallback(callback, err && err.message ? err : new Error(err));\n });\n}\n\nfunction invokeCallback(callback, error, value) {\n try {\n callback(error, value);\n } catch (err) {\n setImmediate$1(e => { throw e }, err);\n }\n}\n\nfunction isAsync(fn) {\n return fn[Symbol.toStringTag] === 'AsyncFunction';\n}\n\nfunction isAsyncGenerator(fn) {\n return fn[Symbol.toStringTag] === 'AsyncGenerator';\n}\n\nfunction isAsyncIterable(obj) {\n return typeof obj[Symbol.asyncIterator] === 'function';\n}\n\nfunction wrapAsync(asyncFn) {\n if (typeof asyncFn !== 'function') throw new Error('expected a function')\n return isAsync(asyncFn) ? asyncify(asyncFn) : asyncFn;\n}\n\n// conditionally promisify a function.\n// only return a promise if a callback is omitted\nfunction awaitify (asyncFn, arity = asyncFn.length) {\n if (!arity) throw new Error('arity is undefined')\n function awaitable (...args) {\n if (typeof args[arity - 1] === 'function') {\n return asyncFn.apply(this, args)\n }\n\n return new Promise((resolve, reject) => {\n args[arity - 1] = (err, ...cbArgs) => {\n if (err) return reject(err)\n resolve(cbArgs.length > 1 ? cbArgs : cbArgs[0]);\n };\n asyncFn.apply(this, args);\n })\n }\n\n return awaitable\n}\n\nfunction applyEach (eachfn) {\n return function applyEach(fns, ...callArgs) {\n const go = awaitify(function (callback) {\n var that = this;\n return eachfn(fns, (fn, cb) => {\n wrapAsync(fn).apply(that, callArgs.concat(cb));\n }, callback);\n });\n return go;\n };\n}\n\nfunction _asyncMap(eachfn, arr, iteratee, callback) {\n arr = arr || [];\n var results = [];\n var counter = 0;\n var _iteratee = wrapAsync(iteratee);\n\n return eachfn(arr, (value, _, iterCb) => {\n var index = counter++;\n _iteratee(value, (err, v) => {\n results[index] = v;\n iterCb(err);\n });\n }, err => {\n callback(err, results);\n });\n}\n\nfunction isArrayLike(value) {\n return value &&\n typeof value.length === 'number' &&\n value.length >= 0 &&\n value.length % 1 === 0;\n}\n\n// A temporary value used to identify if the loop should be broken.\n// See #1064, #1293\nconst breakLoop = {};\n\nfunction once(fn) {\n function wrapper (...args) {\n if (fn === null) return;\n var callFn = fn;\n fn = null;\n callFn.apply(this, args);\n }\n Object.assign(wrapper, fn);\n return wrapper\n}\n\nfunction getIterator (coll) {\n return coll[Symbol.iterator] && coll[Symbol.iterator]();\n}\n\nfunction createArrayIterator(coll) {\n var i = -1;\n var len = coll.length;\n return function next() {\n return ++i < len ? {value: coll[i], key: i} : null;\n }\n}\n\nfunction createES2015Iterator(iterator) {\n var i = -1;\n return function next() {\n var item = iterator.next();\n if (item.done)\n return null;\n i++;\n return {value: item.value, key: i};\n }\n}\n\nfunction createObjectIterator(obj) {\n var okeys = obj ? Object.keys(obj) : [];\n var i = -1;\n var len = okeys.length;\n return function next() {\n var key = okeys[++i];\n if (key === '__proto__') {\n return next();\n }\n return i < len ? {value: obj[key], key} : null;\n };\n}\n\nfunction createIterator(coll) {\n if (isArrayLike(coll)) {\n return createArrayIterator(coll);\n }\n\n var iterator = getIterator(coll);\n return iterator ? createES2015Iterator(iterator) : createObjectIterator(coll);\n}\n\nfunction onlyOnce(fn) {\n return function (...args) {\n if (fn === null) throw new Error(\"Callback was already called.\");\n var callFn = fn;\n fn = null;\n callFn.apply(this, args);\n };\n}\n\n// for async generators\nfunction asyncEachOfLimit(generator, limit, iteratee, callback) {\n let done = false;\n let canceled = false;\n let awaiting = false;\n let running = 0;\n let idx = 0;\n\n function replenish() {\n //console.log('replenish')\n if (running >= limit || awaiting || done) return\n //console.log('replenish awaiting')\n awaiting = true;\n generator.next().then(({value, done: iterDone}) => {\n //console.log('got value', value)\n if (canceled || done) return\n awaiting = false;\n if (iterDone) {\n done = true;\n if (running <= 0) {\n //console.log('done nextCb')\n callback(null);\n }\n return;\n }\n running++;\n iteratee(value, idx, iterateeCallback);\n idx++;\n replenish();\n }).catch(handleError);\n }\n\n function iterateeCallback(err, result) {\n //console.log('iterateeCallback')\n running -= 1;\n if (canceled) return\n if (err) return handleError(err)\n\n if (err === false) {\n done = true;\n canceled = true;\n return\n }\n\n if (result === breakLoop || (done && running <= 0)) {\n done = true;\n //console.log('done iterCb')\n return callback(null);\n }\n replenish();\n }\n\n function handleError(err) {\n if (canceled) return\n awaiting = false;\n done = true;\n callback(err);\n }\n\n replenish();\n}\n\nvar eachOfLimit = (limit) => {\n return (obj, iteratee, callback) => {\n callback = once(callback);\n if (limit <= 0) {\n throw new RangeError('concurrency limit cannot be less than 1')\n }\n if (!obj) {\n return callback(null);\n }\n if (isAsyncGenerator(obj)) {\n return asyncEachOfLimit(obj, limit, iteratee, callback)\n }\n if (isAsyncIterable(obj)) {\n return asyncEachOfLimit(obj[Symbol.asyncIterator](), limit, iteratee, callback)\n }\n var nextElem = createIterator(obj);\n var done = false;\n var canceled = false;\n var running = 0;\n var looping = false;\n\n function iterateeCallback(err, value) {\n if (canceled) return\n running -= 1;\n if (err) {\n done = true;\n callback(err);\n }\n else if (err === false) {\n done = true;\n canceled = true;\n }\n else if (value === breakLoop || (done && running <= 0)) {\n done = true;\n return callback(null);\n }\n else if (!looping) {\n replenish();\n }\n }\n\n function replenish () {\n looping = true;\n while (running < limit && !done) {\n var elem = nextElem();\n if (elem === null) {\n done = true;\n if (running <= 0) {\n callback(null);\n }\n return;\n }\n running += 1;\n iteratee(elem.value, elem.key, onlyOnce(iterateeCallback));\n }\n looping = false;\n }\n\n replenish();\n };\n};\n\n/**\n * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a\n * time.\n *\n * @name eachOfLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.eachOf]{@link module:Collections.eachOf}\n * @alias forEachOfLimit\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - An async function to apply to each\n * item in `coll`. The `key` is the item's key, or index in the case of an\n * array.\n * Invoked with (item, key, callback).\n * @param {Function} [callback] - A callback which is called when all\n * `iteratee` functions have finished, or an error occurs. Invoked with (err).\n * @returns {Promise} a promise, if a callback is omitted\n */\nfunction eachOfLimit$1(coll, limit, iteratee, callback) {\n return eachOfLimit(limit)(coll, wrapAsync(iteratee), callback);\n}\n\nvar eachOfLimit$2 = awaitify(eachOfLimit$1, 4);\n\n// eachOf implementation optimized for array-likes\nfunction eachOfArrayLike(coll, iteratee, callback) {\n callback = once(callback);\n var index = 0,\n completed = 0,\n {length} = coll,\n canceled = false;\n if (length === 0) {\n callback(null);\n }\n\n function iteratorCallback(err, value) {\n if (err === false) {\n canceled = true;\n }\n if (canceled === true) return\n if (err) {\n callback(err);\n } else if ((++completed === length) || value === breakLoop) {\n callback(null);\n }\n }\n\n for (; index < length; index++) {\n iteratee(coll[index], index, onlyOnce(iteratorCallback));\n }\n}\n\n// a generic version of eachOf which can handle array, object, and iterator cases.\nfunction eachOfGeneric (coll, iteratee, callback) {\n return eachOfLimit$2(coll, Infinity, iteratee, callback);\n}\n\n/**\n * Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument\n * to the iteratee.\n *\n * @name eachOf\n * @static\n * @memberOf module:Collections\n * @method\n * @alias forEachOf\n * @category Collection\n * @see [async.each]{@link module:Collections.each}\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - A function to apply to each\n * item in `coll`.\n * The `key` is the item's key, or index in the case of an array.\n * Invoked with (item, key, callback).\n * @param {Function} [callback] - A callback which is called when all\n * `iteratee` functions have finished, or an error occurs. Invoked with (err).\n * @returns {Promise} a promise, if a callback is omitted\n * @example\n *\n * // dev.json is a file containing a valid json object config for dev environment\n * // dev.json is a file containing a valid json object config for test environment\n * // prod.json is a file containing a valid json object config for prod environment\n * // invalid.json is a file with a malformed json object\n *\n * let configs = {}; //global variable\n * let validConfigFileMap = {dev: 'dev.json', test: 'test.json', prod: 'prod.json'};\n * let invalidConfigFileMap = {dev: 'dev.json', test: 'test.json', invalid: 'invalid.json'};\n *\n * // asynchronous function that reads a json file and parses the contents as json object\n * function parseFile(file, key, callback) {\n * fs.readFile(file, \"utf8\", function(err, data) {\n * if (err) return calback(err);\n * try {\n * configs[key] = JSON.parse(data);\n * } catch (e) {\n * return callback(e);\n * }\n * callback();\n * });\n * }\n *\n * // Using callbacks\n * async.forEachOf(validConfigFileMap, parseFile, function (err) {\n * if (err) {\n * console.error(err);\n * } else {\n * console.log(configs);\n * // configs is now a map of JSON data, e.g.\n * // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json}\n * }\n * });\n *\n * //Error handing\n * async.forEachOf(invalidConfigFileMap, parseFile, function (err) {\n * if (err) {\n * console.error(err);\n * // JSON parse error exception\n * } else {\n * console.log(configs);\n * }\n * });\n *\n * // Using Promises\n * async.forEachOf(validConfigFileMap, parseFile)\n * .then( () => {\n * console.log(configs);\n * // configs is now a map of JSON data, e.g.\n * // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json}\n * }).catch( err => {\n * console.error(err);\n * });\n *\n * //Error handing\n * async.forEachOf(invalidConfigFileMap, parseFile)\n * .then( () => {\n * console.log(configs);\n * }).catch( err => {\n * console.error(err);\n * // JSON parse error exception\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.forEachOf(validConfigFileMap, parseFile);\n * console.log(configs);\n * // configs is now a map of JSON data, e.g.\n * // { dev: //parsed dev.json, test: //parsed test.json, prod: //parsed prod.json}\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * //Error handing\n * async () => {\n * try {\n * let result = await async.forEachOf(invalidConfigFileMap, parseFile);\n * console.log(configs);\n * }\n * catch (err) {\n * console.log(err);\n * // JSON parse error exception\n * }\n * }\n *\n */\nfunction eachOf(coll, iteratee, callback) {\n var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;\n return eachOfImplementation(coll, wrapAsync(iteratee), callback);\n}\n\nvar eachOf$1 = awaitify(eachOf, 3);\n\n/**\n * Produces a new collection of values by mapping each value in `coll` through\n * the `iteratee` function. The `iteratee` is called with an item from `coll`\n * and a callback for when it has finished processing. Each of these callbacks\n * takes 2 arguments: an `error`, and the transformed item from `coll`. If\n * `iteratee` passes an error to its callback, the main `callback` (for the\n * `map` function) is immediately called with the error.\n *\n * Note, that since this function applies the `iteratee` to each item in\n * parallel, there is no guarantee that the `iteratee` functions will complete\n * in order. However, the results array will be in the same order as the\n * original `coll`.\n *\n * If `map` is passed an Object, the results will be an Array. The results\n * will roughly be in the order of the original Objects' keys (but this can\n * vary across JavaScript engines).\n *\n * @name map\n * @static\n * @memberOf module:Collections\n * @method\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The iteratee should complete with the transformed item.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. Results is an Array of the\n * transformed items from the `coll`. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback is passed\n * @example\n *\n * // file1.txt is a file that is 1000 bytes in size\n * // file2.txt is a file that is 2000 bytes in size\n * // file3.txt is a file that is 3000 bytes in size\n * // file4.txt does not exist\n *\n * const fileList = ['file1.txt','file2.txt','file3.txt'];\n * const withMissingFileList = ['file1.txt','file2.txt','file4.txt'];\n *\n * // asynchronous function that returns the file size in bytes\n * function getFileSizeInBytes(file, callback) {\n * fs.stat(file, function(err, stat) {\n * if (err) {\n * return callback(err);\n * }\n * callback(null, stat.size);\n * });\n * }\n *\n * // Using callbacks\n * async.map(fileList, getFileSizeInBytes, function(err, results) {\n * if (err) {\n * console.log(err);\n * } else {\n * console.log(results);\n * // results is now an array of the file size in bytes for each file, e.g.\n * // [ 1000, 2000, 3000]\n * }\n * });\n *\n * // Error Handling\n * async.map(withMissingFileList, getFileSizeInBytes, function(err, results) {\n * if (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * } else {\n * console.log(results);\n * }\n * });\n *\n * // Using Promises\n * async.map(fileList, getFileSizeInBytes)\n * .then( results => {\n * console.log(results);\n * // results is now an array of the file size in bytes for each file, e.g.\n * // [ 1000, 2000, 3000]\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Error Handling\n * async.map(withMissingFileList, getFileSizeInBytes)\n * .then( results => {\n * console.log(results);\n * }).catch( err => {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let results = await async.map(fileList, getFileSizeInBytes);\n * console.log(results);\n * // results is now an array of the file size in bytes for each file, e.g.\n * // [ 1000, 2000, 3000]\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * // Error Handling\n * async () => {\n * try {\n * let results = await async.map(withMissingFileList, getFileSizeInBytes);\n * console.log(results);\n * }\n * catch (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * }\n * }\n *\n */\nfunction map (coll, iteratee, callback) {\n return _asyncMap(eachOf$1, coll, iteratee, callback)\n}\nvar map$1 = awaitify(map, 3);\n\n/**\n * Applies the provided arguments to each function in the array, calling\n * `callback` after all functions have completed. If you only provide the first\n * argument, `fns`, then it will return a function which lets you pass in the\n * arguments as if it were a single function call. If more arguments are\n * provided, `callback` is required while `args` is still optional. The results\n * for each of the applied async functions are passed to the final callback\n * as an array.\n *\n * @name applyEach\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s\n * to all call with the same arguments\n * @param {...*} [args] - any number of separate arguments to pass to the\n * function.\n * @param {Function} [callback] - the final argument should be the callback,\n * called when all functions have completed processing.\n * @returns {AsyncFunction} - Returns a function that takes no args other than\n * an optional callback, that is the result of applying the `args` to each\n * of the functions.\n * @example\n *\n * const appliedFn = async.applyEach([enableSearch, updateSchema], 'bucket')\n *\n * appliedFn((err, results) => {\n * // results[0] is the results for `enableSearch`\n * // results[1] is the results for `updateSchema`\n * });\n *\n * // partial application example:\n * async.each(\n * buckets,\n * async (bucket) => async.applyEach([enableSearch, updateSchema], bucket)(),\n * callback\n * );\n */\nvar applyEach$1 = applyEach(map$1);\n\n/**\n * The same as [`eachOf`]{@link module:Collections.eachOf} but runs only a single async operation at a time.\n *\n * @name eachOfSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.eachOf]{@link module:Collections.eachOf}\n * @alias forEachOfSeries\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * Invoked with (item, key, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. Invoked with (err).\n * @returns {Promise} a promise, if a callback is omitted\n */\nfunction eachOfSeries(coll, iteratee, callback) {\n return eachOfLimit$2(coll, 1, iteratee, callback)\n}\nvar eachOfSeries$1 = awaitify(eachOfSeries, 3);\n\n/**\n * The same as [`map`]{@link module:Collections.map} but runs only a single async operation at a time.\n *\n * @name mapSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.map]{@link module:Collections.map}\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The iteratee should complete with the transformed item.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. Results is an array of the\n * transformed items from the `coll`. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction mapSeries (coll, iteratee, callback) {\n return _asyncMap(eachOfSeries$1, coll, iteratee, callback)\n}\nvar mapSeries$1 = awaitify(mapSeries, 3);\n\n/**\n * The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time.\n *\n * @name applyEachSeries\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.applyEach]{@link module:ControlFlow.applyEach}\n * @category Control Flow\n * @param {Array|Iterable|AsyncIterable|Object} fns - A collection of {@link AsyncFunction}s to all\n * call with the same arguments\n * @param {...*} [args] - any number of separate arguments to pass to the\n * function.\n * @param {Function} [callback] - the final argument should be the callback,\n * called when all functions have completed processing.\n * @returns {AsyncFunction} - A function, that when called, is the result of\n * appling the `args` to the list of functions. It takes no args, other than\n * a callback.\n */\nvar applyEachSeries = applyEach(mapSeries$1);\n\nconst PROMISE_SYMBOL = Symbol('promiseCallback');\n\nfunction promiseCallback () {\n let resolve, reject;\n function callback (err, ...args) {\n if (err) return reject(err)\n resolve(args.length > 1 ? args : args[0]);\n }\n\n callback[PROMISE_SYMBOL] = new Promise((res, rej) => {\n resolve = res,\n reject = rej;\n });\n\n return callback\n}\n\n/**\n * Determines the best order for running the {@link AsyncFunction}s in `tasks`, based on\n * their requirements. Each function can optionally depend on other functions\n * being completed first, and each function is run as soon as its requirements\n * are satisfied.\n *\n * If any of the {@link AsyncFunction}s pass an error to their callback, the `auto` sequence\n * will stop. Further tasks will not execute (so any other functions depending\n * on it will not run), and the main `callback` is immediately called with the\n * error.\n *\n * {@link AsyncFunction}s also receive an object containing the results of functions which\n * have completed so far as the first argument, if they have dependencies. If a\n * task function has no dependencies, it will only be passed a callback.\n *\n * @name auto\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {Object} tasks - An object. Each of its properties is either a\n * function or an array of requirements, with the {@link AsyncFunction} itself the last item\n * in the array. The object's key of a property serves as the name of the task\n * defined by that property, i.e. can be used when specifying requirements for\n * other tasks. The function receives one or two arguments:\n * * a `results` object, containing the results of the previously executed\n * functions, only passed if the task has any dependencies,\n * * a `callback(err, result)` function, which must be called when finished,\n * passing an `error` (which can be `null`) and the result of the function's\n * execution.\n * @param {number} [concurrency=Infinity] - An optional `integer` for\n * determining the maximum number of tasks that can be run in parallel. By\n * default, as many as possible.\n * @param {Function} [callback] - An optional callback which is called when all\n * the tasks have been completed. It receives the `err` argument if any `tasks`\n * pass an error to their callback. Results are always returned; however, if an\n * error occurs, no further `tasks` will be performed, and the results object\n * will only contain partial results. Invoked with (err, results).\n * @returns {Promise} a promise, if a callback is not passed\n * @example\n *\n * //Using Callbacks\n * async.auto({\n * get_data: function(callback) {\n * // async code to get some data\n * callback(null, 'data', 'converted to array');\n * },\n * make_folder: function(callback) {\n * // async code to create a directory to store a file in\n * // this is run at the same time as getting the data\n * callback(null, 'folder');\n * },\n * write_file: ['get_data', 'make_folder', function(results, callback) {\n * // once there is some data and the directory exists,\n * // write the data to a file in the directory\n * callback(null, 'filename');\n * }],\n * email_link: ['write_file', function(results, callback) {\n * // once the file is written let's email a link to it...\n * callback(null, {'file':results.write_file, 'email':'user@example.com'});\n * }]\n * }, function(err, results) {\n * if (err) {\n * console.log('err = ', err);\n * }\n * console.log('results = ', results);\n * // results = {\n * // get_data: ['data', 'converted to array']\n * // make_folder; 'folder',\n * // write_file: 'filename'\n * // email_link: { file: 'filename', email: 'user@example.com' }\n * // }\n * });\n *\n * //Using Promises\n * async.auto({\n * get_data: function(callback) {\n * console.log('in get_data');\n * // async code to get some data\n * callback(null, 'data', 'converted to array');\n * },\n * make_folder: function(callback) {\n * console.log('in make_folder');\n * // async code to create a directory to store a file in\n * // this is run at the same time as getting the data\n * callback(null, 'folder');\n * },\n * write_file: ['get_data', 'make_folder', function(results, callback) {\n * // once there is some data and the directory exists,\n * // write the data to a file in the directory\n * callback(null, 'filename');\n * }],\n * email_link: ['write_file', function(results, callback) {\n * // once the file is written let's email a link to it...\n * callback(null, {'file':results.write_file, 'email':'user@example.com'});\n * }]\n * }).then(results => {\n * console.log('results = ', results);\n * // results = {\n * // get_data: ['data', 'converted to array']\n * // make_folder; 'folder',\n * // write_file: 'filename'\n * // email_link: { file: 'filename', email: 'user@example.com' }\n * // }\n * }).catch(err => {\n * console.log('err = ', err);\n * });\n *\n * //Using async/await\n * async () => {\n * try {\n * let results = await async.auto({\n * get_data: function(callback) {\n * // async code to get some data\n * callback(null, 'data', 'converted to array');\n * },\n * make_folder: function(callback) {\n * // async code to create a directory to store a file in\n * // this is run at the same time as getting the data\n * callback(null, 'folder');\n * },\n * write_file: ['get_data', 'make_folder', function(results, callback) {\n * // once there is some data and the directory exists,\n * // write the data to a file in the directory\n * callback(null, 'filename');\n * }],\n * email_link: ['write_file', function(results, callback) {\n * // once the file is written let's email a link to it...\n * callback(null, {'file':results.write_file, 'email':'user@example.com'});\n * }]\n * });\n * console.log('results = ', results);\n * // results = {\n * // get_data: ['data', 'converted to array']\n * // make_folder; 'folder',\n * // write_file: 'filename'\n * // email_link: { file: 'filename', email: 'user@example.com' }\n * // }\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction auto(tasks, concurrency, callback) {\n if (typeof concurrency !== 'number') {\n // concurrency is optional, shift the args.\n callback = concurrency;\n concurrency = null;\n }\n callback = once(callback || promiseCallback());\n var numTasks = Object.keys(tasks).length;\n if (!numTasks) {\n return callback(null);\n }\n if (!concurrency) {\n concurrency = numTasks;\n }\n\n var results = {};\n var runningTasks = 0;\n var canceled = false;\n var hasError = false;\n\n var listeners = Object.create(null);\n\n var readyTasks = [];\n\n // for cycle detection:\n var readyToCheck = []; // tasks that have been identified as reachable\n // without the possibility of returning to an ancestor task\n var uncheckedDependencies = {};\n\n Object.keys(tasks).forEach(key => {\n var task = tasks[key];\n if (!Array.isArray(task)) {\n // no dependencies\n enqueueTask(key, [task]);\n readyToCheck.push(key);\n return;\n }\n\n var dependencies = task.slice(0, task.length - 1);\n var remainingDependencies = dependencies.length;\n if (remainingDependencies === 0) {\n enqueueTask(key, task);\n readyToCheck.push(key);\n return;\n }\n uncheckedDependencies[key] = remainingDependencies;\n\n dependencies.forEach(dependencyName => {\n if (!tasks[dependencyName]) {\n throw new Error('async.auto task `' + key +\n '` has a non-existent dependency `' +\n dependencyName + '` in ' +\n dependencies.join(', '));\n }\n addListener(dependencyName, () => {\n remainingDependencies--;\n if (remainingDependencies === 0) {\n enqueueTask(key, task);\n }\n });\n });\n });\n\n checkForDeadlocks();\n processQueue();\n\n function enqueueTask(key, task) {\n readyTasks.push(() => runTask(key, task));\n }\n\n function processQueue() {\n if (canceled) return\n if (readyTasks.length === 0 && runningTasks === 0) {\n return callback(null, results);\n }\n while(readyTasks.length && runningTasks < concurrency) {\n var run = readyTasks.shift();\n run();\n }\n\n }\n\n function addListener(taskName, fn) {\n var taskListeners = listeners[taskName];\n if (!taskListeners) {\n taskListeners = listeners[taskName] = [];\n }\n\n taskListeners.push(fn);\n }\n\n function taskComplete(taskName) {\n var taskListeners = listeners[taskName] || [];\n taskListeners.forEach(fn => fn());\n processQueue();\n }\n\n\n function runTask(key, task) {\n if (hasError) return;\n\n var taskCallback = onlyOnce((err, ...result) => {\n runningTasks--;\n if (err === false) {\n canceled = true;\n return\n }\n if (result.length < 2) {\n [result] = result;\n }\n if (err) {\n var safeResults = {};\n Object.keys(results).forEach(rkey => {\n safeResults[rkey] = results[rkey];\n });\n safeResults[key] = result;\n hasError = true;\n listeners = Object.create(null);\n if (canceled) return\n callback(err, safeResults);\n } else {\n results[key] = result;\n taskComplete(key);\n }\n });\n\n runningTasks++;\n var taskFn = wrapAsync(task[task.length - 1]);\n if (task.length > 1) {\n taskFn(results, taskCallback);\n } else {\n taskFn(taskCallback);\n }\n }\n\n function checkForDeadlocks() {\n // Kahn's algorithm\n // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm\n // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html\n var currentTask;\n var counter = 0;\n while (readyToCheck.length) {\n currentTask = readyToCheck.pop();\n counter++;\n getDependents(currentTask).forEach(dependent => {\n if (--uncheckedDependencies[dependent] === 0) {\n readyToCheck.push(dependent);\n }\n });\n }\n\n if (counter !== numTasks) {\n throw new Error(\n 'async.auto cannot execute tasks due to a recursive dependency'\n );\n }\n }\n\n function getDependents(taskName) {\n var result = [];\n Object.keys(tasks).forEach(key => {\n const task = tasks[key];\n if (Array.isArray(task) && task.indexOf(taskName) >= 0) {\n result.push(key);\n }\n });\n return result;\n }\n\n return callback[PROMISE_SYMBOL]\n}\n\nvar FN_ARGS = /^(?:async\\s+)?(?:function)?\\s*\\w*\\s*\\(\\s*([^)]+)\\s*\\)(?:\\s*{)/;\nvar ARROW_FN_ARGS = /^(?:async\\s+)?\\(?\\s*([^)=]+)\\s*\\)?(?:\\s*=>)/;\nvar FN_ARG_SPLIT = /,/;\nvar FN_ARG = /(=.+)?(\\s*)$/;\n\nfunction stripComments(string) {\n let stripped = '';\n let index = 0;\n let endBlockComment = string.indexOf('*/');\n while (index < string.length) {\n if (string[index] === '/' && string[index+1] === '/') {\n // inline comment\n let endIndex = string.indexOf('\\n', index);\n index = (endIndex === -1) ? string.length : endIndex;\n } else if ((endBlockComment !== -1) && (string[index] === '/') && (string[index+1] === '*')) {\n // block comment\n let endIndex = string.indexOf('*/', index);\n if (endIndex !== -1) {\n index = endIndex + 2;\n endBlockComment = string.indexOf('*/', index);\n } else {\n stripped += string[index];\n index++;\n }\n } else {\n stripped += string[index];\n index++;\n }\n }\n return stripped;\n}\n\nfunction parseParams(func) {\n const src = stripComments(func.toString());\n let match = src.match(FN_ARGS);\n if (!match) {\n match = src.match(ARROW_FN_ARGS);\n }\n if (!match) throw new Error('could not parse args in autoInject\\nSource:\\n' + src)\n let [, args] = match;\n return args\n .replace(/\\s/g, '')\n .split(FN_ARG_SPLIT)\n .map((arg) => arg.replace(FN_ARG, '').trim());\n}\n\n/**\n * A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent\n * tasks are specified as parameters to the function, after the usual callback\n * parameter, with the parameter names matching the names of the tasks it\n * depends on. This can provide even more readable task graphs which can be\n * easier to maintain.\n *\n * If a final callback is specified, the task results are similarly injected,\n * specified as named parameters after the initial error parameter.\n *\n * The autoInject function is purely syntactic sugar and its semantics are\n * otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}.\n *\n * @name autoInject\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.auto]{@link module:ControlFlow.auto}\n * @category Control Flow\n * @param {Object} tasks - An object, each of whose properties is an {@link AsyncFunction} of\n * the form 'func([dependencies...], callback). The object's key of a property\n * serves as the name of the task defined by that property, i.e. can be used\n * when specifying requirements for other tasks.\n * * The `callback` parameter is a `callback(err, result)` which must be called\n * when finished, passing an `error` (which can be `null`) and the result of\n * the function's execution. The remaining parameters name other tasks on\n * which the task is dependent, and the results from those tasks are the\n * arguments of those parameters.\n * @param {Function} [callback] - An optional callback which is called when all\n * the tasks have been completed. It receives the `err` argument if any `tasks`\n * pass an error to their callback, and a `results` object with any completed\n * task results, similar to `auto`.\n * @returns {Promise} a promise, if no callback is passed\n * @example\n *\n * // The example from `auto` can be rewritten as follows:\n * async.autoInject({\n * get_data: function(callback) {\n * // async code to get some data\n * callback(null, 'data', 'converted to array');\n * },\n * make_folder: function(callback) {\n * // async code to create a directory to store a file in\n * // this is run at the same time as getting the data\n * callback(null, 'folder');\n * },\n * write_file: function(get_data, make_folder, callback) {\n * // once there is some data and the directory exists,\n * // write the data to a file in the directory\n * callback(null, 'filename');\n * },\n * email_link: function(write_file, callback) {\n * // once the file is written let's email a link to it...\n * // write_file contains the filename returned by write_file.\n * callback(null, {'file':write_file, 'email':'user@example.com'});\n * }\n * }, function(err, results) {\n * console.log('err = ', err);\n * console.log('email_link = ', results.email_link);\n * });\n *\n * // If you are using a JS minifier that mangles parameter names, `autoInject`\n * // will not work with plain functions, since the parameter names will be\n * // collapsed to a single letter identifier. To work around this, you can\n * // explicitly specify the names of the parameters your task function needs\n * // in an array, similar to Angular.js dependency injection.\n *\n * // This still has an advantage over plain `auto`, since the results a task\n * // depends on are still spread into arguments.\n * async.autoInject({\n * //...\n * write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) {\n * callback(null, 'filename');\n * }],\n * email_link: ['write_file', function(write_file, callback) {\n * callback(null, {'file':write_file, 'email':'user@example.com'});\n * }]\n * //...\n * }, function(err, results) {\n * console.log('err = ', err);\n * console.log('email_link = ', results.email_link);\n * });\n */\nfunction autoInject(tasks, callback) {\n var newTasks = {};\n\n Object.keys(tasks).forEach(key => {\n var taskFn = tasks[key];\n var params;\n var fnIsAsync = isAsync(taskFn);\n var hasNoDeps =\n (!fnIsAsync && taskFn.length === 1) ||\n (fnIsAsync && taskFn.length === 0);\n\n if (Array.isArray(taskFn)) {\n params = [...taskFn];\n taskFn = params.pop();\n\n newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn);\n } else if (hasNoDeps) {\n // no dependencies, use the function as-is\n newTasks[key] = taskFn;\n } else {\n params = parseParams(taskFn);\n if ((taskFn.length === 0 && !fnIsAsync) && params.length === 0) {\n throw new Error(\"autoInject task functions require explicit parameters.\");\n }\n\n // remove callback param\n if (!fnIsAsync) params.pop();\n\n newTasks[key] = params.concat(newTask);\n }\n\n function newTask(results, taskCb) {\n var newArgs = params.map(name => results[name]);\n newArgs.push(taskCb);\n wrapAsync(taskFn)(...newArgs);\n }\n });\n\n return auto(newTasks, callback);\n}\n\n// Simple doubly linked list (https://en.wikipedia.org/wiki/Doubly_linked_list) implementation\n// used for queues. This implementation assumes that the node provided by the user can be modified\n// to adjust the next and last properties. We implement only the minimal functionality\n// for queue support.\nclass DLL {\n constructor() {\n this.head = this.tail = null;\n this.length = 0;\n }\n\n removeLink(node) {\n if (node.prev) node.prev.next = node.next;\n else this.head = node.next;\n if (node.next) node.next.prev = node.prev;\n else this.tail = node.prev;\n\n node.prev = node.next = null;\n this.length -= 1;\n return node;\n }\n\n empty () {\n while(this.head) this.shift();\n return this;\n }\n\n insertAfter(node, newNode) {\n newNode.prev = node;\n newNode.next = node.next;\n if (node.next) node.next.prev = newNode;\n else this.tail = newNode;\n node.next = newNode;\n this.length += 1;\n }\n\n insertBefore(node, newNode) {\n newNode.prev = node.prev;\n newNode.next = node;\n if (node.prev) node.prev.next = newNode;\n else this.head = newNode;\n node.prev = newNode;\n this.length += 1;\n }\n\n unshift(node) {\n if (this.head) this.insertBefore(this.head, node);\n else setInitial(this, node);\n }\n\n push(node) {\n if (this.tail) this.insertAfter(this.tail, node);\n else setInitial(this, node);\n }\n\n shift() {\n return this.head && this.removeLink(this.head);\n }\n\n pop() {\n return this.tail && this.removeLink(this.tail);\n }\n\n toArray() {\n return [...this]\n }\n\n *[Symbol.iterator] () {\n var cur = this.head;\n while (cur) {\n yield cur.data;\n cur = cur.next;\n }\n }\n\n remove (testFn) {\n var curr = this.head;\n while(curr) {\n var {next} = curr;\n if (testFn(curr)) {\n this.removeLink(curr);\n }\n curr = next;\n }\n return this;\n }\n}\n\nfunction setInitial(dll, node) {\n dll.length = 1;\n dll.head = dll.tail = node;\n}\n\nfunction queue(worker, concurrency, payload) {\n if (concurrency == null) {\n concurrency = 1;\n }\n else if(concurrency === 0) {\n throw new RangeError('Concurrency must not be zero');\n }\n\n var _worker = wrapAsync(worker);\n var numRunning = 0;\n var workersList = [];\n const events = {\n error: [],\n drain: [],\n saturated: [],\n unsaturated: [],\n empty: []\n };\n\n function on (event, handler) {\n events[event].push(handler);\n }\n\n function once (event, handler) {\n const handleAndRemove = (...args) => {\n off(event, handleAndRemove);\n handler(...args);\n };\n events[event].push(handleAndRemove);\n }\n\n function off (event, handler) {\n if (!event) return Object.keys(events).forEach(ev => events[ev] = [])\n if (!handler) return events[event] = []\n events[event] = events[event].filter(ev => ev !== handler);\n }\n\n function trigger (event, ...args) {\n events[event].forEach(handler => handler(...args));\n }\n\n var processingScheduled = false;\n function _insert(data, insertAtFront, rejectOnError, callback) {\n if (callback != null && typeof callback !== 'function') {\n throw new Error('task callback must be a function');\n }\n q.started = true;\n\n var res, rej;\n function promiseCallback (err, ...args) {\n // we don't care about the error, let the global error handler\n // deal with it\n if (err) return rejectOnError ? rej(err) : res()\n if (args.length <= 1) return res(args[0])\n res(args);\n }\n\n var item = {\n data,\n callback: rejectOnError ?\n promiseCallback :\n (callback || promiseCallback)\n };\n\n if (insertAtFront) {\n q._tasks.unshift(item);\n } else {\n q._tasks.push(item);\n }\n\n if (!processingScheduled) {\n processingScheduled = true;\n setImmediate$1(() => {\n processingScheduled = false;\n q.process();\n });\n }\n\n if (rejectOnError || !callback) {\n return new Promise((resolve, reject) => {\n res = resolve;\n rej = reject;\n })\n }\n }\n\n function _createCB(tasks) {\n return function (err, ...args) {\n numRunning -= 1;\n\n for (var i = 0, l = tasks.length; i < l; i++) {\n var task = tasks[i];\n\n var index = workersList.indexOf(task);\n if (index === 0) {\n workersList.shift();\n } else if (index > 0) {\n workersList.splice(index, 1);\n }\n\n task.callback(err, ...args);\n\n if (err != null) {\n trigger('error', err, task.data);\n }\n }\n\n if (numRunning <= (q.concurrency - q.buffer) ) {\n trigger('unsaturated');\n }\n\n if (q.idle()) {\n trigger('drain');\n }\n q.process();\n };\n }\n\n function _maybeDrain(data) {\n if (data.length === 0 && q.idle()) {\n // call drain immediately if there are no tasks\n setImmediate$1(() => trigger('drain'));\n return true\n }\n return false\n }\n\n const eventMethod = (name) => (handler) => {\n if (!handler) {\n return new Promise((resolve, reject) => {\n once(name, (err, data) => {\n if (err) return reject(err)\n resolve(data);\n });\n })\n }\n off(name);\n on(name, handler);\n\n };\n\n var isProcessing = false;\n var q = {\n _tasks: new DLL(),\n *[Symbol.iterator] () {\n yield* q._tasks[Symbol.iterator]();\n },\n concurrency,\n payload,\n buffer: concurrency / 4,\n started: false,\n paused: false,\n push (data, callback) {\n if (Array.isArray(data)) {\n if (_maybeDrain(data)) return\n return data.map(datum => _insert(datum, false, false, callback))\n }\n return _insert(data, false, false, callback);\n },\n pushAsync (data, callback) {\n if (Array.isArray(data)) {\n if (_maybeDrain(data)) return\n return data.map(datum => _insert(datum, false, true, callback))\n }\n return _insert(data, false, true, callback);\n },\n kill () {\n off();\n q._tasks.empty();\n },\n unshift (data, callback) {\n if (Array.isArray(data)) {\n if (_maybeDrain(data)) return\n return data.map(datum => _insert(datum, true, false, callback))\n }\n return _insert(data, true, false, callback);\n },\n unshiftAsync (data, callback) {\n if (Array.isArray(data)) {\n if (_maybeDrain(data)) return\n return data.map(datum => _insert(datum, true, true, callback))\n }\n return _insert(data, true, true, callback);\n },\n remove (testFn) {\n q._tasks.remove(testFn);\n },\n process () {\n // Avoid trying to start too many processing operations. This can occur\n // when callbacks resolve synchronously (#1267).\n if (isProcessing) {\n return;\n }\n isProcessing = true;\n while(!q.paused && numRunning < q.concurrency && q._tasks.length){\n var tasks = [], data = [];\n var l = q._tasks.length;\n if (q.payload) l = Math.min(l, q.payload);\n for (var i = 0; i < l; i++) {\n var node = q._tasks.shift();\n tasks.push(node);\n workersList.push(node);\n data.push(node.data);\n }\n\n numRunning += 1;\n\n if (q._tasks.length === 0) {\n trigger('empty');\n }\n\n if (numRunning === q.concurrency) {\n trigger('saturated');\n }\n\n var cb = onlyOnce(_createCB(tasks));\n _worker(data, cb);\n }\n isProcessing = false;\n },\n length () {\n return q._tasks.length;\n },\n running () {\n return numRunning;\n },\n workersList () {\n return workersList;\n },\n idle() {\n return q._tasks.length + numRunning === 0;\n },\n pause () {\n q.paused = true;\n },\n resume () {\n if (q.paused === false) { return; }\n q.paused = false;\n setImmediate$1(q.process);\n }\n };\n // define these as fixed properties, so people get useful errors when updating\n Object.defineProperties(q, {\n saturated: {\n writable: false,\n value: eventMethod('saturated')\n },\n unsaturated: {\n writable: false,\n value: eventMethod('unsaturated')\n },\n empty: {\n writable: false,\n value: eventMethod('empty')\n },\n drain: {\n writable: false,\n value: eventMethod('drain')\n },\n error: {\n writable: false,\n value: eventMethod('error')\n },\n });\n return q;\n}\n\n/**\n * Creates a `cargo` object with the specified payload. Tasks added to the\n * cargo will be processed altogether (up to the `payload` limit). If the\n * `worker` is in progress, the task is queued until it becomes available. Once\n * the `worker` has completed some tasks, each callback of those tasks is\n * called. Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966)\n * for how `cargo` and `queue` work.\n *\n * While [`queue`]{@link module:ControlFlow.queue} passes only one task to one of a group of workers\n * at a time, cargo passes an array of tasks to a single worker, repeating\n * when the worker is finished.\n *\n * @name cargo\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.queue]{@link module:ControlFlow.queue}\n * @category Control Flow\n * @param {AsyncFunction} worker - An asynchronous function for processing an array\n * of queued tasks. Invoked with `(tasks, callback)`.\n * @param {number} [payload=Infinity] - An optional `integer` for determining\n * how many tasks should be processed per round; if omitted, the default is\n * unlimited.\n * @returns {module:ControlFlow.QueueObject} A cargo object to manage the tasks. Callbacks can\n * attached as certain properties to listen for specific events during the\n * lifecycle of the cargo and inner queue.\n * @example\n *\n * // create a cargo object with payload 2\n * var cargo = async.cargo(function(tasks, callback) {\n * for (var i=0; i {\n * console.log(result);\n * // 6000\n * // which is the sum of the file sizes of the three files\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Error Handling\n * async.reduce(withMissingFileList, 0, getFileSizeInBytes)\n * .then( result => {\n * console.log(result);\n * }).catch( err => {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.reduce(fileList, 0, getFileSizeInBytes);\n * console.log(result);\n * // 6000\n * // which is the sum of the file sizes of the three files\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * // Error Handling\n * async () => {\n * try {\n * let result = await async.reduce(withMissingFileList, 0, getFileSizeInBytes);\n * console.log(result);\n * }\n * catch (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * }\n * }\n *\n */\nfunction reduce(coll, memo, iteratee, callback) {\n callback = once(callback);\n var _iteratee = wrapAsync(iteratee);\n return eachOfSeries$1(coll, (x, i, iterCb) => {\n _iteratee(memo, x, (err, v) => {\n memo = v;\n iterCb(err);\n });\n }, err => callback(err, memo));\n}\nvar reduce$1 = awaitify(reduce, 4);\n\n/**\n * Version of the compose function that is more natural to read. Each function\n * consumes the return value of the previous function. It is the equivalent of\n * [compose]{@link module:ControlFlow.compose} with the arguments reversed.\n *\n * Each function is executed with the `this` binding of the composed function.\n *\n * @name seq\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.compose]{@link module:ControlFlow.compose}\n * @category Control Flow\n * @param {...AsyncFunction} functions - the asynchronous functions to compose\n * @returns {Function} a function that composes the `functions` in order\n * @example\n *\n * // Requires lodash (or underscore), express3 and dresende's orm2.\n * // Part of an app, that fetches cats of the logged user.\n * // This example uses `seq` function to avoid overnesting and error\n * // handling clutter.\n * app.get('/cats', function(request, response) {\n * var User = request.models.User;\n * async.seq(\n * User.get.bind(User), // 'User.get' has signature (id, callback(err, data))\n * function(user, fn) {\n * user.getCats(fn); // 'getCats' has signature (callback(err, data))\n * }\n * )(req.session.user_id, function (err, cats) {\n * if (err) {\n * console.error(err);\n * response.json({ status: 'error', message: err.message });\n * } else {\n * response.json({ status: 'ok', message: 'Cats found', data: cats });\n * }\n * });\n * });\n */\nfunction seq(...functions) {\n var _functions = functions.map(wrapAsync);\n return function (...args) {\n var that = this;\n\n var cb = args[args.length - 1];\n if (typeof cb == 'function') {\n args.pop();\n } else {\n cb = promiseCallback();\n }\n\n reduce$1(_functions, args, (newargs, fn, iterCb) => {\n fn.apply(that, newargs.concat((err, ...nextargs) => {\n iterCb(err, nextargs);\n }));\n },\n (err, results) => cb(err, ...results));\n\n return cb[PROMISE_SYMBOL]\n };\n}\n\n/**\n * Creates a function which is a composition of the passed asynchronous\n * functions. Each function consumes the return value of the function that\n * follows. Composing functions `f()`, `g()`, and `h()` would produce the result\n * of `f(g(h()))`, only this version uses callbacks to obtain the return values.\n *\n * If the last argument to the composed function is not a function, a promise\n * is returned when you call it.\n *\n * Each function is executed with the `this` binding of the composed function.\n *\n * @name compose\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {...AsyncFunction} functions - the asynchronous functions to compose\n * @returns {Function} an asynchronous function that is the composed\n * asynchronous `functions`\n * @example\n *\n * function add1(n, callback) {\n * setTimeout(function () {\n * callback(null, n + 1);\n * }, 10);\n * }\n *\n * function mul3(n, callback) {\n * setTimeout(function () {\n * callback(null, n * 3);\n * }, 10);\n * }\n *\n * var add1mul3 = async.compose(mul3, add1);\n * add1mul3(4, function (err, result) {\n * // result now equals 15\n * });\n */\nfunction compose(...args) {\n return seq(...args.reverse());\n}\n\n/**\n * The same as [`map`]{@link module:Collections.map} but runs a maximum of `limit` async operations at a time.\n *\n * @name mapLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.map]{@link module:Collections.map}\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The iteratee should complete with the transformed item.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. Results is an array of the\n * transformed items from the `coll`. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction mapLimit (coll, limit, iteratee, callback) {\n return _asyncMap(eachOfLimit(limit), coll, iteratee, callback)\n}\nvar mapLimit$1 = awaitify(mapLimit, 4);\n\n/**\n * The same as [`concat`]{@link module:Collections.concat} but runs a maximum of `limit` async operations at a time.\n *\n * @name concatLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.concat]{@link module:Collections.concat}\n * @category Collection\n * @alias flatMapLimit\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,\n * which should use an array as its result. Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished, or an error occurs. Results is an array\n * containing the concatenated results of the `iteratee` function. Invoked with\n * (err, results).\n * @returns A Promise, if no callback is passed\n */\nfunction concatLimit(coll, limit, iteratee, callback) {\n var _iteratee = wrapAsync(iteratee);\n return mapLimit$1(coll, limit, (val, iterCb) => {\n _iteratee(val, (err, ...args) => {\n if (err) return iterCb(err);\n return iterCb(err, args);\n });\n }, (err, mapResults) => {\n var result = [];\n for (var i = 0; i < mapResults.length; i++) {\n if (mapResults[i]) {\n result = result.concat(...mapResults[i]);\n }\n }\n\n return callback(err, result);\n });\n}\nvar concatLimit$1 = awaitify(concatLimit, 4);\n\n/**\n * Applies `iteratee` to each item in `coll`, concatenating the results. Returns\n * the concatenated list. The `iteratee`s are called in parallel, and the\n * results are concatenated as they return. The results array will be returned in\n * the original order of `coll` passed to the `iteratee` function.\n *\n * @name concat\n * @static\n * @memberOf module:Collections\n * @method\n * @category Collection\n * @alias flatMap\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`,\n * which should use an array as its result. Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished, or an error occurs. Results is an array\n * containing the concatenated results of the `iteratee` function. Invoked with\n * (err, results).\n * @returns A Promise, if no callback is passed\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n * // dir4 does not exist\n *\n * let directoryList = ['dir1','dir2','dir3'];\n * let withMissingDirectoryList = ['dir1','dir2','dir3', 'dir4'];\n *\n * // Using callbacks\n * async.concat(directoryList, fs.readdir, function(err, results) {\n * if (err) {\n * console.log(err);\n * } else {\n * console.log(results);\n * // [ 'file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', file5.txt ]\n * }\n * });\n *\n * // Error Handling\n * async.concat(withMissingDirectoryList, fs.readdir, function(err, results) {\n * if (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * // since dir4 does not exist\n * } else {\n * console.log(results);\n * }\n * });\n *\n * // Using Promises\n * async.concat(directoryList, fs.readdir)\n * .then(results => {\n * console.log(results);\n * // [ 'file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', file5.txt ]\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * // Error Handling\n * async.concat(withMissingDirectoryList, fs.readdir)\n * .then(results => {\n * console.log(results);\n * }).catch(err => {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * // since dir4 does not exist\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let results = await async.concat(directoryList, fs.readdir);\n * console.log(results);\n * // [ 'file1.txt', 'file2.txt', 'file3.txt', 'file4.txt', file5.txt ]\n * } catch (err) {\n * console.log(err);\n * }\n * }\n *\n * // Error Handling\n * async () => {\n * try {\n * let results = await async.concat(withMissingDirectoryList, fs.readdir);\n * console.log(results);\n * } catch (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * // since dir4 does not exist\n * }\n * }\n *\n */\nfunction concat(coll, iteratee, callback) {\n return concatLimit$1(coll, Infinity, iteratee, callback)\n}\nvar concat$1 = awaitify(concat, 3);\n\n/**\n * The same as [`concat`]{@link module:Collections.concat} but runs only a single async operation at a time.\n *\n * @name concatSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.concat]{@link module:Collections.concat}\n * @category Collection\n * @alias flatMapSeries\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - A function to apply to each item in `coll`.\n * The iteratee should complete with an array an array of results.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished, or an error occurs. Results is an array\n * containing the concatenated results of the `iteratee` function. Invoked with\n * (err, results).\n * @returns A Promise, if no callback is passed\n */\nfunction concatSeries(coll, iteratee, callback) {\n return concatLimit$1(coll, 1, iteratee, callback)\n}\nvar concatSeries$1 = awaitify(concatSeries, 3);\n\n/**\n * Returns a function that when called, calls-back with the values provided.\n * Useful as the first function in a [`waterfall`]{@link module:ControlFlow.waterfall}, or for plugging values in to\n * [`auto`]{@link module:ControlFlow.auto}.\n *\n * @name constant\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {...*} arguments... - Any number of arguments to automatically invoke\n * callback with.\n * @returns {AsyncFunction} Returns a function that when invoked, automatically\n * invokes the callback with the previous given arguments.\n * @example\n *\n * async.waterfall([\n * async.constant(42),\n * function (value, next) {\n * // value === 42\n * },\n * //...\n * ], callback);\n *\n * async.waterfall([\n * async.constant(filename, \"utf8\"),\n * fs.readFile,\n * function (fileData, next) {\n * //...\n * }\n * //...\n * ], callback);\n *\n * async.auto({\n * hostname: async.constant(\"https://server.net/\"),\n * port: findFreePort,\n * launchServer: [\"hostname\", \"port\", function (options, cb) {\n * startServer(options, cb);\n * }],\n * //...\n * }, callback);\n */\nfunction constant(...args) {\n return function (...ignoredArgs/*, callback*/) {\n var callback = ignoredArgs.pop();\n return callback(null, ...args);\n };\n}\n\nfunction _createTester(check, getResult) {\n return (eachfn, arr, _iteratee, cb) => {\n var testPassed = false;\n var testResult;\n const iteratee = wrapAsync(_iteratee);\n eachfn(arr, (value, _, callback) => {\n iteratee(value, (err, result) => {\n if (err || err === false) return callback(err);\n\n if (check(result) && !testResult) {\n testPassed = true;\n testResult = getResult(true, value);\n return callback(null, breakLoop);\n }\n callback();\n });\n }, err => {\n if (err) return cb(err);\n cb(null, testPassed ? testResult : getResult(false));\n });\n };\n}\n\n/**\n * Returns the first value in `coll` that passes an async truth test. The\n * `iteratee` is applied in parallel, meaning the first iteratee to return\n * `true` will fire the detect `callback` with that result. That means the\n * result might not be the first item in the original `coll` (in terms of order)\n * that passes the test.\n\n * If order within the original `coll` is important, then look at\n * [`detectSeries`]{@link module:Collections.detectSeries}.\n *\n * @name detect\n * @static\n * @memberOf module:Collections\n * @method\n * @alias find\n * @category Collections\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.\n * The iteratee must complete with a boolean value as its result.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called as soon as any\n * iteratee returns `true`, or after all the `iteratee` functions have finished.\n * Result will be the first item in the array that passes the truth test\n * (iteratee) or the value `undefined` if none passed. Invoked with\n * (err, result).\n * @returns A Promise, if no callback is passed\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n *\n * // asynchronous function that checks if a file exists\n * function fileExists(file, callback) {\n * fs.access(file, fs.constants.F_OK, (err) => {\n * callback(null, !err);\n * });\n * }\n *\n * async.detect(['file3.txt','file2.txt','dir1/file1.txt'], fileExists,\n * function(err, result) {\n * console.log(result);\n * // dir1/file1.txt\n * // result now equals the first file in the list that exists\n * }\n *);\n *\n * // Using Promises\n * async.detect(['file3.txt','file2.txt','dir1/file1.txt'], fileExists)\n * .then(result => {\n * console.log(result);\n * // dir1/file1.txt\n * // result now equals the first file in the list that exists\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.detect(['file3.txt','file2.txt','dir1/file1.txt'], fileExists);\n * console.log(result);\n * // dir1/file1.txt\n * // result now equals the file in the list that exists\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction detect(coll, iteratee, callback) {\n return _createTester(bool => bool, (res, item) => item)(eachOf$1, coll, iteratee, callback)\n}\nvar detect$1 = awaitify(detect, 3);\n\n/**\n * The same as [`detect`]{@link module:Collections.detect} but runs a maximum of `limit` async operations at a\n * time.\n *\n * @name detectLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.detect]{@link module:Collections.detect}\n * @alias findLimit\n * @category Collections\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.\n * The iteratee must complete with a boolean value as its result.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called as soon as any\n * iteratee returns `true`, or after all the `iteratee` functions have finished.\n * Result will be the first item in the array that passes the truth test\n * (iteratee) or the value `undefined` if none passed. Invoked with\n * (err, result).\n * @returns a Promise if no callback is passed\n */\nfunction detectLimit(coll, limit, iteratee, callback) {\n return _createTester(bool => bool, (res, item) => item)(eachOfLimit(limit), coll, iteratee, callback)\n}\nvar detectLimit$1 = awaitify(detectLimit, 4);\n\n/**\n * The same as [`detect`]{@link module:Collections.detect} but runs only a single async operation at a time.\n *\n * @name detectSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.detect]{@link module:Collections.detect}\n * @alias findSeries\n * @category Collections\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - A truth test to apply to each item in `coll`.\n * The iteratee must complete with a boolean value as its result.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called as soon as any\n * iteratee returns `true`, or after all the `iteratee` functions have finished.\n * Result will be the first item in the array that passes the truth test\n * (iteratee) or the value `undefined` if none passed. Invoked with\n * (err, result).\n * @returns a Promise if no callback is passed\n */\nfunction detectSeries(coll, iteratee, callback) {\n return _createTester(bool => bool, (res, item) => item)(eachOfLimit(1), coll, iteratee, callback)\n}\n\nvar detectSeries$1 = awaitify(detectSeries, 3);\n\nfunction consoleFunc(name) {\n return (fn, ...args) => wrapAsync(fn)(...args, (err, ...resultArgs) => {\n /* istanbul ignore else */\n if (typeof console === 'object') {\n /* istanbul ignore else */\n if (err) {\n /* istanbul ignore else */\n if (console.error) {\n console.error(err);\n }\n } else if (console[name]) { /* istanbul ignore else */\n resultArgs.forEach(x => console[name](x));\n }\n }\n })\n}\n\n/**\n * Logs the result of an [`async` function]{@link AsyncFunction} to the\n * `console` using `console.dir` to display the properties of the resulting object.\n * Only works in Node.js or in browsers that support `console.dir` and\n * `console.error` (such as FF and Chrome).\n * If multiple arguments are returned from the async function,\n * `console.dir` is called on each argument in order.\n *\n * @name dir\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {AsyncFunction} function - The function you want to eventually apply\n * all arguments to.\n * @param {...*} arguments... - Any number of arguments to apply to the function.\n * @example\n *\n * // in a module\n * var hello = function(name, callback) {\n * setTimeout(function() {\n * callback(null, {hello: name});\n * }, 1000);\n * };\n *\n * // in the node repl\n * node> async.dir(hello, 'world');\n * {hello: 'world'}\n */\nvar dir = consoleFunc('dir');\n\n/**\n * The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in\n * the order of operations, the arguments `test` and `iteratee` are switched.\n *\n * `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.\n *\n * @name doWhilst\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.whilst]{@link module:ControlFlow.whilst}\n * @category Control Flow\n * @param {AsyncFunction} iteratee - A function which is called each time `test`\n * passes. Invoked with (callback).\n * @param {AsyncFunction} test - asynchronous truth test to perform after each\n * execution of `iteratee`. Invoked with (...args, callback), where `...args` are the\n * non-error args from the previous callback of `iteratee`.\n * @param {Function} [callback] - A callback which is called after the test\n * function has failed and repeated execution of `iteratee` has stopped.\n * `callback` will be passed an error and any arguments passed to the final\n * `iteratee`'s callback. Invoked with (err, [results]);\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction doWhilst(iteratee, test, callback) {\n callback = onlyOnce(callback);\n var _fn = wrapAsync(iteratee);\n var _test = wrapAsync(test);\n var results;\n\n function next(err, ...args) {\n if (err) return callback(err);\n if (err === false) return;\n results = args;\n _test(...args, check);\n }\n\n function check(err, truth) {\n if (err) return callback(err);\n if (err === false) return;\n if (!truth) return callback(null, ...results);\n _fn(next);\n }\n\n return check(null, true);\n}\n\nvar doWhilst$1 = awaitify(doWhilst, 3);\n\n/**\n * Like ['doWhilst']{@link module:ControlFlow.doWhilst}, except the `test` is inverted. Note the\n * argument ordering differs from `until`.\n *\n * @name doUntil\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.doWhilst]{@link module:ControlFlow.doWhilst}\n * @category Control Flow\n * @param {AsyncFunction} iteratee - An async function which is called each time\n * `test` fails. Invoked with (callback).\n * @param {AsyncFunction} test - asynchronous truth test to perform after each\n * execution of `iteratee`. Invoked with (...args, callback), where `...args` are the\n * non-error args from the previous callback of `iteratee`\n * @param {Function} [callback] - A callback which is called after the test\n * function has passed and repeated execution of `iteratee` has stopped. `callback`\n * will be passed an error and any arguments passed to the final `iteratee`'s\n * callback. Invoked with (err, [results]);\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction doUntil(iteratee, test, callback) {\n const _test = wrapAsync(test);\n return doWhilst$1(iteratee, (...args) => {\n const cb = args.pop();\n _test(...args, (err, truth) => cb (err, !truth));\n }, callback);\n}\n\nfunction _withoutIndex(iteratee) {\n return (value, index, callback) => iteratee(value, callback);\n}\n\n/**\n * Applies the function `iteratee` to each item in `coll`, in parallel.\n * The `iteratee` is called with an item from the list, and a callback for when\n * it has finished. If the `iteratee` passes an error to its `callback`, the\n * main `callback` (for the `each` function) is immediately called with the\n * error.\n *\n * Note, that since this function applies `iteratee` to each item in parallel,\n * there is no guarantee that the iteratee functions will complete in order.\n *\n * @name each\n * @static\n * @memberOf module:Collections\n * @method\n * @alias forEach\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to\n * each item in `coll`. Invoked with (item, callback).\n * The array index is not passed to the iteratee.\n * If you need the index, use `eachOf`.\n * @param {Function} [callback] - A callback which is called when all\n * `iteratee` functions have finished, or an error occurs. Invoked with (err).\n * @returns {Promise} a promise, if a callback is omitted\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n * // dir4 does not exist\n *\n * const fileList = [ 'dir1/file2.txt', 'dir2/file3.txt', 'dir/file5.txt'];\n * const withMissingFileList = ['dir1/file1.txt', 'dir4/file2.txt'];\n *\n * // asynchronous function that deletes a file\n * const deleteFile = function(file, callback) {\n * fs.unlink(file, callback);\n * };\n *\n * // Using callbacks\n * async.each(fileList, deleteFile, function(err) {\n * if( err ) {\n * console.log(err);\n * } else {\n * console.log('All files have been deleted successfully');\n * }\n * });\n *\n * // Error Handling\n * async.each(withMissingFileList, deleteFile, function(err){\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * // since dir4/file2.txt does not exist\n * // dir1/file1.txt could have been deleted\n * });\n *\n * // Using Promises\n * async.each(fileList, deleteFile)\n * .then( () => {\n * console.log('All files have been deleted successfully');\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Error Handling\n * async.each(fileList, deleteFile)\n * .then( () => {\n * console.log('All files have been deleted successfully');\n * }).catch( err => {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * // since dir4/file2.txt does not exist\n * // dir1/file1.txt could have been deleted\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * await async.each(files, deleteFile);\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * // Error Handling\n * async () => {\n * try {\n * await async.each(withMissingFileList, deleteFile);\n * }\n * catch (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * // since dir4/file2.txt does not exist\n * // dir1/file1.txt could have been deleted\n * }\n * }\n *\n */\nfunction eachLimit(coll, iteratee, callback) {\n return eachOf$1(coll, _withoutIndex(wrapAsync(iteratee)), callback);\n}\n\nvar each = awaitify(eachLimit, 3);\n\n/**\n * The same as [`each`]{@link module:Collections.each} but runs a maximum of `limit` async operations at a time.\n *\n * @name eachLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.each]{@link module:Collections.each}\n * @alias forEachLimit\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The array index is not passed to the iteratee.\n * If you need the index, use `eachOfLimit`.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called when all\n * `iteratee` functions have finished, or an error occurs. Invoked with (err).\n * @returns {Promise} a promise, if a callback is omitted\n */\nfunction eachLimit$1(coll, limit, iteratee, callback) {\n return eachOfLimit(limit)(coll, _withoutIndex(wrapAsync(iteratee)), callback);\n}\nvar eachLimit$2 = awaitify(eachLimit$1, 4);\n\n/**\n * The same as [`each`]{@link module:Collections.each} but runs only a single async operation at a time.\n *\n * Note, that unlike [`each`]{@link module:Collections.each}, this function applies iteratee to each item\n * in series and therefore the iteratee functions will complete in order.\n\n * @name eachSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.each]{@link module:Collections.each}\n * @alias forEachSeries\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to each\n * item in `coll`.\n * The array index is not passed to the iteratee.\n * If you need the index, use `eachOfSeries`.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called when all\n * `iteratee` functions have finished, or an error occurs. Invoked with (err).\n * @returns {Promise} a promise, if a callback is omitted\n */\nfunction eachSeries(coll, iteratee, callback) {\n return eachLimit$2(coll, 1, iteratee, callback)\n}\nvar eachSeries$1 = awaitify(eachSeries, 3);\n\n/**\n * Wrap an async function and ensure it calls its callback on a later tick of\n * the event loop. If the function already calls its callback on a next tick,\n * no extra deferral is added. This is useful for preventing stack overflows\n * (`RangeError: Maximum call stack size exceeded`) and generally keeping\n * [Zalgo](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)\n * contained. ES2017 `async` functions are returned as-is -- they are immune\n * to Zalgo's corrupting influences, as they always resolve on a later tick.\n *\n * @name ensureAsync\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {AsyncFunction} fn - an async function, one that expects a node-style\n * callback as its last argument.\n * @returns {AsyncFunction} Returns a wrapped function with the exact same call\n * signature as the function passed in.\n * @example\n *\n * function sometimesAsync(arg, callback) {\n * if (cache[arg]) {\n * return callback(null, cache[arg]); // this would be synchronous!!\n * } else {\n * doSomeIO(arg, callback); // this IO would be asynchronous\n * }\n * }\n *\n * // this has a risk of stack overflows if many results are cached in a row\n * async.mapSeries(args, sometimesAsync, done);\n *\n * // this will defer sometimesAsync's callback if necessary,\n * // preventing stack overflows\n * async.mapSeries(args, async.ensureAsync(sometimesAsync), done);\n */\nfunction ensureAsync(fn) {\n if (isAsync(fn)) return fn;\n return function (...args/*, callback*/) {\n var callback = args.pop();\n var sync = true;\n args.push((...innerArgs) => {\n if (sync) {\n setImmediate$1(() => callback(...innerArgs));\n } else {\n callback(...innerArgs);\n }\n });\n fn.apply(this, args);\n sync = false;\n };\n}\n\n/**\n * Returns `true` if every element in `coll` satisfies an async test. If any\n * iteratee call returns `false`, the main `callback` is immediately called.\n *\n * @name every\n * @static\n * @memberOf module:Collections\n * @method\n * @alias all\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async truth test to apply to each item\n * in the collection in parallel.\n * The iteratee must complete with a boolean result value.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Result will be either `true` or `false`\n * depending on the values of the async tests. Invoked with (err, result).\n * @returns {Promise} a promise, if no callback provided\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n * // dir4 does not exist\n *\n * const fileList = ['dir1/file1.txt','dir2/file3.txt','dir3/file5.txt'];\n * const withMissingFileList = ['file1.txt','file2.txt','file4.txt'];\n *\n * // asynchronous function that checks if a file exists\n * function fileExists(file, callback) {\n * fs.access(file, fs.constants.F_OK, (err) => {\n * callback(null, !err);\n * });\n * }\n *\n * // Using callbacks\n * async.every(fileList, fileExists, function(err, result) {\n * console.log(result);\n * // true\n * // result is true since every file exists\n * });\n *\n * async.every(withMissingFileList, fileExists, function(err, result) {\n * console.log(result);\n * // false\n * // result is false since NOT every file exists\n * });\n *\n * // Using Promises\n * async.every(fileList, fileExists)\n * .then( result => {\n * console.log(result);\n * // true\n * // result is true since every file exists\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * async.every(withMissingFileList, fileExists)\n * .then( result => {\n * console.log(result);\n * // false\n * // result is false since NOT every file exists\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.every(fileList, fileExists);\n * console.log(result);\n * // true\n * // result is true since every file exists\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * async () => {\n * try {\n * let result = await async.every(withMissingFileList, fileExists);\n * console.log(result);\n * // false\n * // result is false since NOT every file exists\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction every(coll, iteratee, callback) {\n return _createTester(bool => !bool, res => !res)(eachOf$1, coll, iteratee, callback)\n}\nvar every$1 = awaitify(every, 3);\n\n/**\n * The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time.\n *\n * @name everyLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.every]{@link module:Collections.every}\n * @alias allLimit\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - An async truth test to apply to each item\n * in the collection in parallel.\n * The iteratee must complete with a boolean result value.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Result will be either `true` or `false`\n * depending on the values of the async tests. Invoked with (err, result).\n * @returns {Promise} a promise, if no callback provided\n */\nfunction everyLimit(coll, limit, iteratee, callback) {\n return _createTester(bool => !bool, res => !res)(eachOfLimit(limit), coll, iteratee, callback)\n}\nvar everyLimit$1 = awaitify(everyLimit, 4);\n\n/**\n * The same as [`every`]{@link module:Collections.every} but runs only a single async operation at a time.\n *\n * @name everySeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.every]{@link module:Collections.every}\n * @alias allSeries\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async truth test to apply to each item\n * in the collection in series.\n * The iteratee must complete with a boolean result value.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Result will be either `true` or `false`\n * depending on the values of the async tests. Invoked with (err, result).\n * @returns {Promise} a promise, if no callback provided\n */\nfunction everySeries(coll, iteratee, callback) {\n return _createTester(bool => !bool, res => !res)(eachOfSeries$1, coll, iteratee, callback)\n}\nvar everySeries$1 = awaitify(everySeries, 3);\n\nfunction filterArray(eachfn, arr, iteratee, callback) {\n var truthValues = new Array(arr.length);\n eachfn(arr, (x, index, iterCb) => {\n iteratee(x, (err, v) => {\n truthValues[index] = !!v;\n iterCb(err);\n });\n }, err => {\n if (err) return callback(err);\n var results = [];\n for (var i = 0; i < arr.length; i++) {\n if (truthValues[i]) results.push(arr[i]);\n }\n callback(null, results);\n });\n}\n\nfunction filterGeneric(eachfn, coll, iteratee, callback) {\n var results = [];\n eachfn(coll, (x, index, iterCb) => {\n iteratee(x, (err, v) => {\n if (err) return iterCb(err);\n if (v) {\n results.push({index, value: x});\n }\n iterCb(err);\n });\n }, err => {\n if (err) return callback(err);\n callback(null, results\n .sort((a, b) => a.index - b.index)\n .map(v => v.value));\n });\n}\n\nfunction _filter(eachfn, coll, iteratee, callback) {\n var filter = isArrayLike(coll) ? filterArray : filterGeneric;\n return filter(eachfn, coll, wrapAsync(iteratee), callback);\n}\n\n/**\n * Returns a new array of all the values in `coll` which pass an async truth\n * test. This operation is performed in parallel, but the results array will be\n * in the same order as the original.\n *\n * @name filter\n * @static\n * @memberOf module:Collections\n * @method\n * @alias select\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {Function} iteratee - A truth test to apply to each item in `coll`.\n * The `iteratee` is passed a `callback(err, truthValue)`, which must be called\n * with a boolean argument once it has completed. Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback provided\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n *\n * const files = ['dir1/file1.txt','dir2/file3.txt','dir3/file6.txt'];\n *\n * // asynchronous function that checks if a file exists\n * function fileExists(file, callback) {\n * fs.access(file, fs.constants.F_OK, (err) => {\n * callback(null, !err);\n * });\n * }\n *\n * // Using callbacks\n * async.filter(files, fileExists, function(err, results) {\n * if(err) {\n * console.log(err);\n * } else {\n * console.log(results);\n * // [ 'dir1/file1.txt', 'dir2/file3.txt' ]\n * // results is now an array of the existing files\n * }\n * });\n *\n * // Using Promises\n * async.filter(files, fileExists)\n * .then(results => {\n * console.log(results);\n * // [ 'dir1/file1.txt', 'dir2/file3.txt' ]\n * // results is now an array of the existing files\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let results = await async.filter(files, fileExists);\n * console.log(results);\n * // [ 'dir1/file1.txt', 'dir2/file3.txt' ]\n * // results is now an array of the existing files\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction filter (coll, iteratee, callback) {\n return _filter(eachOf$1, coll, iteratee, callback)\n}\nvar filter$1 = awaitify(filter, 3);\n\n/**\n * The same as [`filter`]{@link module:Collections.filter} but runs a maximum of `limit` async operations at a\n * time.\n *\n * @name filterLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.filter]{@link module:Collections.filter}\n * @alias selectLimit\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {Function} iteratee - A truth test to apply to each item in `coll`.\n * The `iteratee` is passed a `callback(err, truthValue)`, which must be called\n * with a boolean argument once it has completed. Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback provided\n */\nfunction filterLimit (coll, limit, iteratee, callback) {\n return _filter(eachOfLimit(limit), coll, iteratee, callback)\n}\nvar filterLimit$1 = awaitify(filterLimit, 4);\n\n/**\n * The same as [`filter`]{@link module:Collections.filter} but runs only a single async operation at a time.\n *\n * @name filterSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.filter]{@link module:Collections.filter}\n * @alias selectSeries\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {Function} iteratee - A truth test to apply to each item in `coll`.\n * The `iteratee` is passed a `callback(err, truthValue)`, which must be called\n * with a boolean argument once it has completed. Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Invoked with (err, results)\n * @returns {Promise} a promise, if no callback provided\n */\nfunction filterSeries (coll, iteratee, callback) {\n return _filter(eachOfSeries$1, coll, iteratee, callback)\n}\nvar filterSeries$1 = awaitify(filterSeries, 3);\n\n/**\n * Calls the asynchronous function `fn` with a callback parameter that allows it\n * to call itself again, in series, indefinitely.\n\n * If an error is passed to the callback then `errback` is called with the\n * error, and execution stops, otherwise it will never be called.\n *\n * @name forever\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {AsyncFunction} fn - an async function to call repeatedly.\n * Invoked with (next).\n * @param {Function} [errback] - when `fn` passes an error to it's callback,\n * this function will be called, and execution stops. Invoked with (err).\n * @returns {Promise} a promise that rejects if an error occurs and an errback\n * is not passed\n * @example\n *\n * async.forever(\n * function(next) {\n * // next is suitable for passing to things that need a callback(err [, whatever]);\n * // it will result in this function being called again.\n * },\n * function(err) {\n * // if next is called with a value in its first parameter, it will appear\n * // in here as 'err', and execution will stop.\n * }\n * );\n */\nfunction forever(fn, errback) {\n var done = onlyOnce(errback);\n var task = wrapAsync(ensureAsync(fn));\n\n function next(err) {\n if (err) return done(err);\n if (err === false) return;\n task(next);\n }\n return next();\n}\nvar forever$1 = awaitify(forever, 2);\n\n/**\n * The same as [`groupBy`]{@link module:Collections.groupBy} but runs a maximum of `limit` async operations at a time.\n *\n * @name groupByLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.groupBy]{@link module:Collections.groupBy}\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The iteratee should complete with a `key` to group the value under.\n * Invoked with (value, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. Result is an `Object` whoses\n * properties are arrays of values which returned the corresponding key.\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction groupByLimit(coll, limit, iteratee, callback) {\n var _iteratee = wrapAsync(iteratee);\n return mapLimit$1(coll, limit, (val, iterCb) => {\n _iteratee(val, (err, key) => {\n if (err) return iterCb(err);\n return iterCb(err, {key, val});\n });\n }, (err, mapResults) => {\n var result = {};\n // from MDN, handle object having an `hasOwnProperty` prop\n var {hasOwnProperty} = Object.prototype;\n\n for (var i = 0; i < mapResults.length; i++) {\n if (mapResults[i]) {\n var {key} = mapResults[i];\n var {val} = mapResults[i];\n\n if (hasOwnProperty.call(result, key)) {\n result[key].push(val);\n } else {\n result[key] = [val];\n }\n }\n }\n\n return callback(err, result);\n });\n}\n\nvar groupByLimit$1 = awaitify(groupByLimit, 4);\n\n/**\n * Returns a new object, where each value corresponds to an array of items, from\n * `coll`, that returned the corresponding key. That is, the keys of the object\n * correspond to the values passed to the `iteratee` callback.\n *\n * Note: Since this function applies the `iteratee` to each item in parallel,\n * there is no guarantee that the `iteratee` functions will complete in order.\n * However, the values for each key in the `result` will be in the same order as\n * the original `coll`. For Objects, the values will roughly be in the order of\n * the original Objects' keys (but this can vary across JavaScript engines).\n *\n * @name groupBy\n * @static\n * @memberOf module:Collections\n * @method\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The iteratee should complete with a `key` to group the value under.\n * Invoked with (value, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. Result is an `Object` whoses\n * properties are arrays of values which returned the corresponding key.\n * @returns {Promise} a promise, if no callback is passed\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n * // dir4 does not exist\n *\n * const files = ['dir1/file1.txt','dir2','dir4']\n *\n * // asynchronous function that detects file type as none, file, or directory\n * function detectFile(file, callback) {\n * fs.stat(file, function(err, stat) {\n * if (err) {\n * return callback(null, 'none');\n * }\n * callback(null, stat.isDirectory() ? 'directory' : 'file');\n * });\n * }\n *\n * //Using callbacks\n * async.groupBy(files, detectFile, function(err, result) {\n * if(err) {\n * console.log(err);\n * } else {\n *\t console.log(result);\n * // {\n * // file: [ 'dir1/file1.txt' ],\n * // none: [ 'dir4' ],\n * // directory: [ 'dir2']\n * // }\n * // result is object containing the files grouped by type\n * }\n * });\n *\n * // Using Promises\n * async.groupBy(files, detectFile)\n * .then( result => {\n * console.log(result);\n * // {\n * // file: [ 'dir1/file1.txt' ],\n * // none: [ 'dir4' ],\n * // directory: [ 'dir2']\n * // }\n * // result is object containing the files grouped by type\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.groupBy(files, detectFile);\n * console.log(result);\n * // {\n * // file: [ 'dir1/file1.txt' ],\n * // none: [ 'dir4' ],\n * // directory: [ 'dir2']\n * // }\n * // result is object containing the files grouped by type\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction groupBy (coll, iteratee, callback) {\n return groupByLimit$1(coll, Infinity, iteratee, callback)\n}\n\n/**\n * The same as [`groupBy`]{@link module:Collections.groupBy} but runs only a single async operation at a time.\n *\n * @name groupBySeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.groupBy]{@link module:Collections.groupBy}\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The iteratee should complete with a `key` to group the value under.\n * Invoked with (value, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. Result is an `Object` whose\n * properties are arrays of values which returned the corresponding key.\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction groupBySeries (coll, iteratee, callback) {\n return groupByLimit$1(coll, 1, iteratee, callback)\n}\n\n/**\n * Logs the result of an `async` function to the `console`. Only works in\n * Node.js or in browsers that support `console.log` and `console.error` (such\n * as FF and Chrome). If multiple arguments are returned from the async\n * function, `console.log` is called on each argument in order.\n *\n * @name log\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {AsyncFunction} function - The function you want to eventually apply\n * all arguments to.\n * @param {...*} arguments... - Any number of arguments to apply to the function.\n * @example\n *\n * // in a module\n * var hello = function(name, callback) {\n * setTimeout(function() {\n * callback(null, 'hello ' + name);\n * }, 1000);\n * };\n *\n * // in the node repl\n * node> async.log(hello, 'world');\n * 'hello world'\n */\nvar log = consoleFunc('log');\n\n/**\n * The same as [`mapValues`]{@link module:Collections.mapValues} but runs a maximum of `limit` async operations at a\n * time.\n *\n * @name mapValuesLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.mapValues]{@link module:Collections.mapValues}\n * @category Collection\n * @param {Object} obj - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - A function to apply to each value and key\n * in `coll`.\n * The iteratee should complete with the transformed value as its result.\n * Invoked with (value, key, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. `result` is a new object consisting\n * of each key from `obj`, with each transformed value on the right-hand side.\n * Invoked with (err, result).\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction mapValuesLimit(obj, limit, iteratee, callback) {\n callback = once(callback);\n var newObj = {};\n var _iteratee = wrapAsync(iteratee);\n return eachOfLimit(limit)(obj, (val, key, next) => {\n _iteratee(val, key, (err, result) => {\n if (err) return next(err);\n newObj[key] = result;\n next(err);\n });\n }, err => callback(err, newObj));\n}\n\nvar mapValuesLimit$1 = awaitify(mapValuesLimit, 4);\n\n/**\n * A relative of [`map`]{@link module:Collections.map}, designed for use with objects.\n *\n * Produces a new Object by mapping each value of `obj` through the `iteratee`\n * function. The `iteratee` is called each `value` and `key` from `obj` and a\n * callback for when it has finished processing. Each of these callbacks takes\n * two arguments: an `error`, and the transformed item from `obj`. If `iteratee`\n * passes an error to its callback, the main `callback` (for the `mapValues`\n * function) is immediately called with the error.\n *\n * Note, the order of the keys in the result is not guaranteed. The keys will\n * be roughly in the order they complete, (but this is very engine-specific)\n *\n * @name mapValues\n * @static\n * @memberOf module:Collections\n * @method\n * @category Collection\n * @param {Object} obj - A collection to iterate over.\n * @param {AsyncFunction} iteratee - A function to apply to each value and key\n * in `coll`.\n * The iteratee should complete with the transformed value as its result.\n * Invoked with (value, key, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. `result` is a new object consisting\n * of each key from `obj`, with each transformed value on the right-hand side.\n * Invoked with (err, result).\n * @returns {Promise} a promise, if no callback is passed\n * @example\n *\n * // file1.txt is a file that is 1000 bytes in size\n * // file2.txt is a file that is 2000 bytes in size\n * // file3.txt is a file that is 3000 bytes in size\n * // file4.txt does not exist\n *\n * const fileMap = {\n * f1: 'file1.txt',\n * f2: 'file2.txt',\n * f3: 'file3.txt'\n * };\n *\n * const withMissingFileMap = {\n * f1: 'file1.txt',\n * f2: 'file2.txt',\n * f3: 'file4.txt'\n * };\n *\n * // asynchronous function that returns the file size in bytes\n * function getFileSizeInBytes(file, key, callback) {\n * fs.stat(file, function(err, stat) {\n * if (err) {\n * return callback(err);\n * }\n * callback(null, stat.size);\n * });\n * }\n *\n * // Using callbacks\n * async.mapValues(fileMap, getFileSizeInBytes, function(err, result) {\n * if (err) {\n * console.log(err);\n * } else {\n * console.log(result);\n * // result is now a map of file size in bytes for each file, e.g.\n * // {\n * // f1: 1000,\n * // f2: 2000,\n * // f3: 3000\n * // }\n * }\n * });\n *\n * // Error handling\n * async.mapValues(withMissingFileMap, getFileSizeInBytes, function(err, result) {\n * if (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * } else {\n * console.log(result);\n * }\n * });\n *\n * // Using Promises\n * async.mapValues(fileMap, getFileSizeInBytes)\n * .then( result => {\n * console.log(result);\n * // result is now a map of file size in bytes for each file, e.g.\n * // {\n * // f1: 1000,\n * // f2: 2000,\n * // f3: 3000\n * // }\n * }).catch (err => {\n * console.log(err);\n * });\n *\n * // Error Handling\n * async.mapValues(withMissingFileMap, getFileSizeInBytes)\n * .then( result => {\n * console.log(result);\n * }).catch (err => {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.mapValues(fileMap, getFileSizeInBytes);\n * console.log(result);\n * // result is now a map of file size in bytes for each file, e.g.\n * // {\n * // f1: 1000,\n * // f2: 2000,\n * // f3: 3000\n * // }\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * // Error Handling\n * async () => {\n * try {\n * let result = await async.mapValues(withMissingFileMap, getFileSizeInBytes);\n * console.log(result);\n * }\n * catch (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * }\n * }\n *\n */\nfunction mapValues(obj, iteratee, callback) {\n return mapValuesLimit$1(obj, Infinity, iteratee, callback)\n}\n\n/**\n * The same as [`mapValues`]{@link module:Collections.mapValues} but runs only a single async operation at a time.\n *\n * @name mapValuesSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.mapValues]{@link module:Collections.mapValues}\n * @category Collection\n * @param {Object} obj - A collection to iterate over.\n * @param {AsyncFunction} iteratee - A function to apply to each value and key\n * in `coll`.\n * The iteratee should complete with the transformed value as its result.\n * Invoked with (value, key, callback).\n * @param {Function} [callback] - A callback which is called when all `iteratee`\n * functions have finished, or an error occurs. `result` is a new object consisting\n * of each key from `obj`, with each transformed value on the right-hand side.\n * Invoked with (err, result).\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction mapValuesSeries(obj, iteratee, callback) {\n return mapValuesLimit$1(obj, 1, iteratee, callback)\n}\n\n/**\n * Caches the results of an async function. When creating a hash to store\n * function results against, the callback is omitted from the hash and an\n * optional hash function can be used.\n *\n * **Note: if the async function errs, the result will not be cached and\n * subsequent calls will call the wrapped function.**\n *\n * If no hash function is specified, the first argument is used as a hash key,\n * which may work reasonably if it is a string or a data type that converts to a\n * distinct string. Note that objects and arrays will not behave reasonably.\n * Neither will cases where the other arguments are significant. In such cases,\n * specify your own hash function.\n *\n * The cache of results is exposed as the `memo` property of the function\n * returned by `memoize`.\n *\n * @name memoize\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {AsyncFunction} fn - The async function to proxy and cache results from.\n * @param {Function} hasher - An optional function for generating a custom hash\n * for storing results. It has all the arguments applied to it apart from the\n * callback, and must be synchronous.\n * @returns {AsyncFunction} a memoized version of `fn`\n * @example\n *\n * var slow_fn = function(name, callback) {\n * // do something\n * callback(null, result);\n * };\n * var fn = async.memoize(slow_fn);\n *\n * // fn can now be used as if it were slow_fn\n * fn('some name', function() {\n * // callback\n * });\n */\nfunction memoize(fn, hasher = v => v) {\n var memo = Object.create(null);\n var queues = Object.create(null);\n var _fn = wrapAsync(fn);\n var memoized = initialParams((args, callback) => {\n var key = hasher(...args);\n if (key in memo) {\n setImmediate$1(() => callback(null, ...memo[key]));\n } else if (key in queues) {\n queues[key].push(callback);\n } else {\n queues[key] = [callback];\n _fn(...args, (err, ...resultArgs) => {\n // #1465 don't memoize if an error occurred\n if (!err) {\n memo[key] = resultArgs;\n }\n var q = queues[key];\n delete queues[key];\n for (var i = 0, l = q.length; i < l; i++) {\n q[i](err, ...resultArgs);\n }\n });\n }\n });\n memoized.memo = memo;\n memoized.unmemoized = fn;\n return memoized;\n}\n\n/* istanbul ignore file */\n\n/**\n * Calls `callback` on a later loop around the event loop. In Node.js this just\n * calls `process.nextTick`. In the browser it will use `setImmediate` if\n * available, otherwise `setTimeout(callback, 0)`, which means other higher\n * priority events may precede the execution of `callback`.\n *\n * This is used internally for browser-compatibility purposes.\n *\n * @name nextTick\n * @static\n * @memberOf module:Utils\n * @method\n * @see [async.setImmediate]{@link module:Utils.setImmediate}\n * @category Util\n * @param {Function} callback - The function to call on a later loop around\n * the event loop. Invoked with (args...).\n * @param {...*} args... - any number of additional arguments to pass to the\n * callback on the next tick.\n * @example\n *\n * var call_order = [];\n * async.nextTick(function() {\n * call_order.push('two');\n * // call_order now equals ['one','two']\n * });\n * call_order.push('one');\n *\n * async.setImmediate(function (a, b, c) {\n * // a, b, and c equal 1, 2, and 3\n * }, 1, 2, 3);\n */\nvar _defer$1;\n\nif (hasNextTick) {\n _defer$1 = process.nextTick;\n} else if (hasSetImmediate) {\n _defer$1 = setImmediate;\n} else {\n _defer$1 = fallback;\n}\n\nvar nextTick = wrap(_defer$1);\n\nvar _parallel = awaitify((eachfn, tasks, callback) => {\n var results = isArrayLike(tasks) ? [] : {};\n\n eachfn(tasks, (task, key, taskCb) => {\n wrapAsync(task)((err, ...result) => {\n if (result.length < 2) {\n [result] = result;\n }\n results[key] = result;\n taskCb(err);\n });\n }, err => callback(err, results));\n}, 3);\n\n/**\n * Run the `tasks` collection of functions in parallel, without waiting until\n * the previous function has completed. If any of the functions pass an error to\n * its callback, the main `callback` is immediately called with the value of the\n * error. Once the `tasks` have completed, the results are passed to the final\n * `callback` as an array.\n *\n * **Note:** `parallel` is about kicking-off I/O tasks in parallel, not about\n * parallel execution of code. If your tasks do not use any timers or perform\n * any I/O, they will actually be executed in series. Any synchronous setup\n * sections for each task will happen one after the other. JavaScript remains\n * single-threaded.\n *\n * **Hint:** Use [`reflect`]{@link module:Utils.reflect} to continue the\n * execution of other tasks when a task fails.\n *\n * It is also possible to use an object instead of an array. Each property will\n * be run as a function and the results will be passed to the final `callback`\n * as an object instead of an array. This can be a more readable way of handling\n * results from {@link async.parallel}.\n *\n * @name parallel\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {Array|Iterable|AsyncIterable|Object} tasks - A collection of\n * [async functions]{@link AsyncFunction} to run.\n * Each async function can complete with any number of optional `result` values.\n * @param {Function} [callback] - An optional callback to run once all the\n * functions have completed successfully. This function gets a results array\n * (or object) containing all the result arguments passed to the task callbacks.\n * Invoked with (err, results).\n * @returns {Promise} a promise, if a callback is not passed\n *\n * @example\n *\n * //Using Callbacks\n * async.parallel([\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'two');\n * }, 100);\n * }\n * ], function(err, results) {\n * console.log(results);\n * // results is equal to ['one','two'] even though\n * // the second function had a shorter timeout.\n * });\n *\n * // an example using an object instead of an array\n * async.parallel({\n * one: function(callback) {\n * setTimeout(function() {\n * callback(null, 1);\n * }, 200);\n * },\n * two: function(callback) {\n * setTimeout(function() {\n * callback(null, 2);\n * }, 100);\n * }\n * }, function(err, results) {\n * console.log(results);\n * // results is equal to: { one: 1, two: 2 }\n * });\n *\n * //Using Promises\n * async.parallel([\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'two');\n * }, 100);\n * }\n * ]).then(results => {\n * console.log(results);\n * // results is equal to ['one','two'] even though\n * // the second function had a shorter timeout.\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * // an example using an object instead of an array\n * async.parallel({\n * one: function(callback) {\n * setTimeout(function() {\n * callback(null, 1);\n * }, 200);\n * },\n * two: function(callback) {\n * setTimeout(function() {\n * callback(null, 2);\n * }, 100);\n * }\n * }).then(results => {\n * console.log(results);\n * // results is equal to: { one: 1, two: 2 }\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * //Using async/await\n * async () => {\n * try {\n * let results = await async.parallel([\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'two');\n * }, 100);\n * }\n * ]);\n * console.log(results);\n * // results is equal to ['one','two'] even though\n * // the second function had a shorter timeout.\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * // an example using an object instead of an array\n * async () => {\n * try {\n * let results = await async.parallel({\n * one: function(callback) {\n * setTimeout(function() {\n * callback(null, 1);\n * }, 200);\n * },\n * two: function(callback) {\n * setTimeout(function() {\n * callback(null, 2);\n * }, 100);\n * }\n * });\n * console.log(results);\n * // results is equal to: { one: 1, two: 2 }\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction parallel(tasks, callback) {\n return _parallel(eachOf$1, tasks, callback);\n}\n\n/**\n * The same as [`parallel`]{@link module:ControlFlow.parallel} but runs a maximum of `limit` async operations at a\n * time.\n *\n * @name parallelLimit\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.parallel]{@link module:ControlFlow.parallel}\n * @category Control Flow\n * @param {Array|Iterable|AsyncIterable|Object} tasks - A collection of\n * [async functions]{@link AsyncFunction} to run.\n * Each async function can complete with any number of optional `result` values.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {Function} [callback] - An optional callback to run once all the\n * functions have completed successfully. This function gets a results array\n * (or object) containing all the result arguments passed to the task callbacks.\n * Invoked with (err, results).\n * @returns {Promise} a promise, if a callback is not passed\n */\nfunction parallelLimit(tasks, limit, callback) {\n return _parallel(eachOfLimit(limit), tasks, callback);\n}\n\n/**\n * A queue of tasks for the worker function to complete.\n * @typedef {Iterable} QueueObject\n * @memberOf module:ControlFlow\n * @property {Function} length - a function returning the number of items\n * waiting to be processed. Invoke with `queue.length()`.\n * @property {boolean} started - a boolean indicating whether or not any\n * items have been pushed and processed by the queue.\n * @property {Function} running - a function returning the number of items\n * currently being processed. Invoke with `queue.running()`.\n * @property {Function} workersList - a function returning the array of items\n * currently being processed. Invoke with `queue.workersList()`.\n * @property {Function} idle - a function returning false if there are items\n * waiting or being processed, or true if not. Invoke with `queue.idle()`.\n * @property {number} concurrency - an integer for determining how many `worker`\n * functions should be run in parallel. This property can be changed after a\n * `queue` is created to alter the concurrency on-the-fly.\n * @property {number} payload - an integer that specifies how many items are\n * passed to the worker function at a time. only applies if this is a\n * [cargo]{@link module:ControlFlow.cargo} object\n * @property {AsyncFunction} push - add a new task to the `queue`. Calls `callback`\n * once the `worker` has finished processing the task. Instead of a single task,\n * a `tasks` array can be submitted. The respective callback is used for every\n * task in the list. Invoke with `queue.push(task, [callback])`,\n * @property {AsyncFunction} unshift - add a new task to the front of the `queue`.\n * Invoke with `queue.unshift(task, [callback])`.\n * @property {AsyncFunction} pushAsync - the same as `q.push`, except this returns\n * a promise that rejects if an error occurs.\n * @property {AsyncFunction} unshiftAsync - the same as `q.unshift`, except this returns\n * a promise that rejects if an error occurs.\n * @property {Function} remove - remove items from the queue that match a test\n * function. The test function will be passed an object with a `data` property,\n * and a `priority` property, if this is a\n * [priorityQueue]{@link module:ControlFlow.priorityQueue} object.\n * Invoked with `queue.remove(testFn)`, where `testFn` is of the form\n * `function ({data, priority}) {}` and returns a Boolean.\n * @property {Function} saturated - a function that sets a callback that is\n * called when the number of running workers hits the `concurrency` limit, and\n * further tasks will be queued. If the callback is omitted, `q.saturated()`\n * returns a promise for the next occurrence.\n * @property {Function} unsaturated - a function that sets a callback that is\n * called when the number of running workers is less than the `concurrency` &\n * `buffer` limits, and further tasks will not be queued. If the callback is\n * omitted, `q.unsaturated()` returns a promise for the next occurrence.\n * @property {number} buffer - A minimum threshold buffer in order to say that\n * the `queue` is `unsaturated`.\n * @property {Function} empty - a function that sets a callback that is called\n * when the last item from the `queue` is given to a `worker`. If the callback\n * is omitted, `q.empty()` returns a promise for the next occurrence.\n * @property {Function} drain - a function that sets a callback that is called\n * when the last item from the `queue` has returned from the `worker`. If the\n * callback is omitted, `q.drain()` returns a promise for the next occurrence.\n * @property {Function} error - a function that sets a callback that is called\n * when a task errors. Has the signature `function(error, task)`. If the\n * callback is omitted, `error()` returns a promise that rejects on the next\n * error.\n * @property {boolean} paused - a boolean for determining whether the queue is\n * in a paused state.\n * @property {Function} pause - a function that pauses the processing of tasks\n * until `resume()` is called. Invoke with `queue.pause()`.\n * @property {Function} resume - a function that resumes the processing of\n * queued tasks when the queue is paused. Invoke with `queue.resume()`.\n * @property {Function} kill - a function that removes the `drain` callback and\n * empties remaining tasks from the queue forcing it to go idle. No more tasks\n * should be pushed to the queue after calling this function. Invoke with `queue.kill()`.\n *\n * @example\n * const q = async.queue(worker, 2)\n * q.push(item1)\n * q.push(item2)\n * q.push(item3)\n * // queues are iterable, spread into an array to inspect\n * const items = [...q] // [item1, item2, item3]\n * // or use for of\n * for (let item of q) {\n * console.log(item)\n * }\n *\n * q.drain(() => {\n * console.log('all done')\n * })\n * // or\n * await q.drain()\n */\n\n/**\n * Creates a `queue` object with the specified `concurrency`. Tasks added to the\n * `queue` are processed in parallel (up to the `concurrency` limit). If all\n * `worker`s are in progress, the task is queued until one becomes available.\n * Once a `worker` completes a `task`, that `task`'s callback is called.\n *\n * @name queue\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {AsyncFunction} worker - An async function for processing a queued task.\n * If you want to handle errors from an individual task, pass a callback to\n * `q.push()`. Invoked with (task, callback).\n * @param {number} [concurrency=1] - An `integer` for determining how many\n * `worker` functions should be run in parallel. If omitted, the concurrency\n * defaults to `1`. If the concurrency is `0`, an error is thrown.\n * @returns {module:ControlFlow.QueueObject} A queue object to manage the tasks. Callbacks can be\n * attached as certain properties to listen for specific events during the\n * lifecycle of the queue.\n * @example\n *\n * // create a queue object with concurrency 2\n * var q = async.queue(function(task, callback) {\n * console.log('hello ' + task.name);\n * callback();\n * }, 2);\n *\n * // assign a callback\n * q.drain(function() {\n * console.log('all items have been processed');\n * });\n * // or await the end\n * await q.drain()\n *\n * // assign an error callback\n * q.error(function(err, task) {\n * console.error('task experienced an error');\n * });\n *\n * // add some items to the queue\n * q.push({name: 'foo'}, function(err) {\n * console.log('finished processing foo');\n * });\n * // callback is optional\n * q.push({name: 'bar'});\n *\n * // add some items to the queue (batch-wise)\n * q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {\n * console.log('finished processing item');\n * });\n *\n * // add some items to the front of the queue\n * q.unshift({name: 'bar'}, function (err) {\n * console.log('finished processing bar');\n * });\n */\nfunction queue$1 (worker, concurrency) {\n var _worker = wrapAsync(worker);\n return queue((items, cb) => {\n _worker(items[0], cb);\n }, concurrency, 1);\n}\n\n// Binary min-heap implementation used for priority queue.\n// Implementation is stable, i.e. push time is considered for equal priorities\nclass Heap {\n constructor() {\n this.heap = [];\n this.pushCount = Number.MIN_SAFE_INTEGER;\n }\n\n get length() {\n return this.heap.length;\n }\n\n empty () {\n this.heap = [];\n return this;\n }\n\n percUp(index) {\n let p;\n\n while (index > 0 && smaller(this.heap[index], this.heap[p=parent(index)])) {\n let t = this.heap[index];\n this.heap[index] = this.heap[p];\n this.heap[p] = t;\n\n index = p;\n }\n }\n\n percDown(index) {\n let l;\n\n while ((l=leftChi(index)) < this.heap.length) {\n if (l+1 < this.heap.length && smaller(this.heap[l+1], this.heap[l])) {\n l = l+1;\n }\n\n if (smaller(this.heap[index], this.heap[l])) {\n break;\n }\n\n let t = this.heap[index];\n this.heap[index] = this.heap[l];\n this.heap[l] = t;\n\n index = l;\n }\n }\n\n push(node) {\n node.pushCount = ++this.pushCount;\n this.heap.push(node);\n this.percUp(this.heap.length-1);\n }\n\n unshift(node) {\n return this.heap.push(node);\n }\n\n shift() {\n let [top] = this.heap;\n\n this.heap[0] = this.heap[this.heap.length-1];\n this.heap.pop();\n this.percDown(0);\n\n return top;\n }\n\n toArray() {\n return [...this];\n }\n\n *[Symbol.iterator] () {\n for (let i = 0; i < this.heap.length; i++) {\n yield this.heap[i].data;\n }\n }\n\n remove (testFn) {\n let j = 0;\n for (let i = 0; i < this.heap.length; i++) {\n if (!testFn(this.heap[i])) {\n this.heap[j] = this.heap[i];\n j++;\n }\n }\n\n this.heap.splice(j);\n\n for (let i = parent(this.heap.length-1); i >= 0; i--) {\n this.percDown(i);\n }\n\n return this;\n }\n}\n\nfunction leftChi(i) {\n return (i<<1)+1;\n}\n\nfunction parent(i) {\n return ((i+1)>>1)-1;\n}\n\nfunction smaller(x, y) {\n if (x.priority !== y.priority) {\n return x.priority < y.priority;\n }\n else {\n return x.pushCount < y.pushCount;\n }\n}\n\n/**\n * The same as [async.queue]{@link module:ControlFlow.queue} only tasks are assigned a priority and\n * completed in ascending priority order.\n *\n * @name priorityQueue\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.queue]{@link module:ControlFlow.queue}\n * @category Control Flow\n * @param {AsyncFunction} worker - An async function for processing a queued task.\n * If you want to handle errors from an individual task, pass a callback to\n * `q.push()`.\n * Invoked with (task, callback).\n * @param {number} concurrency - An `integer` for determining how many `worker`\n * functions should be run in parallel. If omitted, the concurrency defaults to\n * `1`. If the concurrency is `0`, an error is thrown.\n * @returns {module:ControlFlow.QueueObject} A priorityQueue object to manage the tasks. There are two\n * differences between `queue` and `priorityQueue` objects:\n * * `push(task, priority, [callback])` - `priority` should be a number. If an\n * array of `tasks` is given, all tasks will be assigned the same priority.\n * * The `unshift` method was removed.\n */\nfunction priorityQueue(worker, concurrency) {\n // Start with a normal queue\n var q = queue$1(worker, concurrency);\n var processingScheduled = false;\n\n q._tasks = new Heap();\n\n // Override push to accept second parameter representing priority\n q.push = function(data, priority = 0, callback = () => {}) {\n if (typeof callback !== 'function') {\n throw new Error('task callback must be a function');\n }\n q.started = true;\n if (!Array.isArray(data)) {\n data = [data];\n }\n if (data.length === 0 && q.idle()) {\n // call drain immediately if there are no tasks\n return setImmediate$1(() => q.drain());\n }\n\n for (var i = 0, l = data.length; i < l; i++) {\n var item = {\n data: data[i],\n priority,\n callback\n };\n\n q._tasks.push(item);\n }\n\n if (!processingScheduled) {\n processingScheduled = true;\n setImmediate$1(() => {\n processingScheduled = false;\n q.process();\n });\n }\n };\n\n // Remove unshift function\n delete q.unshift;\n\n return q;\n}\n\n/**\n * Runs the `tasks` array of functions in parallel, without waiting until the\n * previous function has completed. Once any of the `tasks` complete or pass an\n * error to its callback, the main `callback` is immediately called. It's\n * equivalent to `Promise.race()`.\n *\n * @name race\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {Array} tasks - An array containing [async functions]{@link AsyncFunction}\n * to run. Each function can complete with an optional `result` value.\n * @param {Function} callback - A callback to run once any of the functions have\n * completed. This function gets an error or result from the first function that\n * completed. Invoked with (err, result).\n * @returns undefined\n * @example\n *\n * async.race([\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'two');\n * }, 100);\n * }\n * ],\n * // main callback\n * function(err, result) {\n * // the result will be equal to 'two' as it finishes earlier\n * });\n */\nfunction race(tasks, callback) {\n callback = once(callback);\n if (!Array.isArray(tasks)) return callback(new TypeError('First argument to race must be an array of functions'));\n if (!tasks.length) return callback();\n for (var i = 0, l = tasks.length; i < l; i++) {\n wrapAsync(tasks[i])(callback);\n }\n}\n\nvar race$1 = awaitify(race, 2);\n\n/**\n * Same as [`reduce`]{@link module:Collections.reduce}, only operates on `array` in reverse order.\n *\n * @name reduceRight\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.reduce]{@link module:Collections.reduce}\n * @alias foldr\n * @category Collection\n * @param {Array} array - A collection to iterate over.\n * @param {*} memo - The initial state of the reduction.\n * @param {AsyncFunction} iteratee - A function applied to each item in the\n * array to produce the next step in the reduction.\n * The `iteratee` should complete with the next state of the reduction.\n * If the iteratee completes with an error, the reduction is stopped and the\n * main `callback` is immediately called with the error.\n * Invoked with (memo, item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Result is the reduced value. Invoked with\n * (err, result).\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction reduceRight (array, memo, iteratee, callback) {\n var reversed = [...array].reverse();\n return reduce$1(reversed, memo, iteratee, callback);\n}\n\n/**\n * Wraps the async function in another function that always completes with a\n * result object, even when it errors.\n *\n * The result object has either the property `error` or `value`.\n *\n * @name reflect\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {AsyncFunction} fn - The async function you want to wrap\n * @returns {Function} - A function that always passes null to it's callback as\n * the error. The second argument to the callback will be an `object` with\n * either an `error` or a `value` property.\n * @example\n *\n * async.parallel([\n * async.reflect(function(callback) {\n * // do some stuff ...\n * callback(null, 'one');\n * }),\n * async.reflect(function(callback) {\n * // do some more stuff but error ...\n * callback('bad stuff happened');\n * }),\n * async.reflect(function(callback) {\n * // do some more stuff ...\n * callback(null, 'two');\n * })\n * ],\n * // optional callback\n * function(err, results) {\n * // values\n * // results[0].value = 'one'\n * // results[1].error = 'bad stuff happened'\n * // results[2].value = 'two'\n * });\n */\nfunction reflect(fn) {\n var _fn = wrapAsync(fn);\n return initialParams(function reflectOn(args, reflectCallback) {\n args.push((error, ...cbArgs) => {\n let retVal = {};\n if (error) {\n retVal.error = error;\n }\n if (cbArgs.length > 0){\n var value = cbArgs;\n if (cbArgs.length <= 1) {\n [value] = cbArgs;\n }\n retVal.value = value;\n }\n reflectCallback(null, retVal);\n });\n\n return _fn.apply(this, args);\n });\n}\n\n/**\n * A helper function that wraps an array or an object of functions with `reflect`.\n *\n * @name reflectAll\n * @static\n * @memberOf module:Utils\n * @method\n * @see [async.reflect]{@link module:Utils.reflect}\n * @category Util\n * @param {Array|Object|Iterable} tasks - The collection of\n * [async functions]{@link AsyncFunction} to wrap in `async.reflect`.\n * @returns {Array} Returns an array of async functions, each wrapped in\n * `async.reflect`\n * @example\n *\n * let tasks = [\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * // do some more stuff but error ...\n * callback(new Error('bad stuff happened'));\n * },\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'two');\n * }, 100);\n * }\n * ];\n *\n * async.parallel(async.reflectAll(tasks),\n * // optional callback\n * function(err, results) {\n * // values\n * // results[0].value = 'one'\n * // results[1].error = Error('bad stuff happened')\n * // results[2].value = 'two'\n * });\n *\n * // an example using an object instead of an array\n * let tasks = {\n * one: function(callback) {\n * setTimeout(function() {\n * callback(null, 'one');\n * }, 200);\n * },\n * two: function(callback) {\n * callback('two');\n * },\n * three: function(callback) {\n * setTimeout(function() {\n * callback(null, 'three');\n * }, 100);\n * }\n * };\n *\n * async.parallel(async.reflectAll(tasks),\n * // optional callback\n * function(err, results) {\n * // values\n * // results.one.value = 'one'\n * // results.two.error = 'two'\n * // results.three.value = 'three'\n * });\n */\nfunction reflectAll(tasks) {\n var results;\n if (Array.isArray(tasks)) {\n results = tasks.map(reflect);\n } else {\n results = {};\n Object.keys(tasks).forEach(key => {\n results[key] = reflect.call(this, tasks[key]);\n });\n }\n return results;\n}\n\nfunction reject(eachfn, arr, _iteratee, callback) {\n const iteratee = wrapAsync(_iteratee);\n return _filter(eachfn, arr, (value, cb) => {\n iteratee(value, (err, v) => {\n cb(err, !v);\n });\n }, callback);\n}\n\n/**\n * The opposite of [`filter`]{@link module:Collections.filter}. Removes values that pass an `async` truth test.\n *\n * @name reject\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.filter]{@link module:Collections.filter}\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {Function} iteratee - An async truth test to apply to each item in\n * `coll`.\n * The should complete with a boolean value as its `result`.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback is passed\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n *\n * const fileList = ['dir1/file1.txt','dir2/file3.txt','dir3/file6.txt'];\n *\n * // asynchronous function that checks if a file exists\n * function fileExists(file, callback) {\n * fs.access(file, fs.constants.F_OK, (err) => {\n * callback(null, !err);\n * });\n * }\n *\n * // Using callbacks\n * async.reject(fileList, fileExists, function(err, results) {\n * // [ 'dir3/file6.txt' ]\n * // results now equals an array of the non-existing files\n * });\n *\n * // Using Promises\n * async.reject(fileList, fileExists)\n * .then( results => {\n * console.log(results);\n * // [ 'dir3/file6.txt' ]\n * // results now equals an array of the non-existing files\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let results = await async.reject(fileList, fileExists);\n * console.log(results);\n * // [ 'dir3/file6.txt' ]\n * // results now equals an array of the non-existing files\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction reject$1 (coll, iteratee, callback) {\n return reject(eachOf$1, coll, iteratee, callback)\n}\nvar reject$2 = awaitify(reject$1, 3);\n\n/**\n * The same as [`reject`]{@link module:Collections.reject} but runs a maximum of `limit` async operations at a\n * time.\n *\n * @name rejectLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.reject]{@link module:Collections.reject}\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {Function} iteratee - An async truth test to apply to each item in\n * `coll`.\n * The should complete with a boolean value as its `result`.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction rejectLimit (coll, limit, iteratee, callback) {\n return reject(eachOfLimit(limit), coll, iteratee, callback)\n}\nvar rejectLimit$1 = awaitify(rejectLimit, 4);\n\n/**\n * The same as [`reject`]{@link module:Collections.reject} but runs only a single async operation at a time.\n *\n * @name rejectSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.reject]{@link module:Collections.reject}\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {Function} iteratee - An async truth test to apply to each item in\n * `coll`.\n * The should complete with a boolean value as its `result`.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback is passed\n */\nfunction rejectSeries (coll, iteratee, callback) {\n return reject(eachOfSeries$1, coll, iteratee, callback)\n}\nvar rejectSeries$1 = awaitify(rejectSeries, 3);\n\nfunction constant$1(value) {\n return function () {\n return value;\n }\n}\n\n/**\n * Attempts to get a successful response from `task` no more than `times` times\n * before returning an error. If the task is successful, the `callback` will be\n * passed the result of the successful task. If all attempts fail, the callback\n * will be passed the error and result (if any) of the final attempt.\n *\n * @name retry\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @see [async.retryable]{@link module:ControlFlow.retryable}\n * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - Can be either an\n * object with `times` and `interval` or a number.\n * * `times` - The number of attempts to make before giving up. The default\n * is `5`.\n * * `interval` - The time to wait between retries, in milliseconds. The\n * default is `0`. The interval may also be specified as a function of the\n * retry count (see example).\n * * `errorFilter` - An optional synchronous function that is invoked on\n * erroneous result. If it returns `true` the retry attempts will continue;\n * if the function returns `false` the retry flow is aborted with the current\n * attempt's error and result being returned to the final callback.\n * Invoked with (err).\n * * If `opts` is a number, the number specifies the number of times to retry,\n * with the default interval of `0`.\n * @param {AsyncFunction} task - An async function to retry.\n * Invoked with (callback).\n * @param {Function} [callback] - An optional callback which is called when the\n * task has succeeded, or after the final failed attempt. It receives the `err`\n * and `result` arguments of the last attempt at completing the `task`. Invoked\n * with (err, results).\n * @returns {Promise} a promise if no callback provided\n *\n * @example\n *\n * // The `retry` function can be used as a stand-alone control flow by passing\n * // a callback, as shown below:\n *\n * // try calling apiMethod 3 times\n * async.retry(3, apiMethod, function(err, result) {\n * // do something with the result\n * });\n *\n * // try calling apiMethod 3 times, waiting 200 ms between each retry\n * async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {\n * // do something with the result\n * });\n *\n * // try calling apiMethod 10 times with exponential backoff\n * // (i.e. intervals of 100, 200, 400, 800, 1600, ... milliseconds)\n * async.retry({\n * times: 10,\n * interval: function(retryCount) {\n * return 50 * Math.pow(2, retryCount);\n * }\n * }, apiMethod, function(err, result) {\n * // do something with the result\n * });\n *\n * // try calling apiMethod the default 5 times no delay between each retry\n * async.retry(apiMethod, function(err, result) {\n * // do something with the result\n * });\n *\n * // try calling apiMethod only when error condition satisfies, all other\n * // errors will abort the retry control flow and return to final callback\n * async.retry({\n * errorFilter: function(err) {\n * return err.message === 'Temporary error'; // only retry on a specific error\n * }\n * }, apiMethod, function(err, result) {\n * // do something with the result\n * });\n *\n * // to retry individual methods that are not as reliable within other\n * // control flow functions, use the `retryable` wrapper:\n * async.auto({\n * users: api.getUsers.bind(api),\n * payments: async.retryable(3, api.getPayments.bind(api))\n * }, function(err, results) {\n * // do something with the results\n * });\n *\n */\nconst DEFAULT_TIMES = 5;\nconst DEFAULT_INTERVAL = 0;\n\nfunction retry(opts, task, callback) {\n var options = {\n times: DEFAULT_TIMES,\n intervalFunc: constant$1(DEFAULT_INTERVAL)\n };\n\n if (arguments.length < 3 && typeof opts === 'function') {\n callback = task || promiseCallback();\n task = opts;\n } else {\n parseTimes(options, opts);\n callback = callback || promiseCallback();\n }\n\n if (typeof task !== 'function') {\n throw new Error(\"Invalid arguments for async.retry\");\n }\n\n var _task = wrapAsync(task);\n\n var attempt = 1;\n function retryAttempt() {\n _task((err, ...args) => {\n if (err === false) return\n if (err && attempt++ < options.times &&\n (typeof options.errorFilter != 'function' ||\n options.errorFilter(err))) {\n setTimeout(retryAttempt, options.intervalFunc(attempt - 1));\n } else {\n callback(err, ...args);\n }\n });\n }\n\n retryAttempt();\n return callback[PROMISE_SYMBOL]\n}\n\nfunction parseTimes(acc, t) {\n if (typeof t === 'object') {\n acc.times = +t.times || DEFAULT_TIMES;\n\n acc.intervalFunc = typeof t.interval === 'function' ?\n t.interval :\n constant$1(+t.interval || DEFAULT_INTERVAL);\n\n acc.errorFilter = t.errorFilter;\n } else if (typeof t === 'number' || typeof t === 'string') {\n acc.times = +t || DEFAULT_TIMES;\n } else {\n throw new Error(\"Invalid arguments for async.retry\");\n }\n}\n\n/**\n * A close relative of [`retry`]{@link module:ControlFlow.retry}. This method\n * wraps a task and makes it retryable, rather than immediately calling it\n * with retries.\n *\n * @name retryable\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.retry]{@link module:ControlFlow.retry}\n * @category Control Flow\n * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - optional\n * options, exactly the same as from `retry`, except for a `opts.arity` that\n * is the arity of the `task` function, defaulting to `task.length`\n * @param {AsyncFunction} task - the asynchronous function to wrap.\n * This function will be passed any arguments passed to the returned wrapper.\n * Invoked with (...args, callback).\n * @returns {AsyncFunction} The wrapped function, which when invoked, will\n * retry on an error, based on the parameters specified in `opts`.\n * This function will accept the same parameters as `task`.\n * @example\n *\n * async.auto({\n * dep1: async.retryable(3, getFromFlakyService),\n * process: [\"dep1\", async.retryable(3, function (results, cb) {\n * maybeProcessData(results.dep1, cb);\n * })]\n * }, callback);\n */\nfunction retryable (opts, task) {\n if (!task) {\n task = opts;\n opts = null;\n }\n let arity = (opts && opts.arity) || task.length;\n if (isAsync(task)) {\n arity += 1;\n }\n var _task = wrapAsync(task);\n return initialParams((args, callback) => {\n if (args.length < arity - 1 || callback == null) {\n args.push(callback);\n callback = promiseCallback();\n }\n function taskFn(cb) {\n _task(...args, cb);\n }\n\n if (opts) retry(opts, taskFn, callback);\n else retry(taskFn, callback);\n\n return callback[PROMISE_SYMBOL]\n });\n}\n\n/**\n * Run the functions in the `tasks` collection in series, each one running once\n * the previous function has completed. If any functions in the series pass an\n * error to its callback, no more functions are run, and `callback` is\n * immediately called with the value of the error. Otherwise, `callback`\n * receives an array of results when `tasks` have completed.\n *\n * It is also possible to use an object instead of an array. Each property will\n * be run as a function, and the results will be passed to the final `callback`\n * as an object instead of an array. This can be a more readable way of handling\n * results from {@link async.series}.\n *\n * **Note** that while many implementations preserve the order of object\n * properties, the [ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6)\n * explicitly states that\n *\n * > The mechanics and order of enumerating the properties is not specified.\n *\n * So if you rely on the order in which your series of functions are executed,\n * and want this to work on all platforms, consider using an array.\n *\n * @name series\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {Array|Iterable|AsyncIterable|Object} tasks - A collection containing\n * [async functions]{@link AsyncFunction} to run in series.\n * Each function can complete with any number of optional `result` values.\n * @param {Function} [callback] - An optional callback to run once all the\n * functions have completed. This function gets a results array (or object)\n * containing all the result arguments passed to the `task` callbacks. Invoked\n * with (err, result).\n * @return {Promise} a promise, if no callback is passed\n * @example\n *\n * //Using Callbacks\n * async.series([\n * function(callback) {\n * setTimeout(function() {\n * // do some async task\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * setTimeout(function() {\n * // then do another async task\n * callback(null, 'two');\n * }, 100);\n * }\n * ], function(err, results) {\n * console.log(results);\n * // results is equal to ['one','two']\n * });\n *\n * // an example using objects instead of arrays\n * async.series({\n * one: function(callback) {\n * setTimeout(function() {\n * // do some async task\n * callback(null, 1);\n * }, 200);\n * },\n * two: function(callback) {\n * setTimeout(function() {\n * // then do another async task\n * callback(null, 2);\n * }, 100);\n * }\n * }, function(err, results) {\n * console.log(results);\n * // results is equal to: { one: 1, two: 2 }\n * });\n *\n * //Using Promises\n * async.series([\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * setTimeout(function() {\n * callback(null, 'two');\n * }, 100);\n * }\n * ]).then(results => {\n * console.log(results);\n * // results is equal to ['one','two']\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * // an example using an object instead of an array\n * async.series({\n * one: function(callback) {\n * setTimeout(function() {\n * // do some async task\n * callback(null, 1);\n * }, 200);\n * },\n * two: function(callback) {\n * setTimeout(function() {\n * // then do another async task\n * callback(null, 2);\n * }, 100);\n * }\n * }).then(results => {\n * console.log(results);\n * // results is equal to: { one: 1, two: 2 }\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * //Using async/await\n * async () => {\n * try {\n * let results = await async.series([\n * function(callback) {\n * setTimeout(function() {\n * // do some async task\n * callback(null, 'one');\n * }, 200);\n * },\n * function(callback) {\n * setTimeout(function() {\n * // then do another async task\n * callback(null, 'two');\n * }, 100);\n * }\n * ]);\n * console.log(results);\n * // results is equal to ['one','two']\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * // an example using an object instead of an array\n * async () => {\n * try {\n * let results = await async.parallel({\n * one: function(callback) {\n * setTimeout(function() {\n * // do some async task\n * callback(null, 1);\n * }, 200);\n * },\n * two: function(callback) {\n * setTimeout(function() {\n * // then do another async task\n * callback(null, 2);\n * }, 100);\n * }\n * });\n * console.log(results);\n * // results is equal to: { one: 1, two: 2 }\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction series(tasks, callback) {\n return _parallel(eachOfSeries$1, tasks, callback);\n}\n\n/**\n * Returns `true` if at least one element in the `coll` satisfies an async test.\n * If any iteratee call returns `true`, the main `callback` is immediately\n * called.\n *\n * @name some\n * @static\n * @memberOf module:Collections\n * @method\n * @alias any\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async truth test to apply to each item\n * in the collections in parallel.\n * The iteratee should complete with a boolean `result` value.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called as soon as any\n * iteratee returns `true`, or after all the iteratee functions have finished.\n * Result will be either `true` or `false` depending on the values of the async\n * tests. Invoked with (err, result).\n * @returns {Promise} a promise, if no callback provided\n * @example\n *\n * // dir1 is a directory that contains file1.txt, file2.txt\n * // dir2 is a directory that contains file3.txt, file4.txt\n * // dir3 is a directory that contains file5.txt\n * // dir4 does not exist\n *\n * // asynchronous function that checks if a file exists\n * function fileExists(file, callback) {\n * fs.access(file, fs.constants.F_OK, (err) => {\n * callback(null, !err);\n * });\n * }\n *\n * // Using callbacks\n * async.some(['dir1/missing.txt','dir2/missing.txt','dir3/file5.txt'], fileExists,\n * function(err, result) {\n * console.log(result);\n * // true\n * // result is true since some file in the list exists\n * }\n *);\n *\n * async.some(['dir1/missing.txt','dir2/missing.txt','dir4/missing.txt'], fileExists,\n * function(err, result) {\n * console.log(result);\n * // false\n * // result is false since none of the files exists\n * }\n *);\n *\n * // Using Promises\n * async.some(['dir1/missing.txt','dir2/missing.txt','dir3/file5.txt'], fileExists)\n * .then( result => {\n * console.log(result);\n * // true\n * // result is true since some file in the list exists\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * async.some(['dir1/missing.txt','dir2/missing.txt','dir4/missing.txt'], fileExists)\n * .then( result => {\n * console.log(result);\n * // false\n * // result is false since none of the files exists\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.some(['dir1/missing.txt','dir2/missing.txt','dir3/file5.txt'], fileExists);\n * console.log(result);\n * // true\n * // result is true since some file in the list exists\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n * async () => {\n * try {\n * let result = await async.some(['dir1/missing.txt','dir2/missing.txt','dir4/missing.txt'], fileExists);\n * console.log(result);\n * // false\n * // result is false since none of the files exists\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction some(coll, iteratee, callback) {\n return _createTester(Boolean, res => res)(eachOf$1, coll, iteratee, callback)\n}\nvar some$1 = awaitify(some, 3);\n\n/**\n * The same as [`some`]{@link module:Collections.some} but runs a maximum of `limit` async operations at a time.\n *\n * @name someLimit\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.some]{@link module:Collections.some}\n * @alias anyLimit\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - An async truth test to apply to each item\n * in the collections in parallel.\n * The iteratee should complete with a boolean `result` value.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called as soon as any\n * iteratee returns `true`, or after all the iteratee functions have finished.\n * Result will be either `true` or `false` depending on the values of the async\n * tests. Invoked with (err, result).\n * @returns {Promise} a promise, if no callback provided\n */\nfunction someLimit(coll, limit, iteratee, callback) {\n return _createTester(Boolean, res => res)(eachOfLimit(limit), coll, iteratee, callback)\n}\nvar someLimit$1 = awaitify(someLimit, 4);\n\n/**\n * The same as [`some`]{@link module:Collections.some} but runs only a single async operation at a time.\n *\n * @name someSeries\n * @static\n * @memberOf module:Collections\n * @method\n * @see [async.some]{@link module:Collections.some}\n * @alias anySeries\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async truth test to apply to each item\n * in the collections in series.\n * The iteratee should complete with a boolean `result` value.\n * Invoked with (item, callback).\n * @param {Function} [callback] - A callback which is called as soon as any\n * iteratee returns `true`, or after all the iteratee functions have finished.\n * Result will be either `true` or `false` depending on the values of the async\n * tests. Invoked with (err, result).\n * @returns {Promise} a promise, if no callback provided\n */\nfunction someSeries(coll, iteratee, callback) {\n return _createTester(Boolean, res => res)(eachOfSeries$1, coll, iteratee, callback)\n}\nvar someSeries$1 = awaitify(someSeries, 3);\n\n/**\n * Sorts a list by the results of running each `coll` value through an async\n * `iteratee`.\n *\n * @name sortBy\n * @static\n * @memberOf module:Collections\n * @method\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {AsyncFunction} iteratee - An async function to apply to each item in\n * `coll`.\n * The iteratee should complete with a value to use as the sort criteria as\n * its `result`.\n * Invoked with (item, callback).\n * @param {Function} callback - A callback which is called after all the\n * `iteratee` functions have finished, or an error occurs. Results is the items\n * from the original `coll` sorted by the values returned by the `iteratee`\n * calls. Invoked with (err, results).\n * @returns {Promise} a promise, if no callback passed\n * @example\n *\n * // bigfile.txt is a file that is 251100 bytes in size\n * // mediumfile.txt is a file that is 11000 bytes in size\n * // smallfile.txt is a file that is 121 bytes in size\n *\n * // asynchronous function that returns the file size in bytes\n * function getFileSizeInBytes(file, callback) {\n * fs.stat(file, function(err, stat) {\n * if (err) {\n * return callback(err);\n * }\n * callback(null, stat.size);\n * });\n * }\n *\n * // Using callbacks\n * async.sortBy(['mediumfile.txt','smallfile.txt','bigfile.txt'], getFileSizeInBytes,\n * function(err, results) {\n * if (err) {\n * console.log(err);\n * } else {\n * console.log(results);\n * // results is now the original array of files sorted by\n * // file size (ascending by default), e.g.\n * // [ 'smallfile.txt', 'mediumfile.txt', 'bigfile.txt']\n * }\n * }\n * );\n *\n * // By modifying the callback parameter the\n * // sorting order can be influenced:\n *\n * // ascending order\n * async.sortBy(['mediumfile.txt','smallfile.txt','bigfile.txt'], function(file, callback) {\n * getFileSizeInBytes(file, function(getFileSizeErr, fileSize) {\n * if (getFileSizeErr) return callback(getFileSizeErr);\n * callback(null, fileSize);\n * });\n * }, function(err, results) {\n * if (err) {\n * console.log(err);\n * } else {\n * console.log(results);\n * // results is now the original array of files sorted by\n * // file size (ascending by default), e.g.\n * // [ 'smallfile.txt', 'mediumfile.txt', 'bigfile.txt']\n * }\n * }\n * );\n *\n * // descending order\n * async.sortBy(['bigfile.txt','mediumfile.txt','smallfile.txt'], function(file, callback) {\n * getFileSizeInBytes(file, function(getFileSizeErr, fileSize) {\n * if (getFileSizeErr) {\n * return callback(getFileSizeErr);\n * }\n * callback(null, fileSize * -1);\n * });\n * }, function(err, results) {\n * if (err) {\n * console.log(err);\n * } else {\n * console.log(results);\n * // results is now the original array of files sorted by\n * // file size (ascending by default), e.g.\n * // [ 'bigfile.txt', 'mediumfile.txt', 'smallfile.txt']\n * }\n * }\n * );\n *\n * // Error handling\n * async.sortBy(['mediumfile.txt','smallfile.txt','missingfile.txt'], getFileSizeInBytes,\n * function(err, results) {\n * if (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * } else {\n * console.log(results);\n * }\n * }\n * );\n *\n * // Using Promises\n * async.sortBy(['mediumfile.txt','smallfile.txt','bigfile.txt'], getFileSizeInBytes)\n * .then( results => {\n * console.log(results);\n * // results is now the original array of files sorted by\n * // file size (ascending by default), e.g.\n * // [ 'smallfile.txt', 'mediumfile.txt', 'bigfile.txt']\n * }).catch( err => {\n * console.log(err);\n * });\n *\n * // Error handling\n * async.sortBy(['mediumfile.txt','smallfile.txt','missingfile.txt'], getFileSizeInBytes)\n * .then( results => {\n * console.log(results);\n * }).catch( err => {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * });\n *\n * // Using async/await\n * (async () => {\n * try {\n * let results = await async.sortBy(['bigfile.txt','mediumfile.txt','smallfile.txt'], getFileSizeInBytes);\n * console.log(results);\n * // results is now the original array of files sorted by\n * // file size (ascending by default), e.g.\n * // [ 'smallfile.txt', 'mediumfile.txt', 'bigfile.txt']\n * }\n * catch (err) {\n * console.log(err);\n * }\n * })();\n *\n * // Error handling\n * async () => {\n * try {\n * let results = await async.sortBy(['missingfile.txt','mediumfile.txt','smallfile.txt'], getFileSizeInBytes);\n * console.log(results);\n * }\n * catch (err) {\n * console.log(err);\n * // [ Error: ENOENT: no such file or directory ]\n * }\n * }\n *\n */\nfunction sortBy (coll, iteratee, callback) {\n var _iteratee = wrapAsync(iteratee);\n return map$1(coll, (x, iterCb) => {\n _iteratee(x, (err, criteria) => {\n if (err) return iterCb(err);\n iterCb(err, {value: x, criteria});\n });\n }, (err, results) => {\n if (err) return callback(err);\n callback(null, results.sort(comparator).map(v => v.value));\n });\n\n function comparator(left, right) {\n var a = left.criteria, b = right.criteria;\n return a < b ? -1 : a > b ? 1 : 0;\n }\n}\nvar sortBy$1 = awaitify(sortBy, 3);\n\n/**\n * Sets a time limit on an asynchronous function. If the function does not call\n * its callback within the specified milliseconds, it will be called with a\n * timeout error. The code property for the error object will be `'ETIMEDOUT'`.\n *\n * @name timeout\n * @static\n * @memberOf module:Utils\n * @method\n * @category Util\n * @param {AsyncFunction} asyncFn - The async function to limit in time.\n * @param {number} milliseconds - The specified time limit.\n * @param {*} [info] - Any variable you want attached (`string`, `object`, etc)\n * to timeout Error for more information..\n * @returns {AsyncFunction} Returns a wrapped function that can be used with any\n * of the control flow functions.\n * Invoke this function with the same parameters as you would `asyncFunc`.\n * @example\n *\n * function myFunction(foo, callback) {\n * doAsyncTask(foo, function(err, data) {\n * // handle errors\n * if (err) return callback(err);\n *\n * // do some stuff ...\n *\n * // return processed data\n * return callback(null, data);\n * });\n * }\n *\n * var wrapped = async.timeout(myFunction, 1000);\n *\n * // call `wrapped` as you would `myFunction`\n * wrapped({ bar: 'bar' }, function(err, data) {\n * // if `myFunction` takes < 1000 ms to execute, `err`\n * // and `data` will have their expected values\n *\n * // else `err` will be an Error with the code 'ETIMEDOUT'\n * });\n */\nfunction timeout(asyncFn, milliseconds, info) {\n var fn = wrapAsync(asyncFn);\n\n return initialParams((args, callback) => {\n var timedOut = false;\n var timer;\n\n function timeoutCallback() {\n var name = asyncFn.name || 'anonymous';\n var error = new Error('Callback function \"' + name + '\" timed out.');\n error.code = 'ETIMEDOUT';\n if (info) {\n error.info = info;\n }\n timedOut = true;\n callback(error);\n }\n\n args.push((...cbArgs) => {\n if (!timedOut) {\n callback(...cbArgs);\n clearTimeout(timer);\n }\n });\n\n // setup timer and call original function\n timer = setTimeout(timeoutCallback, milliseconds);\n fn(...args);\n });\n}\n\nfunction range(size) {\n var result = Array(size);\n while (size--) {\n result[size] = size;\n }\n return result;\n}\n\n/**\n * The same as [times]{@link module:ControlFlow.times} but runs a maximum of `limit` async operations at a\n * time.\n *\n * @name timesLimit\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.times]{@link module:ControlFlow.times}\n * @category Control Flow\n * @param {number} count - The number of times to run the function.\n * @param {number} limit - The maximum number of async operations at a time.\n * @param {AsyncFunction} iteratee - The async function to call `n` times.\n * Invoked with the iteration index and a callback: (n, next).\n * @param {Function} callback - see [async.map]{@link module:Collections.map}.\n * @returns {Promise} a promise, if no callback is provided\n */\nfunction timesLimit(count, limit, iteratee, callback) {\n var _iteratee = wrapAsync(iteratee);\n return mapLimit$1(range(count), limit, _iteratee, callback);\n}\n\n/**\n * Calls the `iteratee` function `n` times, and accumulates results in the same\n * manner you would use with [map]{@link module:Collections.map}.\n *\n * @name times\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.map]{@link module:Collections.map}\n * @category Control Flow\n * @param {number} n - The number of times to run the function.\n * @param {AsyncFunction} iteratee - The async function to call `n` times.\n * Invoked with the iteration index and a callback: (n, next).\n * @param {Function} callback - see {@link module:Collections.map}.\n * @returns {Promise} a promise, if no callback is provided\n * @example\n *\n * // Pretend this is some complicated async factory\n * var createUser = function(id, callback) {\n * callback(null, {\n * id: 'user' + id\n * });\n * };\n *\n * // generate 5 users\n * async.times(5, function(n, next) {\n * createUser(n, function(err, user) {\n * next(err, user);\n * });\n * }, function(err, users) {\n * // we should now have 5 users\n * });\n */\nfunction times (n, iteratee, callback) {\n return timesLimit(n, Infinity, iteratee, callback)\n}\n\n/**\n * The same as [times]{@link module:ControlFlow.times} but runs only a single async operation at a time.\n *\n * @name timesSeries\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.times]{@link module:ControlFlow.times}\n * @category Control Flow\n * @param {number} n - The number of times to run the function.\n * @param {AsyncFunction} iteratee - The async function to call `n` times.\n * Invoked with the iteration index and a callback: (n, next).\n * @param {Function} callback - see {@link module:Collections.map}.\n * @returns {Promise} a promise, if no callback is provided\n */\nfunction timesSeries (n, iteratee, callback) {\n return timesLimit(n, 1, iteratee, callback)\n}\n\n/**\n * A relative of `reduce`. Takes an Object or Array, and iterates over each\n * element in parallel, each step potentially mutating an `accumulator` value.\n * The type of the accumulator defaults to the type of collection passed in.\n *\n * @name transform\n * @static\n * @memberOf module:Collections\n * @method\n * @category Collection\n * @param {Array|Iterable|AsyncIterable|Object} coll - A collection to iterate over.\n * @param {*} [accumulator] - The initial state of the transform. If omitted,\n * it will default to an empty Object or Array, depending on the type of `coll`\n * @param {AsyncFunction} iteratee - A function applied to each item in the\n * collection that potentially modifies the accumulator.\n * Invoked with (accumulator, item, key, callback).\n * @param {Function} [callback] - A callback which is called after all the\n * `iteratee` functions have finished. Result is the transformed accumulator.\n * Invoked with (err, result).\n * @returns {Promise} a promise, if no callback provided\n * @example\n *\n * // file1.txt is a file that is 1000 bytes in size\n * // file2.txt is a file that is 2000 bytes in size\n * // file3.txt is a file that is 3000 bytes in size\n *\n * // helper function that returns human-readable size format from bytes\n * function formatBytes(bytes, decimals = 2) {\n * // implementation not included for brevity\n * return humanReadbleFilesize;\n * }\n *\n * const fileList = ['file1.txt','file2.txt','file3.txt'];\n *\n * // asynchronous function that returns the file size, transformed to human-readable format\n * // e.g. 1024 bytes = 1KB, 1234 bytes = 1.21 KB, 1048576 bytes = 1MB, etc.\n * function transformFileSize(acc, value, key, callback) {\n * fs.stat(value, function(err, stat) {\n * if (err) {\n * return callback(err);\n * }\n * acc[key] = formatBytes(stat.size);\n * callback(null);\n * });\n * }\n *\n * // Using callbacks\n * async.transform(fileList, transformFileSize, function(err, result) {\n * if(err) {\n * console.log(err);\n * } else {\n * console.log(result);\n * // [ '1000 Bytes', '1.95 KB', '2.93 KB' ]\n * }\n * });\n *\n * // Using Promises\n * async.transform(fileList, transformFileSize)\n * .then(result => {\n * console.log(result);\n * // [ '1000 Bytes', '1.95 KB', '2.93 KB' ]\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * (async () => {\n * try {\n * let result = await async.transform(fileList, transformFileSize);\n * console.log(result);\n * // [ '1000 Bytes', '1.95 KB', '2.93 KB' ]\n * }\n * catch (err) {\n * console.log(err);\n * }\n * })();\n *\n * @example\n *\n * // file1.txt is a file that is 1000 bytes in size\n * // file2.txt is a file that is 2000 bytes in size\n * // file3.txt is a file that is 3000 bytes in size\n *\n * // helper function that returns human-readable size format from bytes\n * function formatBytes(bytes, decimals = 2) {\n * // implementation not included for brevity\n * return humanReadbleFilesize;\n * }\n *\n * const fileMap = { f1: 'file1.txt', f2: 'file2.txt', f3: 'file3.txt' };\n *\n * // asynchronous function that returns the file size, transformed to human-readable format\n * // e.g. 1024 bytes = 1KB, 1234 bytes = 1.21 KB, 1048576 bytes = 1MB, etc.\n * function transformFileSize(acc, value, key, callback) {\n * fs.stat(value, function(err, stat) {\n * if (err) {\n * return callback(err);\n * }\n * acc[key] = formatBytes(stat.size);\n * callback(null);\n * });\n * }\n *\n * // Using callbacks\n * async.transform(fileMap, transformFileSize, function(err, result) {\n * if(err) {\n * console.log(err);\n * } else {\n * console.log(result);\n * // { f1: '1000 Bytes', f2: '1.95 KB', f3: '2.93 KB' }\n * }\n * });\n *\n * // Using Promises\n * async.transform(fileMap, transformFileSize)\n * .then(result => {\n * console.log(result);\n * // { f1: '1000 Bytes', f2: '1.95 KB', f3: '2.93 KB' }\n * }).catch(err => {\n * console.log(err);\n * });\n *\n * // Using async/await\n * async () => {\n * try {\n * let result = await async.transform(fileMap, transformFileSize);\n * console.log(result);\n * // { f1: '1000 Bytes', f2: '1.95 KB', f3: '2.93 KB' }\n * }\n * catch (err) {\n * console.log(err);\n * }\n * }\n *\n */\nfunction transform (coll, accumulator, iteratee, callback) {\n if (arguments.length <= 3 && typeof accumulator === 'function') {\n callback = iteratee;\n iteratee = accumulator;\n accumulator = Array.isArray(coll) ? [] : {};\n }\n callback = once(callback || promiseCallback());\n var _iteratee = wrapAsync(iteratee);\n\n eachOf$1(coll, (v, k, cb) => {\n _iteratee(accumulator, v, k, cb);\n }, err => callback(err, accumulator));\n return callback[PROMISE_SYMBOL]\n}\n\n/**\n * It runs each task in series but stops whenever any of the functions were\n * successful. If one of the tasks were successful, the `callback` will be\n * passed the result of the successful task. If all tasks fail, the callback\n * will be passed the error and result (if any) of the final attempt.\n *\n * @name tryEach\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {Array|Iterable|AsyncIterable|Object} tasks - A collection containing functions to\n * run, each function is passed a `callback(err, result)` it must call on\n * completion with an error `err` (which can be `null`) and an optional `result`\n * value.\n * @param {Function} [callback] - An optional callback which is called when one\n * of the tasks has succeeded, or all have failed. It receives the `err` and\n * `result` arguments of the last attempt at completing the `task`. Invoked with\n * (err, results).\n * @returns {Promise} a promise, if no callback is passed\n * @example\n * async.tryEach([\n * function getDataFromFirstWebsite(callback) {\n * // Try getting the data from the first website\n * callback(err, data);\n * },\n * function getDataFromSecondWebsite(callback) {\n * // First website failed,\n * // Try getting the data from the backup website\n * callback(err, data);\n * }\n * ],\n * // optional callback\n * function(err, results) {\n * Now do something with the data.\n * });\n *\n */\nfunction tryEach(tasks, callback) {\n var error = null;\n var result;\n return eachSeries$1(tasks, (task, taskCb) => {\n wrapAsync(task)((err, ...args) => {\n if (err === false) return taskCb(err);\n\n if (args.length < 2) {\n [result] = args;\n } else {\n result = args;\n }\n error = err;\n taskCb(err ? null : {});\n });\n }, () => callback(error, result));\n}\n\nvar tryEach$1 = awaitify(tryEach);\n\n/**\n * Undoes a [memoize]{@link module:Utils.memoize}d function, reverting it to the original,\n * unmemoized form. Handy for testing.\n *\n * @name unmemoize\n * @static\n * @memberOf module:Utils\n * @method\n * @see [async.memoize]{@link module:Utils.memoize}\n * @category Util\n * @param {AsyncFunction} fn - the memoized function\n * @returns {AsyncFunction} a function that calls the original unmemoized function\n */\nfunction unmemoize(fn) {\n return (...args) => {\n return (fn.unmemoized || fn)(...args);\n };\n}\n\n/**\n * Repeatedly call `iteratee`, while `test` returns `true`. Calls `callback` when\n * stopped, or an error occurs.\n *\n * @name whilst\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {AsyncFunction} test - asynchronous truth test to perform before each\n * execution of `iteratee`. Invoked with ().\n * @param {AsyncFunction} iteratee - An async function which is called each time\n * `test` passes. Invoked with (callback).\n * @param {Function} [callback] - A callback which is called after the test\n * function has failed and repeated execution of `iteratee` has stopped. `callback`\n * will be passed an error and any arguments passed to the final `iteratee`'s\n * callback. Invoked with (err, [results]);\n * @returns {Promise} a promise, if no callback is passed\n * @example\n *\n * var count = 0;\n * async.whilst(\n * function test(cb) { cb(null, count < 5); },\n * function iter(callback) {\n * count++;\n * setTimeout(function() {\n * callback(null, count);\n * }, 1000);\n * },\n * function (err, n) {\n * // 5 seconds have passed, n = 5\n * }\n * );\n */\nfunction whilst(test, iteratee, callback) {\n callback = onlyOnce(callback);\n var _fn = wrapAsync(iteratee);\n var _test = wrapAsync(test);\n var results = [];\n\n function next(err, ...rest) {\n if (err) return callback(err);\n results = rest;\n if (err === false) return;\n _test(check);\n }\n\n function check(err, truth) {\n if (err) return callback(err);\n if (err === false) return;\n if (!truth) return callback(null, ...results);\n _fn(next);\n }\n\n return _test(check);\n}\nvar whilst$1 = awaitify(whilst, 3);\n\n/**\n * Repeatedly call `iteratee` until `test` returns `true`. Calls `callback` when\n * stopped, or an error occurs. `callback` will be passed an error and any\n * arguments passed to the final `iteratee`'s callback.\n *\n * The inverse of [whilst]{@link module:ControlFlow.whilst}.\n *\n * @name until\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @see [async.whilst]{@link module:ControlFlow.whilst}\n * @category Control Flow\n * @param {AsyncFunction} test - asynchronous truth test to perform before each\n * execution of `iteratee`. Invoked with (callback).\n * @param {AsyncFunction} iteratee - An async function which is called each time\n * `test` fails. Invoked with (callback).\n * @param {Function} [callback] - A callback which is called after the test\n * function has passed and repeated execution of `iteratee` has stopped. `callback`\n * will be passed an error and any arguments passed to the final `iteratee`'s\n * callback. Invoked with (err, [results]);\n * @returns {Promise} a promise, if a callback is not passed\n *\n * @example\n * const results = []\n * let finished = false\n * async.until(function test(cb) {\n * cb(null, finished)\n * }, function iter(next) {\n * fetchPage(url, (err, body) => {\n * if (err) return next(err)\n * results = results.concat(body.objects)\n * finished = !!body.next\n * next(err)\n * })\n * }, function done (err) {\n * // all pages have been fetched\n * })\n */\nfunction until(test, iteratee, callback) {\n const _test = wrapAsync(test);\n return whilst$1((cb) => _test((err, truth) => cb (err, !truth)), iteratee, callback);\n}\n\n/**\n * Runs the `tasks` array of functions in series, each passing their results to\n * the next in the array. However, if any of the `tasks` pass an error to their\n * own callback, the next function is not executed, and the main `callback` is\n * immediately called with the error.\n *\n * @name waterfall\n * @static\n * @memberOf module:ControlFlow\n * @method\n * @category Control Flow\n * @param {Array} tasks - An array of [async functions]{@link AsyncFunction}\n * to run.\n * Each function should complete with any number of `result` values.\n * The `result` values will be passed as arguments, in order, to the next task.\n * @param {Function} [callback] - An optional callback to run once all the\n * functions have completed. This will be passed the results of the last task's\n * callback. Invoked with (err, [results]).\n * @returns undefined\n * @example\n *\n * async.waterfall([\n * function(callback) {\n * callback(null, 'one', 'two');\n * },\n * function(arg1, arg2, callback) {\n * // arg1 now equals 'one' and arg2 now equals 'two'\n * callback(null, 'three');\n * },\n * function(arg1, callback) {\n * // arg1 now equals 'three'\n * callback(null, 'done');\n * }\n * ], function (err, result) {\n * // result now equals 'done'\n * });\n *\n * // Or, with named functions:\n * async.waterfall([\n * myFirstFunction,\n * mySecondFunction,\n * myLastFunction,\n * ], function (err, result) {\n * // result now equals 'done'\n * });\n * function myFirstFunction(callback) {\n * callback(null, 'one', 'two');\n * }\n * function mySecondFunction(arg1, arg2, callback) {\n * // arg1 now equals 'one' and arg2 now equals 'two'\n * callback(null, 'three');\n * }\n * function myLastFunction(arg1, callback) {\n * // arg1 now equals 'three'\n * callback(null, 'done');\n * }\n */\nfunction waterfall (tasks, callback) {\n callback = once(callback);\n if (!Array.isArray(tasks)) return callback(new Error('First argument to waterfall must be an array of functions'));\n if (!tasks.length) return callback();\n var taskIndex = 0;\n\n function nextTask(args) {\n var task = wrapAsync(tasks[taskIndex++]);\n task(...args, onlyOnce(next));\n }\n\n function next(err, ...args) {\n if (err === false) return\n if (err || taskIndex === tasks.length) {\n return callback(err, ...args);\n }\n nextTask(args);\n }\n\n nextTask([]);\n}\n\nvar waterfall$1 = awaitify(waterfall);\n\n/**\n * An \"async function\" in the context of Async is an asynchronous function with\n * a variable number of parameters, with the final parameter being a callback.\n * (`function (arg1, arg2, ..., callback) {}`)\n * The final callback is of the form `callback(err, results...)`, which must be\n * called once the function is completed. The callback should be called with a\n * Error as its first argument to signal that an error occurred.\n * Otherwise, if no error occurred, it should be called with `null` as the first\n * argument, and any additional `result` arguments that may apply, to signal\n * successful completion.\n * The callback must be called exactly once, ideally on a later tick of the\n * JavaScript event loop.\n *\n * This type of function is also referred to as a \"Node-style async function\",\n * or a \"continuation passing-style function\" (CPS). Most of the methods of this\n * library are themselves CPS/Node-style async functions, or functions that\n * return CPS/Node-style async functions.\n *\n * Wherever we accept a Node-style async function, we also directly accept an\n * [ES2017 `async` function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function}.\n * In this case, the `async` function will not be passed a final callback\n * argument, and any thrown error will be used as the `err` argument of the\n * implicit callback, and the return value will be used as the `result` value.\n * (i.e. a `rejected` of the returned Promise becomes the `err` callback\n * argument, and a `resolved` value becomes the `result`.)\n *\n * Note, due to JavaScript limitations, we can only detect native `async`\n * functions and not transpilied implementations.\n * Your environment must have `async`/`await` support for this to work.\n * (e.g. Node > v7.6, or a recent version of a modern browser).\n * If you are using `async` functions through a transpiler (e.g. Babel), you\n * must still wrap the function with [asyncify]{@link module:Utils.asyncify},\n * because the `async function` will be compiled to an ordinary function that\n * returns a promise.\n *\n * @typedef {Function} AsyncFunction\n * @static\n */\n\nvar index = {\n apply,\n applyEach: applyEach$1,\n applyEachSeries,\n asyncify,\n auto,\n autoInject,\n cargo,\n cargoQueue: cargo$1,\n compose,\n concat: concat$1,\n concatLimit: concatLimit$1,\n concatSeries: concatSeries$1,\n constant,\n detect: detect$1,\n detectLimit: detectLimit$1,\n detectSeries: detectSeries$1,\n dir,\n doUntil,\n doWhilst: doWhilst$1,\n each,\n eachLimit: eachLimit$2,\n eachOf: eachOf$1,\n eachOfLimit: eachOfLimit$2,\n eachOfSeries: eachOfSeries$1,\n eachSeries: eachSeries$1,\n ensureAsync,\n every: every$1,\n everyLimit: everyLimit$1,\n everySeries: everySeries$1,\n filter: filter$1,\n filterLimit: filterLimit$1,\n filterSeries: filterSeries$1,\n forever: forever$1,\n groupBy,\n groupByLimit: groupByLimit$1,\n groupBySeries,\n log,\n map: map$1,\n mapLimit: mapLimit$1,\n mapSeries: mapSeries$1,\n mapValues,\n mapValuesLimit: mapValuesLimit$1,\n mapValuesSeries,\n memoize,\n nextTick,\n parallel,\n parallelLimit,\n priorityQueue,\n queue: queue$1,\n race: race$1,\n reduce: reduce$1,\n reduceRight,\n reflect,\n reflectAll,\n reject: reject$2,\n rejectLimit: rejectLimit$1,\n rejectSeries: rejectSeries$1,\n retry,\n retryable,\n seq,\n series,\n setImmediate: setImmediate$1,\n some: some$1,\n someLimit: someLimit$1,\n someSeries: someSeries$1,\n sortBy: sortBy$1,\n timeout,\n times,\n timesLimit,\n timesSeries,\n transform,\n tryEach: tryEach$1,\n unmemoize,\n until,\n waterfall: waterfall$1,\n whilst: whilst$1,\n\n // aliases\n all: every$1,\n allLimit: everyLimit$1,\n allSeries: everySeries$1,\n any: some$1,\n anyLimit: someLimit$1,\n anySeries: someSeries$1,\n find: detect$1,\n findLimit: detectLimit$1,\n findSeries: detectSeries$1,\n flatMap: concat$1,\n flatMapLimit: concatLimit$1,\n flatMapSeries: concatSeries$1,\n forEach: each,\n forEachSeries: eachSeries$1,\n forEachLimit: eachLimit$2,\n forEachOf: eachOf$1,\n forEachOfSeries: eachOfSeries$1,\n forEachOfLimit: eachOfLimit$2,\n inject: reduce$1,\n foldl: reduce$1,\n foldr: reduceRight,\n select: filter$1,\n selectLimit: filterLimit$1,\n selectSeries: filterSeries$1,\n wrapSync: asyncify,\n during: whilst$1,\n doDuring: doWhilst$1\n};\n\nexport default index;\nexport { apply, applyEach$1 as applyEach, applyEachSeries, asyncify, auto, autoInject, cargo, cargo$1 as cargoQueue, compose, concat$1 as concat, concatLimit$1 as concatLimit, concatSeries$1 as concatSeries, constant, detect$1 as detect, detectLimit$1 as detectLimit, detectSeries$1 as detectSeries, dir, doUntil, doWhilst$1 as doWhilst, each, eachLimit$2 as eachLimit, eachOf$1 as eachOf, eachOfLimit$2 as eachOfLimit, eachOfSeries$1 as eachOfSeries, eachSeries$1 as eachSeries, ensureAsync, every$1 as every, everyLimit$1 as everyLimit, everySeries$1 as everySeries, filter$1 as filter, filterLimit$1 as filterLimit, filterSeries$1 as filterSeries, forever$1 as forever, groupBy, groupByLimit$1 as groupByLimit, groupBySeries, log, map$1 as map, mapLimit$1 as mapLimit, mapSeries$1 as mapSeries, mapValues, mapValuesLimit$1 as mapValuesLimit, mapValuesSeries, memoize, nextTick, parallel, parallelLimit, priorityQueue, queue$1 as queue, race$1 as race, reduce$1 as reduce, reduceRight, reflect, reflectAll, reject$2 as reject, rejectLimit$1 as rejectLimit, rejectSeries$1 as rejectSeries, retry, retryable, seq, series, setImmediate$1 as setImmediate, some$1 as some, someLimit$1 as someLimit, someSeries$1 as someSeries, sortBy$1 as sortBy, timeout, times, timesLimit, timesSeries, transform, tryEach$1 as tryEach, unmemoize, until, waterfall$1 as waterfall, whilst$1 as whilst, every$1 as all, everyLimit$1 as allLimit, everySeries$1 as allSeries, some$1 as any, someLimit$1 as anyLimit, someSeries$1 as anySeries, detect$1 as find, detectLimit$1 as findLimit, detectSeries$1 as findSeries, concat$1 as flatMap, concatLimit$1 as flatMapLimit, concatSeries$1 as flatMapSeries, each as forEach, eachSeries$1 as forEachSeries, eachLimit$2 as forEachLimit, eachOf$1 as forEachOf, eachOfSeries$1 as forEachOfSeries, eachOfLimit$2 as forEachOfLimit, reduce$1 as inject, reduce$1 as foldl, reduceRight as foldr, filter$1 as select, filterLimit$1 as selectLimit, filterSeries$1 as selectSeries, asyncify as wrapSync, whilst$1 as during, doWhilst$1 as doDuring };\n","import { getLogger } from '@jitsi/logger';\r\nimport { queue } from 'async';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * A queue for async task execution.\r\n */\r\nexport default class AsyncQueue {\r\n /**\r\n * Creates new instance.\r\n */\r\n constructor() {\r\n this._queue = queue(this._processQueueTasks.bind(this), 1);\r\n this._stopped = false;\r\n }\r\n\r\n /**\r\n * Removes any pending tasks from the queue.\r\n */\r\n clear() {\r\n this._queue.kill();\r\n }\r\n\r\n /**\r\n * Internal task processing implementation which makes things work.\r\n */\r\n _processQueueTasks(task, finishedCallback) {\r\n try {\r\n task(finishedCallback);\r\n } catch (error) {\r\n logger.error(`Task failed: ${error?.stack}`);\r\n finishedCallback(error);\r\n }\r\n }\r\n\r\n /**\r\n * The 'task' function will be given a callback it MUST call with either:\r\n * 1) No arguments if it was successful or\r\n * 2) An error argument if there was an error\r\n * If the task wants to process the success or failure of the task, it\r\n * should pass the {@code callback} to the push function, e.g.:\r\n * queue.push(task, (err) => {\r\n * if (err) {\r\n * // error handling\r\n * } else {\r\n * // success handling\r\n * }\r\n * });\r\n *\r\n * @param {function} task - The task to be executed. See the description above.\r\n * @param {function} [callback] - Optional callback to be called after the task has been executed.\r\n */\r\n push(task, callback) {\r\n if (this._stopped) {\r\n callback && callback(new Error('The queue has been stopped'));\r\n\r\n return;\r\n }\r\n this._queue.push(task, callback);\r\n }\r\n\r\n /**\r\n * Shutdowns the queue. All already queued tasks will execute, but no future tasks can be added. If a task is added\r\n * after the queue has been shutdown then the callback will be called with an error.\r\n */\r\n shutdown() {\r\n this._stopped = true;\r\n }\r\n}\r\n","export enum JingleSessionState {\r\n /**\r\n * The pending Jingle session state which means the session as defined in\r\n * XEP-0166(before 'session-invite/session-accept' took place).\r\n */\r\n PENDING = 'pending',\r\n\r\n /**\r\n * The active Jingle session state as defined in XEP-0166\r\n * (after 'session-invite'/'session-accept').\r\n */\r\n ACTIVE = 'active',\r\n\r\n /**\r\n * The ended Jingle session state as defined in XEP-0166\r\n * (after 'session-terminate').\r\n */\r\n ENDED = 'ended'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const PENDING = JingleSessionState.PENDING;\r\nexport const ACTIVE = JingleSessionState.ACTIVE;\r\nexport const ENDED = JingleSessionState.ENDED;\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport Listenable from '../util/Listenable';\r\n\r\nimport * as JingleSessionState from './JingleSessionState';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * JingleSession provides an API to manage a single Jingle session. We will\r\n * have different implementations depending on the underlying interface used\r\n * (i.e. WebRTC and ORTC) and here we hold the code common to all of them.\r\n */\r\nexport default class JingleSession extends Listenable {\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n * Creates new JingleSession.\r\n * @param {string} sid the Jingle session identifier\r\n * @param {string} localJid our JID\r\n * @param {string} remoteJid the JID of the remote peer\r\n * @param {XmppConnection} connection the XMPP connection\r\n * @param {Object} mediaConstraints the media constraints object passed to the PeerConnection onCreateAnswer/Offer.\r\n * @param {Object} pcConfig The {@code RTCConfiguration} object passed to the PeerConnection's constructor.\r\n * @param {boolean} isInitiator indicates if it will be the side which initiates the session.\r\n */\r\n constructor(\r\n sid,\r\n localJid,\r\n remoteJid,\r\n connection,\r\n mediaConstraints,\r\n pcConfig,\r\n isInitiator) {\r\n super();\r\n this.sid = sid;\r\n this.localJid = localJid;\r\n this.remoteJid = remoteJid;\r\n this.connection = connection;\r\n this.mediaConstraints = mediaConstraints;\r\n this.pcConfig = pcConfig;\r\n\r\n /**\r\n * Indicates whether this instance is an initiator or an answerer of\r\n * the Jingle session.\r\n * @type {boolean}\r\n */\r\n this.isInitiator = isInitiator;\r\n\r\n /**\r\n * Whether to use dripping or not. Dripping is sending trickle\r\n * candidates not one-by-one.\r\n */\r\n this.usedrip = true;\r\n\r\n /**\r\n * When dripping is used, stores ICE candidates which are to be sent.\r\n */\r\n this.dripContainer = [];\r\n\r\n /**\r\n * The chat room instance associated with the session.\r\n * @type {ChatRoom}\r\n */\r\n this.room = null;\r\n\r\n /**\r\n * The signaling layer.\r\n * @type {SignalingLayerImpl | null}\r\n * @private\r\n */\r\n this._signalingLayer = null;\r\n\r\n /**\r\n * Jingle session state - uninitialized until {@link initialize} is\r\n * called @type {JingleSessionState}\r\n */\r\n this.state = null;\r\n\r\n /**\r\n * The RTC service instance\r\n * @type {RTC}\r\n */\r\n this.rtc = null;\r\n }\r\n\r\n /**\r\n * Returns XMPP address of this session's initiator.\r\n * @return {string}\r\n */\r\n get initiatorJid() {\r\n return this.isInitiator ? this.localJid : this.remoteJid;\r\n }\r\n\r\n /**\r\n * Returns XMPP address of this session's responder.\r\n * @return {string}\r\n */\r\n get responderJid() {\r\n return this.isInitiator ? this.remoteJid : this.localJid;\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Prepares this object to initiate a session.\r\n * @param {ChatRoom} room the chat room for the conference associated with\r\n * this session\r\n * @param {RTC} rtc the RTC service instance\r\n * @param {SignalingLayerImpl} signalingLayer - The signaling layer instance.\r\n * @param {object} options - the options, see implementing class's\r\n * {@link #doInitialize} description for more details.\r\n */\r\n initialize(room, rtc, signalingLayer, options) {\r\n if (this.state !== null) {\r\n const errmsg\r\n = `attempt to initiate on session ${this.sid}\r\n in state ${this.state}`;\r\n\r\n logger.error(errmsg);\r\n throw new Error(errmsg);\r\n }\r\n\r\n // TODO decouple from room\r\n this.room = room;\r\n this.rtc = rtc;\r\n this._signalingLayer = signalingLayer;\r\n this.state = JingleSessionState.PENDING;\r\n this.doInitialize(options);\r\n }\r\n\r\n /**\r\n * The implementing class finishes initialization here. Called at the end of\r\n * {@link initialize}.\r\n * @param {Object} options - The options specific to the implementing class.\r\n * @protected\r\n */\r\n doInitialize(options) { } // eslint-disable-line no-unused-vars, no-empty-function, max-len\r\n\r\n /* eslint-disable no-unused-vars, no-empty-function */\r\n\r\n /**\r\n * Adds the ICE candidates found in the 'contents' array as remote\r\n * candidates?\r\n * Note: currently only used on transport-info\r\n *\r\n * @param contents\r\n */\r\n addIceCandidates(contents) {}\r\n\r\n /* eslint-enable no-unused-vars, no-empty-function */\r\n\r\n /**\r\n * Returns current state of this JingleSession instance.\r\n * @returns {JingleSessionState} the current state of this session instance.\r\n */\r\n getState() {\r\n return this.state;\r\n }\r\n\r\n /* eslint-disable no-unused-vars, no-empty-function */\r\n\r\n /**\r\n * Handles an 'add-source' event.\r\n *\r\n * @param contents an array of Jingle 'content' elements.\r\n */\r\n addSources(contents) {}\r\n\r\n /**\r\n * Handles a 'remove-source' event.\r\n *\r\n * @param contents an array of Jingle 'content' elements.\r\n */\r\n removeSources(contents) {}\r\n\r\n /**\r\n * Terminates this Jingle session by sending session-terminate\r\n * @param success a callback called once the 'session-terminate' packet has\r\n * been acknowledged with RESULT.\r\n * @param failure a callback called when either timeout occurs or ERROR\r\n * response is received.\r\n * @param {Object} options\r\n * @param {string} [options.reason] XMPP Jingle error condition\r\n * @param {string} [options.reasonDescription] some meaningful error message\r\n * @param {boolean} [options.requestRestart=false] set to true to ask Jicofo to start a new session one this once is\r\n * terminated.\r\n * @param {boolean} [options.sendSessionTerminate=true] set to false to skip\r\n * sending session-terminate. It may not make sense to send it if the XMPP\r\n * connection has been closed already or if the remote peer has disconnected\r\n */\r\n terminate(success, failure, options) {}\r\n\r\n /**\r\n * Handles an offer from the remote peer (prepares to accept a session).\r\n * @param jingle the 'jingle' XML element.\r\n * @param success callback called when we the incoming session has been\r\n * accepted\r\n * @param failure callback called when we fail for any reason, will supply\r\n * error object with details(which is meant more to be printed to the logger\r\n * than analysed in the code, as the error is unrecoverable anyway)\r\n */\r\n acceptOffer(jingle, success, failure) {}\r\n\r\n /**\r\n * Returns the JID of the initiator of the jingle session.\r\n */\r\n _getInitiatorJid() {\r\n return this.isInitiator ? this.localJid : this.remoteJid;\r\n }\r\n\r\n /* eslint-enable no-unused-vars, no-empty-function */\r\n}\r\n","enum MediaSessionEvents {\r\n /**\r\n * Event triggered when the remote party signals video max frame heights for its local sources.\r\n */\r\n REMOTE_SOURCE_CONSTRAINTS_CHANGED = 'media_session.REMOTE_SOURCE_CONSTRAINTS_CHANGED',\r\n\r\n /**\r\n * Event triggered when the remote party signals it's receive video max frame height.\r\n */\r\n REMOTE_VIDEO_CONSTRAINTS_CHANGED = 'media_session.REMOTE_VIDEO_CONSTRAINTS_CHANGED'\r\n};\r\n\r\nexport default MediaSessionEvents;","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $iq, Strophe } from 'strophe.js';\r\n\r\nimport * as CodecMimeType from '../../service/RTC/CodecMimeType';\r\nimport { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport {\r\n ICE_DURATION,\r\n ICE_STATE_CHANGED\r\n} from '../../service/statistics/AnalyticsEvents';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\nimport { SS_DEFAULT_FRAME_RATE } from '../RTC/ScreenObtainer';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport SDP from '../sdp/SDP';\r\nimport SDPDiffer from '../sdp/SDPDiffer';\r\nimport SDPUtil from '../sdp/SDPUtil';\r\nimport Statistics from '../statistics/statistics';\r\nimport AsyncQueue from '../util/AsyncQueue';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\nimport { integerHash } from '../util/StringUtils';\r\n\r\nimport browser from './../browser';\r\nimport JingleSession from './JingleSession';\r\nimport * as JingleSessionState from './JingleSessionState';\r\nimport MediaSessionEvents from './MediaSessionEvents';\r\nimport XmppConnection from './XmppConnection';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Constant tells how long we're going to wait for IQ response, before timeout\r\n * error is triggered.\r\n * @type {number}\r\n */\r\nconst IQ_TIMEOUT = 10000;\r\n\r\n/*\r\n * The default number of samples (per stat) to keep when webrtc stats gathering\r\n * is enabled in TraceablePeerConnection.\r\n */\r\nconst DEFAULT_MAX_STATS = 300;\r\n\r\n/**\r\n * The time duration for which the client keeps gathering ICE candidates to be sent out in a single IQ.\r\n * @type {number} timeout in ms.\r\n */\r\nconst ICE_CAND_GATHERING_TIMEOUT = 150;\r\n\r\n/**\r\n * Reads the endpoint ID given a string which represents either the endpoint's full JID, or the endpoint ID itself.\r\n * @param {String} jidOrEndpointId A string which is either the full JID of a participant, or the ID of an\r\n * endpoint/participant.\r\n * @returns The endpoint ID associated with 'jidOrEndpointId'.\r\n */\r\nfunction getEndpointId(jidOrEndpointId) {\r\n return Strophe.getResourceFromJid(jidOrEndpointId) || jidOrEndpointId;\r\n}\r\n\r\n/**\r\n * @typedef {Object} JingleSessionPCOptions\r\n * @property {Object} abTesting - A/B testing related options (ask George).\r\n * @property {boolean} abTesting.enableSuspendVideoTest - enables the suspend\r\n * video test ?(ask George).\r\n * @property {boolean} disableH264 - Described in the config.js[1].\r\n * @property {boolean} disableRtx - Described in the config.js[1].\r\n * @property {boolean} disableSimulcast - Described in the config.js[1].\r\n * @property {boolean} enableInsertableStreams - Set to true when the insertable streams constraints is to be enabled\r\n * on the PeerConnection.\r\n * @property {boolean} enableLayerSuspension - Described in the config.js[1].\r\n * @property {boolean} failICE - it's an option used in the tests. Set to\r\n * true to block any real candidates and make the ICE fail.\r\n * @property {boolean} gatherStats - Described in the config.js[1].\r\n * @property {object} p2p - Peer to peer related options (FIXME those could be\r\n * fetched from config.p2p on the upper level).\r\n * @property {boolean} preferH264 - Described in the config.js[1].\r\n * @property {Object} testing - Testing and/or experimental options.\r\n * @property {boolean} webrtcIceUdpDisable - Described in the config.js[1].\r\n * @property {boolean} webrtcIceTcpDisable - Described in the config.js[1].\r\n *\r\n * [1]: https://github.com/jitsi/jitsi-meet/blob/master/config.js\r\n */\r\n/**\r\n *\r\n */\r\nexport default class JingleSessionPC extends JingleSession {\r\n /**\r\n * Parses 'senders' attribute of the video content.\r\n * @param {jQuery} jingleContents\r\n * @return {string|null} one of the values of content \"senders\" attribute\r\n * defined by Jingle. If there is no \"senders\" attribute or if the value is\r\n * invalid then null will be returned.\r\n * @private\r\n */\r\n static parseVideoSenders(jingleContents) {\r\n const videoContents = jingleContents.find('>content[name=\"video\"]');\r\n\r\n if (videoContents.length) {\r\n const senders = videoContents[0].getAttribute('senders');\r\n\r\n if (senders === 'both'\r\n || senders === 'initiator'\r\n || senders === 'responder'\r\n || senders === 'none') {\r\n return senders;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Parses the video max frame height value out of the 'content-modify' IQ.\r\n *\r\n * @param {jQuery} jingleContents - A jQuery selector pointing to the '>jingle' element.\r\n * @returns {Number|null}\r\n */\r\n static parseMaxFrameHeight(jingleContents) {\r\n const maxFrameHeightSel = jingleContents.find('>content[name=\"video\"]>max-frame-height');\r\n\r\n return maxFrameHeightSel.length ? Number(maxFrameHeightSel.text()) : null;\r\n }\r\n\r\n /**\r\n * Parses the source-name and max frame height value of the 'content-modify' IQ when source-name signaling\r\n * is enabled.\r\n *\r\n * @param {jQuery} jingleContents - A jQuery selector pointing to the '>jingle' element.\r\n * @returns {Object|null}\r\n */\r\n static parseSourceMaxFrameHeight(jingleContents) {\r\n const receiverConstraints = [];\r\n const sourceFrameHeightSel = jingleContents.find('>content[name=\"video\"]>source-frame-height');\r\n let maxHeight, sourceName;\r\n\r\n if (sourceFrameHeightSel.length) {\r\n sourceFrameHeightSel.each((_, source) => {\r\n sourceName = source.getAttribute('sourceName');\r\n maxHeight = source.getAttribute('maxHeight');\r\n receiverConstraints.push({\r\n maxHeight,\r\n sourceName\r\n });\r\n });\r\n\r\n return receiverConstraints;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n * Creates new JingleSessionPC\r\n * @param {string} sid the Jingle Session ID - random string which identifies the session\r\n * @param {string} localJid our JID\r\n * @param {string} remoteJid remote peer JID\r\n * @param {XmppConnection} connection - The XMPP connection instance.\r\n * @param mediaConstraints the media constraints object passed to createOffer/Answer, as defined\r\n * by the WebRTC standard\r\n * @param pcConfig The {@code RTCConfiguration} to use for the WebRTC peer connection.\r\n * @param {boolean} isP2P indicates whether this instance is meant to be used in a direct, peer to\r\n * peer connection or false if it's a JVB connection.\r\n * @param {boolean} isInitiator indicates if it will be the side which initiates the session.\r\n * @constructor\r\n *\r\n * @implements {SignalingLayer}\r\n */\r\n constructor(\r\n sid,\r\n localJid,\r\n remoteJid,\r\n connection,\r\n mediaConstraints,\r\n pcConfig,\r\n isP2P,\r\n isInitiator) {\r\n super(\r\n sid,\r\n localJid,\r\n remoteJid, connection, mediaConstraints, pcConfig, isInitiator);\r\n\r\n /**\r\n * The bridge session's identifier. One Jingle session can during\r\n * it's lifetime participate in multiple bridge sessions managed by\r\n * Jicofo. A new bridge session is started whenever Jicofo sends\r\n * 'session-initiate' or 'transport-replace'.\r\n *\r\n * @type {?string}\r\n * @private\r\n */\r\n this._bridgeSessionId = null;\r\n\r\n /**\r\n * The oldest SDP passed to {@link notifyMySSRCUpdate} while the XMPP connection was offline that will be\r\n * used to update Jicofo once the XMPP connection goes back online.\r\n * @type {SDP|undefined}\r\n * @private\r\n */\r\n this._cachedOldLocalSdp = undefined;\r\n\r\n /**\r\n * The latest SDP passed to {@link notifyMySSRCUpdate} while the XMPP connection was offline that will be\r\n * used to update Jicofo once the XMPP connection goes back online.\r\n * @type {SDP|undefined}\r\n * @private\r\n */\r\n this._cachedNewLocalSdp = undefined;\r\n\r\n /**\r\n * Stores result of {@link window.performance.now()} at the time when\r\n * ICE enters 'checking' state.\r\n * @type {number|null} null if no value has been stored yet\r\n * @private\r\n */\r\n this._iceCheckingStartedTimestamp = null;\r\n\r\n /**\r\n * Stores result of {@link window.performance.now()} at the time when\r\n * first ICE candidate is spawned by the peerconnection to mark when\r\n * ICE gathering started. That's, because ICE gathering state changed\r\n * events are not supported by most of the browsers, so we try something\r\n * that will work everywhere. It may not be as accurate, but given that\r\n * 'host' candidate usually comes first, the delay should be minimal.\r\n * @type {number|null} null if no value has been stored yet\r\n * @private\r\n */\r\n this._gatheringStartedTimestamp = null;\r\n\r\n /**\r\n * Local preference for the receive video max frame height.\r\n *\r\n * @type {Number|undefined}\r\n */\r\n this.localRecvMaxFrameHeight = undefined;\r\n\r\n /**\r\n * Receiver constraints (max height) set by the application per remote source. Will be used for p2p connection\r\n * in lieu of localRecvMaxFrameHeight when source-name signaling is enabled.\r\n *\r\n * @type {Map}\r\n */\r\n this._sourceReceiverConstraints = undefined;\r\n\r\n /**\r\n * Indicates whether or not this session is willing to send/receive\r\n * video media. When set to false the underlying peer\r\n * connection will disable local video transfer and the remote peer will\r\n * be will be asked to stop sending video via 'content-modify' IQ\r\n * (the senders attribute of video contents will be adjusted\r\n * accordingly). Note that this notification is sent only in P2P\r\n * session, because Jicofo does not support it yet. Obviously when\r\n * the value is changed from false to true another\r\n * notification will be sent to resume video transfer on the remote\r\n * side.\r\n * @type {boolean}\r\n * @private\r\n */\r\n this._localVideoActive = true;\r\n\r\n /**\r\n * Indicates whether or not the remote peer has video transfer active.\r\n * When set to true it means that remote peer is neither\r\n * sending nor willing to receive video. In such case we'll ask\r\n * our peerconnection to stop sending video by calling\r\n * {@link TraceablePeerConnection.setVideoTransferActive} with\r\n * false.\r\n * @type {boolean}\r\n * @private\r\n */\r\n this._remoteVideoActive = true;\r\n\r\n /**\r\n * Marks that ICE gathering duration has been reported already. That\r\n * prevents reporting it again, after eventual 'transport-replace' (JVB\r\n * conference migration/ICE restart).\r\n * @type {boolean}\r\n * @private\r\n */\r\n this._gatheringReported = false;\r\n\r\n this.lasticecandidate = false;\r\n this.closed = false;\r\n\r\n /**\r\n * Indicates whether or not this JingleSessionPC is used in\r\n * a peer to peer type of session.\r\n * @type {boolean} true if it's a peer to peer\r\n * session or false if it's a JVB session\r\n */\r\n this.isP2P = isP2P;\r\n\r\n /**\r\n * Remote preference for the receive video max frame height.\r\n *\r\n * @type {Number|undefined}\r\n */\r\n this.remoteRecvMaxFrameHeight = undefined;\r\n\r\n /**\r\n * Remote preference for the receive video max frame heights when source-name signaling is enabled.\r\n *\r\n * @type {Map|undefined}\r\n */\r\n this.remoteSourceMaxFrameHeights = undefined;\r\n\r\n /**\r\n * The queue used to serialize operations done on the peerconnection.\r\n *\r\n * @type {AsyncQueue}\r\n */\r\n this.modificationQueue = new AsyncQueue();\r\n\r\n /**\r\n * Flag used to guarantee that the connection established event is\r\n * triggered just once.\r\n * @type {boolean}\r\n */\r\n this.wasConnected = false;\r\n\r\n /**\r\n * Keeps track of how long (in ms) it took from ICE start to ICE\r\n * connect.\r\n *\r\n * @type {number}\r\n */\r\n this.establishmentDuration = undefined;\r\n\r\n this._xmppListeners = [];\r\n this._xmppListeners.push(\r\n connection.addEventListener(\r\n XmppConnection.Events.CONN_STATUS_CHANGED,\r\n this.onXmppStatusChanged.bind(this))\r\n );\r\n\r\n this._removeSenderVideoConstraintsChangeListener = undefined;\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Checks whether or not this session instance is still operational.\r\n * @private\r\n * @returns {boolean} {@code true} if operation or {@code false} otherwise.\r\n */\r\n _assertNotEnded() {\r\n return this.state !== JingleSessionState.ENDED;\r\n }\r\n\r\n /**\r\n * @inheritDoc\r\n * @param {JingleSessionPCOptions} options - a set of config options.\r\n */\r\n doInitialize(options) {\r\n this.failICE = Boolean(options.failICE);\r\n this.lasticecandidate = false;\r\n this.options = options;\r\n\r\n /**\r\n * {@code true} if reconnect is in progress.\r\n * @type {boolean}\r\n */\r\n this.isReconnect = false;\r\n\r\n /**\r\n * Set to {@code true} if the connection was ever stable\r\n * @type {boolean}\r\n */\r\n this.wasstable = false;\r\n this.webrtcIceUdpDisable = Boolean(options.webrtcIceUdpDisable);\r\n this.webrtcIceTcpDisable = Boolean(options.webrtcIceTcpDisable);\r\n\r\n const pcOptions = { disableRtx: options.disableRtx };\r\n\r\n if (options.gatherStats) {\r\n pcOptions.maxstats = DEFAULT_MAX_STATS;\r\n }\r\n pcOptions.capScreenshareBitrate = false;\r\n pcOptions.enableInsertableStreams = options.enableInsertableStreams;\r\n pcOptions.videoQuality = options.videoQuality;\r\n pcOptions.forceTurnRelay = options.forceTurnRelay;\r\n pcOptions.audioQuality = options.audioQuality;\r\n pcOptions.usesUnifiedPlan = this.usesUnifiedPlan\r\n = browser.supportsUnifiedPlan()\r\n && (browser.isFirefox()\r\n || browser.isWebKitBased()\r\n || (browser.isChromiumBased()\r\n\r\n // Provide a way to control the behavior for jvb and p2p connections independently.\r\n && this.isP2P\r\n ? options.p2p?.enableUnifiedOnChrome ?? true\r\n : options.enableUnifiedOnChrome ?? true));\r\n\r\n if (this.isP2P) {\r\n // simulcast needs to be disabled for P2P (121) calls\r\n pcOptions.disableSimulcast = true;\r\n const abtestSuspendVideo = this._abtestSuspendVideoEnabled(options);\r\n\r\n if (typeof abtestSuspendVideo !== 'undefined') {\r\n pcOptions.abtestSuspendVideo = abtestSuspendVideo;\r\n }\r\n } else {\r\n // H264 does not support simulcast, so it needs to be disabled.\r\n pcOptions.disableSimulcast\r\n = options.disableSimulcast\r\n || (options.preferH264 && !options.disableH264)\r\n || (options.videoQuality && options.videoQuality.preferredCodec === CodecMimeType.H264);\r\n\r\n // Disable simulcast for low fps screenshare and enable it for high fps screenshare.\r\n // testing.capScreenshareBitrate config.js setting has now been deprecated.\r\n pcOptions.capScreenshareBitrate = pcOptions.disableSimulcast\r\n || !(typeof options.desktopSharingFrameRate?.max === 'number'\r\n && options.desktopSharingFrameRate?.max > SS_DEFAULT_FRAME_RATE);\r\n\r\n // add the capScreenshareBitrate to the permanent properties so that it's included with every event that we\r\n // send to the analytics backend.\r\n Statistics.analytics.addPermanentProperties({ capScreenshareBitrate: pcOptions.capScreenshareBitrate });\r\n }\r\n\r\n if (options.startSilent) {\r\n pcOptions.startSilent = true;\r\n }\r\n\r\n this.peerconnection\r\n = this.rtc.createPeerConnection(\r\n this._signalingLayer,\r\n this.pcConfig,\r\n this.isP2P,\r\n pcOptions);\r\n\r\n this.peerconnection.onicecandidate = ev => {\r\n if (!ev) {\r\n // There was an incomplete check for ev before which left\r\n // the last line of the function unprotected from a potential\r\n // throw of an exception. Consequently, it may be argued that\r\n // the check is unnecessary. Anyway, I'm leaving it and making\r\n // the check complete.\r\n return;\r\n }\r\n\r\n // XXX this is broken, candidate is not parsed.\r\n const candidate = ev.candidate;\r\n const now = window.performance.now();\r\n\r\n if (candidate) {\r\n if (this._gatheringStartedTimestamp === null) {\r\n this._gatheringStartedTimestamp = now;\r\n }\r\n\r\n // Discard candidates of disabled protocols.\r\n let protocol = candidate.protocol;\r\n\r\n if (typeof protocol === 'string') {\r\n protocol = protocol.toLowerCase();\r\n if (protocol === 'tcp' || protocol === 'ssltcp') {\r\n if (this.webrtcIceTcpDisable) {\r\n return;\r\n }\r\n } else if (protocol === 'udp') {\r\n if (this.webrtcIceUdpDisable) {\r\n return;\r\n }\r\n }\r\n }\r\n } else if (!this._gatheringReported) {\r\n // End of gathering\r\n Statistics.sendAnalytics(\r\n ICE_DURATION,\r\n {\r\n phase: 'gathering',\r\n value: now - this._gatheringStartedTimestamp,\r\n p2p: this.isP2P,\r\n initiator: this.isInitiator\r\n });\r\n this._gatheringReported = true;\r\n }\r\n this.sendIceCandidate(candidate);\r\n };\r\n\r\n // Note there is a change in the spec about closed:\r\n // This value moved into the RTCPeerConnectionState enum in\r\n // the May 13, 2016 draft of the specification, as it reflects the state\r\n // of the RTCPeerConnection, not the signaling connection. You now\r\n // detect a closed connection by checking for connectionState to be\r\n // \"closed\" instead.\r\n // I suppose at some point this will be moved to onconnectionstatechange\r\n this.peerconnection.onsignalingstatechange = () => {\r\n if (this.peerconnection.signalingState === 'stable') {\r\n this.wasstable = true;\r\n } else if (this.peerconnection.signalingState === 'closed'\r\n || this.peerconnection.connectionState === 'closed') {\r\n this.room.eventEmitter.emit(XMPPEvents.SUSPEND_DETECTED, this);\r\n }\r\n };\r\n\r\n /**\r\n * The oniceconnectionstatechange event handler contains the code to\r\n * execute when the iceconnectionstatechange event, of type Event,\r\n * is received by this RTCPeerConnection. Such an event is sent when\r\n * the value of RTCPeerConnection.iceConnectionState changes.\r\n */\r\n this.peerconnection.oniceconnectionstatechange = () => {\r\n const now = window.performance.now();\r\n let isStable = false;\r\n\r\n if (!this.isP2P) {\r\n this.room.connectionTimes[\r\n `ice.state.${this.peerconnection.iceConnectionState}`]\r\n = now;\r\n }\r\n logger.log(`(TIME) ICE ${this.peerconnection.iceConnectionState} ${this.isP2P ? 'P2P' : 'JVB'}:\\t`, now);\r\n\r\n Statistics.sendAnalytics(\r\n ICE_STATE_CHANGED,\r\n {\r\n p2p: this.isP2P,\r\n state: this.peerconnection.iceConnectionState,\r\n 'signaling_state': this.peerconnection.signalingState,\r\n reconnect: this.isReconnect,\r\n value: now\r\n });\r\n\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.ICE_CONNECTION_STATE_CHANGED,\r\n this,\r\n this.peerconnection.iceConnectionState);\r\n switch (this.peerconnection.iceConnectionState) {\r\n case 'checking':\r\n this._iceCheckingStartedTimestamp = now;\r\n break;\r\n case 'connected':\r\n // Informs interested parties that the connection has been restored. This includes the case when\r\n // media connection to the bridge has been restored after an ICE failure by using session-terminate.\r\n if (this.peerconnection.signalingState === 'stable') {\r\n isStable = true;\r\n const usesTerminateForRestart = !this.options.enableIceRestart\r\n && this.room.supportsRestartByTerminate();\r\n\r\n if (this.isReconnect || usesTerminateForRestart) {\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.CONNECTION_RESTORED, this);\r\n }\r\n }\r\n\r\n // Add a workaround for an issue on chrome in Unified plan when the local endpoint is the offerer.\r\n // The 'signalingstatechange' event for 'stable' is handled after the 'iceconnectionstatechange' event\r\n // for 'completed' is handled by the client. This prevents the client from firing a\r\n // CONNECTION_ESTABLISHED event for the p2p session. As a result, the offerer continues to stay on the\r\n // jvb connection while the remote peer switches to the p2p connection breaking the media flow between\r\n // the endpoints.\r\n // TODO - file a chromium bug and add the information here.\r\n if (!this.wasConnected\r\n && (this.wasstable\r\n || isStable\r\n || (this.usesUnifiedPlan && this.isInitiator && browser.isChromiumBased()))) {\r\n\r\n Statistics.sendAnalytics(\r\n ICE_DURATION,\r\n {\r\n phase: 'checking',\r\n value: now - this._iceCheckingStartedTimestamp,\r\n p2p: this.isP2P,\r\n initiator: this.isInitiator\r\n });\r\n\r\n // Switch between ICE gathering and ICE checking whichever\r\n // started first (scenarios are different for initiator\r\n // vs responder)\r\n const iceStarted\r\n = Math.min(\r\n this._iceCheckingStartedTimestamp,\r\n this._gatheringStartedTimestamp);\r\n\r\n this.establishmentDuration = now - iceStarted;\r\n\r\n Statistics.sendAnalytics(\r\n ICE_DURATION,\r\n {\r\n phase: 'establishment',\r\n value: this.establishmentDuration,\r\n p2p: this.isP2P,\r\n initiator: this.isInitiator\r\n });\r\n\r\n this.wasConnected = true;\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.CONNECTION_ESTABLISHED, this);\r\n }\r\n this.isReconnect = false;\r\n break;\r\n case 'disconnected':\r\n this.isReconnect = true;\r\n\r\n // Informs interested parties that the connection has been\r\n // interrupted.\r\n if (this.wasstable) {\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.CONNECTION_INTERRUPTED, this);\r\n }\r\n break;\r\n case 'failed':\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.CONNECTION_ICE_FAILED, this);\r\n break;\r\n }\r\n };\r\n\r\n\r\n /**\r\n * The connection state event is fired whenever the aggregate of underlying\r\n * transports change their state.\r\n */\r\n this.peerconnection.onconnectionstatechange = () => {\r\n const icestate = this.peerconnection.iceConnectionState;\r\n\r\n switch (this.peerconnection.connectionState) {\r\n case 'failed':\r\n // Since version 76 Chrome no longer switches ICE connection\r\n // state to failed (see\r\n // https://bugs.chromium.org/p/chromium/issues/detail?id=982793\r\n // for details) we use this workaround to recover from lost connections\r\n if (icestate === 'disconnected') {\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.CONNECTION_ICE_FAILED, this);\r\n }\r\n break;\r\n }\r\n };\r\n\r\n /**\r\n * The negotiationneeded event is fired whenever we shake the media on the\r\n * RTCPeerConnection object.\r\n */\r\n this.peerconnection.onnegotiationneeded = () => {\r\n const state = this.peerconnection.signalingState;\r\n const remoteDescription = this.peerconnection.remoteDescription;\r\n\r\n if (this.usesUnifiedPlan\r\n && !this.isP2P\r\n && state === 'stable'\r\n && remoteDescription\r\n && typeof remoteDescription.sdp === 'string') {\r\n logger.info(`${this} onnegotiationneeded fired on ${this.peerconnection}`);\r\n\r\n const workFunction = finishedCallback => {\r\n const oldSdp = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this._renegotiate()\r\n .then(() => this.peerconnection.configureSenderVideoEncodings())\r\n .then(() => {\r\n const newSdp = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this.notifyMySSRCUpdate(oldSdp, newSdp);\r\n })\r\n .then(() => finishedCallback(), error => finishedCallback(error));\r\n };\r\n\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} onnegotiationneeded error`, error);\r\n } else {\r\n logger.debug(`${this} onnegotiationneeded executed - OK`);\r\n }\r\n });\r\n }\r\n };\r\n }\r\n\r\n /**\r\n * Remote preference for receive video max frame height.\r\n *\r\n * @returns {Number|undefined}\r\n */\r\n getRemoteRecvMaxFrameHeight() {\r\n if (this.isP2P) {\r\n return this.remoteRecvMaxFrameHeight;\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Remote preference for receive video max frame heights when source-name signaling is enabled.\r\n *\r\n * @returns {Map|undefined}\r\n */\r\n getRemoteSourcesRecvMaxFrameHeight() {\r\n if (this.isP2P) {\r\n return this.remoteSourceMaxFrameHeights;\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Sends given candidate in Jingle 'transport-info' message.\r\n * @param {RTCIceCandidate} candidate the WebRTC ICE candidate instance\r\n * @private\r\n */\r\n sendIceCandidate(candidate) {\r\n const localSDP = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n if (candidate && candidate.candidate.length && !this.lasticecandidate) {\r\n const ice = SDPUtil.iceparams(localSDP.media[candidate.sdpMLineIndex], localSDP.session);\r\n const jcand = SDPUtil.candidateToJingle(candidate.candidate);\r\n\r\n if (!(ice && jcand)) {\r\n const errorMesssage = 'failed to get ice && jcand';\r\n\r\n GlobalOnErrorHandler.callErrorHandler(new Error(errorMesssage));\r\n logger.error(errorMesssage);\r\n\r\n return;\r\n }\r\n ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';\r\n\r\n if (this.usedrip) {\r\n if (this.dripContainer.length === 0) {\r\n setTimeout(() => {\r\n if (this.dripContainer.length === 0) {\r\n return;\r\n }\r\n this.sendIceCandidates(this.dripContainer);\r\n this.dripContainer = [];\r\n }, ICE_CAND_GATHERING_TIMEOUT);\r\n }\r\n this.dripContainer.push(candidate);\r\n } else {\r\n this.sendIceCandidates([ candidate ]);\r\n }\r\n } else {\r\n logger.log(`${this} sendIceCandidate: last candidate`);\r\n\r\n // FIXME: remember to re-think in ICE-restart\r\n this.lasticecandidate = true;\r\n }\r\n }\r\n\r\n /**\r\n * Sends given candidates in Jingle 'transport-info' message.\r\n * @param {Array} candidates an array of the WebRTC ICE\r\n * candidate instances\r\n * @private\r\n */\r\n sendIceCandidates(candidates) {\r\n if (!this._assertNotEnded('sendIceCandidates')) {\r\n\r\n return;\r\n }\r\n\r\n logger.log(`${this} sendIceCandidates ${JSON.stringify(candidates)}`);\r\n const cand = $iq({ to: this.remoteJid,\r\n type: 'set' })\r\n .c('jingle', { xmlns: 'urn:xmpp:jingle:1',\r\n action: 'transport-info',\r\n initiator: this.initiatorJid,\r\n sid: this.sid });\r\n\r\n const localSDP = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n for (let mid = 0; mid < localSDP.media.length; mid++) {\r\n const cands = candidates.filter(el => el.sdpMLineIndex === mid);\r\n const mline\r\n = SDPUtil.parseMLine(localSDP.media[mid].split('\\r\\n')[0]);\r\n\r\n if (cands.length > 0) {\r\n const ice\r\n = SDPUtil.iceparams(localSDP.media[mid], localSDP.session);\r\n\r\n ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';\r\n cand.c('content', {\r\n creator: this.initiatorJid === this.localJid\r\n ? 'initiator' : 'responder',\r\n name: cands[0].sdpMid ? cands[0].sdpMid : mline.media\r\n }).c('transport', ice);\r\n for (let i = 0; i < cands.length; i++) {\r\n const candidate\r\n = SDPUtil.candidateToJingle(cands[i].candidate);\r\n\r\n // Mangle ICE candidate if 'failICE' test option is enabled\r\n\r\n if (this.failICE) {\r\n candidate.ip = '1.1.1.1';\r\n }\r\n cand.c('candidate', candidate).up();\r\n }\r\n\r\n // add fingerprint\r\n const fingerprintLine\r\n = SDPUtil.findLine(\r\n localSDP.media[mid],\r\n 'a=fingerprint:', localSDP.session);\r\n\r\n if (fingerprintLine) {\r\n const tmp = SDPUtil.parseFingerprint(fingerprintLine);\r\n\r\n tmp.required = true;\r\n cand.c(\r\n 'fingerprint',\r\n { xmlns: 'urn:xmpp:jingle:apps:dtls:0' })\r\n .t(tmp.fingerprint);\r\n delete tmp.fingerprint;\r\n cand.attrs(tmp);\r\n cand.up();\r\n }\r\n cand.up(); // transport\r\n cand.up(); // content\r\n }\r\n }\r\n\r\n // might merge last-candidate notification into this, but it is called\r\n // a lot later. See webrtc issue #2340\r\n // logger.log('was this the last candidate', this.lasticecandidate);\r\n this.connection.sendIQ(\r\n cand, null, this.newJingleErrorHandler(cand), IQ_TIMEOUT);\r\n }\r\n\r\n /**\r\n * Sends Jingle 'session-info' message which includes custom Jitsi Meet\r\n * 'ice-state' element with the text value 'failed' to let Jicofo know\r\n * that the ICE connection has entered the failed state. It can then\r\n * choose to re-create JVB channels and send 'transport-replace' to\r\n * retry the connection.\r\n */\r\n sendIceFailedNotification() {\r\n const sessionInfo\r\n = $iq({\r\n to: this.remoteJid,\r\n type: 'set' })\r\n .c('jingle', { xmlns: 'urn:xmpp:jingle:1',\r\n action: 'session-info',\r\n initiator: this.initiatorJid,\r\n sid: this.sid })\r\n .c('ice-state', { xmlns: 'http://jitsi.org/protocol/focus' })\r\n .t('failed')\r\n .up();\r\n\r\n this._bridgeSessionId\r\n && sessionInfo.c(\r\n 'bridge-session', {\r\n xmlns: 'http://jitsi.org/protocol/focus',\r\n id: this._bridgeSessionId\r\n });\r\n\r\n this.connection.sendIQ2(\r\n sessionInfo, {\r\n /*\r\n * This message will be often sent when there are connectivity\r\n * issues, so make it slightly longer than Prosody's default BOSH\r\n * inactivity timeout of 60 seconds.\r\n */\r\n timeout: 65\r\n })\r\n .catch(this.newJingleErrorHandler(sessionInfo));\r\n }\r\n\r\n /**\r\n * {@inheritDoc}\r\n */\r\n addIceCandidates(elem) {\r\n if (this.peerconnection.signalingState === 'closed') {\r\n logger.warn(`${this} Ignored add ICE candidate when in closed state`);\r\n\r\n return;\r\n }\r\n\r\n const iceCandidates = [];\r\n\r\n elem.find('>content>transport>candidate')\r\n .each((idx, candidate) => {\r\n let line = SDPUtil.candidateFromJingle(candidate);\r\n\r\n line = line.replace('\\r\\n', '').replace('a=', '');\r\n\r\n // FIXME this code does not care to handle\r\n // non-bundle transport\r\n const rtcCandidate = new RTCIceCandidate({\r\n sdpMLineIndex: 0,\r\n\r\n // FF comes up with more complex names like audio-23423,\r\n // Given that it works on both Chrome and FF without\r\n // providing it, let's leave it like this for the time\r\n // being...\r\n // sdpMid: 'audio',\r\n sdpMid: '',\r\n candidate: line\r\n });\r\n\r\n iceCandidates.push(rtcCandidate);\r\n });\r\n\r\n if (!iceCandidates.length) {\r\n logger.error(`${this} No ICE candidates to add ?`, elem[0] && elem[0].outerHTML);\r\n\r\n return;\r\n }\r\n\r\n // We want to have this task queued, so that we know it is executed,\r\n // after the initial sRD/sLD offer/answer cycle was done (based on\r\n // the assumption that candidates are spawned after the offer/answer\r\n // and XMPP preserves order).\r\n const workFunction = finishedCallback => {\r\n for (const iceCandidate of iceCandidates) {\r\n this.peerconnection.addIceCandidate(iceCandidate)\r\n .then(\r\n () => logger.debug(`${this} addIceCandidate ok!`),\r\n err => logger.error(`${this} addIceCandidate failed!`, err));\r\n }\r\n\r\n finishedCallback();\r\n logger.debug(`${this} ICE candidates task finished`);\r\n };\r\n\r\n logger.debug(`${this} Queued add (${iceCandidates.length}) ICE candidates task`);\r\n this.modificationQueue.push(workFunction);\r\n }\r\n\r\n /**\r\n *\r\n * @param contents\r\n */\r\n readSsrcInfo(contents) {\r\n const ssrcs = $(contents).find('>description>source[xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\"]');\r\n\r\n ssrcs.each((i, ssrcElement) => {\r\n const ssrc = Number(ssrcElement.getAttribute('ssrc'));\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n if (ssrcElement.hasAttribute('name')) {\r\n const sourceName = ssrcElement.getAttribute('name');\r\n\r\n this._signalingLayer.setTrackSourceName(ssrc, sourceName);\r\n }\r\n }\r\n\r\n if (this.isP2P) {\r\n // In P2P all SSRCs are owner by the remote peer\r\n this._signalingLayer.setSSRCOwner(ssrc, Strophe.getResourceFromJid(this.remoteJid));\r\n } else {\r\n $(ssrcElement)\r\n .find('>ssrc-info[xmlns=\"http://jitsi.org/jitmeet\"]')\r\n .each((i3, ssrcInfoElement) => {\r\n const owner = ssrcInfoElement.getAttribute('owner');\r\n\r\n if (owner?.length) {\r\n if (isNaN(ssrc) || ssrc < 0) {\r\n logger.warn(`${this} Invalid SSRC ${ssrc} value received for ${owner}`);\r\n } else {\r\n this._signalingLayer.setSSRCOwner(ssrc, getEndpointId(owner));\r\n }\r\n }\r\n });\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Makes the underlying TraceablePeerConnection generate new SSRC for\r\n * the recvonly video stream.\r\n * @deprecated\r\n */\r\n generateRecvonlySsrc() {\r\n if (this.peerconnection) {\r\n this.peerconnection.generateRecvonlySsrc();\r\n } else {\r\n logger.error(`${this} Unable to generate recvonly SSRC - no peerconnection`);\r\n }\r\n }\r\n\r\n /**\r\n * Returns the video codec configured as the preferred codec on the peerconnection.\r\n */\r\n getConfiguredVideoCodec() {\r\n return this.peerconnection.getConfiguredVideoCodec();\r\n }\r\n\r\n /* eslint-disable max-params */\r\n /**\r\n * Accepts incoming Jingle 'session-initiate' and should send\r\n * 'session-accept' in result.\r\n * @param jingleOffer jQuery selector pointing to the jingle element of\r\n * the offer IQ\r\n * @param success callback called when we accept incoming session\r\n * successfully and receive RESULT packet to 'session-accept' sent.\r\n * @param failure function(error) called if for any reason we fail to accept\r\n * the incoming offer. 'error' argument can be used to log some details\r\n * about the error.\r\n * @param {Array} [localTracks] the optional list of\r\n * the local tracks that will be added, before the offer/answer cycle\r\n * executes. We allow the localTracks to optionally be passed in so that\r\n * the addition of the local tracks and the processing of the initial offer\r\n * can all be done atomically. We want to make sure that any other\r\n * operations which originate in the XMPP Jingle messages related with\r\n * this session to be executed with an assumption that the initial\r\n * offer/answer cycle has been executed already.\r\n */\r\n acceptOffer(jingleOffer, success, failure, localTracks) {\r\n this.setOfferAnswerCycle(\r\n jingleOffer,\r\n () => {\r\n // FIXME we may not care about RESULT packet for session-accept\r\n // then we should either call 'success' here immediately or\r\n // modify sendSessionAccept method to do that\r\n this.sendSessionAccept(() => {\r\n success();\r\n this.room.eventEmitter.emit(XMPPEvents.SESSION_ACCEPT, this);\r\n\r\n // The first video track is added to the peerconnection and signaled as part of the session-accept.\r\n // Add secondary video tracks (that were already added to conference) to the peerconnection here.\r\n // This will happen when someone shares a secondary source to a two people call, the other user\r\n // leaves and joins the call again, a new peerconnection is created for p2p/jvb connection. At this\r\n // point, there are 2 video tracks which need to be signaled to the remote peer.\r\n const videoTracks = localTracks.filter(track => track.getType() === MediaType.VIDEO);\r\n\r\n videoTracks.length && videoTracks.splice(0, 1);\r\n if (FeatureFlags.isMultiStreamSupportEnabled() && videoTracks.length) {\r\n this.addTracks(videoTracks);\r\n }\r\n },\r\n error => {\r\n failure(error);\r\n this.room.eventEmitter.emit(XMPPEvents.SESSION_ACCEPT_ERROR, this, error);\r\n });\r\n },\r\n failure,\r\n localTracks);\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Creates an offer and sends Jingle 'session-initiate' to the remote peer.\r\n * @param {Array} localTracks the local tracks that will be\r\n * added, before the offer/answer cycle executes (for the local track\r\n * addition to be an atomic operation together with the offer/answer).\r\n */\r\n invite(localTracks = []) {\r\n if (!this.isInitiator) {\r\n throw new Error('Trying to invite from the responder session');\r\n }\r\n const workFunction = finishedCallback => {\r\n const addTracks = [];\r\n\r\n for (const track of localTracks) {\r\n addTracks.push(this.peerconnection.addTrack(track, this.isInitiator));\r\n }\r\n\r\n Promise.all(addTracks)\r\n .then(() => this.peerconnection.createOffer(this.mediaConstraints))\r\n .then(offerSdp => this.peerconnection.setLocalDescription(offerSdp))\r\n .then(() => {\r\n // NOTE that the offer is obtained from the localDescription getter as it needs to go though\r\n // the transformation chain.\r\n this.sendSessionInitiate(this.peerconnection.localDescription.sdp);\r\n })\r\n .then(() => finishedCallback(), error => finishedCallback(error));\r\n };\r\n\r\n logger.debug(`${this} Queued invite task`);\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} invite error`, error);\r\n } else {\r\n logger.debug(`${this} invite executed - OK`);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Sends 'session-initiate' to the remote peer.\r\n *\r\n * NOTE this method is synchronous and we're not waiting for the RESULT\r\n * response which would delay the startup process.\r\n *\r\n * @param {string} offerSdp - The local session description which will be\r\n * used to generate an offer.\r\n * @private\r\n */\r\n sendSessionInitiate(offerSdp) {\r\n let init = $iq({\r\n to: this.remoteJid,\r\n type: 'set'\r\n }).c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: 'session-initiate',\r\n initiator: this.initiatorJid,\r\n sid: this.sid\r\n });\r\n\r\n new SDP(offerSdp).toJingle(\r\n init,\r\n this.isInitiator ? 'initiator' : 'responder');\r\n init = init.tree();\r\n logger.debug(`${this} Session-initiate: `, init);\r\n this.connection.sendIQ(init,\r\n () => {\r\n logger.info(`${this} Got RESULT for \"session-initiate\"`);\r\n },\r\n error => {\r\n logger.error(`${this} \"session-initiate\" error`, error);\r\n },\r\n IQ_TIMEOUT);\r\n }\r\n\r\n /**\r\n * Sets the answer received from the remote peer.\r\n * @param jingleAnswer\r\n */\r\n setAnswer(jingleAnswer) {\r\n if (!this.isInitiator) {\r\n throw new Error('Trying to set an answer on the responder session');\r\n }\r\n this.setOfferAnswerCycle(\r\n jingleAnswer,\r\n () => {\r\n logger.info(`${this} setAnswer - succeeded`);\r\n if (this.usesUnifiedPlan && browser.isChromiumBased()) {\r\n const workFunction = finishedCallback => {\r\n // This hack is needed for Chrome to create a decoder for the ssrcs in the remote SDP when\r\n // the local endpoint is the offerer and starts muted.\r\n const remoteSdp = this.peerconnection.remoteDescription.sdp;\r\n const remoteDescription = new RTCSessionDescription({\r\n type: 'offer',\r\n sdp: remoteSdp\r\n });\r\n\r\n return this._responderRenegotiate(remoteDescription)\r\n .then(() => finishedCallback(), error => finishedCallback(error));\r\n };\r\n\r\n logger.debug(`${this} Queued responderRenegotiate task`);\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} failed to renegotiate a decoder for muted endpoint ${error}`);\r\n } else {\r\n logger.debug(`${this} renegotiate a decoder for muted endpoint`);\r\n }\r\n });\r\n }\r\n },\r\n error => {\r\n logger.error(`${this} setAnswer failed: `, error);\r\n });\r\n }\r\n\r\n /* eslint-disable max-params */\r\n /**\r\n * This is a setRemoteDescription/setLocalDescription cycle which starts at\r\n * converting Strophe Jingle IQ into remote offer SDP. Once converted\r\n * setRemoteDescription, createAnswer and setLocalDescription calls follow.\r\n * @param jingleOfferAnswerIq jQuery selector pointing to the jingle element\r\n * of the offer (or answer) IQ\r\n * @param success callback called when sRD/sLD cycle finishes successfully.\r\n * @param failure callback called with an error object as an argument if we\r\n * fail at any point during setRD, createAnswer, setLD.\r\n * @param {Array} [localTracks] the optional list of\r\n * the local tracks that will be added, before the offer/answer cycle\r\n * executes (for the local track addition to be an atomic operation together\r\n * with the offer/answer).\r\n */\r\n setOfferAnswerCycle(jingleOfferAnswerIq, success, failure, localTracks = []) {\r\n const workFunction = finishedCallback => {\r\n const addTracks = [];\r\n const audioTracks = localTracks.filter(track => track.getType() === MediaType.AUDIO);\r\n const videoTracks = localTracks.filter(track => track.getType() === MediaType.VIDEO);\r\n let tracks = localTracks;\r\n\r\n // Add only 1 video track at a time. Adding 2 or more video tracks to the peerconnection at the same time\r\n // makes the browser go into a renegotiation loop by firing 'negotiationneeded' event after every\r\n // renegotiation.\r\n if (FeatureFlags.isMultiStreamSupportEnabled() && videoTracks.length > 1) {\r\n tracks = [ ...audioTracks, videoTracks[0] ];\r\n }\r\n for (const track of tracks) {\r\n addTracks.push(this.peerconnection.addTrack(track, this.isInitiator));\r\n }\r\n const newRemoteSdp = this._processNewJingleOfferIq(jingleOfferAnswerIq);\r\n const oldLocalSdp = this.peerconnection.localDescription.sdp;\r\n\r\n const bridgeSession\r\n = $(jingleOfferAnswerIq)\r\n .find('>bridge-session['\r\n + 'xmlns=\"http://jitsi.org/protocol/focus\"]');\r\n const bridgeSessionId = bridgeSession.attr('id');\r\n\r\n if (bridgeSessionId !== this._bridgeSessionId) {\r\n this._bridgeSessionId = bridgeSessionId;\r\n }\r\n\r\n Promise.all(addTracks)\r\n .then(() => this._renegotiate(newRemoteSdp.raw))\r\n .then(() => {\r\n if (this.state === JingleSessionState.PENDING) {\r\n this.state = JingleSessionState.ACTIVE;\r\n\r\n // #1 Sync up video transfer active/inactive only after the initial O/A cycle. We want to\r\n // adjust the video media direction only in the local SDP and the Jingle contents direction\r\n // included in the initial offer/answer is mapped to the remote SDP. Jingle 'content-modify'\r\n // IQ is processed in a way that it will only modify local SDP when remote peer is no longer\r\n // interested in receiving video content. Changing media direction in the remote SDP will mess\r\n // up our SDP translation chain (simulcast, video mute, RTX etc.)\r\n // #2 Sends the max frame height if it was set, before the session-initiate/accept\r\n if (this.isP2P\r\n && (!this._localVideoActive\r\n || this.localRecvMaxFrameHeight\r\n || this._sourceReceiverConstraints)) {\r\n this.sendContentModify();\r\n }\r\n }\r\n\r\n // Old local SDP will be available when we're setting answer for the first time, but not when offer\r\n // and it's fine since we're generating an answer now it will contain all our SSRCs.\r\n if (oldLocalSdp) {\r\n const newLocalSdp = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this.notifyMySSRCUpdate(new SDP(oldLocalSdp), newLocalSdp);\r\n }\r\n })\r\n .then(() => finishedCallback(), error => finishedCallback(error));\r\n };\r\n\r\n logger.debug(`${this} Queued setOfferAnswerCycle task`);\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} setOfferAnswerCycle task failed: ${error}`);\r\n failure(error);\r\n } else {\r\n logger.debug(`${this} setOfferAnswerCycle task done`);\r\n success();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Updates the codecs on the peerconnection and initiates a renegotiation for the\r\n * new codec config to take effect.\r\n *\r\n * @param {CodecMimeType} preferred the preferred codec.\r\n * @param {CodecMimeType} disabled the codec that needs to be disabled.\r\n */\r\n setVideoCodecs(preferred = null, disabled = null) {\r\n const current = this.peerconnection.getConfiguredVideoCodec();\r\n\r\n if (this._assertNotEnded() && preferred !== current) {\r\n logger.info(`${this} Switching video codec from ${current} to ${preferred}`);\r\n this.peerconnection.setVideoCodecs(preferred, disabled);\r\n\r\n // Initiate a renegotiate for the codec setting to take effect.\r\n const workFunction = finishedCallback => {\r\n this._renegotiate().then(\r\n () => {\r\n logger.debug(`${this} setVideoCodecs task is done`);\r\n\r\n return finishedCallback();\r\n }, error => {\r\n logger.error(`${this} setVideoCodecs task failed: ${error}`);\r\n\r\n return finishedCallback(error);\r\n });\r\n };\r\n\r\n logger.debug(`${this} Queued setVideoCodecs task`);\r\n\r\n // Queue and execute\r\n this.modificationQueue.push(workFunction);\r\n }\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Although it states \"replace transport\" it does accept full Jingle offer\r\n * which should contain new ICE transport details.\r\n * @param jingleOfferElem an element Jingle IQ that contains new offer and\r\n * transport info.\r\n * @param success callback called when we succeed to accept new offer.\r\n * @param failure function(error) called when we fail to accept new offer.\r\n */\r\n replaceTransport(jingleOfferElem, success, failure) {\r\n if (this.options.enableForcedReload) {\r\n const sdp = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this.sendTransportAccept(sdp, success, failure);\r\n this.room.eventEmitter.emit(XMPPEvents.CONNECTION_RESTARTED, this);\r\n\r\n return;\r\n }\r\n this.room.eventEmitter.emit(XMPPEvents.ICE_RESTARTING, this);\r\n\r\n // We need to first reject the 'data' section to have the SCTP stack\r\n // cleaned up to signal the known data channel is now invalid. After\r\n // that the original offer is set to have the SCTP connection\r\n // established with the new bridge.\r\n const originalOffer = jingleOfferElem.clone();\r\n\r\n jingleOfferElem\r\n .find('>content[name=\\'data\\']')\r\n .attr('senders', 'rejected');\r\n\r\n // Remove all remote sources in order to reset the client's state\r\n // for the remote MediaStreams. When a conference is moved to\r\n // another bridge it will start streaming with a sequence number\r\n // that is not in sync with the most recently seen by the client.\r\n // The symptoms include frozen or black video and lots of \"failed to\r\n // unprotect SRTP packets\" in Chrome logs.\r\n jingleOfferElem\r\n .find('>content>description>source')\r\n .remove();\r\n jingleOfferElem\r\n .find('>content>description>ssrc-group')\r\n .remove();\r\n\r\n // On the JVB it's not a real ICE restart and all layers are re-initialized from scratch as Jicofo does\r\n // the restart by re-allocating new channels. Chrome (or WebRTC stack) needs to have the DTLS transport layer\r\n // reset to start a new handshake with fresh DTLS transport on the bridge. Make it think that the DTLS\r\n // fingerprint has changed by setting an all zeros key.\r\n const newFingerprint = jingleOfferElem.find('>content>transport>fingerprint');\r\n\r\n newFingerprint.attr('hash', 'sha-1');\r\n newFingerprint.text('00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00');\r\n\r\n // First set an offer with a rejected 'data' section\r\n this.setOfferAnswerCycle(\r\n jingleOfferElem,\r\n () => {\r\n // Now set the original offer(with the 'data' section)\r\n this.setOfferAnswerCycle(\r\n originalOffer,\r\n () => {\r\n const localSDP\r\n = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this.sendTransportAccept(localSDP, success, failure);\r\n\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.ICE_RESTART_SUCCESS,\r\n this,\r\n originalOffer);\r\n },\r\n failure);\r\n },\r\n failure\r\n );\r\n }\r\n\r\n /**\r\n * Sends Jingle 'session-accept' message.\r\n * @param {function()} success callback called when we receive 'RESULT'\r\n * packet for the 'session-accept'\r\n * @param {function(error)} failure called when we receive an error response\r\n * or when the request has timed out.\r\n * @private\r\n */\r\n sendSessionAccept(success, failure) {\r\n // NOTE: since we're just reading from it, we don't need to be within\r\n // the modification queue to access the local description\r\n const localSDP = new SDP(this.peerconnection.localDescription.sdp);\r\n const accept = $iq({ to: this.remoteJid,\r\n type: 'set' })\r\n .c('jingle', { xmlns: 'urn:xmpp:jingle:1',\r\n action: 'session-accept',\r\n initiator: this.initiatorJid,\r\n responder: this.responderJid,\r\n sid: this.sid });\r\n\r\n if (this.webrtcIceTcpDisable) {\r\n localSDP.removeTcpCandidates = true;\r\n }\r\n if (this.webrtcIceUdpDisable) {\r\n localSDP.removeUdpCandidates = true;\r\n }\r\n if (this.failICE) {\r\n localSDP.failICE = true;\r\n }\r\n localSDP.toJingle(\r\n accept,\r\n this.initiatorJid === this.localJid ? 'initiator' : 'responder');\r\n\r\n logger.info(`${this} Sending session-accept`);\r\n logger.debug(accept.tree());\r\n this.connection.sendIQ(accept,\r\n success,\r\n this.newJingleErrorHandler(accept, error => {\r\n failure(error);\r\n\r\n // 'session-accept' is a critical timeout and we'll\r\n // have to restart\r\n this.room.eventEmitter.emit(\r\n XMPPEvents.SESSION_ACCEPT_TIMEOUT, this);\r\n }),\r\n IQ_TIMEOUT);\r\n\r\n // XXX Videobridge needs WebRTC's answer (ICE ufrag and pwd, DTLS\r\n // fingerprint and setup) ASAP in order to start the connection\r\n // establishment.\r\n //\r\n // FIXME Flushing the connection at this point triggers an issue with\r\n // BOSH request handling in Prosody on slow connections.\r\n //\r\n // The problem is that this request will be quite large and it may take\r\n // time before it reaches Prosody. In the meantime Strophe may decide\r\n // to send the next one. And it was observed that a small request with\r\n // 'transport-info' usually follows this one. It does reach Prosody\r\n // before the previous one was completely received. 'rid' on the server\r\n // is increased and Prosody ignores the request with 'session-accept'.\r\n // It will never reach Jicofo and everything in the request table is\r\n // lost. Removing the flush does not guarantee it will never happen, but\r\n // makes it much less likely('transport-info' is bundled with\r\n // 'session-accept' and any immediate requests).\r\n //\r\n // this.connection.flush();\r\n }\r\n\r\n /**\r\n * Will send 'content-modify' IQ in order to ask the remote peer to\r\n * either stop or resume sending video media or to adjust sender's video constraints.\r\n * @private\r\n */\r\n sendContentModify() {\r\n const maxFrameHeight = this.localRecvMaxFrameHeight;\r\n const senders = this._localVideoActive ? 'both' : 'none';\r\n\r\n let sessionModify\r\n = $iq({\r\n to: this.remoteJid,\r\n type: 'set'\r\n })\r\n .c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: 'content-modify',\r\n initiator: this.initiatorJid,\r\n sid: this.sid\r\n })\r\n .c('content', {\r\n name: MediaType.VIDEO,\r\n senders\r\n });\r\n\r\n if (typeof maxFrameHeight !== 'undefined') {\r\n sessionModify = sessionModify\r\n .c('max-frame-height', { xmlns: 'http://jitsi.org/jitmeet/video' })\r\n .t(maxFrameHeight);\r\n logger.info(`${this} sending content-modify, video senders: ${senders},`\r\n + ` max frame height: ${maxFrameHeight}`);\r\n }\r\n\r\n if (typeof this._sourceReceiverConstraints !== 'undefined') {\r\n this._sourceReceiverConstraints.forEach((maxHeight, sourceName) => {\r\n sessionModify\r\n .c('source-frame-height', { xmlns: 'http://jitsi.org/jitmeet/video' })\r\n .attrs({\r\n sourceName,\r\n maxHeight\r\n });\r\n\r\n sessionModify.up();\r\n logger.info(`${this} sending content-modify for source-name: ${sourceName}, maxHeight: ${maxHeight}`);\r\n });\r\n }\r\n\r\n logger.debug(sessionModify.tree());\r\n\r\n this.connection.sendIQ(\r\n sessionModify,\r\n null,\r\n this.newJingleErrorHandler(sessionModify),\r\n IQ_TIMEOUT);\r\n }\r\n\r\n /**\r\n * Adjust the preference for max video frame height that the local party is willing to receive. Signals\r\n * the remote party.\r\n *\r\n * @param {Number} maxFrameHeight - the new value to set.\r\n * @param {Map} sourceReceiverConstraints - The receiver constraints per source.\r\n */\r\n setReceiverVideoConstraint(maxFrameHeight, sourceReceiverConstraints) {\r\n logger.info(`${this} setReceiverVideoConstraint - max frame height: ${maxFrameHeight}`\r\n + ` sourceReceiverConstraints: ${sourceReceiverConstraints}`);\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this._sourceReceiverConstraints = sourceReceiverConstraints;\r\n } else {\r\n this.localRecvMaxFrameHeight = maxFrameHeight;\r\n }\r\n\r\n if (this.isP2P) {\r\n // Tell the remote peer about our receive constraint. If Jingle session is not yet active the state will\r\n // be synced after offer/answer.\r\n if (this.state === JingleSessionState.ACTIVE) {\r\n this.sendContentModify();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Sends Jingle 'transport-accept' message which is a response to\r\n * 'transport-replace'.\r\n * @param localSDP the 'SDP' object with local session description\r\n * @param success callback called when we receive 'RESULT' packet for\r\n * 'transport-replace'\r\n * @param failure function(error) called when we receive an error response\r\n * or when the request has timed out.\r\n * @private\r\n */\r\n sendTransportAccept(localSDP, success, failure) {\r\n const transportAccept = $iq({ to: this.remoteJid,\r\n type: 'set' })\r\n .c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: 'transport-accept',\r\n initiator: this.initiatorJid,\r\n sid: this.sid\r\n });\r\n\r\n localSDP.media.forEach((medialines, idx) => {\r\n const mline = SDPUtil.parseMLine(medialines.split('\\r\\n')[0]);\r\n\r\n transportAccept.c('content',\r\n {\r\n creator:\r\n this.initiatorJid === this.localJid\r\n ? 'initiator'\r\n : 'responder',\r\n name: mline.media\r\n }\r\n );\r\n localSDP.transportToJingle(idx, transportAccept);\r\n transportAccept.up();\r\n });\r\n\r\n logger.info(`${this} Sending transport-accept`);\r\n logger.debug(transportAccept.tree());\r\n\r\n this.connection.sendIQ(transportAccept,\r\n success,\r\n this.newJingleErrorHandler(transportAccept, failure),\r\n IQ_TIMEOUT);\r\n }\r\n\r\n /**\r\n * Sends Jingle 'transport-reject' message which is a response to\r\n * 'transport-replace'.\r\n * @param success callback called when we receive 'RESULT' packet for\r\n * 'transport-replace'\r\n * @param failure function(error) called when we receive an error response\r\n * or when the request has timed out.\r\n *\r\n * FIXME method should be marked as private, but there's some spaghetti that\r\n * needs to be fixed prior doing that\r\n */\r\n sendTransportReject(success, failure) {\r\n // Send 'transport-reject', so that the focus will\r\n // know that we've failed\r\n const transportReject = $iq({ to: this.remoteJid,\r\n type: 'set' })\r\n .c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: 'transport-reject',\r\n initiator: this.initiatorJid,\r\n sid: this.sid\r\n });\r\n\r\n logger.info(`${this} Sending 'transport-reject'`);\r\n logger.debug(transportReject.tree());\r\n\r\n this.connection.sendIQ(transportReject,\r\n success,\r\n this.newJingleErrorHandler(transportReject, failure),\r\n IQ_TIMEOUT);\r\n }\r\n\r\n /**\r\n * Sets the resolution constraint on the local camera track.\r\n * @param {number} maxFrameHeight - The user preferred max frame height.\r\n * @param {string} sourceName - The source name of the track.\r\n * @returns {Promise} promise that will be resolved when the operation is\r\n * successful and rejected otherwise.\r\n */\r\n setSenderVideoConstraint(maxFrameHeight, sourceName = null) {\r\n if (this._assertNotEnded()) {\r\n logger.info(`${this} setSenderVideoConstraint: ${maxFrameHeight}, sourceName: ${sourceName}`);\r\n\r\n const jitsiLocalTrack = sourceName\r\n ? this.rtc.getLocalVideoTracks().find(track => track.getSourceName() === sourceName)\r\n : this.rtc.getLocalVideoTrack();\r\n\r\n return this.peerconnection.setSenderVideoConstraints(maxFrameHeight, jitsiLocalTrack);\r\n }\r\n\r\n return Promise.resolve();\r\n }\r\n\r\n /**\r\n * @inheritDoc\r\n */\r\n terminate(success, failure, options) {\r\n if (this.state === JingleSessionState.ENDED) {\r\n return;\r\n }\r\n\r\n if (!options || Boolean(options.sendSessionTerminate)) {\r\n const sessionTerminate\r\n = $iq({\r\n to: this.remoteJid,\r\n type: 'set'\r\n })\r\n .c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: 'session-terminate',\r\n initiator: this.initiatorJid,\r\n sid: this.sid\r\n })\r\n .c('reason')\r\n .c((options && options.reason) || 'success')\r\n .up();\r\n\r\n if (options && options.reasonDescription) {\r\n sessionTerminate\r\n .c('text')\r\n .t(options.reasonDescription)\r\n .up()\r\n .up();\r\n } else {\r\n sessionTerminate.up();\r\n }\r\n\r\n this._bridgeSessionId\r\n && sessionTerminate.c(\r\n 'bridge-session', {\r\n xmlns: 'http://jitsi.org/protocol/focus',\r\n id: this._bridgeSessionId,\r\n restart: options && options.requestRestart === true\r\n }).up();\r\n\r\n logger.info(`${this} Sending session-terminate`);\r\n logger.debug(sessionTerminate.tree());\r\n\r\n this.connection.sendIQ(\r\n sessionTerminate,\r\n success,\r\n this.newJingleErrorHandler(sessionTerminate, failure),\r\n IQ_TIMEOUT);\r\n } else {\r\n logger.info(`${this} Skipped sending session-terminate`);\r\n }\r\n\r\n // this should result in 'onTerminated' being called by strope.jingle.js\r\n this.connection.jingle.terminate(this.sid);\r\n }\r\n\r\n /**\r\n *\r\n * @param reasonCondition\r\n * @param reasonText\r\n */\r\n onTerminated(reasonCondition, reasonText) {\r\n // Do something with reason and reasonCondition when we start to care\r\n // this.reasonCondition = reasonCondition;\r\n // this.reasonText = reasonText;\r\n logger.info(`${this} Session terminated`, reasonCondition, reasonText);\r\n\r\n this._xmppListeners.forEach(removeListener => removeListener());\r\n this._xmppListeners = [];\r\n\r\n if (this._removeSenderVideoConstraintsChangeListener) {\r\n this._removeSenderVideoConstraintsChangeListener();\r\n }\r\n\r\n this.close();\r\n }\r\n\r\n /**\r\n * Handles XMPP connection state changes.\r\n *\r\n * @param {XmppConnection.Status} status - The new status.\r\n */\r\n onXmppStatusChanged(status) {\r\n if (status === XmppConnection.Status.CONNECTED && this._cachedOldLocalSdp) {\r\n logger.info(`${this} Sending SSRC update on reconnect`);\r\n this.notifyMySSRCUpdate(\r\n this._cachedOldLocalSdp,\r\n this._cachedNewLocalSdp);\r\n }\r\n }\r\n\r\n /**\r\n * Parse the information from the xml sourceAddElem and translate it\r\n * into sdp lines\r\n * @param {jquery xml element} sourceAddElem the source-add\r\n * element from jingle\r\n * @param {SDP object} currentRemoteSdp the current remote\r\n * sdp (as of this new source-add)\r\n * @returns {list} a list of SDP line strings that should\r\n * be added to the remote SDP\r\n */\r\n _parseSsrcInfoFromSourceAdd(sourceAddElem, currentRemoteSdp) {\r\n const addSsrcInfo = [];\r\n const self = this;\r\n\r\n $(sourceAddElem).each((i1, content) => {\r\n const name = $(content).attr('name');\r\n let lines = '';\r\n\r\n $(content)\r\n .find('ssrc-group[xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\"]')\r\n .each(function() {\r\n // eslint-disable-next-line no-invalid-this\r\n const semantics = this.getAttribute('semantics');\r\n const ssrcs\r\n = $(this) // eslint-disable-line no-invalid-this\r\n .find('>source')\r\n .map(function() {\r\n // eslint-disable-next-line no-invalid-this\r\n return this.getAttribute('ssrc');\r\n })\r\n .get();\r\n\r\n if (ssrcs.length) {\r\n lines += `a=ssrc-group:${semantics} ${ssrcs.join(' ')}\\r\\n`;\r\n }\r\n });\r\n\r\n // handles both >source and >description>source\r\n const tmp\r\n = $(content).find(\r\n 'source[xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\"]');\r\n\r\n /* eslint-disable no-invalid-this */\r\n tmp.each(function() {\r\n const ssrc = $(this).attr('ssrc');\r\n\r\n if (currentRemoteSdp.containsSSRC(ssrc)) {\r\n\r\n // Do not print the warning for unified plan p2p case since ssrcs are never removed from the SDP.\r\n !(self.usesUnifiedPlan && self.isP2P)\r\n && logger.warn(`${self} Source-add request for existing SSRC: ${ssrc}`);\r\n\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line newline-per-chained-call\r\n $(this).find('>parameter').each(function() {\r\n lines += `a=ssrc:${ssrc} ${$(this).attr('name')}`;\r\n if ($(this).attr('value') && $(this).attr('value').length) {\r\n lines += `:${$(this).attr('value')}`;\r\n }\r\n lines += '\\r\\n';\r\n });\r\n });\r\n\r\n let midFound = false;\r\n\r\n /* eslint-enable no-invalid-this */\r\n currentRemoteSdp.media.forEach((media, i2) => {\r\n if (!SDPUtil.findLine(media, `a=mid:${name}`)) {\r\n return;\r\n }\r\n if (!addSsrcInfo[i2]) {\r\n addSsrcInfo[i2] = '';\r\n }\r\n addSsrcInfo[i2] += lines;\r\n midFound = true;\r\n });\r\n\r\n // In p2p unified mode with multi-stream enabled, the new sources will have content name that doesn't exist\r\n // in the current remote description. Add a new m-line for this newly signaled source.\r\n if (!midFound && this.isP2P && FeatureFlags.isSourceNameSignalingEnabled()) {\r\n addSsrcInfo[name] = lines;\r\n }\r\n });\r\n\r\n return addSsrcInfo;\r\n }\r\n\r\n /**\r\n * Handles a Jingle source-add message for this Jingle session.\r\n * @param elem An array of Jingle \"content\" elements.\r\n */\r\n addRemoteStream(elem) {\r\n this._addOrRemoveRemoteStream(true /* add */, elem);\r\n }\r\n\r\n /**\r\n * Handles a Jingle source-remove message for this Jingle session.\r\n * @param elem An array of Jingle \"content\" elements.\r\n */\r\n removeRemoteStream(elem) {\r\n this._addOrRemoveRemoteStream(false /* remove */, elem);\r\n }\r\n\r\n /**\r\n * Handles the deletion of SSRCs associated with a remote user from the remote description when the user leaves.\r\n *\r\n * @param {string} id Endpoint id of the participant that has left the call.\r\n * @returns {void}\r\n */\r\n removeRemoteStreamsOnLeave(id) {\r\n const workFunction = finishCallback => {\r\n const removeSsrcInfo = this.peerconnection.getRemoteSourceInfoByParticipant(id);\r\n\r\n if (removeSsrcInfo.length) {\r\n const oldLocalSdp = new SDP(this.peerconnection.localDescription.sdp);\r\n const newRemoteSdp = this._processRemoteRemoveSource(removeSsrcInfo);\r\n\r\n this._renegotiate(newRemoteSdp.raw)\r\n .then(() => {\r\n const newLocalSDP = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this.notifyMySSRCUpdate(oldLocalSdp, newLocalSDP);\r\n finishCallback();\r\n })\r\n .catch(err => finishCallback(err));\r\n } else {\r\n finishCallback();\r\n }\r\n };\r\n\r\n logger.debug(`${this} Queued removeRemoteStreamsOnLeave task for participant ${id}`);\r\n\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} removeRemoteStreamsOnLeave error:`, error);\r\n } else {\r\n logger.info(`${this} removeRemoteStreamsOnLeave done!`);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Handles either Jingle 'source-add' or 'source-remove' message for this\r\n * Jingle session.\r\n * @param {boolean} isAdd true for 'source-add' or false\r\n * otherwise.\r\n * @param {Array} elem an array of Jingle \"content\" elements.\r\n * @private\r\n */\r\n _addOrRemoveRemoteStream(isAdd, elem) {\r\n const logPrefix = isAdd ? 'addRemoteStream' : 'removeRemoteStream';\r\n\r\n if (isAdd) {\r\n this.readSsrcInfo(elem);\r\n }\r\n\r\n const workFunction = finishedCallback => {\r\n if (!this.peerconnection.localDescription\r\n || !this.peerconnection.localDescription.sdp) {\r\n const errMsg = `${logPrefix} - localDescription not ready yet`;\r\n\r\n logger.error(errMsg);\r\n finishedCallback(errMsg);\r\n\r\n return;\r\n }\r\n\r\n logger.log(`${this} Processing ${logPrefix}`);\r\n\r\n const oldLocalSdp = new SDP(this.peerconnection.localDescription.sdp);\r\n const sdp = new SDP(this.peerconnection.remoteDescription.sdp);\r\n const addOrRemoveSsrcInfo\r\n = isAdd\r\n ? this._parseSsrcInfoFromSourceAdd(elem, sdp)\r\n : this._parseSsrcInfoFromSourceRemove(elem, sdp);\r\n const newRemoteSdp\r\n = isAdd\r\n ? this._processRemoteAddSource(addOrRemoveSsrcInfo)\r\n : this._processRemoteRemoveSource(addOrRemoveSsrcInfo);\r\n\r\n // Add a workaround for a bug in Chrome (unified plan) for p2p connection. When the media direction on\r\n // the transceiver goes from \"inactive\" (both users join muted) to \"recvonly\" (peer unmutes), the browser\r\n // doesn't seem to create a decoder if the signaling state changes from \"have-local-offer\" to \"stable\".\r\n // Therefore, initiate a responder renegotiate even if the endpoint is the offerer to workaround this issue.\r\n // TODO - open a chrome bug and update the comments.\r\n const remoteDescription = new RTCSessionDescription({\r\n type: 'offer',\r\n sdp: newRemoteSdp.raw\r\n });\r\n const promise = isAdd && this.usesUnifiedPlan && this.isP2P && browser.isChromiumBased()\r\n ? this._responderRenegotiate(remoteDescription)\r\n : this._renegotiate(newRemoteSdp.raw);\r\n\r\n promise.then(() => {\r\n const newLocalSdp = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n logger.log(`${this} ${logPrefix} - OK`);\r\n this.notifyMySSRCUpdate(oldLocalSdp, newLocalSdp);\r\n finishedCallback();\r\n }, error => {\r\n logger.error(`${this} ${logPrefix} failed:`, error);\r\n finishedCallback(error);\r\n });\r\n };\r\n\r\n logger.debug(`${this} Queued ${logPrefix} task`);\r\n\r\n // Queue and execute\r\n this.modificationQueue.push(workFunction);\r\n }\r\n\r\n /**\r\n * Takes in a jingle offer iq, returns the new sdp offer\r\n * @param {jquery xml element} offerIq the incoming offer\r\n * @returns {SDP object} the jingle offer translated to SDP\r\n */\r\n _processNewJingleOfferIq(offerIq) {\r\n const remoteSdp = new SDP('');\r\n\r\n if (this.webrtcIceTcpDisable) {\r\n remoteSdp.removeTcpCandidates = true;\r\n }\r\n if (this.webrtcIceUdpDisable) {\r\n remoteSdp.removeUdpCandidates = true;\r\n }\r\n if (this.failICE) {\r\n remoteSdp.failICE = true;\r\n }\r\n\r\n remoteSdp.fromJingle(offerIq);\r\n this.readSsrcInfo($(offerIq).find('>content'));\r\n\r\n return remoteSdp;\r\n }\r\n\r\n /**\r\n * Remove the given ssrc lines from the current remote sdp\r\n * @param {list} removeSsrcInfo a list of SDP line strings that\r\n * should be removed from the remote SDP\r\n * @returns type {SDP Object} the new remote SDP (after removing the lines\r\n * in removeSsrcInfo\r\n */\r\n _processRemoteRemoveSource(removeSsrcInfo) {\r\n const remoteSdp = this.usesUnifiedPlan\r\n ? new SDP(this.peerconnection.peerconnection.remoteDescription.sdp)\r\n : new SDP(this.peerconnection.remoteDescription.sdp);\r\n\r\n removeSsrcInfo.forEach((lines, idx) => {\r\n // eslint-disable-next-line no-param-reassign\r\n lines = lines.split('\\r\\n');\r\n lines.pop(); // remove empty last element;\r\n if (this.usesUnifiedPlan) {\r\n let mid;\r\n\r\n lines.forEach(line => {\r\n mid = remoteSdp.media.findIndex(mLine => mLine.includes(line));\r\n\r\n if (mid > -1) {\r\n remoteSdp.media[mid] = remoteSdp.media[mid].replace(`${line}\\r\\n`, '');\r\n if (this.isP2P) {\r\n const mediaType = SDPUtil.parseMLine(remoteSdp.media[mid].split('\\r\\n')[0])?.media;\r\n const desiredDirection = this.peerconnection.getDesiredMediaDirection(mediaType, false);\r\n\r\n [ MediaDirection.SENDRECV, MediaDirection.SENDONLY ].forEach(direction => {\r\n remoteSdp.media[mid] = remoteSdp.media[mid]\r\n .replace(`a=${direction}`, `a=${desiredDirection}`);\r\n });\r\n } else {\r\n // Jvb connections will have direction set to 'sendonly' for the remote sources.\r\n remoteSdp.media[mid] = remoteSdp.media[mid]\r\n .replace(`a=${MediaDirection.SENDONLY}`, `a=${MediaDirection.INACTIVE}`);\r\n }\r\n }\r\n });\r\n\r\n // Reject the m-line so that the browser removes the associated transceiver from the list of available\r\n // transceivers. This will prevent the client from trying to re-use these inactive transceivers when\r\n // additional video sources are added to the peerconnection.\r\n if (mid > -1 && !this.isP2P && FeatureFlags.isMultiStreamSupportEnabled()) {\r\n const { media, port } = SDPUtil.parseMLine(remoteSdp.media[mid].split('\\r\\n')[0]);\r\n\r\n remoteSdp.media[mid] = remoteSdp.media[mid].replace(`m=${media} ${port}`, `m=${media} 0`);\r\n }\r\n } else {\r\n lines.forEach(line => {\r\n remoteSdp.media[idx] = remoteSdp.media[idx].replace(`${line}\\r\\n`, '');\r\n });\r\n }\r\n });\r\n remoteSdp.raw = remoteSdp.session + remoteSdp.media.join('');\r\n\r\n return remoteSdp;\r\n }\r\n\r\n /**\r\n * Add the given ssrc lines to the current remote sdp\r\n * @param {list} addSsrcInfo a list of SDP line strings that\r\n * should be added to the remote SDP\r\n * @returns type {SDP Object} the new remote SDP (after removing the lines\r\n * in removeSsrcInfo\r\n */\r\n _processRemoteAddSource(addSsrcInfo) {\r\n let remoteSdp = new SDP(this.peerconnection.remoteDescription.sdp);\r\n\r\n // Add a new m-line in the remote description if the source info for a secondary video source is recceived from\r\n // the remote p2p peer when multi-stream support is enabled.\r\n if (addSsrcInfo.length > remoteSdp.media.length\r\n && FeatureFlags.isSourceNameSignalingEnabled()\r\n && this.isP2P\r\n && this.usesUnifiedPlan) {\r\n remoteSdp.addMlineForNewLocalSource(MediaType.VIDEO);\r\n remoteSdp = new SDP(remoteSdp.raw);\r\n }\r\n addSsrcInfo.forEach((lines, idx) => {\r\n remoteSdp.media[idx] += lines;\r\n\r\n // Make sure to change the direction to 'sendrecv/sendonly' only for p2p connections. For jvb connections,\r\n // a new m-line is added for the new remote sources.\r\n if (this.isP2P && this.usesUnifiedPlan) {\r\n const mediaType = SDPUtil.parseMLine(remoteSdp.media[idx].split('\\r\\n')[0])?.media;\r\n const desiredDirection = this.peerconnection.getDesiredMediaDirection(mediaType, true);\r\n\r\n [ MediaDirection.RECVONLY, MediaDirection.INACTIVE ].forEach(direction => {\r\n remoteSdp.media[idx] = remoteSdp.media[idx]\r\n .replace(`a=${direction}`, `a=${desiredDirection}`);\r\n });\r\n }\r\n });\r\n remoteSdp.raw = remoteSdp.session + remoteSdp.media.join('');\r\n\r\n return remoteSdp;\r\n }\r\n\r\n /**\r\n * Do a new o/a flow using the existing remote description\r\n * @param {string} [optionalRemoteSdp] optional, raw remote sdp\r\n * to use. If not provided, the remote sdp from the\r\n * peerconnection will be used\r\n * @returns {Promise} promise which resolves when the\r\n * o/a flow is complete with no arguments or\r\n * rejects with an error {string}\r\n */\r\n _renegotiate(optionalRemoteSdp) {\r\n if (this.peerconnection.signalingState === 'closed') {\r\n const error = new Error('Attempted to renegotiate in state closed');\r\n\r\n this.room.eventEmitter.emit(XMPPEvents.RENEGOTIATION_FAILED, error, this);\r\n\r\n return Promise.reject(error);\r\n }\r\n\r\n const remoteSdp\r\n = optionalRemoteSdp || this.peerconnection.remoteDescription.sdp;\r\n\r\n if (!remoteSdp) {\r\n const error = new Error(`Can not renegotiate without remote description, current state: ${this.state}`);\r\n\r\n this.room.eventEmitter.emit(XMPPEvents.RENEGOTIATION_FAILED, error, this);\r\n\r\n return Promise.reject(error);\r\n }\r\n\r\n const remoteDescription = new RTCSessionDescription({\r\n type: this.isInitiator ? 'answer' : 'offer',\r\n sdp: remoteSdp\r\n });\r\n\r\n if (this.isInitiator) {\r\n return this._initiatorRenegotiate(remoteDescription);\r\n }\r\n\r\n return this._responderRenegotiate(remoteDescription);\r\n }\r\n\r\n /**\r\n * Renegotiate cycle implementation for the responder case.\r\n * @param {object} remoteDescription the SDP object as defined by the WebRTC\r\n * which will be used as remote description in the cycle.\r\n * @private\r\n */\r\n _responderRenegotiate(remoteDescription) {\r\n logger.debug(`${this} Renegotiate: setting remote description`);\r\n\r\n return this.peerconnection.setRemoteDescription(remoteDescription)\r\n .then(() => {\r\n logger.debug(`${this} Renegotiate: creating answer`);\r\n\r\n return this.peerconnection.createAnswer(this.mediaConstraints)\r\n .then(answer => {\r\n logger.debug(`${this} Renegotiate: setting local description`);\r\n\r\n return this.peerconnection.setLocalDescription(answer);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Renegotiate cycle implementation for the initiator's case.\r\n * @param {object} remoteDescription the SDP object as defined by the WebRTC\r\n * which will be used as remote description in the cycle.\r\n * @private\r\n */\r\n _initiatorRenegotiate(remoteDescription) {\r\n logger.debug(`${this} Renegotiate: creating offer`);\r\n\r\n return this.peerconnection.createOffer(this.mediaConstraints)\r\n .then(offer => {\r\n logger.debug(`${this} Renegotiate: setting local description`);\r\n\r\n return this.peerconnection.setLocalDescription(offer)\r\n .then(() => {\r\n logger.debug(`${this} Renegotiate: setting remote description`);\r\n\r\n // eslint-disable-next-line max-len\r\n return this.peerconnection.setRemoteDescription(remoteDescription);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Adds a new track to the peerconnection. This method needs to be called only when a secondary JitsiLocalTrack is\r\n * being added to the peerconnection for the first time.\r\n *\r\n * @param {Array} localTracks - Tracks to be added to the peer connection.\r\n * @returns {Promise} that resolves when the track is successfully added to the peerconnection, rejected\r\n * otherwise.\r\n */\r\n addTracks(localTracks = null) {\r\n if (!FeatureFlags.isMultiStreamSupportEnabled()\r\n || !localTracks?.length\r\n || localTracks.find(track => track.getType() !== MediaType.VIDEO)) {\r\n return Promise.reject(new Error('Multiple tracks of the given media type are not supported'));\r\n }\r\n\r\n const replaceTracks = [];\r\n const workFunction = finishedCallback => {\r\n const oldLocalSDP = new SDP(this.peerconnection.localDescription.sdp);\r\n const remoteSdp = new SDP(this.peerconnection.peerconnection.remoteDescription.sdp);\r\n\r\n // Add transceivers by adding a new mline in the remote description for each track.\r\n for (const track of localTracks) {\r\n remoteSdp.addMlineForNewLocalSource(track.getType());\r\n }\r\n\r\n const remoteDescription = new RTCSessionDescription({\r\n type: 'offer',\r\n sdp: remoteSdp.raw\r\n });\r\n\r\n // Always initiate a responder renegotiate since the new m-line is added to remote SDP.\r\n this._responderRenegotiate(remoteDescription)\r\n .then(() => {\r\n // Replace the tracks on the newly generated transceivers.\r\n for (const track of localTracks) {\r\n replaceTracks.push(this.peerconnection.replaceTrack(null, track));\r\n }\r\n\r\n return Promise.all(replaceTracks);\r\n })\r\n\r\n // Trigger a renegotiation here since renegotiations are suppressed at TPC.replaceTrack for screenshare\r\n // tracks. This is done here so that presence for screenshare tracks is sent before signaling.\r\n .then(() => this._renegotiate())\r\n .then(() => {\r\n const newLocalSDP = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n // Signal the new sources to the peer.\r\n this.notifyMySSRCUpdate(oldLocalSDP, newLocalSDP);\r\n finishedCallback();\r\n })\r\n .catch(error => finishedCallback(error));\r\n };\r\n\r\n return new Promise((resolve, reject) => {\r\n logger.debug(`${this} Queued renegotiation after addTrack`);\r\n\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} renegotiation after addTrack error`, error);\r\n reject(error);\r\n } else {\r\n logger.debug(`${this} renegotiation after addTrack executed - OK`);\r\n resolve();\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Replaces oldTrack with newTrack and performs a single\r\n * offer/answer cycle after both operations are done. Either\r\n * oldTrack or newTrack can be null; replacing a valid\r\n * oldTrack with a null newTrack effectively just removes\r\n * oldTrack\r\n * @param {JitsiLocalTrack|null} oldTrack the current track in use to be\r\n * replaced\r\n * @param {JitsiLocalTrack|null} newTrack the new track to use\r\n * @returns {Promise} which resolves once the replacement is complete\r\n * with no arguments or rejects with an error {string}\r\n */\r\n replaceTrack(oldTrack, newTrack) {\r\n const workFunction = finishedCallback => {\r\n logger.debug(`${this} replaceTrack worker started. oldTrack = ${oldTrack}, newTrack = ${newTrack}`);\r\n\r\n const oldLocalSdp = this.peerconnection.localDescription.sdp;\r\n\r\n if (!this.usesUnifiedPlan) {\r\n // NOTE the code below assumes that no more than 1 video track\r\n // can be added to the peer connection.\r\n // Transition from camera to desktop share\r\n // or transition from one camera source to another.\r\n if (this.peerconnection.options.capScreenshareBitrate\r\n && oldTrack && newTrack && newTrack.isVideoTrack()) {\r\n // Clearing current primary SSRC will make\r\n // the SdpConsistency generate a new one which will result\r\n // with:\r\n // 1. source-remove for the old video stream.\r\n // 2. source-add for the new video stream.\r\n this.peerconnection.clearRecvonlySsrc();\r\n }\r\n\r\n // Transition from no video to video (unmute).\r\n if (!oldTrack && newTrack && newTrack.isVideoTrack()) {\r\n // Clearing current primary SSRC will make\r\n // the SdpConsistency generate a new one which will result\r\n // with:\r\n // 1. source-remove for the recvonly\r\n // 2. source-add for the new video stream\r\n this.peerconnection.clearRecvonlySsrc();\r\n\r\n // Transition from video to no video\r\n } else if (oldTrack && oldTrack.isVideoTrack() && !newTrack) {\r\n // Clearing current primary SSRC and generating the recvonly\r\n // will result in:\r\n // 1. source-remove for the old video stream\r\n // 2. source-add for the recvonly stream\r\n this.peerconnection.clearRecvonlySsrc();\r\n this.peerconnection.generateRecvonlySsrc();\r\n }\r\n }\r\n\r\n this.peerconnection.replaceTrack(oldTrack, newTrack)\r\n .then(shouldRenegotiate => {\r\n let promise = Promise.resolve();\r\n\r\n logger.debug(`${this} TPC.replaceTrack finished. shouldRenegotiate = ${\r\n shouldRenegotiate}, JingleSessionState = ${this.state}`);\r\n\r\n if (shouldRenegotiate\r\n && (oldTrack || newTrack)\r\n && this.state === JingleSessionState.ACTIVE) {\r\n promise = this._renegotiate().then(() => {\r\n const newLocalSDP = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this.notifyMySSRCUpdate(new SDP(oldLocalSdp), newLocalSDP);\r\n });\r\n }\r\n\r\n return promise.then(() => {\r\n // Set the source name of the new track.\r\n if (FeatureFlags.isSourceNameSignalingEnabled()\r\n && oldTrack\r\n && newTrack\r\n && oldTrack.isVideoTrack()) {\r\n newTrack.setSourceName(oldTrack.getSourceName());\r\n }\r\n\r\n if (newTrack?.isVideoTrack()) {\r\n logger.debug(`${this} replaceTrack worker: configuring video stream`);\r\n\r\n // Configure the video encodings after the track is replaced.\r\n return this.peerconnection.configureSenderVideoEncodings(newTrack);\r\n }\r\n });\r\n })\r\n .then(() => finishedCallback(), error => finishedCallback(error));\r\n };\r\n\r\n return new Promise((resolve, reject) => {\r\n logger.debug(`${this} Queued replaceTrack task. Old track = ${oldTrack}, new track = ${newTrack}`);\r\n\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} Replace track error:`, error);\r\n reject(error);\r\n } else {\r\n logger.info(`${this} Replace track done!`);\r\n resolve();\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Parse the information from the xml sourceRemoveElem and translate it\r\n * into sdp lines\r\n * @param {jquery xml element} sourceRemoveElem the source-remove\r\n * element from jingle\r\n * @param {SDP object} currentRemoteSdp the current remote\r\n * sdp (as of this new source-remove)\r\n * @returns {list} a list of SDP line strings that should\r\n * be removed from the remote SDP\r\n */\r\n _parseSsrcInfoFromSourceRemove(sourceRemoveElem, currentRemoteSdp) {\r\n const removeSsrcInfo = [];\r\n\r\n $(sourceRemoveElem).each((i1, content) => {\r\n const name = $(content).attr('name');\r\n let lines = '';\r\n\r\n $(content)\r\n .find('ssrc-group[xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\"]')\r\n .each(function() {\r\n /* eslint-disable no-invalid-this */\r\n const semantics = this.getAttribute('semantics');\r\n const ssrcs\r\n = $(this)\r\n .find('>source')\r\n .map(function() {\r\n return this.getAttribute('ssrc');\r\n })\r\n .get();\r\n\r\n if (ssrcs.length) {\r\n lines\r\n += `a=ssrc-group:${semantics} ${\r\n ssrcs.join(' ')}\\r\\n`;\r\n }\r\n\r\n /* eslint-enable no-invalid-this */\r\n });\r\n const ssrcs = [];\r\n\r\n // handles both >source and >description>source versions\r\n const tmp\r\n = $(content).find(\r\n 'source[xmlns=\"urn:xmpp:jingle:apps:rtp:ssma:0\"]');\r\n\r\n tmp.each(function() {\r\n // eslint-disable-next-line no-invalid-this\r\n const ssrc = $(this).attr('ssrc');\r\n\r\n ssrcs.push(ssrc);\r\n });\r\n currentRemoteSdp.media.forEach((media, i2) => {\r\n if (!SDPUtil.findLine(media, `a=mid:${name}`)) {\r\n return;\r\n }\r\n if (!removeSsrcInfo[i2]) {\r\n removeSsrcInfo[i2] = '';\r\n }\r\n ssrcs.forEach(ssrc => {\r\n const ssrcLines\r\n = SDPUtil.findLines(media, `a=ssrc:${ssrc}`);\r\n\r\n if (ssrcLines.length) {\r\n removeSsrcInfo[i2] += `${ssrcLines.join('\\r\\n')}\\r\\n`;\r\n }\r\n });\r\n removeSsrcInfo[i2] += lines;\r\n });\r\n });\r\n\r\n return removeSsrcInfo;\r\n }\r\n\r\n /**\r\n * Will print an error if there is any difference, between the SSRCs given\r\n * in the oldSDP and the ones currently described in\r\n * the peerconnection's local description.\r\n * @param {string} operationName the operation's name which will be printed\r\n * in the error message.\r\n * @param {SDP} oldSDP the old local SDP which will be compared with\r\n * the current one.\r\n * @return {boolean} true if there was any change or false\r\n * otherwise.\r\n * @private\r\n */\r\n _verifyNoSSRCChanged(operationName, oldSDP) {\r\n const currentLocalSDP\r\n = new SDP(this.peerconnection.localDescription.sdp);\r\n let sdpDiff = new SDPDiffer(oldSDP, currentLocalSDP);\r\n const addedMedia = sdpDiff.getNewMedia();\r\n\r\n if (Object.keys(addedMedia).length) {\r\n logger.error(`${this} - some SSRC were added on ${operationName}`, addedMedia);\r\n\r\n return false;\r\n }\r\n\r\n sdpDiff = new SDPDiffer(currentLocalSDP, oldSDP);\r\n const removedMedia = sdpDiff.getNewMedia();\r\n\r\n if (Object.keys(removedMedia).length) {\r\n logger.error(`${this} - some SSRCs were removed on ${operationName}`, removedMedia);\r\n\r\n return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Adds local track back to this session, as part of the unmute operation.\r\n * @param {JitsiLocalTrack} track\r\n * @return {Promise} a promise that will resolve once the local track is\r\n * added back to this session and renegotiation succeeds. Will be rejected\r\n * with a string that provides some error details in case something\r\n * goes wrong.\r\n */\r\n addTrackAsUnmute(track) {\r\n return this._addRemoveTrackAsMuteUnmute(\r\n false /* add as unmute */, track)\r\n .then(() => {\r\n // Configure the video encodings after the track is unmuted. If the user joins the call muted and\r\n // unmutes it the first time, all the parameters need to be configured.\r\n if (track.isVideoTrack()) {\r\n return this.peerconnection.configureSenderVideoEncodings(track);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Remove local track as part of the mute operation.\r\n * @param {JitsiLocalTrack} track the local track to be removed\r\n * @return {Promise} a promise which will be resolved once the local track\r\n * is removed from this session and the renegotiation is performed.\r\n * The promise will be rejected with a string that the describes\r\n * the error if anything goes wrong.\r\n */\r\n removeTrackAsMute(track) {\r\n return this._addRemoveTrackAsMuteUnmute(\r\n true /* remove as mute */, track);\r\n }\r\n\r\n /**\r\n * See {@link addTrackAsUnmute} and {@link removeTrackAsMute}.\r\n * @param {boolean} isMute true for \"remove as mute\" or\r\n * false for \"add as unmute\".\r\n * @param {JitsiLocalTrack} track the track that will be added/removed\r\n * @private\r\n */\r\n _addRemoveTrackAsMuteUnmute(isMute, track) {\r\n if (!track) {\r\n return Promise.reject('invalid \"track\" argument value');\r\n }\r\n const operationName = isMute ? 'removeTrackMute' : 'addTrackUnmute';\r\n const workFunction = finishedCallback => {\r\n const tpc = this.peerconnection;\r\n\r\n if (!tpc) {\r\n finishedCallback(\r\n `Error: tried ${operationName} track with no active peer`\r\n + 'connection');\r\n\r\n return;\r\n }\r\n const oldLocalSDP = tpc.localDescription.sdp;\r\n const operationPromise\r\n = isMute\r\n ? tpc.removeTrackMute(track)\r\n : tpc.addTrackUnmute(track);\r\n\r\n operationPromise\r\n .then(shouldRenegotiate => {\r\n if (shouldRenegotiate && oldLocalSDP && tpc.remoteDescription.sdp) {\r\n this._renegotiate()\r\n .then(() => {\r\n // The results are ignored, as this check failure is not enough to fail the whole\r\n // operation. It will log an error inside for plan-b.\r\n !this.usesUnifiedPlan && this._verifyNoSSRCChanged(operationName, new SDP(oldLocalSDP));\r\n const newLocalSdp = tpc.localDescription.sdp;\r\n\r\n // Signal the ssrc if an unmute operation results in a new ssrc being generated.\r\n this.notifyMySSRCUpdate(new SDP(oldLocalSDP), new SDP(newLocalSdp));\r\n finishedCallback();\r\n });\r\n } else {\r\n finishedCallback();\r\n }\r\n },\r\n finishedCallback /* will be called with an error */);\r\n };\r\n\r\n logger.debug(`${this} Queued ${operationName} task`);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} ${operationName} failed`);\r\n reject(error);\r\n } else {\r\n logger.debug(`${this} ${operationName} done`);\r\n resolve();\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Resumes or suspends media transfer over the underlying peer connection.\r\n * @param {boolean} audioActive true to enable audio media\r\n * transfer or false to suspend audio media transmission.\r\n * @param {boolean} videoActive true to enable video media\r\n * transfer or false to suspend video media transmission.\r\n * @return {Promise} a Promise which will resolve once\r\n * the operation is done. It will be rejected with an error description as\r\n * a string in case anything goes wrong.\r\n */\r\n setMediaTransferActive(audioActive, videoActive) {\r\n if (!this.peerconnection) {\r\n return Promise.reject(\r\n 'Can not modify transfer active state,'\r\n + ' before \"initialize\" is called');\r\n }\r\n\r\n const logAudioStr = audioActive ? 'audio active' : 'audio inactive';\r\n const logVideoStr = videoActive ? 'video active' : 'video inactive';\r\n\r\n logger.info(`${this} Queued make ${logVideoStr}, ${logAudioStr} task`);\r\n\r\n const workFunction = finishedCallback => {\r\n const isSessionActive = this.state === JingleSessionState.ACTIVE;\r\n\r\n // Because the value is modified on the queue it's impossible to\r\n // check it's final value reliably prior to submitting the task.\r\n // The rule here is that the last submitted state counts.\r\n // Check the values here to avoid unnecessary renegotiation cycle.\r\n const audioActiveChanged\r\n = this.peerconnection.setAudioTransferActive(audioActive);\r\n\r\n if (this._localVideoActive !== videoActive) {\r\n this._localVideoActive = videoActive;\r\n\r\n // Do only for P2P - Jicofo will reply with 'bad-request'\r\n // We don't want to send 'content-modify', before the initial\r\n // O/A (state === JingleSessionState.ACTIVE), because that will\r\n // mess up video media direction in the remote SDP.\r\n // 'content-modify' when processed only affects the media\r\n // direction in the local SDP. We're doing that, because setting\r\n // 'inactive' on video media in remote SDP will mess up our SDP\r\n // translation chain (simulcast, RTX, video mute etc.).\r\n if (this.isP2P && isSessionActive) {\r\n this.sendContentModify();\r\n }\r\n }\r\n\r\n const pcVideoActiveChanged\r\n = this.peerconnection.setVideoTransferActive(\r\n this._localVideoActive && this._remoteVideoActive);\r\n\r\n // Will do the sRD/sLD cycle to update SDPs and adjust the media\r\n // direction\r\n if (isSessionActive\r\n && (audioActiveChanged || pcVideoActiveChanged)) {\r\n this._renegotiate()\r\n .then(\r\n finishedCallback,\r\n finishedCallback /* will be called with an error */);\r\n } else {\r\n finishedCallback();\r\n }\r\n };\r\n\r\n return new Promise((resolve, reject) => {\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} Make ${logVideoStr}, ${logAudioStr} task failed!`);\r\n reject(error);\r\n } else {\r\n logger.debug(`${this} Make ${logVideoStr}, ${logAudioStr} task done!`);\r\n resolve();\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Will put and execute on the queue a session modify task. Currently it\r\n * only checks the senders attribute of the video content in order to figure\r\n * out if the remote peer has video in the inactive state (stored locally\r\n * in {@link _remoteVideoActive} - see field description for more info).\r\n * @param {jQuery} jingleContents jQuery selector pointing to the jingle\r\n * element of the session modify IQ.\r\n * @see {@link _remoteVideoActive}\r\n * @see {@link _localVideoActive}\r\n */\r\n modifyContents(jingleContents) {\r\n const newVideoSenders = JingleSessionPC.parseVideoSenders(jingleContents);\r\n const newMaxFrameHeight = JingleSessionPC.parseMaxFrameHeight(jingleContents);\r\n const sourceMaxFrameHeights = JingleSessionPC.parseSourceMaxFrameHeight(jingleContents);\r\n\r\n // frame height is optional in our content-modify protocol\r\n if (newMaxFrameHeight) {\r\n logger.info(`${this} received remote max frame height: ${newMaxFrameHeight}`);\r\n this.remoteRecvMaxFrameHeight = newMaxFrameHeight;\r\n this.eventEmitter.emit(MediaSessionEvents.REMOTE_VIDEO_CONSTRAINTS_CHANGED, this);\r\n }\r\n\r\n if (sourceMaxFrameHeights) {\r\n this.remoteSourceMaxFrameHeights = sourceMaxFrameHeights;\r\n this.eventEmitter.emit(MediaSessionEvents.REMOTE_SOURCE_CONSTRAINTS_CHANGED, this, sourceMaxFrameHeights);\r\n }\r\n\r\n if (newVideoSenders === null) {\r\n logger.error(`${this} - failed to parse video \"senders\" attribute in \"content-modify\" action`);\r\n\r\n return;\r\n }\r\n\r\n const workFunction = finishedCallback => {\r\n if (this._assertNotEnded() && this._modifyRemoteVideoActive(newVideoSenders)) {\r\n // Will do the sRD/sLD cycle to update SDPs and adjust the media direction.\r\n this._renegotiate()\r\n .then(finishedCallback, finishedCallback /* (error) */);\r\n } else {\r\n finishedCallback();\r\n }\r\n };\r\n\r\n logger.debug(`${this} queued \"content-modify\" task(video senders=\"${newVideoSenders}\")`);\r\n\r\n this.modificationQueue.push(\r\n workFunction,\r\n error => {\r\n if (error) {\r\n logger.error(`${this} \"content-modify\" failed`, error);\r\n } else {\r\n logger.debug(`${this} \"content-modify\" task(video senders=\"${newVideoSenders}\") done`);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Processes new value of remote video \"senders\" Jingle attribute and tries\r\n * to apply it for {@link _remoteVideoActive}.\r\n * @param {string} remoteVideoSenders the value of \"senders\" attribute of\r\n * Jingle video content element advertised by remote peer.\r\n * @return {boolean} true if the change affected state of\r\n * the underlying peerconnection and renegotiation is required for\r\n * the changes to take effect.\r\n * @private\r\n */\r\n _modifyRemoteVideoActive(remoteVideoSenders) {\r\n const isRemoteVideoActive\r\n = remoteVideoSenders === 'both'\r\n || (remoteVideoSenders === 'initiator' && this.isInitiator)\r\n || (remoteVideoSenders === 'responder' && !this.isInitiator);\r\n\r\n if (isRemoteVideoActive !== this._remoteVideoActive) {\r\n logger.debug(`${this} new remote video active: ${isRemoteVideoActive}`);\r\n this._remoteVideoActive = isRemoteVideoActive;\r\n }\r\n\r\n return this.peerconnection.setVideoTransferActive(this._localVideoActive && this._remoteVideoActive);\r\n }\r\n\r\n /**\r\n * Figures out added/removed ssrcs and send update IQs.\r\n * @param oldSDP SDP object for old description.\r\n * @param newSDP SDP object for new description.\r\n */\r\n notifyMySSRCUpdate(oldSDP, newSDP) {\r\n if (this.state !== JingleSessionState.ACTIVE) {\r\n logger.warn(`${this} Skipping SSRC update in '${this.state} ' state.`);\r\n\r\n return;\r\n }\r\n\r\n if (!this.connection.connected) {\r\n // The goal is to compare the oldest SDP with the latest one upon reconnect\r\n if (!this._cachedOldLocalSdp) {\r\n this._cachedOldLocalSdp = oldSDP;\r\n }\r\n this._cachedNewLocalSdp = newSDP;\r\n logger.warn(`${this} Not sending SSRC update while the signaling is disconnected`);\r\n\r\n return;\r\n }\r\n\r\n this._cachedOldLocalSdp = undefined;\r\n this._cachedNewLocalSdp = undefined;\r\n\r\n const getSignaledSourceInfo = sdpDiffer => {\r\n const newMedia = sdpDiffer.getNewMedia();\r\n let ssrcs = [];\r\n let mediaType = null;\r\n\r\n // It is assumed that sources are signaled one at a time.\r\n Object.keys(newMedia).forEach(mediaIndex => {\r\n const signaledSsrcs = Object.keys(newMedia[mediaIndex].ssrcs);\r\n\r\n mediaType = newMedia[mediaIndex].mid;\r\n if (signaledSsrcs?.length) {\r\n ssrcs = ssrcs.concat(signaledSsrcs);\r\n }\r\n });\r\n\r\n return {\r\n mediaType,\r\n ssrcs\r\n };\r\n };\r\n\r\n // send source-remove IQ.\r\n let sdpDiffer = new SDPDiffer(newSDP, oldSDP);\r\n const remove = $iq({ to: this.remoteJid,\r\n type: 'set' })\r\n .c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: 'source-remove',\r\n initiator: this.initiatorJid,\r\n sid: this.sid\r\n }\r\n );\r\n\r\n sdpDiffer.toJingle(remove);\r\n\r\n // context a common object for one run of ssrc update (source-add and source-remove) so we can match them if we\r\n // need to\r\n const ctx = {};\r\n const removedSsrcInfo = getSignaledSourceInfo(sdpDiffer);\r\n\r\n if (removedSsrcInfo.ssrcs.length) {\r\n // Log only the SSRCs instead of the full IQ.\r\n logger.info(`${this} Sending source-remove for ${removedSsrcInfo.mediaType}`\r\n + ` ssrcs=${removedSsrcInfo.ssrcs}`);\r\n this.connection.sendIQ(\r\n remove,\r\n () => {\r\n this.room.eventEmitter.emit(XMPPEvents.SOURCE_REMOVE, this, ctx);\r\n },\r\n this.newJingleErrorHandler(remove, error => {\r\n this.room.eventEmitter.emit(XMPPEvents.SOURCE_REMOVE_ERROR, this, error, ctx);\r\n }),\r\n IQ_TIMEOUT);\r\n }\r\n\r\n // send source-add IQ.\r\n sdpDiffer = new SDPDiffer(oldSDP, newSDP);\r\n const add = $iq({ to: this.remoteJid,\r\n type: 'set' })\r\n .c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: 'source-add',\r\n initiator: this.initiatorJid,\r\n sid: this.sid\r\n }\r\n );\r\n\r\n sdpDiffer.toJingle(add);\r\n const addedSsrcInfo = getSignaledSourceInfo(sdpDiffer);\r\n\r\n if (addedSsrcInfo.ssrcs.length) {\r\n // Log only the SSRCs instead of the full IQ.\r\n logger.info(`${this} Sending source-add for ${addedSsrcInfo.mediaType} ssrcs=${addedSsrcInfo.ssrcs}`);\r\n this.connection.sendIQ(\r\n add,\r\n () => {\r\n this.room.eventEmitter.emit(XMPPEvents.SOURCE_ADD, this, ctx);\r\n },\r\n this.newJingleErrorHandler(add, error => {\r\n this.room.eventEmitter.emit(XMPPEvents.SOURCE_ADD_ERROR, this, error, addedSsrcInfo.mediaType, ctx);\r\n }),\r\n IQ_TIMEOUT);\r\n }\r\n }\r\n\r\n /**\r\n * Method returns function(errorResponse) which is a callback to be passed\r\n * to Strophe connection.sendIQ method. An 'error' structure is created that\r\n * is passed as 1st argument to given failureCb. The format of this\r\n * structure is as follows:\r\n * {\r\n * code: {XMPP error response code}\r\n * reason: {the name of XMPP error reason element or 'timeout' if the\r\n * request has timed out within IQ_TIMEOUT milliseconds}\r\n * source: {request.tree() that provides original request}\r\n * session: {this JingleSessionPC.toString()}\r\n * }\r\n * @param request Strophe IQ instance which is the request to be dumped into\r\n * the error structure\r\n * @param failureCb function(error) called when error response was returned\r\n * or when a timeout has occurred.\r\n * @returns {function(this:JingleSessionPC)}\r\n */\r\n newJingleErrorHandler(request, failureCb) {\r\n return errResponse => {\r\n\r\n const error = {};\r\n\r\n // Get XMPP error code and condition(reason)\r\n const errorElSel = $(errResponse).find('error');\r\n\r\n if (errorElSel.length) {\r\n error.code = errorElSel.attr('code');\r\n const errorReasonSel = $(errResponse).find('error :first');\r\n\r\n if (errorReasonSel.length) {\r\n error.reason = errorReasonSel[0].tagName;\r\n }\r\n\r\n const errorMsgSel = errorElSel.find('>text');\r\n\r\n if (errorMsgSel.length) {\r\n error.msg = errorMsgSel.text();\r\n }\r\n }\r\n\r\n if (!errResponse) {\r\n error.reason = 'timeout';\r\n }\r\n\r\n error.session = this.toString();\r\n\r\n if (failureCb) {\r\n failureCb(error);\r\n } else if (this.state === JingleSessionState.ENDED\r\n && error.reason === 'item-not-found') {\r\n // When remote peer decides to terminate the session, but it\r\n // still have few messages on the queue for processing,\r\n // it will first send us 'session-terminate' (we enter ENDED)\r\n // and then follow with 'item-not-found' for the queued requests\r\n // We don't want to have that logged on error level.\r\n logger.debug(`${this} Jingle error: ${JSON.stringify(error)}`);\r\n } else {\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(\r\n `Jingle error: ${JSON.stringify(error)}`));\r\n }\r\n };\r\n }\r\n\r\n /**\r\n * Returns the ice connection state for the peer connection.\r\n * @returns the ice connection state for the peer connection.\r\n */\r\n getIceConnectionState() {\r\n return this.peerconnection.getConnectionState();\r\n }\r\n\r\n /**\r\n * Closes the peerconnection.\r\n */\r\n close() {\r\n this.state = JingleSessionState.ENDED;\r\n this.establishmentDuration = undefined;\r\n\r\n if (this.peerconnection) {\r\n this.peerconnection.onicecandidate = null;\r\n this.peerconnection.oniceconnectionstatechange = null;\r\n this.peerconnection.onnegotiationneeded = null;\r\n this.peerconnection.onsignalingstatechange = null;\r\n }\r\n\r\n logger.debug(`${this} Clearing modificationQueue`);\r\n\r\n // Remove any pending tasks from the queue\r\n this.modificationQueue.clear();\r\n\r\n logger.debug(`${this} Queued PC close task`);\r\n this.modificationQueue.push(finishCallback => {\r\n // do not try to close if already closed.\r\n this.peerconnection && this.peerconnection.close();\r\n finishCallback();\r\n logger.debug(`${this} PC close task done!`);\r\n });\r\n\r\n logger.debug(`${this} Shutdown modificationQueue!`);\r\n\r\n // No more tasks can go in after the close task\r\n this.modificationQueue.shutdown();\r\n }\r\n\r\n /**\r\n * Converts to string with minor summary.\r\n * @return {string}\r\n */\r\n toString() {\r\n return `JingleSessionPC[session=${this.isP2P ? 'P2P' : 'JVB'},initiator=${this.isInitiator},sid=${this.sid}]`;\r\n }\r\n\r\n /**\r\n * If the A/B test for suspend video is disabled according to the room's\r\n * configuration, returns undefined. Otherwise returns a boolean which\r\n * indicates whether the suspend video option should be enabled or disabled.\r\n * @param {JingleSessionPCOptions} options - The config options.\r\n */\r\n _abtestSuspendVideoEnabled({ abTesting }) {\r\n if (!abTesting || !abTesting.enableSuspendVideoTest) {\r\n return;\r\n }\r\n\r\n // We want the two participants in a P2P call to agree on the value of\r\n // the \"suspend\" option. We use the JID of the initiator, because it is\r\n // both randomly selected and agreed upon by both participants.\r\n const jid = this._getInitiatorJid();\r\n\r\n return integerHash(jid) % 2 === 0;\r\n }\r\n}\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $iq, Strophe } from 'strophe.js';\r\n\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport {\r\n ACTION_JINGLE_TR_RECEIVED,\r\n ACTION_JINGLE_TR_SUCCESS,\r\n createJingleEvent\r\n} from '../../service/statistics/AnalyticsEvents';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\nimport Statistics from '../statistics/statistics';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\nimport RandomUtil from '../util/RandomUtil';\r\n\r\nimport ConnectionPlugin from './ConnectionPlugin';\r\nimport { expandSourcesFromJson } from './JingleHelperFunctions';\r\nimport JingleSessionPC from './JingleSessionPC';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n// XXX Strophe is build around the idea of chaining function calls so allow long\r\n// function call chains.\r\n/* eslint-disable newline-per-chained-call */\r\n\r\n/**\r\n * Parses the transport XML element and returns the list of ICE candidates formatted as text.\r\n *\r\n * @param {*} transport Transport XML element extracted from the IQ.\r\n * @returns {Array}\r\n */\r\nfunction _parseIceCandidates(transport) {\r\n const candidates = $(transport).find('>candidate');\r\n const parseCandidates = [];\r\n\r\n // Extract the candidate information from the IQ.\r\n candidates.each((_, candidate) => {\r\n const attributes = candidate.attributes;\r\n const candidateAttrs = [];\r\n\r\n for (let i = 0; i < attributes.length; i++) {\r\n const attr = attributes[i];\r\n\r\n candidateAttrs.push(`${attr.name}: ${attr.value}`);\r\n }\r\n parseCandidates.push(candidateAttrs.join(' '));\r\n });\r\n\r\n return parseCandidates;\r\n}\r\n\r\n/**\r\n *\r\n */\r\nexport default class JingleConnectionPlugin extends ConnectionPlugin {\r\n /**\r\n * Creates new JingleConnectionPlugin\r\n * @param {XMPP} xmpp\r\n * @param {EventEmitter} eventEmitter\r\n * @param {Object} iceConfig an object that holds the iceConfig to be passed\r\n * to the p2p and the jvb PeerConnection.\r\n */\r\n constructor(xmpp, eventEmitter, iceConfig) {\r\n super();\r\n this.xmpp = xmpp;\r\n this.eventEmitter = eventEmitter;\r\n this.sessions = {};\r\n this.jvbIceConfig = iceConfig.jvb;\r\n this.p2pIceConfig = iceConfig.p2p;\r\n this.mediaConstraints = {\r\n offerToReceiveAudio: true,\r\n offerToReceiveVideo: true\r\n };\r\n }\r\n\r\n /**\r\n *\r\n * @param connection\r\n */\r\n init(connection) {\r\n super.init(connection);\r\n this.connection.addHandler(this.onJingle.bind(this),\r\n 'urn:xmpp:jingle:1', 'iq', 'set', null, null);\r\n }\r\n\r\n /**\r\n *\r\n * @param iq\r\n */\r\n onJingle(iq) {\r\n const sid = $(iq).find('jingle').attr('sid');\r\n const action = $(iq).find('jingle').attr('action');\r\n const fromJid = iq.getAttribute('from');\r\n\r\n // send ack first\r\n const ack = $iq({ type: 'result',\r\n to: fromJid,\r\n id: iq.getAttribute('id')\r\n });\r\n\r\n let sess = this.sessions[sid];\r\n\r\n if (action !== 'session-initiate') {\r\n if (!sess) {\r\n ack.attrs({ type: 'error' });\r\n ack.c('error', { type: 'cancel' })\r\n .c('item-not-found', {\r\n xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'\r\n })\r\n .up()\r\n .c('unknown-session', {\r\n xmlns: 'urn:xmpp:jingle:errors:1'\r\n });\r\n logger.warn(`invalid session id: ${sid}`);\r\n logger.debug(iq);\r\n this.connection.send(ack);\r\n\r\n return true;\r\n }\r\n\r\n // local jid is not checked\r\n if (fromJid !== sess.remoteJid) {\r\n logger.warn(\r\n 'jid mismatch for session id', sid, sess.remoteJid, iq);\r\n ack.attrs({ type: 'error' });\r\n ack.c('error', { type: 'cancel' })\r\n .c('item-not-found', {\r\n xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'\r\n })\r\n .up()\r\n .c('unknown-session', {\r\n xmlns: 'urn:xmpp:jingle:errors:1'\r\n });\r\n this.connection.send(ack);\r\n\r\n return true;\r\n }\r\n } else if (sess !== undefined) {\r\n // Existing session with same session id. This might be out-of-order\r\n // if the sess.remoteJid is the same as from.\r\n ack.attrs({ type: 'error' });\r\n ack.c('error', { type: 'cancel' })\r\n .c('service-unavailable', {\r\n xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'\r\n })\r\n .up();\r\n logger.warn('duplicate session id', sid, iq);\r\n this.connection.send(ack);\r\n\r\n return true;\r\n }\r\n const now = window.performance.now();\r\n\r\n // FIXME that should work most of the time, but we'd have to\r\n // think how secure it is to assume that user with \"focus\"\r\n // nickname is Jicofo.\r\n const isP2P = Strophe.getResourceFromJid(fromJid) !== 'focus';\r\n\r\n // see http://xmpp.org/extensions/xep-0166.html#concepts-session\r\n\r\n const jsonMessages = $(iq).find('jingle>json-message');\r\n\r\n if (jsonMessages?.length) {\r\n let audioVideoSsrcs;\r\n\r\n logger.info(`Found a JSON-encoded element in ${action}, translating to standard Jingle.`);\r\n for (let i = 0; i < jsonMessages.length; i++) {\r\n // Currently there is always a single json-message in the IQ with the source information.\r\n audioVideoSsrcs = expandSourcesFromJson(iq, jsonMessages[i]);\r\n }\r\n\r\n if (audioVideoSsrcs?.size) {\r\n const logMessage = [];\r\n\r\n for (const endpoint of audioVideoSsrcs.keys()) {\r\n logMessage.push(`${endpoint}:[${audioVideoSsrcs.get(endpoint)}]`);\r\n }\r\n logger.debug(`Received ${action} from ${fromJid} with sources=${logMessage.join(', ')}`);\r\n }\r\n\r\n // TODO: is there a way to remove the json-message elements once we've extracted the information?\r\n // removeChild doesn't seem to work.\r\n }\r\n\r\n switch (action) {\r\n case 'session-initiate': {\r\n logger.log('(TIME) received session-initiate:\\t', now);\r\n const startMuted = $(iq).find('jingle>startmuted');\r\n\r\n isP2P && logger.debug(`Received ${action} from ${fromJid}`);\r\n if (startMuted?.length) {\r\n const audioMuted = startMuted.attr(MediaType.AUDIO);\r\n const videoMuted = startMuted.attr(MediaType.VIDEO);\r\n\r\n this.eventEmitter.emit(\r\n XMPPEvents.START_MUTED_FROM_FOCUS,\r\n audioMuted === 'true',\r\n videoMuted === 'true');\r\n }\r\n const pcConfig = isP2P ? this.p2pIceConfig : this.jvbIceConfig;\r\n\r\n sess\r\n = new JingleSessionPC(\r\n $(iq).find('jingle').attr('sid'),\r\n $(iq).attr('to'),\r\n fromJid,\r\n this.connection,\r\n this.mediaConstraints,\r\n\r\n // Makes a copy in order to prevent exception thrown on RN when either this.p2pIceConfig or\r\n // this.jvbIceConfig is modified and there's a PeerConnection instance holding a reference\r\n JSON.parse(JSON.stringify(pcConfig)),\r\n isP2P,\r\n /* initiator */ false);\r\n\r\n this.sessions[sess.sid] = sess;\r\n this.eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, $(iq).find('>jingle'), now);\r\n break;\r\n }\r\n case 'session-accept': {\r\n const ssrcs = [];\r\n const contents = $(iq).find('jingle>content');\r\n\r\n // Extract the SSRCs from the session-accept received from a p2p peer.\r\n for (const content of contents) {\r\n const ssrc = $(content).find('description').attr('ssrc');\r\n\r\n ssrc && ssrcs.push(ssrc);\r\n }\r\n logger.debug(`Received ${action} from ${fromJid} with ssrcs=${ssrcs}`);\r\n this.eventEmitter.emit(XMPPEvents.CALL_ACCEPTED, sess, $(iq).find('>jingle'));\r\n break;\r\n }\r\n case 'content-modify': {\r\n const height = $(iq).find('jingle>content[name=\"video\"]>max-frame-height');\r\n\r\n logger.debug(`Received ${action} from ${fromJid} with a max-frame-height=${height?.text()}`);\r\n sess.modifyContents($(iq).find('>jingle'));\r\n break;\r\n }\r\n case 'transport-info': {\r\n const candidates = _parseIceCandidates($(iq).find('jingle>content>transport'));\r\n\r\n logger.debug(`Received ${action} from ${fromJid} for candidates=${candidates.join(', ')}`);\r\n this.eventEmitter.emit(XMPPEvents.TRANSPORT_INFO, sess, $(iq).find('>jingle'));\r\n break;\r\n }\r\n case 'session-terminate': {\r\n logger.log('terminating...', sess.sid);\r\n let reasonCondition = null;\r\n let reasonText = null;\r\n\r\n if ($(iq).find('>jingle>reason').length) {\r\n reasonCondition\r\n = $(iq).find('>jingle>reason>:first')[0].tagName;\r\n reasonText = $(iq).find('>jingle>reason>text').text();\r\n }\r\n logger.debug(`Received ${action} from ${fromJid} disconnect reason=${reasonText}`);\r\n this.terminate(sess.sid, reasonCondition, reasonText);\r\n this.eventEmitter.emit(XMPPEvents.CALL_ENDED, sess, reasonCondition, reasonText);\r\n break;\r\n }\r\n case 'transport-replace': {\r\n logger.info('(TIME) Start transport replace:\\t', now);\r\n const transport = $(iq).find('jingle>content>transport');\r\n const candidates = _parseIceCandidates(transport);\r\n const iceUfrag = $(transport).attr('ufrag');\r\n const icePwd = $(transport).attr('pwd');\r\n const dtlsFingerprint = $(transport).find('>fingerprint')?.text();\r\n\r\n logger.debug(`Received ${action} from ${fromJid} with iceUfrag=${iceUfrag},`\r\n + ` icePwd=${icePwd}, DTLS fingerprint=${dtlsFingerprint}, candidates=${candidates.join(', ')}`);\r\n\r\n Statistics.sendAnalytics(createJingleEvent(\r\n ACTION_JINGLE_TR_RECEIVED,\r\n {\r\n p2p: isP2P,\r\n value: now\r\n }));\r\n\r\n sess.replaceTransport($(iq).find('>jingle'), () => {\r\n const successTime = window.performance.now();\r\n\r\n logger.info('(TIME) Transport replace success:\\t', successTime);\r\n Statistics.sendAnalytics(createJingleEvent(\r\n ACTION_JINGLE_TR_SUCCESS,\r\n {\r\n p2p: isP2P,\r\n value: successTime\r\n }));\r\n }, error => {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error('Transport replace failed', error);\r\n sess.sendTransportReject();\r\n });\r\n break;\r\n }\r\n case 'source-add':\r\n sess.addRemoteStream($(iq).find('>jingle>content'));\r\n break;\r\n case 'source-remove':\r\n sess.removeRemoteStream($(iq).find('>jingle>content'));\r\n break;\r\n default:\r\n logger.warn('jingle action not implemented', action);\r\n ack.attrs({ type: 'error' });\r\n ack.c('error', { type: 'cancel' })\r\n .c('bad-request',\r\n { xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas' })\r\n .up();\r\n break;\r\n }\r\n this.connection.send(ack);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Creates new JingleSessionPC meant to be used in a direct P2P\r\n * connection, configured as 'initiator'.\r\n * @param {string} me our JID\r\n * @param {string} peer remote participant's JID\r\n * @return {JingleSessionPC}\r\n */\r\n newP2PJingleSession(me, peer) {\r\n const sess\r\n = new JingleSessionPC(\r\n RandomUtil.randomHexString(12),\r\n me,\r\n peer,\r\n this.connection,\r\n this.mediaConstraints,\r\n this.p2pIceConfig,\r\n /* P2P */ true,\r\n /* initiator */ true);\r\n\r\n this.sessions[sess.sid] = sess;\r\n\r\n return sess;\r\n }\r\n\r\n /**\r\n *\r\n * @param sid\r\n * @param reasonCondition\r\n * @param reasonText\r\n */\r\n terminate(sid, reasonCondition, reasonText) {\r\n if (this.sessions.hasOwnProperty(sid)) {\r\n if (this.sessions[sid].state !== 'ended') {\r\n this.sessions[sid].onTerminated(reasonCondition, reasonText);\r\n }\r\n delete this.sessions[sid];\r\n }\r\n }\r\n\r\n /**\r\n *\r\n */\r\n getStunAndTurnCredentials() {\r\n // get stun and turn configuration from server via xep-0215\r\n // uses time-limited credentials as described in\r\n // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00\r\n //\r\n // See https://modules.prosody.im/mod_turncredentials.html\r\n // for a prosody module which implements this.\r\n // Or the new implementation https://modules.prosody.im/mod_external_services which will be in prosody 0.12\r\n //\r\n // Currently, this doesn't work with updateIce and therefore credentials\r\n // with a long validity have to be fetched before creating the\r\n // peerconnection.\r\n // TODO: implement refresh via updateIce as described in\r\n // https://code.google.com/p/webrtc/issues/detail?id=1650\r\n this.connection.sendIQ(\r\n $iq({ type: 'get',\r\n to: this.xmpp.options.hosts.domain })\r\n .c('services', { xmlns: 'urn:xmpp:extdisco:2' }),\r\n v2Res => this.onReceiveStunAndTurnCredentials(v2Res),\r\n () => {\r\n logger.warn('getting turn credentials with extdisco:2 failed, trying extdisco:1');\r\n this.connection.sendIQ(\r\n $iq({ type: 'get',\r\n to: this.xmpp.options.hosts.domain })\r\n .c('services', { xmlns: 'urn:xmpp:extdisco:1' }),\r\n v1Res => this.onReceiveStunAndTurnCredentials(v1Res),\r\n () => {\r\n logger.warn('getting turn credentials failed');\r\n logger.warn('is mod_turncredentials or similar installed and configured?');\r\n }\r\n );\r\n });\r\n }\r\n\r\n /**\r\n * Parses response when querying for services using urn:xmpp:extdisco:1 or urn:xmpp:extdisco:2.\r\n * Stores results in jvbIceConfig and p2pIceConfig.\r\n * @param res The response iq.\r\n * @return {boolean} Whether something was processed from the supplied message.\r\n */\r\n onReceiveStunAndTurnCredentials(res) {\r\n const iceservers = [];\r\n\r\n $(res).find('>services>service').each((idx, el) => {\r\n // eslint-disable-next-line no-param-reassign\r\n el = $(el);\r\n const dict = {};\r\n const type = el.attr('type');\r\n\r\n switch (type) {\r\n case 'stun':\r\n dict.urls = `stun:${el.attr('host')}`;\r\n if (el.attr('port')) {\r\n dict.urls += `:${el.attr('port')}`;\r\n }\r\n iceservers.push(dict);\r\n break;\r\n case 'turn':\r\n case 'turns': {\r\n dict.urls = `${type}:`;\r\n dict.username = el.attr('username');\r\n dict.urls += el.attr('host');\r\n const port = el.attr('port');\r\n\r\n if (port) {\r\n dict.urls += `:${el.attr('port')}`;\r\n }\r\n const transport = el.attr('transport');\r\n\r\n if (transport && transport !== 'udp') {\r\n dict.urls += `?transport=${transport}`;\r\n }\r\n\r\n dict.credential = el.attr('password')\r\n || dict.credential;\r\n iceservers.push(dict);\r\n break;\r\n }\r\n }\r\n });\r\n\r\n const options = this.xmpp.options;\r\n\r\n // Shuffle ICEServers for loadbalancing\r\n for (let i = iceservers.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1));\r\n const temp = iceservers[i];\r\n\r\n iceservers[i] = iceservers[j];\r\n iceservers[j] = temp;\r\n }\r\n\r\n let filter;\r\n\r\n if (options.useTurnUdp) {\r\n filter = s => s.urls.startsWith('turn');\r\n } else {\r\n // By default we filter out STUN and TURN/UDP and leave only TURN/TCP.\r\n filter = s => s.urls.startsWith('turn') && (s.urls.indexOf('transport=tcp') >= 0);\r\n }\r\n\r\n this.jvbIceConfig.iceServers = iceservers.filter(filter);\r\n this.p2pIceConfig.iceServers = iceservers;\r\n\r\n return iceservers.length > 0;\r\n }\r\n\r\n /**\r\n * Returns the data saved in 'updateLog' in a format to be logged.\r\n */\r\n getLog() {\r\n const data = {};\r\n\r\n Object.keys(this.sessions).forEach(sid => {\r\n const session = this.sessions[sid];\r\n const pc = session.peerconnection;\r\n\r\n if (pc && pc.updateLog) {\r\n // FIXME: should probably be a .dump call\r\n data[`jingle_${sid}`] = {\r\n updateLog: pc.updateLog,\r\n stats: pc.stats,\r\n url: window.location.href\r\n };\r\n }\r\n });\r\n\r\n return data;\r\n }\r\n}\r\n\r\n/* eslint-enable newline-per-chained-call */\r\n","import { Strophe } from 'strophe.js';\r\n\r\nimport ConnectionPlugin from './ConnectionPlugin';\r\n\r\n/**\r\n * Logs raw stanzas and makes them available for download as JSON\r\n */\r\nclass StropheLogger extends ConnectionPlugin {\r\n /**\r\n *\r\n */\r\n constructor() {\r\n super();\r\n this.log = [];\r\n }\r\n\r\n /**\r\n *\r\n * @param connection\r\n */\r\n init(connection) {\r\n super.init(connection);\r\n this.connection.rawInput = this.logIncoming.bind(this);\r\n this.connection.rawOutput = this.logOutgoing.bind(this);\r\n }\r\n\r\n /**\r\n *\r\n * @param stanza\r\n */\r\n logIncoming(stanza) {\r\n this.log.push([ new Date().getTime(), 'incoming', stanza ]);\r\n }\r\n\r\n /**\r\n *\r\n * @param stanza\r\n */\r\n logOutgoing(stanza) {\r\n this.log.push([ new Date().getTime(), 'outgoing', stanza ]);\r\n }\r\n}\r\n\r\n/**\r\n *\r\n */\r\nexport default function() {\r\n Strophe.addConnectionPlugin('logger', new StropheLogger());\r\n}\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $iq } from 'strophe.js';\r\n\r\nimport ConnectionPlugin from './ConnectionPlugin';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\nconst RAYO_XMLNS = 'urn:xmpp:rayo:1';\r\n\r\n/**\r\n *\r\n */\r\nexport default class RayoConnectionPlugin extends ConnectionPlugin {\r\n /**\r\n *\r\n * @param connection\r\n */\r\n init(connection) {\r\n super.init(connection);\r\n\r\n this.connection.addHandler(\r\n this.onRayo.bind(this), RAYO_XMLNS, 'iq', 'set', null, null);\r\n }\r\n\r\n /**\r\n *\r\n * @param iq\r\n */\r\n onRayo(iq) {\r\n logger.info('Rayo IQ', iq);\r\n }\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n *\r\n * @param to\r\n * @param from\r\n * @param roomName\r\n * @param roomPass\r\n * @param focusMucJid\r\n */\r\n dial(to, from, roomName, roomPass, focusMucJid) {\r\n return new Promise((resolve, reject) => {\r\n if (!focusMucJid) {\r\n reject(new Error('Internal error!'));\r\n\r\n return;\r\n }\r\n const req = $iq({\r\n type: 'set',\r\n to: focusMucJid\r\n });\r\n\r\n req.c('dial', {\r\n xmlns: RAYO_XMLNS,\r\n to,\r\n from\r\n });\r\n req.c('header', {\r\n name: 'JvbRoomName',\r\n value: roomName\r\n }).up();\r\n\r\n if (roomPass && roomPass.length) {\r\n req.c('header', {\r\n name: 'JvbRoomPassword',\r\n value: roomPass\r\n }).up();\r\n }\r\n\r\n this.connection.sendIQ(\r\n req,\r\n result => {\r\n logger.info('Dial result ', result);\r\n\r\n // eslint-disable-next-line newline-per-chained-call\r\n const resource = $(result).find('ref').attr('uri');\r\n\r\n this.callResource = resource.substr('xmpp:'.length);\r\n logger.info(`Received call resource: ${this.callResource}`);\r\n resolve();\r\n },\r\n error => {\r\n logger.info('Dial error ', error);\r\n reject(error);\r\n });\r\n });\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n *\r\n */\r\n hangup() {\r\n return new Promise((resolve, reject) => {\r\n if (!this.callResource) {\r\n reject(new Error('No call in progress'));\r\n logger.warn('No call in progress');\r\n\r\n return;\r\n }\r\n\r\n const req = $iq({\r\n type: 'set',\r\n to: this.callResource\r\n });\r\n\r\n req.c('hangup', {\r\n xmlns: RAYO_XMLNS\r\n });\r\n\r\n this.connection.sendIQ(req, result => {\r\n logger.info('Hangup result ', result);\r\n this.callResource = null;\r\n resolve();\r\n }, error => {\r\n logger.info('Hangup error ', error);\r\n this.callResource = null;\r\n reject(new Error('Hangup error '));\r\n });\r\n });\r\n }\r\n}\r\n","/**\r\n * Strophe logger implementation. Logs from level WARN and above.\r\n */\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { Strophe } from 'strophe.js';\r\n\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * This is the last HTTP error status captured from Strophe debug logs.\r\n * The purpose of storing it is to distinguish between the network and\r\n * infrastructure reason for connection being dropped (see connectionHandler in\r\n * xmpp.js). The value will be cleared (-1) if the subsequent request succeeds\r\n * which means that the failure could be transient.\r\n *\r\n * FIXME in the latest Strophe (not released on npm) there is API to handle\r\n * particular HTTP errors, but there is no way to learn if the subsequent\r\n * request succeeded in order to tell if the error was one time incident or if\r\n * it was the reason for dropping the connection by Strophe (the connection is\r\n * dropped after 5 subsequent failures). Ideally Strophe should provide more\r\n * details about the reason on why the connection stopped.\r\n *\r\n * @type {number}\r\n */\r\nlet lastErrorStatus = -1;\r\n\r\n/**\r\n * A regular expression used to catch Strophe's log message indicating that the\r\n * last BOSH request was successful. When there is such message seen the\r\n * {@link lastErrorStatus} will be set back to '-1'.\r\n * @type {RegExp}\r\n */\r\nconst resetLastErrorStatusRegExpr = /request id \\d+.\\d+ got 200/;\r\n\r\n/**\r\n * A regular expression used to capture the current value of the BOSH request\r\n * error status (HTTP error code or '0' or something else).\r\n * @type {RegExp}\r\n */\r\nconst lastErrorStatusRegExpr\r\n = /request errored, status: (\\d+), number of errors: \\d+/;\r\n\r\n/**\r\n *\r\n */\r\nexport default function() {\r\n\r\n Strophe.log = function(level, msg) {\r\n // Our global handler reports uncaught errors to the stats which may\r\n // interpret those as partial call failure.\r\n // Strophe log entry about secondary request timeout does not mean that\r\n // it's a final failure(the request will be restarted), so we lower it's\r\n // level here to a warning.\r\n logger.trace('Strophe', level, msg);\r\n if (typeof msg === 'string'\r\n && msg.indexOf('Request ') !== -1\r\n && msg.indexOf('timed out (secondary), restarting') !== -1) {\r\n // eslint-disable-next-line no-param-reassign\r\n level = Strophe.LogLevel.WARN;\r\n }\r\n\r\n /* eslint-disable no-case-declarations */\r\n switch (level) {\r\n case Strophe.LogLevel.DEBUG:\r\n // The log message which reports successful status is logged on\r\n // Strophe's DEBUG level.\r\n if (lastErrorStatus !== -1\r\n && resetLastErrorStatusRegExpr.test(msg)) {\r\n logger.debug('Reset lastErrorStatus');\r\n lastErrorStatus = -1;\r\n }\r\n break;\r\n case Strophe.LogLevel.WARN:\r\n logger.warn(`Strophe: ${msg}`);\r\n const errStatusCapture = lastErrorStatusRegExpr.exec(msg);\r\n\r\n if (errStatusCapture && errStatusCapture.length === 2) {\r\n lastErrorStatus = parseInt(errStatusCapture[1], 10);\r\n logger.debug(`lastErrorStatus set to: ${lastErrorStatus}`);\r\n }\r\n break;\r\n case Strophe.LogLevel.ERROR:\r\n case Strophe.LogLevel.FATAL:\r\n // eslint-disable-next-line no-param-reassign\r\n msg = `Strophe: ${msg}`;\r\n GlobalOnErrorHandler.callErrorHandler(new Error(msg));\r\n logger.error(msg);\r\n break;\r\n }\r\n\r\n /* eslint-enable no-case-declarations */\r\n };\r\n\r\n /**\r\n * Returns error status (HTTP error code) of the last BOSH request.\r\n *\r\n * @return {number} HTTP error code, '0' for unknown or \"god knows what\"\r\n * (this is a hack).\r\n */\r\n Strophe.getLastErrorStatus = function() {\r\n return lastErrorStatus;\r\n };\r\n\r\n Strophe.getStatusString = function(status) {\r\n switch (status) {\r\n case Strophe.Status.BINDREQUIRED:\r\n return 'BINDREQUIRED';\r\n case Strophe.Status.ERROR:\r\n return 'ERROR';\r\n case Strophe.Status.CONNECTING:\r\n return 'CONNECTING';\r\n case Strophe.Status.CONNFAIL:\r\n return 'CONNFAIL';\r\n case Strophe.Status.AUTHENTICATING:\r\n return 'AUTHENTICATING';\r\n case Strophe.Status.AUTHFAIL:\r\n return 'AUTHFAIL';\r\n case Strophe.Status.CONNECTED:\r\n return 'CONNECTED';\r\n case Strophe.Status.DISCONNECTED:\r\n return 'DISCONNECTED';\r\n case Strophe.Status.DISCONNECTING:\r\n return 'DISCONNECTING';\r\n case Strophe.Status.ATTACHED:\r\n return 'ATTACHED';\r\n default:\r\n return 'unknown';\r\n }\r\n };\r\n}\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $msg, Strophe } from 'strophe.js';\r\nimport 'strophejs-plugin-disco';\r\n\r\nimport * as JitsiConnectionErrors from '../../JitsiConnectionErrors';\r\nimport * as JitsiConnectionEvents from '../../JitsiConnectionEvents';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\nimport browser from '../browser';\r\nimport { E2EEncryption } from '../e2ee/E2EEncryption';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport Statistics from '../statistics/statistics';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\nimport Listenable from '../util/Listenable';\r\nimport RandomUtil from '../util/RandomUtil';\r\n\r\nimport Caps, { parseDiscoInfo } from './Caps';\r\nimport XmppConnection from './XmppConnection';\r\nimport MucConnectionPlugin from './strophe.emuc';\r\nimport JingleConnectionPlugin from './strophe.jingle';\r\nimport initStropheLogger from './strophe.logger';\r\nimport RayoConnectionPlugin from './strophe.rayo';\r\nimport initStropheUtil from './strophe.util';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n* Regex to extract exact error message on jwt error.\r\n*/\r\nconst FAILURE_REGEX = /(.*)<\\/text><\\/failure>/gi;\r\n\r\n/**\r\n * Creates XMPP connection.\r\n *\r\n * @param {Object} options\r\n * @param {string} [options.token] - JWT token used for authentication(JWT authentication module must be enabled in\r\n * Prosody).\r\n * @param {string} options.serviceUrl - The service URL for XMPP connection.\r\n * @param {string} options.shard - The shard where XMPP connection initially landed.\r\n * @param {string} options.enableWebsocketResume - True to enable stream resumption.\r\n * @param {number} [options.websocketKeepAlive] - See {@link XmppConnection} constructor.\r\n * @param {number} [options.websocketKeepAliveUrl] - See {@link XmppConnection} constructor.\r\n * @param {Object} [options.xmppPing] - See {@link XmppConnection} constructor.\r\n * @returns {XmppConnection}\r\n */\r\nfunction createConnection({\r\n enableWebsocketResume,\r\n serviceUrl = '/http-bind',\r\n shard,\r\n token,\r\n websocketKeepAlive,\r\n websocketKeepAliveUrl,\r\n xmppPing }) {\r\n\r\n // Append token as URL param\r\n if (token) {\r\n // eslint-disable-next-line no-param-reassign\r\n serviceUrl += `${serviceUrl.indexOf('?') === -1 ? '?' : '&'}token=${token}`;\r\n }\r\n\r\n return new XmppConnection({\r\n enableWebsocketResume,\r\n serviceUrl,\r\n websocketKeepAlive,\r\n websocketKeepAliveUrl,\r\n xmppPing,\r\n shard\r\n });\r\n}\r\n\r\n/**\r\n * Initializes Strophe plugins that need to work with Strophe.Connection directly rather than the lib-jitsi-meet's\r\n * {@link XmppConnection} wrapper.\r\n *\r\n * @returns {void}\r\n */\r\nfunction initStropheNativePlugins() {\r\n initStropheUtil();\r\n initStropheLogger();\r\n}\r\n\r\n// FIXME: remove once we have a default config template. -saghul\r\n/**\r\n * A list of ice servers to use by default for P2P.\r\n */\r\nexport const DEFAULT_STUN_SERVERS = [\r\n { urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' }\r\n];\r\n\r\n/**\r\n * The name of the field used to recognize a chat message as carrying a JSON\r\n * payload from another endpoint.\r\n * If the json-message of a chat message contains a valid JSON object, and\r\n * the JSON has this key, then it is a valid json-message to be sent.\r\n */\r\nexport const JITSI_MEET_MUC_TYPE = 'type';\r\n\r\n/**\r\n * The feature used by jigasi participants.\r\n * @type {string}\r\n */\r\nexport const FEATURE_JIGASI = 'http://jitsi.org/protocol/jigasi';\r\n\r\n/**\r\n * The feature used by the lib to mark support for e2ee. We use the feature by putting it in the presence\r\n * to avoid additional signaling (disco-info).\r\n * @type {string}\r\n */\r\nexport const FEATURE_E2EE = 'https://jitsi.org/meet/e2ee';\r\n\r\n/**\r\n *\r\n */\r\nexport default class XMPP extends Listenable {\r\n /**\r\n * FIXME describe all options\r\n * @param {Object} options\r\n * @param {String} options.serviceUrl - URL passed to the XMPP client which will be used to establish XMPP\r\n * connection with the server.\r\n * @param {String} options.bosh - Deprecated, use {@code serviceUrl}.\r\n * @param {boolean} options.enableWebsocketResume - Enables XEP-0198 stream management which will make the XMPP\r\n * module try to resume the session in case the Websocket connection breaks.\r\n * @param {number} [options.websocketKeepAlive] - The websocket keep alive interval. See {@link XmppConnection}\r\n * constructor for more details.\r\n * @param {number} [options.websocketKeepAliveUrl] - The websocket keep alive url. See {@link XmppConnection}\r\n * constructor for more details.\r\n * @param {Object} [options.xmppPing] - The xmpp ping settings.\r\n * @param {Array} options.p2pStunServers see {@link JingleConnectionPlugin} for more details.\r\n * @param token\r\n */\r\n constructor(options, token) {\r\n super();\r\n this.connection = null;\r\n this.disconnectInProgress = false;\r\n this.connectionTimes = {};\r\n this.options = options;\r\n this.token = token;\r\n this.authenticatedUser = false;\r\n\r\n if (!this.options.deploymentInfo) {\r\n this.options.deploymentInfo = {};\r\n }\r\n\r\n initStropheNativePlugins();\r\n\r\n const xmppPing = options.xmppPing || {};\r\n\r\n // let's ping the main domain (in case a guest one is used for the connection)\r\n xmppPing.domain = options.hosts.domain;\r\n\r\n this.connection = createConnection({\r\n enableWebsocketResume: options.enableWebsocketResume,\r\n\r\n // FIXME remove deprecated bosh option at some point\r\n serviceUrl: options.serviceUrl || options.bosh,\r\n token,\r\n websocketKeepAlive: options.websocketKeepAlive,\r\n websocketKeepAliveUrl: options.websocketKeepAliveUrl,\r\n xmppPing,\r\n shard: options.deploymentInfo.shard\r\n });\r\n\r\n // forwards the shard changed event\r\n this.connection.on(XmppConnection.Events.CONN_SHARD_CHANGED, () => {\r\n /* eslint-disable camelcase */\r\n const details = {\r\n shard_changed: true,\r\n suspend_time: this.connection.ping.getPingSuspendTime(),\r\n time_since_last_success: this.connection.getTimeSinceLastSuccess()\r\n };\r\n /* eslint-enable camelcase */\r\n\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_FAILED,\r\n JitsiConnectionErrors.OTHER_ERROR,\r\n undefined,\r\n undefined,\r\n details);\r\n });\r\n\r\n this._initStrophePlugins();\r\n\r\n this.caps = new Caps(this.connection, /* clientNode */ 'https://jitsi.org/jitsi-meet');\r\n\r\n // Initialize features advertised in disco-info\r\n this.initFeaturesList();\r\n\r\n // Setup a disconnect on unload as a way to facilitate API consumers. It\r\n // sounds like they would want that. A problem for them though may be if\r\n // they wanted to utilize the connected connection in an unload handler\r\n // of their own. However, it should be fairly easy for them to do that\r\n // by registering their unload handler before us.\r\n $(window).on(`${this.options.disableBeforeUnloadHandlers ? '' : 'beforeunload '}unload`, ev => {\r\n this.disconnect(ev).catch(() => {\r\n // ignore errors in order to not brake the unload.\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Initializes the list of feature advertised through the disco-info\r\n * mechanism.\r\n */\r\n initFeaturesList() {\r\n // http://xmpp.org/extensions/xep-0167.html#support\r\n // http://xmpp.org/extensions/xep-0176.html#support\r\n this.caps.addFeature('urn:xmpp:jingle:1');\r\n this.caps.addFeature('urn:xmpp:jingle:apps:rtp:1');\r\n this.caps.addFeature('urn:xmpp:jingle:transports:ice-udp:1');\r\n this.caps.addFeature('urn:xmpp:jingle:apps:dtls:0');\r\n this.caps.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');\r\n this.caps.addFeature('urn:xmpp:jingle:apps:rtp:audio');\r\n this.caps.addFeature('urn:xmpp:jingle:apps:rtp:video');\r\n this.caps.addFeature('http://jitsi.org/json-encoded-sources');\r\n\r\n if (!(this.options.disableRtx || !browser.supportsRTX())) {\r\n this.caps.addFeature('urn:ietf:rfc:4588');\r\n }\r\n if (this.options.enableOpusRed === true && browser.supportsAudioRed()) {\r\n this.caps.addFeature('http://jitsi.org/opus-red');\r\n }\r\n\r\n if (typeof this.options.enableRemb === 'undefined' || this.options.enableRemb) {\r\n this.caps.addFeature('http://jitsi.org/remb');\r\n }\r\n\r\n // Disable TCC on Firefox because of a known issue where BWE is halved on every renegotiation.\r\n if (!browser.isFirefox() && (typeof this.options.enableTcc === 'undefined' || this.options.enableTcc)) {\r\n this.caps.addFeature('http://jitsi.org/tcc');\r\n }\r\n\r\n // this is dealt with by SDP O/A so we don't need to announce this\r\n // XEP-0293\r\n // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0');\r\n // XEP-0294\r\n // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0');\r\n\r\n this.caps.addFeature('urn:ietf:rfc:5761'); // rtcp-mux\r\n this.caps.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle\r\n\r\n // this.caps.addFeature('urn:ietf:rfc:5576'); // a=ssrc\r\n\r\n // Enable Lipsync ?\r\n if (browser.isChromiumBased() && this.options.enableLipSync === true) {\r\n logger.info('Lip-sync enabled !');\r\n this.caps.addFeature('http://jitsi.org/meet/lipsync');\r\n }\r\n\r\n if (this.connection.rayo) {\r\n this.caps.addFeature('urn:xmpp:rayo:client:1');\r\n }\r\n\r\n if (E2EEncryption.isSupported(this.options)) {\r\n this.caps.addFeature(FEATURE_E2EE, false, true);\r\n }\r\n\r\n // Advertise source-name signaling when the endpoint supports it.\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n logger.info('Source-name signaling is enabled');\r\n this.caps.addFeature('http://jitsi.org/source-name');\r\n }\r\n\r\n if (FeatureFlags.isSsrcRewritingSupported()) {\r\n logger.info('SSRC rewriting is supported');\r\n this.caps.addFeature('http://jitsi.org/ssrc-rewriting');\r\n }\r\n }\r\n\r\n /**\r\n *\r\n */\r\n getConnection() {\r\n return this.connection;\r\n }\r\n\r\n /**\r\n * Receive connection status changes and handles them.\r\n *\r\n * @param {Object} credentials\r\n * @param {string} credentials.jid - The user's XMPP ID passed to the\r\n * connect method. For example, 'user@xmpp.com'.\r\n * @param {string} credentials.password - The password passed to the connect\r\n * method.\r\n * @param {string} status - One of Strophe's connection status strings.\r\n * @param {string} [msg] - The connection error message provided by Strophe.\r\n */\r\n connectionHandler(credentials = {}, status, msg) {\r\n const now = window.performance.now();\r\n const statusStr = Strophe.getStatusString(status).toLowerCase();\r\n\r\n this.connectionTimes[statusStr] = now;\r\n logger.log(\r\n `(TIME) Strophe ${statusStr}${msg ? `[${msg}]` : ''}:\\t`,\r\n now);\r\n\r\n this.eventEmitter.emit(XMPPEvents.CONNECTION_STATUS_CHANGED, credentials, status, msg);\r\n this._maybeSendDeploymentInfoStat();\r\n if (status === Strophe.Status.CONNECTED || status === Strophe.Status.ATTACHED) {\r\n // once connected or attached we no longer need this handle, drop it if it exist\r\n if (this._sysMessageHandler) {\r\n this.connection._stropheConn.deleteHandler(this._sysMessageHandler);\r\n this._sysMessageHandler = null;\r\n }\r\n\r\n this.sendDiscoInfo && this.connection.jingle.getStunAndTurnCredentials();\r\n\r\n logger.info(`My Jabber ID: ${this.connection.jid}`);\r\n\r\n // XmppConnection emits CONNECTED again on reconnect - a good opportunity to clear any \"last error\" flags\r\n this._resetState();\r\n\r\n // make sure we will send the info after the features request succeeds or fails\r\n this.sendDeploymentInfo = false;\r\n this.sendDiscoInfo && this.caps.getFeaturesAndIdentities(this.options.hosts.domain)\r\n .then(({ features, identities }) => {\r\n if (!features.has(Strophe.NS.PING)) {\r\n logger.error(`Ping NOT supported by ${\r\n this.options.hosts.domain} - please enable ping in your XMPP server config`);\r\n }\r\n\r\n this._processDiscoInfoIdentities(\r\n identities, undefined /* when querying we will query for features */);\r\n })\r\n .catch(error => {\r\n const errmsg = 'Feature discovery error';\r\n\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(`${errmsg}: ${error}`));\r\n logger.error(errmsg, error);\r\n\r\n this._maybeSendDeploymentInfoStat(true);\r\n });\r\n\r\n // make sure we don't query again\r\n this.sendDiscoInfo = false;\r\n\r\n if (credentials.password) {\r\n this.authenticatedUser = true;\r\n }\r\n if (this.connection && this.connection.connected\r\n && Strophe.getResourceFromJid(this.connection.jid)) {\r\n // .connected is true while connecting?\r\n // this.connection.send($pres());\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_ESTABLISHED,\r\n Strophe.getResourceFromJid(this.connection.jid));\r\n }\r\n } else if (status === Strophe.Status.CONNFAIL) {\r\n if (msg === 'x-strophe-bad-non-anon-jid') {\r\n this.anonymousConnectionFailed = true;\r\n } else {\r\n this.connectionFailed = true;\r\n }\r\n this.lastErrorMsg = msg;\r\n if (msg === 'giving-up') {\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_FAILED,\r\n JitsiConnectionErrors.OTHER_ERROR, msg);\r\n }\r\n } else if (status === Strophe.Status.ERROR) {\r\n this.lastErrorMsg = msg;\r\n } else if (status === Strophe.Status.DISCONNECTED) {\r\n // Stop ping interval\r\n this.connection.ping.stopInterval();\r\n const wasIntentionalDisconnect = Boolean(this.disconnectInProgress);\r\n const errMsg = msg || this.lastErrorMsg;\r\n\r\n if (this.anonymousConnectionFailed) {\r\n // prompt user for username and password\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_FAILED,\r\n JitsiConnectionErrors.PASSWORD_REQUIRED);\r\n } else if (this.connectionFailed) {\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_FAILED,\r\n JitsiConnectionErrors.OTHER_ERROR,\r\n errMsg,\r\n undefined, /* credentials */\r\n this._getConnectionFailedReasonDetails());\r\n } else if (wasIntentionalDisconnect) {\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_DISCONNECTED, errMsg);\r\n } else {\r\n // XXX if Strophe drops the connection while not being asked to,\r\n // it means that most likely some serious error has occurred.\r\n // One currently known case is when a BOSH request fails for\r\n // more than 4 times. The connection is dropped without\r\n // supplying a reason(error message/event) through the API.\r\n logger.error('XMPP connection dropped!');\r\n\r\n // XXX if the last request error is within 5xx range it means it\r\n // was a server failure\r\n const lastErrorStatus = Strophe.getLastErrorStatus();\r\n\r\n if (lastErrorStatus >= 500 && lastErrorStatus < 600) {\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_FAILED,\r\n JitsiConnectionErrors.SERVER_ERROR,\r\n errMsg || 'server-error',\r\n /* credentials */ undefined,\r\n this._getConnectionFailedReasonDetails());\r\n } else {\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_FAILED,\r\n JitsiConnectionErrors.CONNECTION_DROPPED_ERROR,\r\n errMsg || 'connection-dropped-error',\r\n /* credentials */ undefined,\r\n this._getConnectionFailedReasonDetails());\r\n }\r\n }\r\n } else if (status === Strophe.Status.AUTHFAIL) {\r\n const lastFailedRawMessage = this.getConnection().getLastFailedMessage();\r\n\r\n // wrong password or username, prompt user\r\n this.eventEmitter.emit(\r\n JitsiConnectionEvents.CONNECTION_FAILED,\r\n JitsiConnectionErrors.PASSWORD_REQUIRED,\r\n msg || this._parseConnectionFailedMessage(lastFailedRawMessage),\r\n credentials);\r\n }\r\n }\r\n\r\n /**\r\n * Process received identities.\r\n * @param {Set} identities The identities to process.\r\n * @param {Set} features The features to process, optional. If missing lobby component will be queried\r\n * for more features.\r\n * @private\r\n */\r\n _processDiscoInfoIdentities(identities, features) {\r\n // check for speakerstats\r\n identities.forEach(identity => {\r\n if (identity.type === 'av_moderation') {\r\n this.avModerationComponentAddress = identity.name;\r\n }\r\n\r\n if (identity.type === 'speakerstats') {\r\n this.speakerStatsComponentAddress = identity.name;\r\n }\r\n\r\n if (identity.type === 'conference_duration') {\r\n this.conferenceDurationComponentAddress = identity.name;\r\n }\r\n\r\n if (identity.type === 'lobbyrooms') {\r\n this.lobbySupported = true;\r\n const processLobbyFeatures = f => {\r\n f.forEach(fr => {\r\n if (fr.endsWith('#displayname_required')) {\r\n this.eventEmitter.emit(JitsiConnectionEvents.DISPLAY_NAME_REQUIRED);\r\n }\r\n });\r\n };\r\n\r\n if (features) {\r\n processLobbyFeatures(features);\r\n } else {\r\n identity.name && this.caps.getFeaturesAndIdentities(identity.name, identity.type)\r\n .then(({ features: f }) => processLobbyFeatures(f))\r\n .catch(e => logger.warn('Error getting features from lobby.', e && e.message));\r\n }\r\n }\r\n\r\n if (identity.type === 'shard') {\r\n this.options.deploymentInfo.shard = this.connection.shard = identity.name;\r\n }\r\n\r\n if (identity.type === 'region') {\r\n this.options.deploymentInfo.region = this.connection.region = identity.name;\r\n }\r\n\r\n if (identity.type === 'release') {\r\n this.options.deploymentInfo.backendRelease = identity.name;\r\n }\r\n\r\n if (identity.type === 'breakout_rooms') {\r\n this.breakoutRoomsComponentAddress = identity.name;\r\n }\r\n });\r\n\r\n this._maybeSendDeploymentInfoStat(true);\r\n\r\n if (this.avModerationComponentAddress\r\n || this.speakerStatsComponentAddress\r\n || this.conferenceDurationComponentAddress\r\n || this.breakoutRoomsComponentAddress) {\r\n this.connection.addHandler(this._onPrivateMessage.bind(this), null, 'message', null, null);\r\n }\r\n }\r\n\r\n /**\r\n * Parses a raw failure xmpp xml message received on auth failed.\r\n *\r\n * @param {string} msg - The raw failure message from xmpp.\r\n * @returns {string|null} - The parsed message from the raw xmpp message.\r\n */\r\n _parseConnectionFailedMessage(msg) {\r\n if (!msg) {\r\n return null;\r\n }\r\n\r\n const matches = FAILURE_REGEX.exec(msg);\r\n\r\n return matches ? matches[1] : null;\r\n }\r\n\r\n /**\r\n *\r\n * @param jid\r\n * @param password\r\n */\r\n _connect(jid, password) {\r\n // connection.connect() starts the connection process.\r\n //\r\n // As the connection process proceeds, the user supplied callback will\r\n // be triggered multiple times with status updates. The callback should\r\n // take two arguments - the status code and the error condition.\r\n //\r\n // The status code will be one of the values in the Strophe.Status\r\n // constants. The error condition will be one of the conditions defined\r\n // in RFC 3920 or the condition ‘strophe-parsererror’.\r\n //\r\n // The Parameters wait, hold and route are optional and only relevant\r\n // for BOSH connections. Please see XEP 124 for a more detailed\r\n // explanation of the optional parameters.\r\n //\r\n // Connection status constants for use by the connection handler\r\n // callback.\r\n //\r\n // Status.ERROR - An error has occurred (websockets specific)\r\n // Status.CONNECTING - The connection is currently being made\r\n // Status.CONNFAIL - The connection attempt failed\r\n // Status.AUTHENTICATING - The connection is authenticating\r\n // Status.AUTHFAIL - The authentication attempt failed\r\n // Status.CONNECTED - The connection has succeeded\r\n // Status.DISCONNECTED - The connection has been terminated\r\n // Status.DISCONNECTING - The connection is currently being terminated\r\n // Status.ATTACHED - The connection has been attached\r\n\r\n this._resetState();\r\n\r\n // we want to send this only on the initial connect\r\n this.sendDiscoInfo = true;\r\n this.sendDeploymentInfo = true;\r\n\r\n if (this.connection._stropheConn && this.connection._stropheConn._addSysHandler) {\r\n this._sysMessageHandler = this.connection._stropheConn._addSysHandler(\r\n this._onSystemMessage.bind(this),\r\n null,\r\n 'message'\r\n );\r\n } else {\r\n logger.warn('Cannot attach strophe system handler, jiconop cannot operate');\r\n }\r\n\r\n this.connection.connect(\r\n jid,\r\n password,\r\n this.connectionHandler.bind(this, {\r\n jid,\r\n password\r\n }));\r\n }\r\n\r\n /**\r\n * Receives system messages during the connect/login process and checks for services or\r\n * @param msg The received message.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onSystemMessage(msg) {\r\n // proceed only if the message has any of the expected information\r\n if ($(msg).find('>services').length === 0 && $(msg).find('>query').length === 0) {\r\n return;\r\n }\r\n\r\n this.sendDiscoInfo = false;\r\n\r\n const foundIceServers = this.connection.jingle.onReceiveStunAndTurnCredentials(msg);\r\n\r\n const { features, identities } = parseDiscoInfo(msg);\r\n\r\n this._processDiscoInfoIdentities(identities, features);\r\n\r\n if (foundIceServers || identities.size > 0 || features.size > 0) {\r\n this.connection._stropheConn.deleteHandler(this._sysMessageHandler);\r\n this._sysMessageHandler = null;\r\n }\r\n }\r\n\r\n /**\r\n * Attach to existing connection. Can be used for optimizations. For\r\n * example: if the connection is created on the server we can attach to it\r\n * and start using it.\r\n *\r\n * @param options {object} connecting options - rid, sid, jid and password.\r\n */\r\n attach(options) {\r\n this._resetState();\r\n\r\n // we want to send this only on the initial connect\r\n this.sendDiscoInfo = true;\r\n\r\n const now = this.connectionTimes.attaching = window.performance.now();\r\n\r\n logger.log('(TIME) Strophe Attaching:\\t', now);\r\n this.connection.attach(options.jid, options.sid,\r\n parseInt(options.rid, 10) + 1,\r\n this.connectionHandler.bind(this, {\r\n jid: options.jid,\r\n password: options.password\r\n }));\r\n }\r\n\r\n /**\r\n * Resets any state/flag before starting a new connection.\r\n * @private\r\n */\r\n _resetState() {\r\n this.anonymousConnectionFailed = false;\r\n this.connectionFailed = false;\r\n this.lastErrorMsg = undefined;\r\n this.disconnectInProgress = undefined;\r\n }\r\n\r\n /**\r\n *\r\n * @param jid\r\n * @param password\r\n */\r\n connect(jid, password) {\r\n if (!jid) {\r\n const { anonymousdomain, domain } = this.options.hosts;\r\n let configDomain = anonymousdomain || domain;\r\n\r\n // Force authenticated domain if room is appended with '?login=true'\r\n // or if we're joining with the token\r\n\r\n // FIXME Do not rely on window.location because (1) React Native\r\n // does not have a window.location by default and (2) here we cannot\r\n // know for sure that query/search has not be stripped from\r\n // window.location by the time the following executes.\r\n const { location } = window;\r\n\r\n if (anonymousdomain) {\r\n const search = location && location.search;\r\n\r\n if ((search && search.indexOf('login=true') !== -1)\r\n || this.token) {\r\n configDomain = domain;\r\n }\r\n }\r\n\r\n // eslint-disable-next-line no-param-reassign\r\n jid = configDomain || (location && location.hostname);\r\n }\r\n\r\n return this._connect(jid, password);\r\n }\r\n\r\n /**\r\n * Joins or creates a muc with the provided jid, created from the passed\r\n * in room name and muc host and onCreateResource result.\r\n *\r\n * @param {string} roomName - The name of the muc to join.\r\n * @param {Object} options - Configuration for how to join the muc.\r\n * @param {Function} [onCreateResource] - Callback to invoke when a resource\r\n * is to be added to the jid.\r\n * @returns {Promise} Resolves with an instance of a strophe muc.\r\n */\r\n createRoom(roomName, options, onCreateResource) {\r\n // Support passing the domain in a String object as part of the room name.\r\n const domain = roomName.domain || options.customDomain;\r\n\r\n // There are cases (when using subdomain) where muc can hold an uppercase part\r\n let roomjid = `${this.getRoomJid(roomName, domain)}/`;\r\n const mucNickname = `${this.options.nick}-${RandomUtil.randomHexString(8).toLowerCase()}`;\r\n logger.info(`JID ${this.connection.jid} using MUC nickname ${mucNickname}`);\r\n roomjid += mucNickname;\r\n return this.connection.emuc.createRoom(roomjid, null, options);\r\n }\r\n\r\n /**\r\n * Returns the room JID based on the passed room name and domain.\r\n *\r\n * @param {string} roomName - The room name.\r\n * @param {string} domain - The domain.\r\n * @returns {string} - The room JID.\r\n */\r\n getRoomJid(roomName, domain) {\r\n return `${roomName}@${domain ? domain : this.options.hosts.muc.toLowerCase()}`;\r\n }\r\n\r\n /**\r\n * Check if a room with the passed JID is already created.\r\n *\r\n * @param {string} roomJid - The JID of the room.\r\n * @returns {boolean}\r\n */\r\n isRoomCreated(roomName, domain) {\r\n return this.connection.emuc.isRoomCreated(this.getRoomJid(roomName, domain));\r\n }\r\n\r\n /**\r\n * Returns the jid of the participant associated with the Strophe connection.\r\n *\r\n * @returns {string} The jid of the participant.\r\n */\r\n getJid() {\r\n return this.connection.jid;\r\n }\r\n\r\n /**\r\n * Returns the logs from strophe.jingle.\r\n * @returns {Object}\r\n */\r\n getJingleLog() {\r\n const jingle = this.connection.jingle;\r\n\r\n\r\n return jingle ? jingle.getLog() : {};\r\n }\r\n\r\n /**\r\n * Returns the logs from strophe.\r\n */\r\n getXmppLog() {\r\n return (this.connection.logger || {}).log || null;\r\n }\r\n\r\n /**\r\n *\r\n */\r\n dial(...args) {\r\n this.connection.rayo.dial(...args);\r\n }\r\n\r\n /**\r\n * Pings the server.\r\n * @param timeout how many ms before a timeout should occur.\r\n * @returns {Promise} resolved on ping success and reject on an error or\r\n * a timeout.\r\n */\r\n ping(timeout) {\r\n return new Promise((resolve, reject) => {\r\n this.connection.ping.ping(this.connection.pingDomain, resolve, reject, timeout);\r\n });\r\n }\r\n\r\n /**\r\n *\r\n */\r\n getSessions() {\r\n return this.connection.jingle.sessions;\r\n }\r\n\r\n /**\r\n * Disconnects this from the XMPP server (if this is connected).\r\n *\r\n * @param {Object} ev - Optionally, the event which triggered the necessity to\r\n * disconnect from the XMPP server (e.g. beforeunload, unload).\r\n * @returns {Promise} - Resolves when the disconnect process is finished or rejects with an error.\r\n */\r\n disconnect(ev) {\r\n if (this.disconnectInProgress) {\r\n return this.disconnectInProgress;\r\n } else if (!this.connection) {\r\n return Promise.resolve();\r\n }\r\n\r\n this.disconnectInProgress = new Promise(resolve => {\r\n const disconnectListener = (credentials, status) => {\r\n if (status === Strophe.Status.DISCONNECTED) {\r\n resolve();\r\n this.eventEmitter.removeListener(XMPPEvents.CONNECTION_STATUS_CHANGED, disconnectListener);\r\n }\r\n };\r\n\r\n this.eventEmitter.on(XMPPEvents.CONNECTION_STATUS_CHANGED, disconnectListener);\r\n });\r\n\r\n this._cleanupXmppConnection(ev);\r\n\r\n return this.disconnectInProgress;\r\n }\r\n\r\n /**\r\n * The method is supposed to gracefully close the XMPP connection and the main goal is to make sure that the current\r\n * participant will be removed from the conference XMPP MUC, so that it doesn't leave a \"ghost\" participant behind.\r\n *\r\n * @param {Object} ev - Optionally, the event which triggered the necessity to disconnect from the XMPP server\r\n * (e.g. beforeunload, unload).\r\n * @private\r\n * @returns {void}\r\n */\r\n _cleanupXmppConnection(ev) {\r\n // XXX Strophe is asynchronously sending by default. Unfortunately, that means that there may not be enough time\r\n // to send an unavailable presence or disconnect at all. Switching Strophe to synchronous sending is not much of\r\n // an option because it may lead to a noticeable delay in navigating away from the current location. As\r\n // a compromise, we will try to increase the chances of sending an unavailable presence and/or disconnecting\r\n // within the short time span that we have upon unloading by invoking flush() on the connection. We flush() once\r\n // before disconnect() in order to attempt to have its unavailable presence at the top of the send queue. We\r\n // flush() once more after disconnect() in order to attempt to have its unavailable presence sent as soon as\r\n // possible.\r\n !this.connection.isUsingWebSocket && this.connection.flush();\r\n\r\n if (!this.connection.isUsingWebSocket && ev !== null && typeof ev !== 'undefined') {\r\n const evType = ev.type;\r\n\r\n if (evType === 'beforeunload' || evType === 'unload') {\r\n // XXX Whatever we said above, synchronous sending is the best (known) way to properly disconnect from\r\n // the XMPP server. Consequently, it may be fine to have the source code and comment it in or out\r\n // depending on whether we want to run with it for some time.\r\n this.connection.options.sync = true;\r\n\r\n // This is needed in some browsers where sync xhr sending is disabled by default on unload.\r\n if (this.connection.sendUnavailableBeacon()) {\r\n\r\n return;\r\n }\r\n }\r\n }\r\n\r\n this.connection.disconnect();\r\n\r\n if (this.connection.options.sync !== true) {\r\n this.connection.flush();\r\n }\r\n }\r\n\r\n /**\r\n *\r\n */\r\n _initStrophePlugins() {\r\n const iceConfig = {\r\n jvb: { iceServers: [ ] },\r\n p2p: { iceServers: [ ] }\r\n };\r\n\r\n const p2pStunServers = (this.options.p2p\r\n && this.options.p2p.stunServers) || DEFAULT_STUN_SERVERS;\r\n\r\n if (Array.isArray(p2pStunServers)) {\r\n logger.info('P2P STUN servers: ', p2pStunServers);\r\n iceConfig.p2p.iceServers = p2pStunServers;\r\n }\r\n\r\n if (this.options.p2p && this.options.p2p.iceTransportPolicy) {\r\n logger.info('P2P ICE transport policy: ',\r\n this.options.p2p.iceTransportPolicy);\r\n\r\n iceConfig.p2p.iceTransportPolicy\r\n = this.options.p2p.iceTransportPolicy;\r\n }\r\n\r\n this.connection.addConnectionPlugin('emuc', new MucConnectionPlugin(this));\r\n this.connection.addConnectionPlugin('jingle', new JingleConnectionPlugin(this, this.eventEmitter, iceConfig));\r\n this.connection.addConnectionPlugin('rayo', new RayoConnectionPlugin());\r\n }\r\n\r\n /**\r\n * Returns details about connection failure. Shard change or is it after\r\n * suspend.\r\n * @returns {object} contains details about a connection failure.\r\n * @private\r\n */\r\n _getConnectionFailedReasonDetails() {\r\n const details = {};\r\n\r\n // check for moving between shard if information is available\r\n if (this.options.deploymentInfo\r\n && this.options.deploymentInfo.shard\r\n && this.connection.lastResponseHeaders) {\r\n\r\n // split headers by line\r\n const headersArr = this.connection.lastResponseHeaders\r\n .trim().split(/[\\r\\n]+/);\r\n const headers = {};\r\n\r\n headersArr.forEach(line => {\r\n const parts = line.split(': ');\r\n const header = parts.shift();\r\n const value = parts.join(': ');\r\n\r\n headers[header] = value;\r\n });\r\n\r\n /* eslint-disable camelcase */\r\n details.shard_changed\r\n = this.options.deploymentInfo.shard\r\n !== headers['x-jitsi-shard'];\r\n /* eslint-enable camelcase */\r\n }\r\n\r\n /* eslint-disable camelcase */\r\n // check for possible suspend\r\n details.suspend_time = this.connection.ping.getPingSuspendTime();\r\n details.time_since_last_success = this.connection.getTimeSinceLastSuccess();\r\n /* eslint-enable camelcase */\r\n\r\n return details;\r\n }\r\n\r\n /**\r\n * Notifies speaker stats component if available that we are the new\r\n * dominant speaker in the conference.\r\n * @param {String} roomJid - The room jid where the speaker event occurred.\r\n */\r\n sendDominantSpeakerEvent(roomJid) {\r\n // no speaker stats component advertised\r\n if (!this.speakerStatsComponentAddress || !roomJid) {\r\n return;\r\n }\r\n\r\n const msg = $msg({ to: this.speakerStatsComponentAddress });\r\n\r\n msg.c('speakerstats', {\r\n xmlns: 'http://jitsi.org/jitmeet',\r\n room: roomJid })\r\n .up();\r\n\r\n this.connection.send(msg);\r\n }\r\n\r\n /**\r\n * Sends face expressions to speaker stats component.\r\n * @param {String} roomJid - The room jid where the speaker event occurred.\r\n * @param {Object} payload - The expression to be sent to the speaker stats.\r\n */\r\n sendFaceExpressionEvent(roomJid, payload) {\r\n // no speaker stats component advertised\r\n if (!this.speakerStatsComponentAddress || !roomJid) {\r\n return;\r\n }\r\n\r\n const msg = $msg({ to: this.speakerStatsComponentAddress });\r\n\r\n msg.c('faceExpression', {\r\n xmlns: 'http://jitsi.org/jitmeet',\r\n room: roomJid,\r\n expression: payload.faceExpression,\r\n duration: payload.duration\r\n }).up();\r\n\r\n this.connection.send(msg);\r\n }\r\n\r\n /**\r\n * Check if the given argument is a valid JSON ENDPOINT_MESSAGE string by\r\n * parsing it and checking if it has a field called 'type'.\r\n *\r\n * @param {string} jsonString check if this string is a valid json string\r\n * and contains the special structure.\r\n * @returns {boolean, object} if given object is a valid JSON string, return\r\n * the json object. Otherwise, returns false.\r\n */\r\n tryParseJSONAndVerify(jsonString) {\r\n // ignore empty strings, like message errors\r\n if (!jsonString) {\r\n return false;\r\n }\r\n\r\n try {\r\n const json = JSON.parse(jsonString);\r\n\r\n // Handle non-exception-throwing cases:\r\n // Neither JSON.parse(false) or JSON.parse(1234) throw errors,\r\n // hence the type-checking,\r\n // but... JSON.parse(null) returns null, and\r\n // typeof null === \"object\",\r\n // so we must check for that, too.\r\n // Thankfully, null is falsey, so this suffices:\r\n if (json && typeof json === 'object') {\r\n const type = json[JITSI_MEET_MUC_TYPE];\r\n\r\n if (typeof type !== 'undefined') {\r\n return json;\r\n }\r\n\r\n logger.debug('parsing valid json but does not have correct '\r\n + 'structure', 'topic: ', type);\r\n }\r\n } catch (e) {\r\n logger.error(`Error parsing json ${jsonString}`, e);\r\n\r\n return false;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * A private message is received, message that is not addressed to the muc.\r\n * We expect private message coming from plugins component if it is\r\n * enabled and running.\r\n *\r\n * @param {string} msg - The message.\r\n */\r\n _onPrivateMessage(msg) {\r\n const from = msg.getAttribute('from');\r\n\r\n if (!(from === this.speakerStatsComponentAddress\r\n || from === this.conferenceDurationComponentAddress\r\n || from === this.avModerationComponentAddress\r\n || from === this.breakoutRoomsComponentAddress)) {\r\n return true;\r\n }\r\n\r\n const jsonMessage = $(msg).find('>json-message')\r\n .text();\r\n const parsedJson = this.tryParseJSONAndVerify(jsonMessage);\r\n\r\n if (!parsedJson) {\r\n return true;\r\n }\r\n\r\n if (parsedJson[JITSI_MEET_MUC_TYPE] === 'speakerstats' && parsedJson.users) {\r\n this.eventEmitter.emit(XMPPEvents.SPEAKER_STATS_RECEIVED, parsedJson.users);\r\n } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'conference_duration' && parsedJson.created_timestamp) {\r\n this.eventEmitter.emit(XMPPEvents.CONFERENCE_TIMESTAMP_RECEIVED, parsedJson.created_timestamp);\r\n } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'av_moderation') {\r\n this.eventEmitter.emit(XMPPEvents.AV_MODERATION_RECEIVED, parsedJson);\r\n } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'breakout_rooms') {\r\n this.eventEmitter.emit(XMPPEvents.BREAKOUT_ROOMS_EVENT, parsedJson);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Sends deployment info to stats if not sent already.\r\n * We want to try sending it on failure to connect\r\n * or when we get a sys message(from jiconop2)\r\n * or after success or failure of disco-info\r\n * @param force Whether to force sending without checking anything.\r\n * @private\r\n */\r\n _maybeSendDeploymentInfoStat(force) {\r\n const acceptedStatuses = [\r\n Strophe.Status.ERROR,\r\n Strophe.Status.CONNFAIL,\r\n Strophe.Status.AUTHFAIL,\r\n Strophe.Status.DISCONNECTED,\r\n Strophe.Status.CONNTIMEOUT\r\n ];\r\n\r\n if (!force && !(acceptedStatuses.includes(this.connection.status) && this.sendDeploymentInfo)) {\r\n return;\r\n }\r\n\r\n // Log deployment-specific information, if available. Defined outside\r\n // the application by individual deployments\r\n const aprops = this.options.deploymentInfo;\r\n\r\n if (aprops && Object.keys(aprops).length > 0) {\r\n const logObject = {};\r\n\r\n for (const attr in aprops) {\r\n if (aprops.hasOwnProperty(attr)) {\r\n logObject[attr] = aprops[attr];\r\n }\r\n }\r\n\r\n // Let's push to analytics any updates that may have come from the backend\r\n Statistics.analytics.addPermanentProperties({ ...logObject });\r\n\r\n logObject.id = 'deployment_info';\r\n Statistics.sendLog(JSON.stringify(logObject));\r\n }\r\n\r\n this.sendDeploymentInfo = false;\r\n }\r\n}\r\n","import {\r\n CONNECTION_DISCONNECTED,\r\n CONNECTION_ESTABLISHED,\r\n CONNECTION_FAILED\r\n} from './JitsiConnectionEvents';\r\nimport XMPP from './modules/xmpp/xmpp';\r\n\r\n/**\r\n * @typedef {Object} UpgradeRoleError\r\n *\r\n * @property {JitsiConnectionErrors} [connectionError] - One of\r\n * {@link JitsiConnectionErrors} which occurred when trying to connect to the\r\n * XMPP server.\r\n * @property {String} [authenticationError] - One of XMPP error conditions\r\n * returned by Jicofo on authentication attempt. See\r\n * {@link https://xmpp.org/rfcs/rfc3920.html#streams-error}.\r\n * @property {String} [message] - More details about the error.\r\n * @property {Object} [credentials] - The credentials that failed the\r\n * authentication.\r\n * @property {String} [credentials.jid] - The XMPP ID part of the credentials\r\n * that failed the authentication.\r\n * @property {string} [credentials.password] - The password part of the\r\n * credentials that failed the authentication.\r\n *\r\n * NOTE If neither one of the errors is present, then the operation has been\r\n * canceled.\r\n */\r\n\r\n/* eslint-disable no-invalid-this */\r\n\r\n/**\r\n * Connects to the XMPP server using the specified credentials and contacts\r\n * Jicofo in order to obtain a session ID (which is then stored in the local\r\n * storage). The user's role of the parent conference will be upgraded to\r\n * moderator (by Jicofo). It's also used to join the conference when starting\r\n * from anonymous domain and only authenticated users are allowed to create new\r\n * rooms.\r\n *\r\n * @param {Object} options\r\n * @param {string} options.id - XMPP user's ID to log in. For example,\r\n * user@xmpp-server.com.\r\n * @param {string} options.password - XMPP user's password to log in with.\r\n * @param {string} [options.roomPassword] - The password to join the MUC with.\r\n * @param {Function} [options.onLoginSuccessful] - Callback called when logging\r\n * into the XMPP server was successful. The next step will be to obtain a new\r\n * session ID from Jicofo and join the MUC using it which will effectively\r\n * upgrade the user's role to moderator.\r\n * @returns {Object} A thenable which (1) settles when the process of\r\n * authenticating and upgrading the role of the specified XMPP user finishes and\r\n * (2) has a cancel method that allows the caller to interrupt the\r\n * process. If the process finishes successfully, the session ID has been stored\r\n * in the settings and the thenable is resolved. If the process\r\n * finishes with failure, the thenable is rejected with reason of type\r\n * {@link UpgradeRoleError} which will have either connectionError or\r\n * authenticationError property set depending on which of the steps has\r\n * failed. If cancel is called before the process finishes, then the\r\n * thenable will be rejected with an empty object (i.e. no error property will\r\n * be set on the rejection reason).\r\n */\r\nexport default function authenticateAndUpgradeRole({\r\n // 1. Log the specified XMPP user in.\r\n id,\r\n password,\r\n onCreateResource,\r\n\r\n // 2. Let the API client/consumer know as soon as the XMPP user has been\r\n // successfully logged in.\r\n onLoginSuccessful,\r\n\r\n // 3. Join the MUC.\r\n roomPassword\r\n}) {\r\n let canceled = false;\r\n let rejectPromise;\r\n let xmpp = new XMPP(this.connection.options);\r\n\r\n const process = new Promise((resolve, reject) => {\r\n // The process is represented by a Thenable with a cancel method. The\r\n // Thenable is implemented using Promise and the cancel using the\r\n // Promise's reject function.\r\n rejectPromise = reject;\r\n\r\n\r\n xmpp.addListener(\r\n CONNECTION_DISCONNECTED,\r\n () => {\r\n xmpp = undefined;\r\n });\r\n xmpp.addListener(\r\n CONNECTION_ESTABLISHED,\r\n () => {\r\n if (canceled) {\r\n return;\r\n }\r\n\r\n // Let the caller know that the XMPP login was successful.\r\n onLoginSuccessful && onLoginSuccessful();\r\n\r\n // Now authenticate with Jicofo and get a new session ID.\r\n const room = xmpp.createRoom(\r\n this.options.name,\r\n this.options.config,\r\n onCreateResource\r\n );\r\n\r\n room.moderator.authenticate()\r\n .then(() => {\r\n xmpp && xmpp.disconnect();\r\n\r\n if (canceled) {\r\n return;\r\n }\r\n\r\n // At this point we should have the new session ID\r\n // stored in the settings. Jicofo will allow to join the\r\n // room.\r\n this.join(roomPassword);\r\n\r\n resolve();\r\n })\r\n .catch(({ error, message }) => {\r\n xmpp.disconnect();\r\n\r\n reject({\r\n authenticationError: error,\r\n message\r\n });\r\n });\r\n });\r\n xmpp.addListener(\r\n CONNECTION_FAILED,\r\n (connectionError, message, credentials) => {\r\n reject({\r\n connectionError,\r\n credentials,\r\n message\r\n });\r\n xmpp = undefined;\r\n });\r\n\r\n canceled || xmpp.connect(id, password);\r\n });\r\n\r\n /**\r\n * Cancels the process, if it's in progress, of authenticating and upgrading\r\n * the role of the local participant/user.\r\n *\r\n * @public\r\n * @returns {void}\r\n */\r\n process.cancel = () => {\r\n canceled = true;\r\n rejectPromise({});\r\n xmpp && xmpp.disconnect();\r\n };\r\n\r\n return process;\r\n}\r\n\r\n/* eslint-enable no-invalid-this */\r\n","\r\nimport { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport CodecMimeType from '../../service/RTC/CodecMimeType';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport browser from '../browser';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * This class handles the codec selection mechanism for the conference based on the config.js settings.\r\n * The preferred codec is selected based on the settings and the list of codecs supported by the browser.\r\n * The preferred codec is published in presence which is then used by the other endpoints in the\r\n * conference to pick a supported codec at join time and when the call transitions between p2p and jvb\r\n * connections.\r\n */\r\nexport class CodecSelection {\r\n /**\r\n * Creates a new instance for a given conference.\r\n *\r\n * @param {JitsiConference} conference the conference instance\r\n * @param {*} options\r\n * @param {string} options.disabledCodec the codec that needs to be disabled.\r\n * @param {boolean} options.enforcePreferredCodec whether codec preference has to be\r\n * enforced even when an endpoints that doesn't support the preferred codec joins the call.\r\n * Falling back to the standard codec will be skipped when this option is true, endpoints\r\n * that do not support the preferred codec may not be able to encode/decode video when this happens.\r\n * @param {string} options.jvbCodec the codec that is preferred on jvb connection.\r\n * @param {string} options.p2pCodec the codec that is preferred on p2p connection.\r\n */\r\n constructor(conference, options) {\r\n this.conference = conference;\r\n this.options = options;\r\n\r\n // VP8 cannot be disabled and it will be the default codec when no preference is set.\r\n this.disabledCodec = options.disabledCodec === CodecMimeType.VP8\r\n ? undefined\r\n : this._getCodecMimeType(options.disabledCodec);\r\n\r\n // Check if the codec values passed are valid.\r\n const jvbCodec = this._getCodecMimeType(options.jvbCodec);\r\n const p2pCodec = this._getCodecMimeType(options.p2pCodec);\r\n\r\n this.jvbPreferredCodec = jvbCodec && this._isCodecSupported(jvbCodec) ? jvbCodec : CodecMimeType.VP8;\r\n this.p2pPreferredCodec = p2pCodec && this._isCodecSupported(p2pCodec) ? p2pCodec : CodecMimeType.VP8;\r\n logger.debug(`Codec preferences for the conference are JVB: ${this.jvbPreferredCodec},\r\n P2P: ${this.p2pPreferredCodec}`);\r\n\r\n if (this.jvbPreferredCodec === CodecMimeType.VP9 && !browser.supportsVP9()) {\r\n this.jvbPreferredCodec = CodecMimeType.VP8;\r\n }\r\n\r\n this.conference.on(\r\n JitsiConferenceEvents.USER_JOINED,\r\n () => this._selectPreferredCodec());\r\n this.conference.on(\r\n JitsiConferenceEvents.USER_LEFT,\r\n () => this._selectPreferredCodec());\r\n this.conference.on(\r\n JitsiConferenceEvents._MEDIA_SESSION_STARTED,\r\n session => this._onMediaSessionStarted(session));\r\n }\r\n\r\n /**\r\n * Checks if a given string is a valid video codec mime type.\r\n *\r\n * @param {string} codec the codec string that needs to be validated.\r\n * @returns {CodecMimeType|null} mime type if valid, null otherwise.\r\n * @private\r\n */\r\n _getCodecMimeType(codec) {\r\n if (typeof codec === 'string') {\r\n return Object.values(CodecMimeType).find(value => value === codec.toLowerCase());\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Checks if the given codec is supported by the browser.\r\n *\r\n * @param {CodecMimeType} preferredCodec codec to be checked.\r\n * @returns {boolean} true if the given codec is supported, false otherwise.\r\n * @private\r\n */\r\n _isCodecSupported(preferredCodec) {\r\n // Skip the check on FF and RN because they do not support the getCapabilities API.\r\n // It is safe to assume both of them support all the codecs supported by Chrome.\r\n if (browser.isFirefox() || browser.isReactNative()) {\r\n return true;\r\n }\r\n\r\n return window.RTCRtpReceiver\r\n && window.RTCRtpReceiver.getCapabilities\r\n && window.RTCRtpReceiver.getCapabilities('video').codecs\r\n .some(codec => codec.mimeType.toLowerCase() === `video/${preferredCodec}`);\r\n }\r\n\r\n /**\r\n * Handles the {@link JitsiConferenceEvents._MEDIA_SESSION_STARTED} event. Codecs need to be\r\n * configured on the media session that is newly created.\r\n *\r\n * @param {JingleSessionPC} mediaSession media session that started.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onMediaSessionStarted(mediaSession) {\r\n const preferredCodec = mediaSession.isP2P ? this.p2pPreferredCodec : this.jvbPreferredCodec;\r\n const disabledCodec = this.disabledCodec && this._isCodecSupported(this.disabledCodec)\r\n ? this.disabledCodec\r\n : null;\r\n\r\n this._selectPreferredCodec(mediaSession, preferredCodec, disabledCodec);\r\n }\r\n\r\n /**\r\n * Sets the codec on the media session based on the preferred codec setting and the supported codecs\r\n * published by the remote participants in their presence.\r\n *\r\n * @param {JingleSessionPC} mediaSession session for which the codec selection has to be made.\r\n * @param {CodecMimeType} preferredCodec preferred codec.\r\n * @param {CodecMimeType} disabledCodec codec that needs to be disabled.\r\n */\r\n _selectPreferredCodec(mediaSession = null, preferredCodec = null, disabledCodec = null) {\r\n const session = mediaSession ? mediaSession : this.conference.jvbJingleSession;\r\n const currentCodec = preferredCodec ? preferredCodec : this.jvbPreferredCodec;\r\n let selectedCodec = currentCodec;\r\n\r\n if (session && !session.isP2P && !this.options.enforcePreferredCodec) {\r\n const remoteParticipants = this.conference.getParticipants().map(participant => participant.getId());\r\n\r\n for (const remote of remoteParticipants) {\r\n const peerMediaInfo = session._signalingLayer.getPeerMediaInfo(remote, MediaType.VIDEO);\r\n const peerCodec = peerMediaInfo?.codecType;\r\n\r\n if (peerCodec\r\n && peerCodec !== currentCodec\r\n && (peerCodec !== CodecMimeType.VP9 || browser.supportsVP9())) {\r\n selectedCodec = peerCodec;\r\n }\r\n }\r\n }\r\n session && session.setVideoCodecs(selectedCodec, disabledCodec);\r\n }\r\n\r\n /**\r\n * Returns the preferred codec for the conference. The preferred codec for the JVB media session\r\n * is the one that gets published in presence and a comparision is made whenever a participant joins\r\n * or leaves the call.\r\n *\r\n * @returns {CodecMimeType} preferred codec.\r\n */\r\n getPreferredCodec() {\r\n return this.jvbPreferredCodec;\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport { createBridgeChannelClosedEvent } from '../../service/statistics/AnalyticsEvents';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport Statistics from '../statistics/statistics';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Handles a WebRTC RTCPeerConnection or a WebSocket instance to communicate\r\n * with the videobridge.\r\n */\r\nexport default class BridgeChannel {\r\n /**\r\n * Binds \"ondatachannel\" event listener on the given RTCPeerConnection\r\n * instance, or creates a WebSocket connection with the videobridge.\r\n * At least one of both, peerconnection or wsUrl parameters, must be\r\n * given.\r\n * @param {RTCPeerConnection} [peerconnection] WebRTC peer connection\r\n * instance.\r\n * @param {string} [wsUrl] WebSocket URL.\r\n * @param {EventEmitter} emitter the EventEmitter instance to use for event emission.\r\n */\r\n constructor(peerconnection, wsUrl, emitter) {\r\n if (!peerconnection && !wsUrl) {\r\n throw new TypeError('At least peerconnection or wsUrl must be given');\r\n } else if (peerconnection && wsUrl) {\r\n throw new TypeError('Just one of peerconnection or wsUrl must be given');\r\n }\r\n\r\n if (peerconnection) {\r\n logger.debug('constructor() with peerconnection');\r\n } else {\r\n logger.debug(`constructor() with wsUrl:\"${wsUrl}\"`);\r\n }\r\n\r\n // The underlying WebRTC RTCDataChannel or WebSocket instance.\r\n // @type {RTCDataChannel|WebSocket}\r\n this._channel = null;\r\n\r\n // @type {EventEmitter}\r\n this._eventEmitter = emitter;\r\n\r\n // Whether a RTCDataChannel or WebSocket is internally used.\r\n // @type {string} \"datachannel\" / \"websocket\"\r\n this._mode = null;\r\n\r\n // Indicates whether the connection retries are enabled or not.\r\n this._areRetriesEnabled = false;\r\n\r\n // Indicates whether the connection was closed from the client or not.\r\n this._closedFromClient = false;\r\n\r\n // If a RTCPeerConnection is given, listen for new RTCDataChannel\r\n // event.\r\n if (peerconnection) {\r\n const datachannel\r\n = peerconnection.createDataChannel(\r\n 'JVB data channel', {\r\n protocol: 'http://jitsi.org/protocols/colibri'\r\n });\r\n\r\n // Handle the RTCDataChannel.\r\n this._handleChannel(datachannel);\r\n this._mode = 'datachannel';\r\n\r\n // Otherwise create a WebSocket connection.\r\n } else if (wsUrl) {\r\n this._areRetriesEnabled = true;\r\n this._wsUrl = wsUrl;\r\n this._initWebSocket();\r\n }\r\n }\r\n\r\n /**\r\n * Initializes the web socket channel.\r\n *\r\n * @returns {void}\r\n */\r\n _initWebSocket() {\r\n // Create a WebSocket instance.\r\n const ws = new WebSocket(this._wsUrl);\r\n\r\n // Handle the WebSocket.\r\n this._handleChannel(ws);\r\n this._mode = 'websocket';\r\n }\r\n\r\n /**\r\n * Starts the websocket connection retries.\r\n *\r\n * @returns {void}\r\n */\r\n _startConnectionRetries() {\r\n let timeoutS = 1;\r\n\r\n const reload = () => {\r\n if (this.isOpen()) {\r\n return;\r\n }\r\n this._initWebSocket(this._wsUrl);\r\n timeoutS = Math.min(timeoutS * 2, 60);\r\n this._retryTimeout = setTimeout(reload, timeoutS * 1000);\r\n };\r\n\r\n this._retryTimeout = setTimeout(reload, timeoutS * 1000);\r\n }\r\n\r\n /**\r\n * Stops the websocket connection retries.\r\n *\r\n * @returns {void}\r\n */\r\n _stopConnectionRetries() {\r\n if (this._retryTimeout) {\r\n clearTimeout(this._retryTimeout);\r\n this._retryTimeout = undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Retries to establish the websocket connection after the connection was closed by the server.\r\n *\r\n * @param {CloseEvent} closeEvent - The close event that triggered the retries.\r\n * @returns {void}\r\n */\r\n _retryWebSocketConnection(closeEvent) {\r\n if (!this._areRetriesEnabled) {\r\n return;\r\n }\r\n const { code, reason } = closeEvent;\r\n\r\n Statistics.sendAnalytics(createBridgeChannelClosedEvent(code, reason));\r\n this._areRetriesEnabled = false;\r\n this._eventEmitter.once(RTCEvents.DATA_CHANNEL_OPEN, () => {\r\n this._stopConnectionRetries();\r\n this._areRetriesEnabled = true;\r\n });\r\n this._startConnectionRetries();\r\n }\r\n\r\n /**\r\n * The channel mode.\r\n * @return {string} \"datachannel\" or \"websocket\" (or null if not yet set).\r\n */\r\n get mode() {\r\n return this._mode;\r\n }\r\n\r\n /**\r\n * Closes the currently opened channel.\r\n */\r\n close() {\r\n this._closedFromClient = true;\r\n this._stopConnectionRetries();\r\n this._areRetriesEnabled = false;\r\n if (this._channel) {\r\n try {\r\n this._channel.close();\r\n } catch (error) {} // eslint-disable-line no-empty\r\n\r\n this._channel = null;\r\n }\r\n }\r\n\r\n /**\r\n * Whether there is an underlying RTCDataChannel or WebSocket and it's\r\n * open.\r\n * @return {boolean}\r\n */\r\n isOpen() {\r\n return this._channel && (this._channel.readyState === 'open'\r\n || this._channel.readyState === WebSocket.OPEN);\r\n }\r\n\r\n /**\r\n * Sends local stats via the bridge channel.\r\n * @param {Object} payload The payload of the message.\r\n * @throws NetworkError/InvalidStateError/Error if the operation fails or if there is no data channel created.\r\n */\r\n sendEndpointStatsMessage(payload) {\r\n this._send({\r\n colibriClass: 'EndpointStats',\r\n ...payload\r\n });\r\n }\r\n\r\n /**\r\n * Sends message via the channel.\r\n * @param {string} to The id of the endpoint that should receive the\r\n * message. If \"\" the message will be sent to all participants.\r\n * @param {object} payload The payload of the message.\r\n * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see\r\n * {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})\r\n * or from WebSocket#send or Error with \"No opened channel\" message.\r\n */\r\n sendMessage(to, payload) {\r\n this._send({\r\n colibriClass: 'EndpointMessage',\r\n msgPayload: payload,\r\n to\r\n });\r\n }\r\n\r\n /**\r\n * Sends a \"lastN value changed\" message via the channel.\r\n * @param {number} value The new value for lastN. -1 means unlimited.\r\n */\r\n sendSetLastNMessage(value) {\r\n logger.log(`Sending lastN=${value}.`);\r\n\r\n this._send({\r\n colibriClass: 'LastNChangedEvent',\r\n lastN: value\r\n });\r\n }\r\n\r\n /**\r\n * Sends a \"selected endpoints changed\" message via the channel.\r\n *\r\n * @param {Array} endpointIds - The ids of the selected endpoints.\r\n * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see\r\n * {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})\r\n * or from WebSocket#send or Error with \"No opened channel\" message.\r\n */\r\n sendSelectedEndpointsMessage(endpointIds) {\r\n logger.log(`Sending selected endpoints: ${endpointIds}.`);\r\n\r\n this._send({\r\n colibriClass: 'SelectedEndpointsChangedEvent',\r\n selectedEndpoints: endpointIds\r\n });\r\n }\r\n\r\n /**\r\n * Sends a \"receiver video constraint\" message via the channel.\r\n * @param {Number} maxFrameHeightPixels the maximum frame height,\r\n * in pixels, this receiver is willing to receive\r\n */\r\n sendReceiverVideoConstraintMessage(maxFrameHeightPixels) {\r\n logger.log(`Sending ReceiverVideoConstraint with maxFrameHeight=${maxFrameHeightPixels}px`);\r\n this._send({\r\n colibriClass: 'ReceiverVideoConstraint',\r\n maxFrameHeight: maxFrameHeightPixels\r\n });\r\n }\r\n\r\n /**\r\n * Sends a 'ReceiverVideoConstraints' message via the bridge channel.\r\n *\r\n * @param {ReceiverVideoConstraints} constraints video constraints.\r\n */\r\n sendNewReceiverVideoConstraintsMessage(constraints) {\r\n logger.log(`Sending ReceiverVideoConstraints with ${JSON.stringify(constraints)}`);\r\n this._send({\r\n colibriClass: 'ReceiverVideoConstraints',\r\n ...constraints\r\n });\r\n }\r\n\r\n /**\r\n * Sends a 'VideoTypeMessage' message via the bridge channel.\r\n *\r\n * @param {string} videoType 'camera', 'desktop' or 'none'.\r\n * @deprecated to be replaced with sendSourceVideoTypeMessage\r\n */\r\n sendVideoTypeMessage(videoType) {\r\n logger.debug(`Sending VideoTypeMessage with video type as ${videoType}`);\r\n this._send({\r\n colibriClass: 'VideoTypeMessage',\r\n videoType\r\n });\r\n }\r\n\r\n /**\r\n * Sends a 'VideoTypeMessage' message via the bridge channel.\r\n *\r\n * @param {BridgeVideoType} videoType - the video type.\r\n * @param {SourceName} sourceName - the source name of the video track.\r\n * @returns {void}\r\n */\r\n sendSourceVideoTypeMessage(sourceName, videoType) {\r\n logger.info(`Sending SourceVideoTypeMessage with video type ${sourceName}: ${videoType}`);\r\n this._send({\r\n colibriClass: 'SourceVideoTypeMessage',\r\n sourceName,\r\n videoType\r\n });\r\n }\r\n\r\n /**\r\n * Set events on the given RTCDataChannel or WebSocket instance.\r\n */\r\n _handleChannel(channel) {\r\n const emitter = this._eventEmitter;\r\n\r\n channel.onopen = () => {\r\n logger.info(`${this._mode} channel opened`);\r\n\r\n // Code sample for sending string and/or binary data.\r\n // Sends string message to the bridge:\r\n // channel.send(\"Hello bridge!\");\r\n // Sends 12 bytes binary message to the bridge:\r\n // channel.send(new ArrayBuffer(12));\r\n\r\n emitter.emit(RTCEvents.DATA_CHANNEL_OPEN);\r\n };\r\n\r\n channel.onerror = event => {\r\n // WS error events contain no information about the failure (this is available in the onclose event) and\r\n // the event references the WS object itself, which causes hangs on mobile.\r\n if (this._mode !== 'websocket') {\r\n logger.error(`Channel error: ${event.message}`);\r\n }\r\n };\r\n\r\n channel.onmessage = ({ data }) => {\r\n // JSON object.\r\n let obj;\r\n\r\n try {\r\n obj = JSON.parse(data);\r\n } catch (error) {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error('Failed to parse channel message as JSON: ', data, error);\r\n\r\n return;\r\n }\r\n\r\n const colibriClass = obj.colibriClass;\r\n\r\n switch (colibriClass) {\r\n case 'DominantSpeakerEndpointChangeEvent': {\r\n const { dominantSpeakerEndpoint, previousSpeakers = [] } = obj;\r\n\r\n logger.debug(`Dominant speaker: ${dominantSpeakerEndpoint}, previous speakers: ${previousSpeakers}`);\r\n emitter.emit(RTCEvents.DOMINANT_SPEAKER_CHANGED, dominantSpeakerEndpoint, previousSpeakers);\r\n break;\r\n }\r\n case 'EndpointConnectivityStatusChangeEvent': {\r\n const endpoint = obj.endpoint;\r\n const isActive = obj.active === 'true';\r\n\r\n logger.info(`Endpoint connection status changed: ${endpoint} active=${isActive}`);\r\n emitter.emit(RTCEvents.ENDPOINT_CONN_STATUS_CHANGED, endpoint, isActive);\r\n\r\n break;\r\n }\r\n case 'EndpointMessage': {\r\n emitter.emit(RTCEvents.ENDPOINT_MESSAGE_RECEIVED, obj.from, obj.msgPayload);\r\n\r\n break;\r\n }\r\n case 'EndpointStats': {\r\n emitter.emit(RTCEvents.ENDPOINT_STATS_RECEIVED, obj.from, obj);\r\n\r\n break;\r\n }\r\n case 'LastNEndpointsChangeEvent': {\r\n if (!FeatureFlags.isSourceNameSignalingEnabled()) {\r\n // The new/latest list of last-n endpoint IDs (i.e. endpoints for which the bridge is sending\r\n // video).\r\n const lastNEndpoints = obj.lastNEndpoints;\r\n\r\n logger.info(`New forwarded endpoints: ${lastNEndpoints}`);\r\n emitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED, lastNEndpoints);\r\n }\r\n\r\n break;\r\n }\r\n case 'ForwardedSources': {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n // The new/latest list of forwarded sources\r\n const forwardedSources = obj.forwardedSources;\r\n\r\n logger.info(`New forwarded sources: ${forwardedSources}`);\r\n emitter.emit(RTCEvents.FORWARDED_SOURCES_CHANGED, forwardedSources);\r\n }\r\n\r\n break;\r\n }\r\n case 'SenderVideoConstraints': {\r\n const videoConstraints = obj.videoConstraints;\r\n\r\n if (videoConstraints) {\r\n logger.info(`SenderVideoConstraints: ${JSON.stringify(videoConstraints)}`);\r\n emitter.emit(RTCEvents.SENDER_VIDEO_CONSTRAINTS_CHANGED, videoConstraints);\r\n }\r\n break;\r\n }\r\n case 'SenderSourceConstraints': {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n const { sourceName, maxHeight } = obj;\r\n\r\n if (typeof sourceName === 'string' && typeof maxHeight === 'number') {\r\n // eslint-disable-next-line object-property-newline\r\n logger.info(`SenderSourceConstraints: ${JSON.stringify({ sourceName, maxHeight })}`);\r\n emitter.emit(\r\n RTCEvents.SENDER_VIDEO_CONSTRAINTS_CHANGED, {\r\n sourceName,\r\n maxHeight\r\n }\r\n );\r\n } else {\r\n logger.error(`Invalid SenderSourceConstraints: ${JSON.stringify(obj)}`);\r\n }\r\n }\r\n break;\r\n }\r\n case 'ServerHello': {\r\n logger.info(`Received ServerHello, version=${obj.version}.`);\r\n break;\r\n }\r\n default: {\r\n logger.debug('Channel JSON-formatted message: ', obj);\r\n\r\n // The received message appears to be appropriately formatted\r\n // (i.e. is a JSON object which assigns a value to the\r\n // mandatory property colibriClass) so don't just swallow it,\r\n // expose it to public consumption.\r\n emitter.emit(`rtc.datachannel.${colibriClass}`, obj);\r\n }\r\n }\r\n };\r\n\r\n channel.onclose = event => {\r\n logger.info(`Channel closed by ${this._closedFromClient ? 'client' : 'server'}`);\r\n\r\n if (this._mode === 'websocket') {\r\n if (!this._closedFromClient) {\r\n logger.error(`Channel closed: ${event.code} ${event.reason}`);\r\n this._retryWebSocketConnection(event);\r\n }\r\n }\r\n\r\n // Remove the channel.\r\n this._channel = null;\r\n };\r\n\r\n // Store the channel.\r\n this._channel = channel;\r\n }\r\n\r\n /**\r\n * Sends passed object via the channel.\r\n * @param {object} jsonObject The object that will be sent.\r\n * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see\r\n * {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})\r\n * or from WebSocket#send or Error with \"No opened channel\" message.\r\n */\r\n _send(jsonObject) {\r\n const channel = this._channel;\r\n\r\n if (!this.isOpen()) {\r\n logger.error('Bridge Channel send: no opened channel.');\r\n throw new Error('No opened channel');\r\n }\r\n\r\n channel.send(JSON.stringify(jsonObject));\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport EventEmitter from 'events';\r\nimport clonedeep from 'lodash.clonedeep';\r\n\r\nimport JitsiTrackError from '../../JitsiTrackError';\r\nimport * as JitsiTrackErrors from '../../JitsiTrackErrors';\r\nimport CameraFacingMode from '../../service/RTC/CameraFacingMode';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport Resolutions from '../../service/RTC/Resolutions';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport { AVAILABLE_DEVICE } from '../../service/statistics/AnalyticsEvents';\r\nimport browser from '../browser';\r\nimport SDPUtil from '../sdp/SDPUtil';\r\nimport Statistics from '../statistics/statistics';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\nimport Listenable from '../util/Listenable';\r\n\r\nimport screenObtainer from './ScreenObtainer';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n// Require adapter only for certain browsers. This is being done for\r\n// react-native, which has its own shims, and while browsers are being migrated\r\n// over to use adapter's shims.\r\nif (browser.usesAdapter()) {\r\n require('webrtc-adapter');\r\n}\r\n\r\nconst eventEmitter = new EventEmitter();\r\n\r\nconst AVAILABLE_DEVICES_POLL_INTERVAL_TIME = 3000; // ms\r\n\r\n/**\r\n * Default MediaStreamConstraints to use for calls to getUserMedia.\r\n *\r\n * @private\r\n */\r\nconst DEFAULT_CONSTRAINTS = {\r\n video: {\r\n height: {\r\n ideal: 720,\r\n max: 720,\r\n min: 180\r\n },\r\n width: {\r\n ideal: 1280,\r\n max: 1280,\r\n min: 320\r\n }\r\n }\r\n};\r\n\r\n// Currently audio output device change is supported only in Chrome and\r\n// default output always has 'default' device ID\r\nlet audioOutputDeviceId = 'default'; // default device\r\n// whether user has explicitly set a device to use\r\nlet audioOutputChanged = false;\r\n\r\n// Disables all audio processing\r\nlet disableAP = false;\r\n\r\n// Disables Acoustic Echo Cancellation\r\nlet disableAEC = false;\r\n\r\n// Disables Noise Suppression\r\nlet disableNS = false;\r\n\r\n// Disables Automatic Gain Control\r\nlet disableAGC = false;\r\n\r\n// Enables stereo.\r\nlet stereo = null;\r\n\r\nconst featureDetectionAudioEl = document.createElement('audio');\r\nconst isAudioOutputDeviceChangeAvailable\r\n = typeof featureDetectionAudioEl.setSinkId !== 'undefined';\r\n\r\nlet availableDevices = [];\r\nlet availableDevicesPollTimer;\r\n\r\n/**\r\n * An empty function.\r\n */\r\nfunction emptyFuncton() {\r\n // no-op\r\n}\r\n\r\n/**\r\n * Creates a constraints object to be passed into a call to getUserMedia.\r\n *\r\n * @param {Array} um - An array of user media types to get. The accepted types are \"video\", \"audio\", and \"desktop.\"\r\n * @param {Object} options - Various values to be added to the constraints.\r\n * @param {string} options.cameraDeviceId - The device id for the video capture device to get video from.\r\n * @param {Object} options.constraints - Default constraints object to use as a base for the returned constraints.\r\n * @param {Object} options.desktopStream - The desktop source id from which to capture a desktop sharing video.\r\n * @param {string} options.facingMode - Which direction the camera is pointing to (applicable on mobile)\r\n * @param {string} options.micDeviceId - The device id for the audio capture device to get audio from.\r\n * @private\r\n * @returns {Object}\r\n */\r\nfunction getConstraints(um = [], options = {}) {\r\n // Create a deep copy of the constraints to avoid any modification of\r\n // the passed in constraints object.\r\n const constraints = clonedeep(options.constraints || DEFAULT_CONSTRAINTS);\r\n\r\n if (um.indexOf('video') >= 0) {\r\n // The \"resolution\" option is a shortcut and takes precendence.\r\n if (Resolutions[options.resolution]) {\r\n const r = Resolutions[options.resolution];\r\n\r\n constraints.video.height = { ideal: r.height };\r\n constraints.video.width = { ideal: r.width };\r\n }\r\n\r\n if (!constraints.video) {\r\n constraints.video = {};\r\n }\r\n\r\n // Override the constraints on Safari because of the following webkit bug.\r\n // https://bugs.webkit.org/show_bug.cgi?id=210932\r\n // Camera doesn't start on older macOS versions if min/max constraints are specified.\r\n // TODO: remove this hack when the bug fix is available on Mojave, Sierra and High Sierra.\r\n if (browser.isWebKitBased()) {\r\n if (constraints.video.height && constraints.video.height.ideal) {\r\n constraints.video.height = { ideal: constraints.video.height.ideal };\r\n } else {\r\n logger.warn('Ideal camera height missing, camera may not start properly');\r\n }\r\n if (constraints.video.width && constraints.video.width.ideal) {\r\n constraints.video.width = { ideal: constraints.video.width.ideal };\r\n } else {\r\n logger.warn('Ideal camera width missing, camera may not start properly');\r\n }\r\n }\r\n if (options.cameraDeviceId) {\r\n constraints.video.deviceId = options.cameraDeviceId;\r\n } else {\r\n const facingMode = options.facingMode || CameraFacingMode.USER;\r\n\r\n constraints.video.facingMode = facingMode;\r\n }\r\n } else {\r\n constraints.video = false;\r\n }\r\n\r\n if (um.indexOf('audio') >= 0) {\r\n if (!constraints.audio || typeof constraints.audio === 'boolean') {\r\n constraints.audio = {};\r\n }\r\n\r\n constraints.audio = {\r\n autoGainControl: !disableAGC && !disableAP,\r\n deviceId: options.micDeviceId,\r\n echoCancellation: !disableAEC && !disableAP,\r\n noiseSuppression: !disableNS && !disableAP\r\n };\r\n\r\n if (stereo) {\r\n Object.assign(constraints.audio, { channelCount: 2 });\r\n }\r\n } else {\r\n constraints.audio = false;\r\n }\r\n\r\n return constraints;\r\n}\r\n\r\n/**\r\n * Updates the granted permissions based on the options we requested and the\r\n * streams we received.\r\n * @param um the options we requested to getUserMedia.\r\n * @param stream the stream we received from calling getUserMedia.\r\n */\r\nfunction updateGrantedPermissions(um, stream) {\r\n const audioTracksReceived\r\n = Boolean(stream) && stream.getAudioTracks().length > 0;\r\n const videoTracksReceived\r\n = Boolean(stream) && stream.getVideoTracks().length > 0;\r\n const grantedPermissions = {};\r\n\r\n if (um.indexOf('video') !== -1) {\r\n grantedPermissions.video = videoTracksReceived;\r\n }\r\n if (um.indexOf('audio') !== -1) {\r\n grantedPermissions.audio = audioTracksReceived;\r\n }\r\n\r\n eventEmitter.emit(RTCEvents.PERMISSIONS_CHANGED, grantedPermissions);\r\n}\r\n\r\n/**\r\n * Checks if new list of available media devices differs from previous one.\r\n * @param {MediaDeviceInfo[]} newDevices - list of new devices.\r\n * @returns {boolean} - true if list is different, false otherwise.\r\n */\r\nfunction compareAvailableMediaDevices(newDevices) {\r\n if (newDevices.length !== availableDevices.length) {\r\n return true;\r\n }\r\n\r\n /* eslint-disable newline-per-chained-call */\r\n\r\n return (\r\n newDevices.map(mediaDeviceInfoToJSON).sort().join('')\r\n !== availableDevices\r\n .map(mediaDeviceInfoToJSON).sort().join(''));\r\n\r\n /* eslint-enable newline-per-chained-call */\r\n\r\n /**\r\n *\r\n * @param info\r\n */\r\n function mediaDeviceInfoToJSON(info) {\r\n return JSON.stringify({\r\n kind: info.kind,\r\n deviceId: info.deviceId,\r\n groupId: info.groupId,\r\n label: info.label,\r\n facing: info.facing\r\n });\r\n }\r\n}\r\n\r\n/**\r\n * Sends analytics event with the passed device list.\r\n *\r\n * @param {Array} deviceList - List with info about the\r\n * available devices.\r\n * @returns {void}\r\n */\r\nfunction sendDeviceListToAnalytics(deviceList) {\r\n const audioInputDeviceCount\r\n = deviceList.filter(d => d.kind === 'audioinput').length;\r\n const audioOutputDeviceCount\r\n = deviceList.filter(d => d.kind === 'audiooutput').length;\r\n const videoInputDeviceCount\r\n = deviceList.filter(d => d.kind === 'videoinput').length;\r\n const videoOutputDeviceCount\r\n = deviceList.filter(d => d.kind === 'videooutput').length;\r\n\r\n deviceList.forEach(device => {\r\n const attributes = {\r\n 'audio_input_device_count': audioInputDeviceCount,\r\n 'audio_output_device_count': audioOutputDeviceCount,\r\n 'video_input_device_count': videoInputDeviceCount,\r\n 'video_output_device_count': videoOutputDeviceCount,\r\n 'device_id': device.deviceId,\r\n 'device_group_id': device.groupId,\r\n 'device_kind': device.kind,\r\n 'device_label': device.label\r\n };\r\n\r\n Statistics.sendAnalytics(AVAILABLE_DEVICE, attributes);\r\n });\r\n}\r\n\r\n\r\n/**\r\n * Update known devices.\r\n *\r\n * @param {Array} pds - The new devices.\r\n * @returns {void}\r\n *\r\n * NOTE: Use this function as a shared callback to handle both the devicechange event and the polling implementations.\r\n * This prevents duplication and works around a chrome bug (verified to occur on 68) where devicechange fires twice in\r\n * a row, which can cause async post devicechange processing to collide.\r\n */\r\nfunction updateKnownDevices(pds) {\r\n if (compareAvailableMediaDevices(pds)) {\r\n onMediaDevicesListChanged(pds);\r\n }\r\n}\r\n\r\n/**\r\n * Event handler for the 'devicechange' event.\r\n *\r\n * @param {MediaDeviceInfo[]} devices - list of media devices.\r\n * @emits RTCEvents.DEVICE_LIST_CHANGED\r\n */\r\nfunction onMediaDevicesListChanged(devicesReceived) {\r\n availableDevices = devicesReceived.slice(0);\r\n logger.info('list of media devices has changed:', availableDevices);\r\n\r\n sendDeviceListToAnalytics(availableDevices);\r\n\r\n // Used by tracks to update the real device id before the consumer of lib-jitsi-meet receives the new device list.\r\n eventEmitter.emit(RTCEvents.DEVICE_LIST_WILL_CHANGE, availableDevices);\r\n\r\n eventEmitter.emit(RTCEvents.DEVICE_LIST_CHANGED, availableDevices);\r\n}\r\n\r\n/**\r\n *\r\n */\r\nclass RTCUtils extends Listenable {\r\n /**\r\n *\r\n */\r\n constructor() {\r\n super(eventEmitter);\r\n }\r\n\r\n /**\r\n * Depending on the browser, sets difference instance methods for\r\n * interacting with user media and adds methods to native WebRTC-related\r\n * objects. Also creates an instance variable for peer connection\r\n * constraints.\r\n *\r\n * @param {Object} options\r\n * @returns {void}\r\n */\r\n init(options = {}) {\r\n if (typeof options.disableAEC === 'boolean') {\r\n disableAEC = options.disableAEC;\r\n logger.info(`Disable AEC: ${disableAEC}`);\r\n }\r\n if (typeof options.disableNS === 'boolean') {\r\n disableNS = options.disableNS;\r\n logger.info(`Disable NS: ${disableNS}`);\r\n }\r\n if (typeof options.disableAP === 'boolean') {\r\n disableAP = options.disableAP;\r\n logger.info(`Disable AP: ${disableAP}`);\r\n }\r\n if (typeof options.disableAGC === 'boolean') {\r\n disableAGC = options.disableAGC;\r\n logger.info(`Disable AGC: ${disableAGC}`);\r\n }\r\n if (typeof options.audioQuality?.stereo === 'boolean') {\r\n stereo = options.audioQuality.stereo;\r\n logger.info(`Stereo: ${stereo}`);\r\n }\r\n\r\n window.clearInterval(availableDevicesPollTimer);\r\n availableDevicesPollTimer = undefined;\r\n\r\n if (browser.isReactNative()) {\r\n this.RTCPeerConnectionType = RTCPeerConnection;\r\n\r\n this.attachMediaStream = undefined; // Unused on React Native.\r\n\r\n this.getStreamID = function({ id }) {\r\n // The react-native-webrtc implementation that we use at the\r\n // time of this writing returns a number for the id of\r\n // MediaStream. Let's just say that a number contains no special\r\n // characters.\r\n return (\r\n typeof id === 'number'\r\n ? id\r\n : SDPUtil.filterSpecialChars(id));\r\n };\r\n this.getTrackID = ({ id }) => id;\r\n } else {\r\n this.RTCPeerConnectionType = RTCPeerConnection;\r\n\r\n this.attachMediaStream\r\n = wrapAttachMediaStream((element, stream) => {\r\n if (element) {\r\n element.srcObject = stream;\r\n }\r\n });\r\n\r\n this.getStreamID = ({ id }) => id;\r\n this.getTrackID = ({ id }) => id;\r\n }\r\n\r\n this.pcConstraints = {};\r\n\r\n screenObtainer.init(options);\r\n\r\n if (this.isDeviceListAvailable()) {\r\n this.enumerateDevices(ds => {\r\n availableDevices = ds.slice(0);\r\n\r\n logger.debug('Available devices: ', availableDevices);\r\n sendDeviceListToAnalytics(availableDevices);\r\n\r\n eventEmitter.emit(\r\n RTCEvents.DEVICE_LIST_AVAILABLE,\r\n availableDevices);\r\n\r\n if (browser.supportsDeviceChangeEvent()) {\r\n navigator.mediaDevices.addEventListener(\r\n 'devicechange',\r\n () => this.enumerateDevices(emptyFuncton));\r\n } else {\r\n // Periodically poll enumerateDevices() method to check if\r\n // list of media devices has changed.\r\n availableDevicesPollTimer = window.setInterval(\r\n () => this.enumerateDevices(emptyFuncton),\r\n AVAILABLE_DEVICES_POLL_INTERVAL_TIME);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param {Function} callback\r\n */\r\n enumerateDevices(callback) {\r\n navigator.mediaDevices.enumerateDevices()\r\n .then(devices => {\r\n updateKnownDevices(devices);\r\n callback(devices);\r\n })\r\n .catch(error => {\r\n logger.warn(`Failed to enumerate devices. ${error}`);\r\n updateKnownDevices([]);\r\n callback([]);\r\n });\r\n }\r\n\r\n /**\r\n * Acquires a media stream via getUserMedia that\r\n * matches the given constraints\r\n *\r\n * @param {array} umDevices which devices to acquire (e.g. audio, video)\r\n * @param {Object} constraints - Stream specifications to use.\r\n * @param {number} timeout - The timeout in ms for GUM.\r\n * @returns {Promise}\r\n */\r\n _getUserMedia(umDevices, constraints = {}, timeout = 0) {\r\n return new Promise((resolve, reject) => {\r\n let gumTimeout, timeoutExpired = false;\r\n\r\n if (typeof timeout === 'number' && !isNaN(timeout) && timeout > 0) {\r\n gumTimeout = setTimeout(() => {\r\n timeoutExpired = true;\r\n gumTimeout = undefined;\r\n reject(new JitsiTrackError(JitsiTrackErrors.TIMEOUT));\r\n }, timeout);\r\n }\r\n\r\n navigator.mediaDevices.getUserMedia(constraints)\r\n .then(stream => {\r\n logger.log('onUserMediaSuccess');\r\n updateGrantedPermissions(umDevices, stream);\r\n if (!timeoutExpired) {\r\n if (typeof gumTimeout !== 'undefined') {\r\n clearTimeout(gumTimeout);\r\n }\r\n resolve(stream);\r\n }\r\n })\r\n .catch(error => {\r\n logger.warn(`Failed to get access to local media. ${error} ${JSON.stringify(constraints)}`);\r\n const jitsiError = new JitsiTrackError(error, constraints, umDevices);\r\n\r\n if (!timeoutExpired) {\r\n if (typeof gumTimeout !== 'undefined') {\r\n clearTimeout(gumTimeout);\r\n }\r\n reject(jitsiError);\r\n }\r\n\r\n if (jitsiError.name === JitsiTrackErrors.PERMISSION_DENIED) {\r\n updateGrantedPermissions(umDevices, undefined);\r\n }\r\n\r\n // else {\r\n // Probably the error is not caused by the lack of permissions and we don't need to update them.\r\n // }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Acquire a display stream via the screenObtainer. This requires extra\r\n * logic compared to use screenObtainer versus normal device capture logic\r\n * in RTCUtils#_getUserMedia.\r\n *\r\n * @returns {Promise} A promise which will be resolved with an object which\r\n * contains the acquired display stream. If desktop sharing is not supported\r\n * then a rejected promise will be returned.\r\n */\r\n _getDesktopMedia() {\r\n if (!screenObtainer.isSupported()) {\r\n return Promise.reject(new Error('Desktop sharing is not supported!'));\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n screenObtainer.obtainStream(\r\n stream => {\r\n resolve(stream);\r\n },\r\n error => {\r\n reject(error);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Private utility for determining if the passed in MediaStream contains\r\n * tracks of the type(s) specified in the requested devices.\r\n *\r\n * @param {string[]} requestedDevices - The track types that are expected to\r\n * be includes in the stream.\r\n * @param {MediaStream} stream - The MediaStream to check if it has the\r\n * expected track types.\r\n * @returns {string[]} An array of string with the missing track types. The\r\n * array will be empty if all requestedDevices are found in the stream.\r\n */\r\n _getMissingTracks(requestedDevices = [], stream) {\r\n const missingDevices = [];\r\n\r\n const audioDeviceRequested = requestedDevices.includes('audio');\r\n const audioTracksReceived\r\n = stream && stream.getAudioTracks().length > 0;\r\n\r\n if (audioDeviceRequested && !audioTracksReceived) {\r\n missingDevices.push('audio');\r\n }\r\n\r\n const videoDeviceRequested = requestedDevices.includes('video');\r\n const videoTracksReceived\r\n = stream && stream.getVideoTracks().length > 0;\r\n\r\n if (videoDeviceRequested && !videoTracksReceived) {\r\n missingDevices.push('video');\r\n }\r\n\r\n return missingDevices;\r\n }\r\n\r\n /**\r\n * Gets streams from specified device types. This function intentionally\r\n * ignores errors for upstream to catch and handle instead.\r\n *\r\n * @param {Object} options - A hash describing what devices to get and\r\n * relevant constraints.\r\n * @param {string[]} options.devices - The types of media to capture. Valid\r\n * values are \"desktop\", \"audio\", and \"video\".\r\n * @param {Object} options.desktopSharingFrameRate\r\n * @param {Object} options.desktopSharingFrameRate.min - Minimum fps\r\n * @param {Object} options.desktopSharingFrameRate.max - Maximum fps\r\n * @param {String} options.desktopSharingSourceDevice - The device id or\r\n * label for a video input source that should be used for screensharing.\r\n * @returns {Promise} The promise, when successful, will return an array of\r\n * meta data for the requested device type, which includes the stream and\r\n * track. If an error occurs, it will be deferred to the caller for\r\n * handling.\r\n */\r\n obtainAudioAndVideoPermissions(options) {\r\n const {\r\n timeout,\r\n ...otherOptions\r\n } = options;\r\n\r\n const mediaStreamsMetaData = [];\r\n\r\n // Declare private functions to be used in the promise chain below.\r\n // These functions are declared in the scope of this function because\r\n // they are not being used anywhere else, so only this function needs to\r\n // know about them.\r\n\r\n /**\r\n * Executes a request for desktop media if specified in options.\r\n *\r\n * @returns {Promise}\r\n */\r\n const maybeRequestDesktopDevice = function() {\r\n const umDevices = otherOptions.devices || [];\r\n const isDesktopDeviceRequested\r\n = umDevices.indexOf('desktop') !== -1;\r\n\r\n if (!isDesktopDeviceRequested) {\r\n return Promise.resolve();\r\n }\r\n\r\n const {\r\n desktopSharingSourceDevice\r\n } = otherOptions;\r\n\r\n // Attempt to use a video input device as a screenshare source if\r\n // the option is defined.\r\n if (desktopSharingSourceDevice) {\r\n const matchingDevice\r\n = availableDevices && availableDevices.find(device =>\r\n device.kind === 'videoinput'\r\n && (device.deviceId === desktopSharingSourceDevice\r\n || device.label === desktopSharingSourceDevice));\r\n\r\n if (!matchingDevice) {\r\n return Promise.reject(new JitsiTrackError(\r\n { name: 'ConstraintNotSatisfiedError' },\r\n {},\r\n [ desktopSharingSourceDevice ]\r\n ));\r\n }\r\n\r\n const requestedDevices = [ 'video' ];\r\n const constraints = {\r\n video: {\r\n deviceId: matchingDevice.deviceId\r\n\r\n // frameRate is omited here on purpose since this is a device that we'll pretend is a screen.\r\n }\r\n };\r\n\r\n return this._getUserMedia(requestedDevices, constraints, timeout)\r\n .then(stream => {\r\n return {\r\n sourceType: 'device',\r\n stream\r\n };\r\n });\r\n }\r\n\r\n return this._getDesktopMedia();\r\n }.bind(this);\r\n\r\n /**\r\n * Creates a meta data object about the passed in desktopStream and\r\n * pushes the meta data to the internal array mediaStreamsMetaData to be\r\n * returned later.\r\n *\r\n * @param {MediaStreamTrack} desktopStream - A track for a desktop\r\n * capture.\r\n * @returns {void}\r\n */\r\n const maybeCreateAndAddDesktopTrack = function(desktopStream) {\r\n if (!desktopStream) {\r\n return;\r\n }\r\n\r\n const { stream, sourceId, sourceType } = desktopStream;\r\n\r\n const desktopAudioTracks = stream.getAudioTracks();\r\n\r\n if (desktopAudioTracks.length) {\r\n const desktopAudioStream = new MediaStream(desktopAudioTracks);\r\n\r\n mediaStreamsMetaData.push({\r\n stream: desktopAudioStream,\r\n sourceId,\r\n sourceType,\r\n track: desktopAudioStream.getAudioTracks()[0]\r\n });\r\n }\r\n\r\n const desktopVideoTracks = stream.getVideoTracks();\r\n\r\n if (desktopVideoTracks.length) {\r\n const desktopVideoStream = new MediaStream(desktopVideoTracks);\r\n\r\n mediaStreamsMetaData.push({\r\n stream: desktopVideoStream,\r\n sourceId,\r\n sourceType,\r\n track: desktopVideoStream.getVideoTracks()[0],\r\n videoType: VideoType.DESKTOP\r\n });\r\n }\r\n };\r\n\r\n /**\r\n * Executes a request for audio and/or video, as specified in options.\r\n * By default both audio and video will be captured if options.devices\r\n * is not defined.\r\n *\r\n * @returns {Promise}\r\n */\r\n const maybeRequestCaptureDevices = function() {\r\n const umDevices = otherOptions.devices || [ 'audio', 'video' ];\r\n const requestedCaptureDevices = umDevices.filter(device => device === 'audio' || device === 'video');\r\n\r\n if (!requestedCaptureDevices.length) {\r\n return Promise.resolve();\r\n }\r\n\r\n const constraints = getConstraints(requestedCaptureDevices, otherOptions);\r\n\r\n logger.info('Got media constraints: ', JSON.stringify(constraints));\r\n\r\n return this._getUserMedia(requestedCaptureDevices, constraints, timeout);\r\n }.bind(this);\r\n\r\n /**\r\n * Splits the passed in media stream into separate audio and video\r\n * streams and creates meta data objects for each and pushes them to the\r\n * internal array mediaStreamsMetaData to be returned later.\r\n *\r\n * @param {MediaStreamTrack} avStream - A track for with audio and/or\r\n * video track.\r\n * @returns {void}\r\n */\r\n const maybeCreateAndAddAVTracks = function(avStream) {\r\n if (!avStream) {\r\n return;\r\n }\r\n\r\n const audioTracks = avStream.getAudioTracks();\r\n\r\n if (audioTracks.length) {\r\n const audioStream = new MediaStream(audioTracks);\r\n\r\n mediaStreamsMetaData.push({\r\n stream: audioStream,\r\n track: audioStream.getAudioTracks()[0],\r\n effects: otherOptions.effects\r\n });\r\n }\r\n\r\n const videoTracks = avStream.getVideoTracks();\r\n\r\n if (videoTracks.length) {\r\n const videoStream = new MediaStream(videoTracks);\r\n\r\n mediaStreamsMetaData.push({\r\n stream: videoStream,\r\n track: videoStream.getVideoTracks()[0],\r\n videoType: VideoType.CAMERA,\r\n effects: otherOptions.effects\r\n });\r\n }\r\n };\r\n\r\n return maybeRequestDesktopDevice()\r\n .then(maybeCreateAndAddDesktopTrack)\r\n .then(maybeRequestCaptureDevices)\r\n .then(maybeCreateAndAddAVTracks)\r\n .then(() => mediaStreamsMetaData)\r\n .catch(error => {\r\n mediaStreamsMetaData.forEach(({ stream }) => {\r\n this.stopMediaStream(stream);\r\n });\r\n\r\n return Promise.reject(error);\r\n });\r\n }\r\n\r\n /**\r\n * Checks whether it is possible to enumerate available cameras/microphones.\r\n *\r\n * @returns {boolean} {@code true} if the device listing is available;\r\n * {@code false}, otherwise.\r\n */\r\n isDeviceListAvailable() {\r\n return Boolean(\r\n navigator.mediaDevices\r\n && navigator.mediaDevices.enumerateDevices);\r\n }\r\n\r\n /**\r\n * Returns true if changing the input (camera / microphone) or output\r\n * (audio) device is supported and false if not.\r\n * @params {string} [deviceType] - type of device to change. Default is\r\n * undefined or 'input', 'output' - for audio output device change.\r\n * @returns {boolean} true if available, false otherwise.\r\n */\r\n isDeviceChangeAvailable(deviceType) {\r\n if (deviceType === 'output' || deviceType === 'audiooutput') {\r\n return isAudioOutputDeviceChangeAvailable;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * A method to handle stopping of the stream.\r\n * One point to handle the differences in various implementations.\r\n * @param mediaStream MediaStream object to stop.\r\n */\r\n stopMediaStream(mediaStream) {\r\n if (!mediaStream) {\r\n return;\r\n }\r\n\r\n mediaStream.getTracks().forEach(track => {\r\n if (track.stop) {\r\n track.stop();\r\n }\r\n });\r\n\r\n // leave stop for implementation still using it\r\n if (mediaStream.stop) {\r\n mediaStream.stop();\r\n }\r\n\r\n // The MediaStream implementation of the react-native-webrtc project has\r\n // an explicit release method that is to be invoked in order to release\r\n // used resources such as memory.\r\n if (mediaStream.release) {\r\n mediaStream.release();\r\n }\r\n }\r\n\r\n /**\r\n * Returns whether the desktop sharing is enabled or not.\r\n * @returns {boolean}\r\n */\r\n isDesktopSharingEnabled() {\r\n return screenObtainer.isSupported();\r\n }\r\n\r\n /**\r\n * Sets current audio output device.\r\n * @param {string} deviceId - id of 'audiooutput' device from\r\n * navigator.mediaDevices.enumerateDevices(), 'default' for default\r\n * device\r\n * @returns {Promise} - resolves when audio output is changed, is rejected\r\n * otherwise\r\n */\r\n setAudioOutputDevice(deviceId) {\r\n if (!this.isDeviceChangeAvailable('output')) {\r\n return Promise.reject(\r\n new Error('Audio output device change is not supported'));\r\n }\r\n\r\n return featureDetectionAudioEl.setSinkId(deviceId)\r\n .then(() => {\r\n audioOutputDeviceId = deviceId;\r\n audioOutputChanged = true;\r\n\r\n logger.log(`Audio output device set to ${deviceId}`);\r\n\r\n eventEmitter.emit(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,\r\n deviceId);\r\n });\r\n }\r\n\r\n /**\r\n * Sets the capture frame rate for desktop tracks.\r\n *\r\n * @param {number} maxFps - max fps to be used as the capture frame rate.\r\n * @returns {void}\r\n */\r\n setDesktopSharingFrameRate(maxFps) {\r\n screenObtainer.setDesktopSharingFrameRate(maxFps);\r\n }\r\n\r\n /**\r\n * Returns currently used audio output device id, '' stands for default\r\n * device\r\n * @returns {string}\r\n */\r\n getAudioOutputDevice() {\r\n return audioOutputDeviceId;\r\n }\r\n\r\n /**\r\n * Returns list of available media devices if its obtained, otherwise an\r\n * empty array is returned/\r\n * @returns {Array} list of available media devices.\r\n */\r\n getCurrentlyAvailableMediaDevices() {\r\n return availableDevices;\r\n }\r\n\r\n /**\r\n * Returns whether available devices have permissions granted\r\n * @returns {Boolean}\r\n */\r\n arePermissionsGrantedForAvailableDevices() {\r\n return availableDevices.some(device => Boolean(device.label));\r\n }\r\n\r\n /**\r\n * Returns event data for device to be reported to stats.\r\n * @returns {MediaDeviceInfo} device.\r\n */\r\n getEventDataForActiveDevice(device) {\r\n const deviceList = [];\r\n const deviceData = {\r\n 'deviceId': device.deviceId,\r\n 'kind': device.kind,\r\n 'label': device.label,\r\n 'groupId': device.groupId\r\n };\r\n\r\n deviceList.push(deviceData);\r\n\r\n return { deviceList };\r\n }\r\n}\r\n\r\nconst rtcUtils = new RTCUtils();\r\n\r\n/**\r\n * Wraps original attachMediaStream function to set current audio output device\r\n * if this is supported.\r\n * @param {Function} origAttachMediaStream\r\n * @returns {Function}\r\n */\r\nfunction wrapAttachMediaStream(origAttachMediaStream) {\r\n return function(element, stream) {\r\n // eslint-disable-next-line prefer-rest-params\r\n const res = origAttachMediaStream.apply(rtcUtils, arguments);\r\n\r\n if (stream\r\n && rtcUtils.isDeviceChangeAvailable('output')\r\n && stream.getAudioTracks\r\n && stream.getAudioTracks().length\r\n\r\n // we skip setting audio output if there was no explicit change\r\n && audioOutputChanged) {\r\n element.setSinkId(rtcUtils.getAudioOutputDevice())\r\n .catch(function(ex) {\r\n const err\r\n = new JitsiTrackError(ex, null, [ 'audiooutput' ]);\r\n\r\n GlobalOnErrorHandler.callUnhandledRejectionHandler({\r\n promise: this, // eslint-disable-line no-invalid-this\r\n reason: err\r\n });\r\n\r\n logger.warn(\r\n 'Failed to set audio output device for the element.'\r\n + ' Default audio output device will be used'\r\n + ' instead',\r\n element,\r\n err);\r\n });\r\n }\r\n\r\n return res;\r\n };\r\n}\r\n\r\nexport default rtcUtils;\r\n","import { getLogger } from '@jitsi/logger';\r\nimport EventEmitter from 'events';\r\n\r\nimport * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport browser from '../browser';\r\n\r\nimport RTCUtils from './RTCUtils';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Maps our handler types to MediaStreamTrack properties.\r\n */\r\nconst trackHandler2Prop = {\r\n 'track_mute': 'onmute', // Not supported on FF\r\n 'track_unmute': 'onunmute',\r\n 'track_ended': 'onended'\r\n};\r\n\r\n/**\r\n * Represents a single media track (either audio or video).\r\n */\r\nexport default class JitsiTrack extends EventEmitter {\r\n /* eslint-disable max-params */\r\n /**\r\n * Represents a single media track (either audio or video).\r\n * @constructor\r\n * @param conference the rtc instance\r\n * @param stream the WebRTC MediaStream instance\r\n * @param track the WebRTC MediaStreamTrack instance, must be part of\r\n * the given stream.\r\n * @param streamInactiveHandler the function that will handle\r\n * onended/oninactive events of the stream.\r\n * @param trackMediaType the media type of the JitsiTrack\r\n * @param videoType the VideoType for this track if any\r\n */\r\n constructor(\r\n conference,\r\n stream,\r\n track,\r\n streamInactiveHandler,\r\n trackMediaType,\r\n videoType) {\r\n super();\r\n\r\n // aliases for addListener/removeListener\r\n this.addEventListener = this.addListener;\r\n this.removeEventListener = this.off = this.removeListener;\r\n\r\n /**\r\n * Array with the HTML elements that are displaying the streams.\r\n * @type {Array}\r\n */\r\n this.containers = [];\r\n this.conference = conference;\r\n this.audioLevel = -1;\r\n this.type = trackMediaType;\r\n this.track = track;\r\n this.videoType = videoType;\r\n this.handlers = new Map();\r\n\r\n /**\r\n * Indicates whether this JitsiTrack has been disposed. If true, this\r\n * JitsiTrack is to be considered unusable and operations involving it\r\n * are to fail (e.g. {@link JitsiConference#addTrack(JitsiTrack)},\r\n * {@link JitsiConference#removeTrack(JitsiTrack)}).\r\n * @type {boolean}\r\n */\r\n this.disposed = false;\r\n\r\n /**\r\n * The inactive handler which will be triggered when the underlying\r\n * MediaStream ends.\r\n *\r\n * @private\r\n * @type {Function}\r\n */\r\n this._streamInactiveHandler = streamInactiveHandler;\r\n\r\n this._setStream(stream);\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Adds onended/oninactive handler to a MediaStream or a MediaStreamTrack.\r\n * Firefox doesn't fire a inactive event on the MediaStream, instead it fires\r\n * a onended event on the MediaStreamTrack.\r\n * @param {Function} handler the handler\r\n */\r\n _addMediaStreamInactiveHandler(handler) {\r\n if (browser.isFirefox()) {\r\n this.track.onended = handler;\r\n } else {\r\n this.stream.oninactive = handler;\r\n }\r\n }\r\n\r\n /**\r\n * Sets handler to the WebRTC MediaStream or MediaStreamTrack object\r\n * depending on the passed type.\r\n * @param {string} type the type of the handler that is going to be set\r\n * @param {Function} handler the handler.\r\n */\r\n _setHandler(type, handler) {\r\n if (!trackHandler2Prop.hasOwnProperty(type)) {\r\n logger.error(`Invalid handler type ${type}`);\r\n\r\n return;\r\n }\r\n if (handler) {\r\n this.handlers.set(type, handler);\r\n } else {\r\n this.handlers.delete(type);\r\n }\r\n\r\n if (this.stream) {\r\n for (const track of this.stream.getTracks()) {\r\n track[trackHandler2Prop[type]] = handler;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Unregisters all event handlers bound to the underlying media stream/track\r\n * @private\r\n */\r\n _unregisterHandlers() {\r\n if (!this.stream) {\r\n logger.warn(\r\n `${this}: unable to unregister handlers - no stream object`);\r\n\r\n return;\r\n }\r\n\r\n for (const type of this.handlers.keys()) {\r\n // FIXME Why only video tracks?\r\n for (const videoTrack of this.stream.getVideoTracks()) {\r\n videoTrack[trackHandler2Prop[type]] = undefined;\r\n }\r\n }\r\n if (this._streamInactiveHandler) {\r\n this._addMediaStreamInactiveHandler(undefined);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the stream property of JitsiTrack object and sets all stored\r\n * handlers to it.\r\n *\r\n * @param {MediaStream} stream the new stream.\r\n * @protected\r\n */\r\n _setStream(stream) {\r\n if (this.stream === stream) {\r\n return;\r\n }\r\n\r\n this.stream = stream;\r\n\r\n // TODO Practically, that's like the opposite of _unregisterHandlers\r\n // i.e. may be abstracted into a function/method called\r\n // _registerHandlers for clarity and easing the maintenance of the two\r\n // pieces of source code.\r\n if (this.stream) {\r\n for (const type of this.handlers.keys()) {\r\n this._setHandler(type, this.handlers.get(type));\r\n }\r\n if (this._streamInactiveHandler) {\r\n this._addMediaStreamInactiveHandler(this._streamInactiveHandler);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Returns the video type (camera or desktop) of this track.\r\n */\r\n getVideoType() {\r\n return this.videoType;\r\n }\r\n\r\n /**\r\n * Returns the type (audio or video) of this track.\r\n */\r\n getType() {\r\n return this.type;\r\n }\r\n\r\n /**\r\n * Check if this is an audio track.\r\n */\r\n isAudioTrack() {\r\n return this.getType() === MediaType.AUDIO;\r\n }\r\n\r\n /**\r\n * Checks whether the underlying WebRTC MediaStreamTrack is muted\r\n * according to it's 'muted' field status.\r\n * @return {boolean} true if the underlying\r\n * MediaStreamTrack is muted or false otherwise.\r\n */\r\n isWebRTCTrackMuted() {\r\n return this.track && this.track.muted;\r\n }\r\n\r\n /**\r\n * Check if this is a video track.\r\n */\r\n isVideoTrack() {\r\n return this.getType() === MediaType.VIDEO;\r\n }\r\n\r\n /**\r\n * Checks whether this is a local track.\r\n * @abstract\r\n * @return {boolean} 'true' if it's a local track or 'false' otherwise.\r\n */\r\n isLocal() {\r\n throw new Error('Not implemented by subclass');\r\n }\r\n\r\n /**\r\n * Check whether this is a local audio track.\r\n *\r\n * @return {boolean} - true if track represents a local audio track, false otherwise.\r\n */\r\n isLocalAudioTrack() {\r\n return this.isAudioTrack() && this.isLocal();\r\n }\r\n\r\n /**\r\n * Returns the WebRTC MediaStream instance.\r\n */\r\n getOriginalStream() {\r\n return this.stream;\r\n }\r\n\r\n /**\r\n * Returns the ID of the underlying WebRTC Media Stream(if any)\r\n * @returns {String|null}\r\n */\r\n getStreamId() {\r\n return this.stream ? this.stream.id : null;\r\n }\r\n\r\n /**\r\n * Return the underlying WebRTC MediaStreamTrack\r\n * @returns {MediaStreamTrack}\r\n */\r\n getTrack() {\r\n return this.track;\r\n }\r\n\r\n /**\r\n * Return the underlying WebRTC MediaStreamTrack label\r\n * @returns {string}\r\n */\r\n getTrackLabel() {\r\n return this.track.label;\r\n }\r\n\r\n /**\r\n * Returns the ID of the underlying WebRTC MediaStreamTrack(if any)\r\n * @returns {String|null}\r\n */\r\n getTrackId() {\r\n return this.track ? this.track.id : null;\r\n }\r\n\r\n /**\r\n * Return meaningful usage label for this track depending on it's media and\r\n * eventual video type.\r\n * @returns {string}\r\n */\r\n getUsageLabel() {\r\n if (this.isAudioTrack()) {\r\n return 'mic';\r\n }\r\n\r\n return this.videoType ? this.videoType : 'default';\r\n }\r\n\r\n /**\r\n * Eventually will trigger RTCEvents.TRACK_ATTACHED event.\r\n * @param container the video/audio container to which this stream is\r\n * attached and for which event will be fired.\r\n * @private\r\n */\r\n _maybeFireTrackAttached(container) {\r\n if (this.conference && container) {\r\n this.conference._onTrackAttach(this, container);\r\n }\r\n }\r\n\r\n /**\r\n * Attaches the MediaStream of this track to an HTML container.\r\n * Adds the container to the list of containers that are displaying the\r\n * track.\r\n *\r\n * @param container the HTML container which can be 'video' or 'audio'\r\n * element.\r\n *\r\n * @returns {void}\r\n */\r\n attach(container) {\r\n if (this.stream) {\r\n this._onTrackAttach(container);\r\n RTCUtils.attachMediaStream(container, this.stream);\r\n }\r\n this.containers.push(container);\r\n this._maybeFireTrackAttached(container);\r\n this._attachTTFMTracker(container);\r\n }\r\n\r\n /**\r\n * Removes this JitsiTrack from the passed HTML container.\r\n *\r\n * @param container the HTML container to detach from this JitsiTrack. If\r\n * null or undefined, all containers are removed. A\r\n * container can be a 'video', 'audio' or 'object' HTML element instance to\r\n * which this JitsiTrack is currently attached.\r\n */\r\n detach(container) {\r\n for (let cs = this.containers, i = cs.length - 1; i >= 0; --i) {\r\n const c = cs[i];\r\n\r\n if (!container) {\r\n this._onTrackDetach(c);\r\n RTCUtils.attachMediaStream(c, null);\r\n }\r\n if (!container || c === container) {\r\n cs.splice(i, 1);\r\n }\r\n }\r\n\r\n if (container) {\r\n this._onTrackDetach(container);\r\n RTCUtils.attachMediaStream(container, null);\r\n }\r\n }\r\n\r\n /**\r\n * Called when the track has been attached to a new container.\r\n *\r\n * @param {HTMLElement} container the HTML container which can be 'video' or\r\n * 'audio' element.\r\n * @private\r\n */\r\n _onTrackAttach(container) { // eslint-disable-line no-unused-vars\r\n // Should be defined by the classes that are extending JitsiTrack\r\n }\r\n\r\n /**\r\n * Called when the track has been detached from a container.\r\n *\r\n * @param {HTMLElement} container the HTML container which can be 'video' or\r\n * 'audio' element.\r\n * @private\r\n */\r\n _onTrackDetach(container) { // eslint-disable-line no-unused-vars\r\n // Should be defined by the classes that are extending JitsiTrack\r\n }\r\n\r\n /**\r\n * Attach time to first media tracker only if there is conference and only\r\n * for the first element.\r\n *\r\n * @param {HTMLElement} container the HTML container which can be 'video' or\r\n * 'audio' element.\r\n * @private\r\n */\r\n _attachTTFMTracker(container) { // eslint-disable-line no-unused-vars\r\n // Should be defined by the classes that are extending JitsiTrack\r\n }\r\n\r\n /**\r\n * Removes attached event listeners.\r\n *\r\n * @returns {Promise}\r\n */\r\n dispose() {\r\n this.removeAllListeners();\r\n\r\n this.disposed = true;\r\n\r\n return Promise.resolve();\r\n }\r\n\r\n /**\r\n * Returns true if this is a video track and the source of the video is a\r\n * screen capture as opposed to a camera.\r\n */\r\n isScreenSharing() {\r\n // FIXME: Should be fixed or removed.\r\n }\r\n\r\n /**\r\n * Returns id of the track.\r\n * @returns {string|null} id of the track or null if this is fake track.\r\n */\r\n getId() {\r\n if (this.stream) {\r\n return RTCUtils.getStreamID(this.stream);\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Checks whether the MediaStream is active/not ended.\r\n * When there is no check for active we don't have information and so\r\n * will return that stream is active (in case of FF).\r\n * @returns {boolean} whether MediaStream is active.\r\n */\r\n isActive() {\r\n if (typeof this.stream.active !== 'undefined') {\r\n return this.stream.active;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Sets the audio level for the stream\r\n * @param {number} audioLevel value between 0 and 1\r\n * @param {TraceablePeerConnection} [tpc] the peerconnection instance which\r\n * is source for the audio level. It can be undefined for\r\n * a local track if the audio level was measured outside of the\r\n * peerconnection (see /modules/statistics/LocalStatsCollector.js).\r\n */\r\n setAudioLevel(audioLevel, tpc) {\r\n let newAudioLevel = audioLevel;\r\n\r\n // When using getSynchornizationSources on the audio receiver to gather audio levels for\r\n // remote tracks, browser reports last known audio levels even when the remote user is\r\n // audio muted, we need to reset the value to zero here so that the audio levels are cleared.\r\n // Remote tracks have the tpc info present while local tracks do not.\r\n if (browser.supportsReceiverStats() && typeof tpc !== 'undefined' && this.isMuted()) {\r\n newAudioLevel = 0;\r\n }\r\n\r\n if (this.audioLevel !== newAudioLevel) {\r\n this.audioLevel = newAudioLevel;\r\n this.emit(\r\n JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,\r\n newAudioLevel,\r\n tpc);\r\n\r\n // LocalStatsCollector reports a value of 0.008 for muted mics\r\n // and a value of 0 when there is no audio input.\r\n } else if (this.audioLevel === 0\r\n && newAudioLevel === 0\r\n && this.isLocal()\r\n && !this.isWebRTCTrackMuted()) {\r\n this.emit(\r\n JitsiTrackEvents.NO_AUDIO_INPUT,\r\n newAudioLevel);\r\n }\r\n }\r\n\r\n /**\r\n * Returns the msid of the stream attached to the JitsiTrack object or null\r\n * if no stream is attached.\r\n */\r\n getMSID() {\r\n const streamId = this.getStreamId();\r\n const trackId = this.getTrackId();\r\n\r\n return streamId && trackId ? `${streamId} ${trackId}` : null;\r\n }\r\n\r\n /**\r\n * Sets new audio output device for track's DOM elements. Video tracks are\r\n * ignored.\r\n * @param {string} audioOutputDeviceId - id of 'audiooutput' device from\r\n * navigator.mediaDevices.enumerateDevices(), '' for default device\r\n * @emits JitsiTrackEvents.TRACK_AUDIO_OUTPUT_CHANGED\r\n * @returns {Promise}\r\n */\r\n setAudioOutput(audioOutputDeviceId) {\r\n if (!RTCUtils.isDeviceChangeAvailable('output')) {\r\n return Promise.reject(\r\n new Error('Audio output device change is not supported'));\r\n }\r\n\r\n // All audio communication is done through audio tracks, so ignore\r\n // changing audio output for video tracks at all.\r\n if (this.isVideoTrack()) {\r\n return Promise.resolve();\r\n }\r\n\r\n return (\r\n Promise.all(\r\n this.containers.map(\r\n element =>\r\n element.setSinkId(audioOutputDeviceId)\r\n .catch(error => {\r\n logger.warn(\r\n 'Failed to change audio output device on'\r\n + ' element. Default or previously set'\r\n + ' audio output device will be used.',\r\n element,\r\n error);\r\n throw error;\r\n }))\r\n )\r\n .then(() => {\r\n this.emit(\r\n JitsiTrackEvents.TRACK_AUDIO_OUTPUT_CHANGED,\r\n audioOutputDeviceId);\r\n }));\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport JitsiTrackError from '../../JitsiTrackError';\r\nimport {\r\n TRACK_IS_DISPOSED,\r\n TRACK_NO_STREAM_FOUND\r\n} from '../../JitsiTrackErrors';\r\nimport {\r\n LOCAL_TRACK_STOPPED,\r\n NO_DATA_FROM_SOURCE,\r\n TRACK_MUTE_CHANGED\r\n} from '../../JitsiTrackEvents';\r\nimport CameraFacingMode from '../../service/RTC/CameraFacingMode';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport {\r\n NO_BYTES_SENT,\r\n TRACK_UNMUTED,\r\n createNoDataFromSourceEvent\r\n} from '../../service/statistics/AnalyticsEvents';\r\nimport browser from '../browser';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport Statistics from '../statistics/statistics';\r\n\r\nimport JitsiTrack from './JitsiTrack';\r\nimport RTCUtils from './RTCUtils';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Represents a single media track(either audio or video).\r\n * One JitsiLocalTrack corresponds to one WebRTC MediaStreamTrack.\r\n */\r\nexport default class JitsiLocalTrack extends JitsiTrack {\r\n /**\r\n * Constructs a new JitsiLocalTrack instance.\r\n *\r\n * @constructor\r\n * @param {Object} trackInfo\r\n * @param {number} trackInfo.rtcId - The ID assigned by the RTC module.\r\n * @param {Object} trackInfo.stream - The WebRTC MediaStream, parent of the track.\r\n * @param {Object} trackInfo.track - The underlying WebRTC MediaStreamTrack for new JitsiLocalTrack.\r\n * @param {string} trackInfo.mediaType - The MediaType of the JitsiLocalTrack.\r\n * @param {string} trackInfo.videoType - The VideoType of the JitsiLocalTrack.\r\n * @param {Array} trackInfo.effects - The effects to be applied to the JitsiLocalTrack.\r\n * @param {number} trackInfo.resolution - The the video resolution if it's a video track\r\n * @param {string} trackInfo.deviceId - The ID of the local device for this track.\r\n * @param {string} trackInfo.facingMode - Thehe camera facing mode used in getUserMedia call (for mobile only).\r\n * @param {sourceId} trackInfo.sourceId - The id of the desktop sharing source. NOTE: defined for desktop sharing\r\n * tracks only.\r\n */\r\n constructor({\r\n deviceId,\r\n facingMode,\r\n mediaType,\r\n resolution,\r\n rtcId,\r\n sourceId,\r\n sourceType,\r\n stream,\r\n track,\r\n videoType,\r\n effects = []\r\n }) {\r\n super(\r\n /* conference */ null,\r\n stream,\r\n track,\r\n /* streamInactiveHandler */ () => this.emit(LOCAL_TRACK_STOPPED),\r\n mediaType,\r\n videoType);\r\n\r\n this._setEffectInProgress = false;\r\n const effect = effects.find(e => e.isEnabled(this));\r\n\r\n if (effect) {\r\n this._startStreamEffect(effect);\r\n }\r\n\r\n const displaySurface = videoType === VideoType.DESKTOP\r\n ? track.getSettings().displaySurface\r\n : null;\r\n\r\n /**\r\n * Track metadata.\r\n */\r\n this.metadata = {\r\n timestamp: Date.now(),\r\n ...displaySurface ? { displaySurface } : {}\r\n };\r\n\r\n\r\n /**\r\n * The ID assigned by the RTC module on instance creation.\r\n *\r\n * @type {number}\r\n */\r\n this.rtcId = rtcId;\r\n this.sourceId = sourceId;\r\n this.sourceType = sourceType;\r\n\r\n // Get the resolution from the track itself because it cannot be\r\n // certain which resolution webrtc has fallen back to using.\r\n this.resolution = track.getSettings().height;\r\n this.maxEnabledResolution = resolution;\r\n\r\n // Cache the constraints of the track in case of any this track\r\n // model needs to call getUserMedia again, such as when unmuting.\r\n this._constraints = track.getConstraints();\r\n\r\n // Safari returns an empty constraints object, construct the constraints using getSettings.\r\n if (!Object.keys(this._constraints).length && videoType === VideoType.CAMERA) {\r\n this._constraints = {\r\n height: track.getSettings().height,\r\n width: track.getSettings().width\r\n };\r\n }\r\n\r\n this.deviceId = deviceId;\r\n\r\n /**\r\n * The Promise which represents the progress of a previously\r\n * queued/scheduled {@link _setMuted} (from the point of view of\r\n * {@link _queueSetMuted}).\r\n *\r\n * @private\r\n * @type {Promise}\r\n */\r\n this._prevSetMuted = Promise.resolve();\r\n\r\n /**\r\n * The facing mode of the camera from which this JitsiLocalTrack\r\n * instance was obtained.\r\n *\r\n * @private\r\n * @type {CameraFacingMode|undefined}\r\n */\r\n this._facingMode = facingMode;\r\n\r\n // Currently there is no way to know the MediaStreamTrack ended due to\r\n // to device disconnect in Firefox through e.g. \"readyState\" property.\r\n // Instead we will compare current track's label with device labels from\r\n // enumerateDevices() list.\r\n this._trackEnded = false;\r\n\r\n /**\r\n * Indicates whether data has been sent or not.\r\n */\r\n this._hasSentData = false;\r\n\r\n /**\r\n * Used only for detection of audio problems. We want to check only once\r\n * whether the track is sending data ot not. This flag is set to false\r\n * after the check.\r\n */\r\n this._testDataSent = true;\r\n\r\n // Currently there is no way to determine with what device track was\r\n // created (until getConstraints() support), however we can associate\r\n // tracks with real devices obtained from enumerateDevices() call as\r\n // soon as it's called.\r\n // NOTE: this.deviceId corresponds to the device id specified in GUM constraints and this._realDeviceId seems to\r\n // correspond to the id of a matching device from the available device list.\r\n this._realDeviceId = this.deviceId === '' ? undefined : this.deviceId;\r\n\r\n // The source name that will be signaled for this track.\r\n this._sourceName = null;\r\n\r\n this._trackMutedTS = 0;\r\n\r\n this._onDeviceListWillChange = devices => {\r\n const oldRealDeviceId = this._realDeviceId;\r\n\r\n this._setRealDeviceIdFromDeviceList(devices);\r\n\r\n if (\r\n // Mark track as ended for those browsers that do not support\r\n // \"readyState\" property. We do not touch tracks created with\r\n // default device ID \"\".\r\n (typeof this.getTrack().readyState === 'undefined'\r\n && typeof this._realDeviceId !== 'undefined'\r\n && !devices.find(d => d.deviceId === this._realDeviceId))\r\n\r\n // If there was an associated realDeviceID and after the device change the realDeviceId is undefined\r\n // then the associated device has been disconnected and the _trackEnded flag needs to be set. In\r\n // addition on some Chrome versions the readyState property is set after the device change event is\r\n // triggered which causes issues in jitsi-meet with the selection of a new device because we don't\r\n // detect that the old one was removed.\r\n || (typeof oldRealDeviceId !== 'undefined' && typeof this._realDeviceId === 'undefined')\r\n ) {\r\n this._trackEnded = true;\r\n }\r\n };\r\n\r\n // Subscribe each created local audio track to\r\n // RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED event. This is different from\r\n // handling this event for remote tracks (which are handled in RTC.js),\r\n // because there might be local tracks not attached to a conference.\r\n if (this.isAudioTrack() && RTCUtils.isDeviceChangeAvailable('output')) {\r\n this._onAudioOutputDeviceChanged = this.setAudioOutput.bind(this);\r\n RTCUtils.addListener(\r\n RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,\r\n this._onAudioOutputDeviceChanged);\r\n }\r\n\r\n RTCUtils.addListener(RTCEvents.DEVICE_LIST_WILL_CHANGE, this._onDeviceListWillChange);\r\n\r\n this._initNoDataFromSourceHandlers();\r\n }\r\n\r\n /**\r\n * Adds stream to conference and marks it as \"unmute\" operation.\r\n *\r\n * @private\r\n * @returns {Promise}\r\n */\r\n _addStreamToConferenceAsUnmute() {\r\n if (!this.conference) {\r\n return Promise.resolve();\r\n }\r\n\r\n // FIXME it would be good to not included conference as part of this process. Only TraceablePeerConnections to\r\n // which the track is attached should care about this action. The TPCs to which the track is not attached can\r\n // sync up when track is re-attached. A problem with that is that the \"modify sources\" queue is part of the\r\n // JingleSessionPC and it would be excluded from the process. One solution would be to extract class between\r\n // TPC and JingleSessionPC which would contain the queue and would notify the signaling layer when local SSRCs\r\n // are changed. This would help to separate XMPP from the RTC module.\r\n return new Promise((resolve, reject) => {\r\n this.conference._addLocalTrackAsUnmute(this)\r\n .then(resolve, error => reject(new Error(error)));\r\n });\r\n }\r\n\r\n /**\r\n * Fires NO_DATA_FROM_SOURCE event and logs it to analytics and callstats.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _fireNoDataFromSourceEvent() {\r\n const value = !this.isReceivingData();\r\n\r\n this.emit(NO_DATA_FROM_SOURCE, value);\r\n\r\n // FIXME: Should we report all of those events\r\n Statistics.sendAnalytics(createNoDataFromSourceEvent(this.getType(), value));\r\n Statistics.sendLog(JSON.stringify({\r\n name: NO_DATA_FROM_SOURCE,\r\n log: value\r\n }));\r\n }\r\n\r\n /**\r\n * Sets handlers to the MediaStreamTrack object that will detect camera issues.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _initNoDataFromSourceHandlers() {\r\n if (!this._isNoDataFromSourceEventsEnabled()) {\r\n return;\r\n }\r\n\r\n this._setHandler('track_mute', () => {\r\n this._trackMutedTS = window.performance.now();\r\n this._fireNoDataFromSourceEvent();\r\n });\r\n\r\n this._setHandler('track_unmute', () => {\r\n this._fireNoDataFromSourceEvent();\r\n Statistics.sendAnalyticsAndLog(\r\n TRACK_UNMUTED,\r\n {\r\n 'media_type': this.getType(),\r\n 'track_type': 'local',\r\n value: window.performance.now() - this._trackMutedTS\r\n });\r\n });\r\n\r\n if (this.isVideoTrack() && this.videoType === VideoType.CAMERA) {\r\n this._setHandler('track_ended', () => {\r\n if (!this.isReceivingData()) {\r\n this._fireNoDataFromSourceEvent();\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Returns true if no data from source events are enabled for this JitsiLocalTrack and false otherwise.\r\n *\r\n * @private\r\n * @returns {boolean} - True if no data from source events are enabled for this JitsiLocalTrack and false otherwise.\r\n */\r\n _isNoDataFromSourceEventsEnabled() {\r\n // Disable the events for screen sharing.\r\n return !this.isVideoTrack() || this.videoType !== VideoType.DESKTOP;\r\n }\r\n\r\n /**\r\n * Initializes a new Promise to execute {@link #_setMuted}. May be called multiple times in a row and the\r\n * invocations of {@link #_setMuted} and, consequently, {@link #mute} and/or {@link #unmute} will be resolved in a\r\n * serialized fashion.\r\n *\r\n * @param {boolean} muted - The value to invoke _setMuted with.\r\n * @private\r\n * @returns {Promise}\r\n */\r\n _queueSetMuted(muted) {\r\n const setMuted = this._setMuted.bind(this, muted);\r\n\r\n this._prevSetMuted = this._prevSetMuted.then(setMuted, setMuted);\r\n\r\n return this._prevSetMuted;\r\n }\r\n\r\n /**\r\n * Removes stream from conference and marks it as \"mute\" operation.\r\n *\r\n * @param {Function} successCallback - Callback that will be called when the operation is successful.\r\n * @param {Function} errorCallback - Callback that will be called when the operation fails.\r\n * @private\r\n * @returns {Promise}\r\n */\r\n _removeStreamFromConferenceAsMute(successCallback, errorCallback) {\r\n if (!this.conference) {\r\n successCallback();\r\n\r\n return;\r\n }\r\n this.conference._removeLocalTrackAsMute(this).then(\r\n successCallback,\r\n error => errorCallback(new Error(error)));\r\n }\r\n\r\n /**\r\n * Sends mute status for a track to conference if any.\r\n *\r\n * @param {boolean} mute - If track is muted.\r\n * @private\r\n * @returns {void}\r\n */\r\n _sendMuteStatus(mute) {\r\n if (this.conference) {\r\n this.conference._setTrackMuteStatus(this.getType(), this, mute) && this.conference.room.sendPresence();\r\n }\r\n }\r\n\r\n /**\r\n * Mutes / unmutes this track.\r\n *\r\n * @param {boolean} muted - If true, this track will be muted; otherwise, this track will be unmuted.\r\n * @private\r\n * @returns {Promise}\r\n */\r\n _setMuted(muted) {\r\n if (this.isMuted() === muted\r\n && !(this.videoType === VideoType.DESKTOP && FeatureFlags.isMultiStreamSupportEnabled())) {\r\n return Promise.resolve();\r\n }\r\n\r\n if (this.disposed) {\r\n return Promise.reject(new JitsiTrackError(TRACK_IS_DISPOSED));\r\n }\r\n\r\n let promise = Promise.resolve();\r\n\r\n // A function that will print info about muted status transition\r\n const logMuteInfo = () => logger.info(`Mute ${this}: ${muted}`);\r\n\r\n // In the multi-stream mode, desktop tracks are muted from jitsi-meet instead of being removed from the\r\n // conference. This is needed because we don't want the client to signal a source-remove to the remote peer for\r\n // the desktop track when screenshare is stopped. Later when screenshare is started again, the same sender will\r\n // be re-used without the need for signaling a new ssrc through source-add.\r\n if (this.isAudioTrack()\r\n || (this.videoType === VideoType.DESKTOP && !FeatureFlags.isMultiStreamSupportEnabled())\r\n || !browser.doesVideoMuteByStreamRemove()) {\r\n logMuteInfo();\r\n\r\n // If we have a stream effect that implements its own mute functionality, prioritize it before\r\n // normal mute e.g. the stream effect that implements system audio sharing has a custom\r\n // mute state in which if the user mutes, system audio still has to go through.\r\n if (this._streamEffect && this._streamEffect.setMuted) {\r\n this._streamEffect.setMuted(muted);\r\n } else if (this.track) {\r\n this.track.enabled = !muted;\r\n }\r\n } else if (muted) {\r\n promise = new Promise((resolve, reject) => {\r\n logMuteInfo();\r\n this._removeStreamFromConferenceAsMute(\r\n () => {\r\n if (this._streamEffect) {\r\n this._stopStreamEffect();\r\n }\r\n\r\n // FIXME: Maybe here we should set the SRC for the\r\n // containers to something\r\n // We don't want any events to be fired on this stream\r\n this._unregisterHandlers();\r\n this.stopStream();\r\n this._setStream(null);\r\n\r\n resolve();\r\n },\r\n reject);\r\n });\r\n } else {\r\n logMuteInfo();\r\n\r\n // This path is only for camera.\r\n const streamOptions = {\r\n cameraDeviceId: this.getDeviceId(),\r\n devices: [ MediaType.VIDEO ],\r\n effects: this._streamEffect ? [ this._streamEffect ] : [],\r\n facingMode: this.getCameraFacingMode()\r\n };\r\n\r\n promise\r\n = RTCUtils.obtainAudioAndVideoPermissions(Object.assign(\r\n {},\r\n streamOptions,\r\n { constraints: { video: this._constraints } }));\r\n\r\n promise = promise.then(streamsInfo => {\r\n // The track kind for presenter track is video as well.\r\n const mediaType = this.getType() === MediaType.PRESENTER ? MediaType.VIDEO : this.getType();\r\n const streamInfo = streamsInfo.find(info => info.track.kind === mediaType);\r\n\r\n if (streamInfo) {\r\n this._setStream(streamInfo.stream);\r\n this.track = streamInfo.track;\r\n\r\n // This is not good when video type changes after\r\n // unmute, but let's not crash here\r\n if (this.videoType !== streamInfo.videoType) {\r\n logger.warn(\r\n `${this}: video type has changed after unmute!`,\r\n this.videoType, streamInfo.videoType);\r\n this.videoType = streamInfo.videoType;\r\n }\r\n } else {\r\n throw new JitsiTrackError(TRACK_NO_STREAM_FOUND);\r\n }\r\n\r\n if (this._streamEffect) {\r\n this._startStreamEffect(this._streamEffect);\r\n }\r\n\r\n this.containers.map(cont => RTCUtils.attachMediaStream(cont, this.stream));\r\n\r\n return this._addStreamToConferenceAsUnmute();\r\n });\r\n }\r\n\r\n return promise\r\n .then(() => {\r\n this._sendMuteStatus(muted);\r\n\r\n // Send the videoType message to the bridge.\r\n this.isVideoTrack() && this.conference && this.conference._sendBridgeVideoTypeMessage(this);\r\n this.emit(TRACK_MUTE_CHANGED, this);\r\n });\r\n }\r\n\r\n /**\r\n * Sets real device ID by comparing track information with device information. This is temporary solution until\r\n * getConstraints() method will be implemented in browsers.\r\n *\r\n * @param {MediaDeviceInfo[]} devices - The list of devices obtained from enumerateDevices() call.\r\n * @private\r\n * @returns {void}\r\n */\r\n _setRealDeviceIdFromDeviceList(devices) {\r\n const track = this.getTrack();\r\n const kind = `${track.kind}input`;\r\n\r\n // We need to match by deviceId as well, in case of multiple devices with the same label.\r\n let device = devices.find(d => d.kind === kind && d.label === track.label && d.deviceId === this.deviceId);\r\n\r\n if (!device && this._realDeviceId === 'default') { // the default device has been changed.\r\n // If the default device was 'A' and the default device is changed to 'B' the label for the track will\r\n // remain 'Default - A' but the label for the device in the device list will be updated to 'A'. That's\r\n // why in order to match it we need to remove the 'Default - ' part.\r\n const label = (track.label || '').replace('Default - ', '');\r\n\r\n device = devices.find(d => d.kind === kind && d.label === label);\r\n }\r\n\r\n if (device) {\r\n this._realDeviceId = device.deviceId;\r\n } else {\r\n this._realDeviceId = undefined;\r\n }\r\n }\r\n\r\n /**\r\n * Sets the stream property of JitsiLocalTrack object and sets all stored handlers to it.\r\n *\r\n * @param {MediaStream} stream - The new MediaStream.\r\n * @private\r\n * @returns {void}\r\n */\r\n _setStream(stream) {\r\n super._setStream(stream);\r\n\r\n if (stream) {\r\n // Store the MSID for video mute/unmute purposes.\r\n this.storedMSID = this.getMSID();\r\n logger.debug(`Setting new MSID: ${this.storedMSID} on ${this}`);\r\n } else {\r\n logger.debug(`Setting 'null' stream on ${this}`);\r\n }\r\n }\r\n\r\n /**\r\n * Starts the effect process and returns the modified stream.\r\n *\r\n * @param {Object} effect - Represents effect instance\r\n * @private\r\n * @returns {void}\r\n */\r\n _startStreamEffect(effect) {\r\n this._streamEffect = effect;\r\n this._originalStream = this.stream;\r\n this._setStream(this._streamEffect.startEffect(this._originalStream));\r\n this.track = this.stream.getTracks()[0];\r\n }\r\n\r\n /**\r\n * Stops the effect process and returns the original stream.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _stopStreamEffect() {\r\n if (this._streamEffect) {\r\n this._streamEffect.stopEffect();\r\n this._setStream(this._originalStream);\r\n this._originalStream = null;\r\n this.track = this.stream ? this.stream.getTracks()[0] : null;\r\n }\r\n }\r\n\r\n /**\r\n * Switches the camera facing mode if the WebRTC implementation supports the custom MediaStreamTrack._switchCamera\r\n * method. Currently, the method in question is implemented in react-native-webrtc only. When such a WebRTC\r\n * implementation is executing, the method is the preferred way to switch between the front/user-facing and the\r\n * back/environment-facing cameras because it will likely be (as is the case of react-native-webrtc) noticeably\r\n * faster that creating a new MediaStreamTrack via a new getUserMedia call with the switched facingMode constraint\r\n * value. Moreover, the approach with a new getUserMedia call may not even work: WebRTC on Android and iOS is\r\n * either very slow to open the camera a second time or plainly freezes attempting to do that.\r\n *\r\n * @returns {void}\r\n */\r\n _switchCamera() {\r\n if (this.isVideoTrack()\r\n && this.videoType === VideoType.CAMERA\r\n && typeof this.track._switchCamera === 'function') {\r\n this.track._switchCamera();\r\n\r\n this._facingMode\r\n = this._facingMode === CameraFacingMode.ENVIRONMENT\r\n ? CameraFacingMode.USER\r\n : CameraFacingMode.ENVIRONMENT;\r\n }\r\n }\r\n\r\n /**\r\n * Stops the currently used effect (if there is one) and starts the passed effect (if there is one).\r\n *\r\n * @param {Object|undefined} effect - The new effect to be set.\r\n * @private\r\n * @returns {void}\r\n */\r\n _switchStreamEffect(effect) {\r\n if (this._streamEffect) {\r\n this._stopStreamEffect();\r\n this._streamEffect = undefined;\r\n }\r\n if (effect) {\r\n this._startStreamEffect(effect);\r\n }\r\n }\r\n\r\n /**\r\n * @inheritdoc\r\n *\r\n * Stops sending the media track. And removes it from the HTML. NOTE: Works for local tracks only.\r\n *\r\n * @extends JitsiTrack#dispose\r\n * @returns {Promise}\r\n */\r\n dispose() {\r\n let promise = Promise.resolve();\r\n\r\n // Remove the effect instead of stopping it so that the original stream is restored\r\n // on both the local track and on the peerconnection.\r\n if (this._streamEffect) {\r\n promise = this.setEffect();\r\n }\r\n\r\n let removeTrackPromise = Promise.resolve();\r\n\r\n if (this.conference) {\r\n removeTrackPromise = this.conference.removeTrack(this);\r\n }\r\n\r\n if (this.stream) {\r\n this.stopStream();\r\n this.detach();\r\n }\r\n\r\n RTCUtils.removeListener(RTCEvents.DEVICE_LIST_WILL_CHANGE, this._onDeviceListWillChange);\r\n\r\n if (this._onAudioOutputDeviceChanged) {\r\n RTCUtils.removeListener(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,\r\n this._onAudioOutputDeviceChanged);\r\n }\r\n\r\n return Promise.allSettled([ promise, removeTrackPromise ]).then(() => super.dispose());\r\n }\r\n\r\n /**\r\n * Returns facing mode for video track from camera. For other cases (e.g. audio track or 'desktop' video track)\r\n * returns undefined.\r\n *\r\n * @returns {CameraFacingMode|undefined}\r\n */\r\n getCameraFacingMode() {\r\n if (this.isVideoTrack() && this.videoType === VideoType.CAMERA) {\r\n // MediaStreamTrack#getSettings() is not implemented in many\r\n // browsers, so we need feature checking here. Progress on the\r\n // respective browser's implementation can be tracked at\r\n // https://bugs.chromium.org/p/webrtc/issues/detail?id=2481 for\r\n // Chromium and https://bugzilla.mozilla.org/show_bug.cgi?id=1213517\r\n // for Firefox. Even if a browser implements getSettings() already,\r\n // it might still not return anything for 'facingMode'.\r\n const trackSettings = this.track.getSettings?.();\r\n\r\n if (trackSettings && 'facingMode' in trackSettings) {\r\n return trackSettings.facingMode;\r\n }\r\n\r\n if (typeof this._facingMode !== 'undefined') {\r\n return this._facingMode;\r\n }\r\n\r\n // In most cases we are showing a webcam. So if we've gotten here,\r\n // it should be relatively safe to assume that we are probably\r\n // showing the user-facing camera.\r\n return CameraFacingMode.USER;\r\n }\r\n\r\n return undefined;\r\n }\r\n\r\n /**\r\n * Returns device id associated with track.\r\n *\r\n * @returns {string}\r\n */\r\n getDeviceId() {\r\n return this._realDeviceId || this.deviceId;\r\n }\r\n\r\n /**\r\n * Get the duration of the track.\r\n *\r\n * @returns {Number} the duration of the track in seconds\r\n */\r\n getDuration() {\r\n return (Date.now() / 1000) - (this.metadata.timestamp / 1000);\r\n }\r\n\r\n /**\r\n * Returns the participant id which owns the track.\r\n *\r\n * @returns {string} the id of the participants. It corresponds to the\r\n * Colibri endpoint id/MUC nickname in case of Jitsi-meet.\r\n */\r\n getParticipantId() {\r\n return this.conference && this.conference.myUserId();\r\n }\r\n\r\n /**\r\n * Returns the source name associated with the jitsi track.\r\n *\r\n * @returns {string | null} source name\r\n */\r\n getSourceName() {\r\n return this._sourceName;\r\n }\r\n\r\n /**\r\n * Returns if associated MediaStreamTrack is in the 'ended' state\r\n *\r\n * @returns {boolean}\r\n */\r\n isEnded() {\r\n if (this.isVideoTrack() && this.isMuted()) {\r\n // If a video track is muted the readyState will be ended, that's why we need to rely only on the\r\n // _trackEnded flag.\r\n return this._trackEnded;\r\n }\r\n\r\n return this.getTrack().readyState === 'ended' || this._trackEnded;\r\n }\r\n\r\n /**\r\n * Returns true.\r\n *\r\n * @returns {boolean} true\r\n */\r\n isLocal() {\r\n return true;\r\n }\r\n\r\n /**\r\n * Returns true - if the stream is muted and false otherwise.\r\n *\r\n * @returns {boolean} true - if the stream is muted and false otherwise.\r\n */\r\n isMuted() {\r\n // this.stream will be null when we mute local video on Chrome\r\n if (!this.stream) {\r\n return true;\r\n }\r\n if (this.isVideoTrack() && !this.isActive()) {\r\n return true;\r\n }\r\n\r\n // If currently used stream effect has its own muted state, use that.\r\n if (this._streamEffect && this._streamEffect.isMuted) {\r\n return this._streamEffect.isMuted();\r\n }\r\n\r\n return !this.track || !this.track.enabled;\r\n }\r\n\r\n /**\r\n * Checks whether the attached MediaStream is receiving data from source or not. If the stream property is null\r\n * (because of mute or another reason) this method will return false.\r\n * NOTE: This method doesn't indicate problem with the streams directly. For example in case of video mute the\r\n * method will return false or if the user has disposed the track.\r\n *\r\n * @returns {boolean} true if the stream is receiving data and false this otherwise.\r\n */\r\n isReceivingData() {\r\n if (this.isVideoTrack()\r\n && (this.isMuted() || this._stopStreamInProgress || this.videoType === VideoType.DESKTOP)) {\r\n return true;\r\n }\r\n\r\n if (!this.stream) {\r\n return false;\r\n }\r\n\r\n // In older version of the spec there is no muted property and readyState can have value muted. In the latest\r\n // versions readyState can have values \"live\" and \"ended\" and there is muted boolean property. If the stream is\r\n // muted that means that we aren't receiving any data from the source. We want to notify the users for error if\r\n // the stream is muted or ended on it's creation.\r\n\r\n // For video blur enabled use the original video stream\r\n const stream = this._effectEnabled ? this._originalStream : this.stream;\r\n\r\n return stream.getTracks().some(track =>\r\n (!('readyState' in track) || track.readyState === 'live')\r\n && (!('muted' in track) || track.muted !== true));\r\n }\r\n\r\n /**\r\n * Asynchronously mutes this track.\r\n *\r\n * @returns {Promise}\r\n */\r\n mute() {\r\n return this._queueSetMuted(true);\r\n }\r\n\r\n /**\r\n * Handles bytes sent statistics. NOTE: used only for audio tracks to detect audio issues.\r\n *\r\n * @param {TraceablePeerConnection} tpc - The peerconnection that is reporting the bytes sent stat.\r\n * @param {number} bytesSent - The new value.\r\n * @returns {void}\r\n */\r\n onByteSentStatsReceived(tpc, bytesSent) {\r\n if (bytesSent > 0) {\r\n this._hasSentData = true;\r\n }\r\n const iceConnectionState = tpc.getConnectionState();\r\n\r\n if (this._testDataSent && iceConnectionState === 'connected') {\r\n setTimeout(() => {\r\n if (!this._hasSentData) {\r\n logger.warn(`${this} 'bytes sent' <= 0: \\\r\n ${bytesSent}`);\r\n\r\n Statistics.analytics.sendEvent(NO_BYTES_SENT, { 'media_type': this.getType() });\r\n }\r\n }, 3000);\r\n this._testDataSent = false;\r\n }\r\n }\r\n\r\n /**\r\n * Sets the JitsiConference object associated with the track. This is temp solution.\r\n *\r\n * @param conference - JitsiConference object.\r\n * @returns {void}\r\n */\r\n setConference(conference) {\r\n this.conference = conference;\r\n\r\n // We want to keep up with postponed events which should have been fired\r\n // on \"attach\" call, but for local track we not always have the\r\n // conference before attaching. However this may result in duplicated\r\n // events if they have been triggered on \"attach\" already.\r\n for (let i = 0; i < this.containers.length; i++) {\r\n this._maybeFireTrackAttached(this.containers[i]);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the effect and switches between the modified stream and original one.\r\n *\r\n * @param {Object} effect - Represents the effect instance to be used.\r\n * @returns {Promise}\r\n */\r\n setEffect(effect) {\r\n if (typeof this._streamEffect === 'undefined' && typeof effect === 'undefined') {\r\n return Promise.resolve();\r\n }\r\n\r\n if (typeof effect !== 'undefined' && !effect.isEnabled(this)) {\r\n return Promise.reject(new Error('Incompatible effect instance!'));\r\n }\r\n\r\n if (this._setEffectInProgress === true) {\r\n return Promise.reject(new Error('setEffect already in progress!'));\r\n }\r\n\r\n // In case we have an audio track that is being enhanced with an effect, we still want it to be applied,\r\n // even if the track is muted. Where as for video the actual track doesn't exists if it's muted.\r\n if (this.isMuted() && !this.isAudioTrack()) {\r\n this._streamEffect = effect;\r\n\r\n return Promise.resolve();\r\n }\r\n\r\n const conference = this.conference;\r\n\r\n if (!conference) {\r\n this._switchStreamEffect(effect);\r\n if (this.isVideoTrack()) {\r\n this.containers.forEach(cont => RTCUtils.attachMediaStream(cont, this.stream));\r\n }\r\n\r\n return Promise.resolve();\r\n }\r\n\r\n this._setEffectInProgress = true;\r\n\r\n // TODO: Create new JingleSessionPC method for replacing a stream in JitsiLocalTrack without offer answer.\r\n return conference.removeTrack(this)\r\n .then(() => {\r\n this._switchStreamEffect(effect);\r\n if (this.isVideoTrack()) {\r\n this.containers.forEach(cont => RTCUtils.attachMediaStream(cont, this.stream));\r\n }\r\n\r\n return conference.addTrack(this);\r\n })\r\n .then(() => {\r\n this._setEffectInProgress = false;\r\n })\r\n .catch(error => {\r\n // Any error will be not recovarable and will trigger CONFERENCE_FAILED event. But let's try to cleanup\r\n // everyhting related to the effect functionality.\r\n this._setEffectInProgress = false;\r\n this._switchStreamEffect();\r\n logger.error('Failed to switch to the new stream!', error);\r\n throw error;\r\n });\r\n }\r\n\r\n /**\r\n * Sets the source name to be used for signaling the jitsi track.\r\n *\r\n * @param {string} name The source name.\r\n */\r\n setSourceName(name) {\r\n this._sourceName = name;\r\n }\r\n\r\n /**\r\n * Stops the associated MediaStream.\r\n *\r\n * @returns {void}\r\n */\r\n stopStream() {\r\n /**\r\n * Indicates that we are executing {@link #stopStream} i.e.\r\n * {@link RTCUtils#stopMediaStream} for the MediaStream\r\n * associated with this JitsiTrack instance.\r\n *\r\n * @private\r\n * @type {boolean}\r\n */\r\n this._stopStreamInProgress = true;\r\n\r\n try {\r\n RTCUtils.stopMediaStream(this.stream);\r\n } finally {\r\n this._stopStreamInProgress = false;\r\n }\r\n }\r\n\r\n /**\r\n * Creates a text representation of this local track instance.\r\n *\r\n * @return {string}\r\n */\r\n toString() {\r\n return `LocalTrack[${this.rtcId},${this.getType()}]`;\r\n }\r\n\r\n /**\r\n * Asynchronously unmutes this track.\r\n *\r\n * @returns {Promise}\r\n */\r\n unmute() {\r\n return this._queueSetMuted(false);\r\n }\r\n}\r\n","/* Copyright @ 2015 - Present, 8x8 Inc\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport transform from 'sdp-transform';\n\n/**\n * Rewrites the source information in the way sdp-transform expects.\n * Source information is split into multiple ssrc objects each containing\n * an id, attribute and value.\n * @param {Object} media - media description to be modified.\n * @returns {void}\n */\nconst write = function(session, opts) {\n if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && Array.isArray(session.media)) {\n session.media.forEach(mLine => {\n if (mLine.sources && mLine.sources.length) {\n mLine.ssrcs = [];\n mLine.sources.forEach(source => {\n Object.keys(source).forEach(attribute => {\n if (attribute === 'id') {\n return;\n }\n mLine.ssrcs.push({\n id: source.id,\n attribute,\n value: source[attribute]\n });\n });\n });\n delete mLine.sources;\n }\n\n // join ssrcs in ssrc groups\n if (mLine.ssrcGroups && mLine.ssrcGroups.length) {\n mLine.ssrcGroups.forEach(ssrcGroup => {\n if (typeof ssrcGroup.ssrcs !== 'undefined'\n && Array.isArray(ssrcGroup.ssrcs)) {\n ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');\n }\n });\n }\n });\n }\n\n return transform.write(session, opts);\n};\n\n/**\n * Rewrites the source information that we get from sdp-transform.\n * All the ssrc lines with different attributes that belong to the\n * same ssrc are grouped into a single soure object with multiple key value pairs.\n * @param {Object} media - media description to be modified.\n * @returns {void}\n */\nconst parse = function(sdp) {\n const session = transform.parse(sdp);\n\n if (typeof session !== 'undefined' && typeof session.media !== 'undefined' && Array.isArray(session.media)) {\n session.media.forEach(mLine => {\n // group sources attributes by ssrc\n if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {\n mLine.sources = [];\n mLine.ssrcs.forEach(ssrc => {\n const found = mLine.sources.findIndex(source => source.id === ssrc.id);\n\n if (found > -1) {\n mLine.sources[found][ssrc.attribute] = ssrc.value;\n } else {\n const src = { id: ssrc.id };\n\n src[ssrc.attribute] = ssrc.value;\n mLine.sources.push(src);\n }\n });\n delete mLine.ssrcs;\n }\n\n // split ssrcs in ssrc groups\n if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) {\n mLine.ssrcGroups.forEach(ssrcGroup => {\n if (typeof ssrcGroup.ssrcs === 'string') {\n ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');\n }\n });\n }\n });\n }\n\n return session;\n};\n\nexport default {\n write,\n parse\n};\n","/* Copyright @ 2015 - Present, 8x8 Inc\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport clonedeep from 'lodash.clonedeep';\n\nimport transform from './transform.js';\n\nconst PLAN_B_MIDS = [ 'audio', 'video', 'data' ];\nconst findSimGroup = ssrcGroup => ssrcGroup.find(grp => grp.semantics === 'SIM');\nconst findFidGroup = ssrcGroup => ssrcGroup.find(grp => grp.semantics === 'FID');\n\n/**\n * Add the ssrcs of the SIM group and their corresponding FID group ssrcs\n * to the m-line.\n * @param {Object} mLine - The m-line to which ssrcs have to be added.\n * @param {Object} simGroup - The SIM group whose ssrcs have to be added to\n * the m-line.\n * @param {Object} sourceGroups - inverted source-group map.\n * @param {Array} sourceList - array containing all the sources.\n */\nfunction addSimGroupSources(mLine, simGroup, sourceGroups, sourceList) {\n if (!mLine || !simGroup) {\n return;\n }\n const findSourcebyId = src => sourceList.find(source => source.id.toString() === src);\n\n simGroup.ssrcs.forEach(src => {\n mLine.sources.push(findSourcebyId(src));\n\n // find the related FID group member for this ssrc.\n const relatedFidGroup = sourceGroups[parseInt(src, 10)].find(grp => grp.semantics === 'FID');\n\n if (relatedFidGroup) {\n const relatedSsrc = relatedFidGroup.ssrcs.find(s => s !== src);\n\n mLine.sources.push(findSourcebyId(relatedSsrc));\n mLine.ssrcGroups.push(relatedFidGroup);\n }\n });\n\n // Add the SIM group last.\n mLine.ssrcGroups.push(simGroup);\n}\n\n/**\n * Add ssrcs and ssrc-groups to the m-line. When a primary ssrc, i.e., the\n * first ssrc in a SIM group is passed, all the other ssrcs from the SIM\n * group and the other ssrcs from the related FID groups are added to the same\n * m-line since they all belong to the same remote source. Since the ssrcs are\n * not guaranteed to be in the correct order, try to find if a SIM group exists,\n * if not, just add the FID group.\n * @param {Object} mLine - The m-line to which ssrcs have to be added.\n * @param {Object} ssrc - the primary ssrc.\n * @param {Object} sourceGroups - inverted source-group map.\n * @param {Array} sourceList - array containing all the sources.\n * @returns {void}\n */\nfunction addSourcesToMline(mLine, ssrc, sourceGroups, sourceList) {\n if (!mLine || !ssrc) {\n return;\n }\n mLine.sources = [];\n mLine.ssrcGroups = [];\n\n // If there are no associated ssrc-groups, just add the ssrc and msid.\n if (!sourceGroups[ssrc.id]) {\n mLine.sources.push(ssrc);\n mLine.msid = ssrc.msid;\n\n return;\n }\n const findSourcebyId = src => sourceList.find(source => source.id.toString() === src);\n\n // Find the SIM and FID groups that this ssrc belongs to.\n const simGroup = findSimGroup(sourceGroups[ssrc.id]);\n const fidGroup = findFidGroup(sourceGroups[ssrc.id]);\n\n // Add the ssrcs for the SIM group and their corresponding FID groups.\n if (simGroup) {\n addSimGroupSources(mLine, simGroup, sourceGroups, sourceList);\n } else if (fidGroup) {\n // check if the other ssrc from this FID group is part of a SIM group\n const otherSsrc = fidGroup.ssrcs.find(s => s !== ssrc);\n const simGroup2 = findSimGroup(sourceGroups[otherSsrc]);\n\n if (simGroup2) {\n addSimGroupSources(mLine, simGroup2, sourceGroups, sourceList);\n } else {\n // Add the FID group ssrcs.\n fidGroup.ssrcs.forEach(src => {\n mLine.sources.push(findSourcebyId(src));\n });\n mLine.ssrcGroups.push(fidGroup);\n }\n }\n\n // Set the msid for the media description using the msid attribute of the ssrcs.\n mLine.msid = mLine.sources[0].msid;\n}\n\n/**\n * Checks if there is a mline for the given ssrc or its related primary ssrc.\n * We always implode the SIM group to the first ssrc in the SIM group before sRD,\n * so we also check if mline for that ssrc exists.\n * For example:\n * If the following ssrcs are in a SIM group,\n * \n * \n * \n * \n * \n * This method returns true for any one of the 3 ssrcs if there is a mline for 1806330949.\n * @param {Object} ssrc - ssrc to check.\n * @param {Object} sourceGroups - inverted source-group map.\n * @param {Array} mlines - mlines in the description\n\n * @returns {Boolean} - Returns true if mline for the given ssrc or the related primary ssrc\n * exists, returns false otherwise.\n */\nfunction checkIfMlineForSsrcExists(ssrc, sourceGroups, mlines) {\n const findMatchingMline = mline => {\n if (mline.sources) {\n return mline.sources.some(source => source.id === ssrc.id);\n }\n\n return false;\n };\n\n if (!mlines.find(findMatchingMline)) {\n // check if this ssrc is member of a SIM group. If so, check if there\n // is a matching m-line for the primary ssrc of the SIM group.\n if (!sourceGroups[ssrc.id]) {\n return false;\n }\n const simGroup = findSimGroup(sourceGroups[ssrc.id]);\n const fidGroup = findFidGroup(sourceGroups[ssrc.id]);\n\n if (simGroup) {\n return mlines.some(mline => mline.sources\n && mline.sources.some(src => src.id.toString() === simGroup.ssrcs[0]));\n } else if (fidGroup && ssrc.id.toString() !== fidGroup.ssrcs[0]) {\n const otherSsrc = { id: fidGroup.ssrcs[0] };\n\n return checkIfMlineForSsrcExists(otherSsrc, sourceGroups, mlines);\n\n }\n\n return false;\n }\n\n return true;\n}\n\n/**\n * Create an inverted sourceGroup map to put all the grouped ssrcs\n * in the same m-line.\n * @param {Array} sourceGroups\n * @returns {Object} - An inverted sourceGroup map.\n */\nfunction createSourceGroupMap(sourceGroups) {\n const ssrc2group = {};\n\n if (!sourceGroups || !Array.isArray(sourceGroups)) {\n return ssrc2group;\n }\n sourceGroups.forEach(group => {\n if (group.ssrcs && Array.isArray(group.ssrcs)) {\n group.ssrcs.forEach(ssrc => {\n if (typeof ssrc2group[ssrc] === 'undefined') {\n ssrc2group[ssrc] = [];\n }\n ssrc2group[ssrc].push(group);\n });\n }\n });\n\n return ssrc2group;\n}\n\n/**\n * Check if a new SDP requests an ICE restart.\n * @param {Object} - the parsed new SDP\n * @param {Object} - the parsed previous SDP\n * @returns {Boolean} - Returns true if an ICE restart is requested otherwise false.\n */\nfunction checkForIceRestart(newDesc, oldDesc) {\n if (!newDesc || !oldDesc || newDesc.media.length === 0 || oldDesc.media.length === 0) {\n return false;\n }\n\n const newMLine = newDesc.media[0];\n const oldMLine = oldDesc.media[0];\n\n return newMLine.iceUfrag !== oldMLine.iceUfrag || newMLine.icePwd !== oldMLine.icePwd;\n}\n\n/**\n * Interop provides an API for tranforming a Plan B SDP to a Unified Plan SDP and\n * vice versa.\n */\nexport class Interop {\n /**\n * This method transforms a Unified Plan SDP to an equivalent Plan B SDP.\n * @param {RTCSessionDescription} description - The description in Unified plan format.\n * @returns RTCSessionDescription - The transformed session description.\n */\n toPlanB(description) {\n if (!description || typeof description.sdp !== 'string') {\n console.warn('An empty description was passed as an argument.');\n\n return description;\n }\n\n // Objectify the SDP for easier manipulation.\n const session = transform.parse(description.sdp);\n\n // If the SDP contains no media, there's nothing to transform.\n if (!session.media || !session.media.length) {\n console.warn('The description has no media.');\n\n return description;\n }\n\n // Make sure this is a unified plan sdp\n if (session.media.every(m => PLAN_B_MIDS.indexOf(m.mid) !== -1)) {\n console.warn('The description does not look like unified plan sdp');\n\n return description;\n }\n\n const media = {};\n const sessionMedia = session.media;\n\n session.media = [];\n sessionMedia.forEach(mLine => {\n const type = mLine.type;\n\n if (type === 'application') {\n mLine.mid = 'data';\n media[mLine.mid] = mLine;\n\n return;\n }\n if (typeof media[type] === 'undefined') {\n const bLine = clonedeep(mLine);\n\n // Copy the msid attribute to all the ssrcs if they belong to the same source group\n if (bLine.sources && Array.isArray(bLine.sources)) {\n bLine.sources.forEach(source => {\n mLine.msid ? source.msid = mLine.msid : delete source.msid;\n });\n }\n\n // Do not signal the FID groups if there is no msid attribute present\n // on the sources as sesison-accept with this source info will fail strophe\n // validation and the session will not be established. This behavior is seen\n // on Firefox (with RTX enabled) when no video source is added at the join time.\n // FF generates two recvonly ssrcs with no msid and a corresponding FID group in\n // this case.\n if (!bLine.ssrcGroups || !mLine.msid) {\n bLine.ssrcGroups = [];\n }\n delete bLine.msid;\n bLine.mid = type;\n media[type] = bLine;\n } else if (mLine.msid) {\n // Add sources and source-groups to the existing m-line of the same media type.\n const bLine = clonedeep(mLine);\n\n if (bLine.sources && Array.isArray(bLine.sources)) {\n // Copy the msid attribute to each ssrc.\n bLine.sources.forEach(ssrc => {\n ssrc.msid = mLine.msid;\n });\n media[type].sources = (media[type].sources || []).concat(bLine.sources);\n }\n if (typeof bLine.ssrcGroups !== 'undefined' && Array.isArray(bLine.ssrcGroups)) {\n media[type].ssrcGroups = (media[type].ssrcGroups || []).concat(bLine.ssrcGroups);\n }\n }\n });\n session.media = Object.values(media);\n\n // Bundle the media only if it is active.\n const bundle = [];\n\n Object.values(media).forEach(mline => {\n if (mline.direction !== 'inactive') {\n bundle.push(mline.mid);\n }\n });\n\n // We regenerate the BUNDLE group with the new mids.\n session.groups.forEach(group => {\n if (group.type === 'BUNDLE') {\n group.mids = bundle.join(' ');\n }\n });\n\n // msid semantic\n session.msidSemantic = {\n semantic: 'WMS',\n token: '*'\n };\n const resStr = transform.write(session);\n\n return new RTCSessionDescription({\n type: description.type,\n sdp: resStr\n });\n }\n\n /**\n * This method transforms a Plan B SDP to an equivalent Unified Plan SDP.\n * @param {RTCSessionDescription} description - The description in plan-b format.\n * @param {RTCSessionDescription} current - The current description set on\n * the peerconnection in Unified-plan format, i.e., the readonly attribute\n * remoteDescription on the RTCPeerConnection object.\n * @returns RTCSessionDescription - The transformed session description.\n */\n toUnifiedPlan(description, current = null) {\n if (!description || typeof description.sdp !== 'string') {\n console.warn('An empty description was passed as an argument.');\n\n return description;\n }\n\n // Objectify the SDP for easier manipulation.\n const session = transform.parse(description.sdp);\n\n // If the SDP contains no media, there's nothing to transform.\n if (!session.media || !session.media.length) {\n console.warn('The description has no media.');\n\n return description;\n }\n\n // Make sure this is a plan-b sdp.\n if (session.media.length > 3 || session.media.every(m => PLAN_B_MIDS.indexOf(m.mid) === -1)) {\n console.warn('The description does not look like plan-b');\n\n return description;\n }\n const currentDesc = current ? transform.parse(current.sdp) : null;\n const iceRestart = checkForIceRestart(session, currentDesc);\n const newIceUfrag = session.media[0].iceUfrag;\n const newIcePwd = session.media[0].icePwd;\n const newFingerprint = session.media[0].fingerprint;\n const media = {};\n\n session.media.forEach(mLine => {\n const type = mLine.type;\n\n if (type === 'application') {\n if (!currentDesc || !currentDesc.media) {\n const newMline = clonedeep(mLine);\n\n newMline.mid = Object.keys(media).length.toString();\n media[mLine.mid] = newMline;\n\n return;\n }\n const mLineForData = currentDesc.media.findIndex(m => m.type === type);\n\n if (mLineForData) {\n currentDesc.media[mLineForData] = mLine;\n currentDesc.media[mLineForData].mid = mLineForData;\n }\n\n return;\n }\n\n // Create an inverted sourceGroup map here to put all the grouped SSRCs in the same m-line.\n const ssrc2group = createSourceGroupMap(mLine.ssrcGroups);\n\n // If there are no sources advertised for a media type, add the description if this is the first\n // remote offer, i.e., no current description was passed. Chrome in Unified plan does not produce\n // recvonly ssrcs unlike Firefox and Safari.\n if (!mLine.sources) {\n if (!currentDesc) {\n const newMline = clonedeep(mLine);\n\n newMline.mid = Object.keys(media).length.toString();\n media[mLine.mid] = newMline;\n }\n\n return;\n }\n mLine.sources.forEach((ssrc, idx) => {\n // Do not add the receive-only ssrcs that Jicofo sends in the source-add.\n // These ssrcs do not have the \"msid\" attribute set.\n if (!ssrc.msid) {\n return;\n }\n\n // If there is no description set on the peerconnection, create new m-lines.\n if (!currentDesc || !currentDesc.media) {\n if (checkIfMlineForSsrcExists(ssrc, ssrc2group, Object.values(media))) {\n return;\n }\n const newMline = clonedeep(mLine);\n\n newMline.mid = Object.keys(media).length.toString();\n newMline.direction = idx\n ? 'sendonly'\n : mLine.direction === 'sendonly' ? 'sendonly' : 'sendrecv';\n newMline.bundleOnly = undefined;\n addSourcesToMline(newMline, ssrc, ssrc2group, mLine.sources);\n media[newMline.mid] = newMline;\n\n return;\n }\n\n // Create and append the m-lines to the existing description.\n if (checkIfMlineForSsrcExists(ssrc, ssrc2group, currentDesc.media)) {\n return;\n }\n const newMline = clonedeep(mLine);\n\n newMline.mid = currentDesc.media.length.toString();\n newMline.direction = 'sendonly';\n addSourcesToMline(newMline, ssrc, ssrc2group, mLine.sources);\n currentDesc.media.push(newMline);\n });\n });\n session.media = currentDesc ? currentDesc.media : Object.values(media);\n const mids = [];\n\n session.media.forEach(mLine => {\n mids.push(mLine.mid);\n if (iceRestart) {\n mLine.iceUfrag = newIceUfrag;\n mLine.icePwd = newIcePwd;\n mLine.fingerprint = newFingerprint;\n }\n });\n\n // We regenerate the BUNDLE group (since we regenerated the mids)\n session.groups.forEach(group => {\n if (group.type === 'BUNDLE') {\n group.mids = mids.join(' ');\n }\n });\n\n // msid semantic\n session.msidSemantic = {\n semantic: 'WMS',\n token: '*'\n };\n\n // Increment the session version every time.\n session.origin.sessionVersion++;\n const resultSdp = transform.write(session);\n\n return new RTCSessionDescription({\n type: description.type,\n sdp: resultSdp\n });\n }\n}\n","export enum SignalingEvents {\r\n /**\r\n * Event triggered when participant's muted status changes.\r\n *\r\n * @param {string} endpointId the track owner's identifier (MUC nickname)\r\n * @param {MediaType} mediaType \"audio\" or \"video\"\r\n * @param {boolean} isMuted the new muted state\r\n */\r\n PEER_MUTED_CHANGED = 'signaling.peerMuted',\r\n\r\n /**\r\n * Event triggered when participant's video type changes.\r\n *\r\n * @param {string} endpointId the video owner's ID (MUC nickname)\r\n * @param {VideoType} videoType the new value\r\n */\r\n PEER_VIDEO_TYPE_CHANGED = 'signaling.peerVideoType',\r\n\r\n /**\r\n * Event triggered when source's muted status changes.\r\n *\r\n * @param {string} sourceName - The name of the source.\r\n * @param {boolean} isMuted - The new muted state.\r\n */\r\n SOURCE_MUTED_CHANGED = 'signaling.sourceMuted',\r\n\r\n /**\r\n * Event triggered when source's video type changes.\r\n *\r\n * @param {string} source - The name of the source.\r\n * @param {VideoType} videoType - The new value.\r\n */\r\n SOURCE_VIDEO_TYPE_CHANGED = 'signaling.sourceVideoType'\r\n}\r\n\r\n// exported for backward compatibility\r\nexport const PEER_MUTED_CHANGED = SignalingEvents.PEER_MUTED_CHANGED;\r\nexport const PEER_VIDEO_TYPE_CHANGED = SignalingEvents.PEER_VIDEO_TYPE_CHANGED;\r\nexport const SOURCE_MUTED_CHANGED = SignalingEvents.SOURCE_MUTED_CHANGED;\r\nexport const SOURCE_VIDEO_TYPE_CHANGED = SignalingEvents.SOURCE_VIDEO_TYPE_CHANGED;\r\n","\r\nimport Listenable from '../../modules/util/Listenable';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\n\r\n/**\r\n * @typedef {string} EndpointId\r\n */\r\n/**\r\n * @typedef {string} SourceName\r\n */\r\n/**\r\n * @typedef {Object} SourceInfo\r\n *\r\n * @property {SourceName} sourceName - Name of the media source.\r\n * @property {boolean} [muted=false] - Tells if the source is muted (paused?).\r\n * @property {string} [videoType] - Type of the video for video type.\r\n */\r\n\r\n/**\r\n * Generates a source name.\r\n *\r\n * @param {EndpointId} endpointId - Jitsi Endpoint Id.\r\n * @param {MediaType} mediaType - the media type string.\r\n * @param {number} trackIdx - Track index (or sender idx? - to be figured out) starting from 0.\r\n * @returns {SourceName} eg. endpointA-v0\r\n */\r\nexport function getSourceNameForJitsiTrack(endpointId, mediaType, trackIdx) {\r\n const firstLetterOfMediaType = mediaType.substring(0, 1);\r\n\r\n return `${endpointId}-${firstLetterOfMediaType}${trackIdx}`;\r\n}\r\n\r\n/**\r\n * Extracts MediaType from give source name (must be in the correct format as generated by\r\n * {@link getSourceNameForJitsiTrack}).\r\n *\r\n * @param {SourceName} sourceName - the source name.\r\n * @returns {MediaType}\r\n */\r\nexport function getMediaTypeFromSourceName(sourceName) {\r\n const firstLetterOfMediaTypeIdx = sourceName.indexOf('-') + 1;\r\n\r\n if (firstLetterOfMediaTypeIdx <= 0) {\r\n throw new Error(`Invalid source name: ${sourceName}`);\r\n }\r\n\r\n const firstLetterOfMediaType = sourceName.substr(firstLetterOfMediaTypeIdx, 1);\r\n\r\n for (const type of Object.values(MediaType)) {\r\n if (type.substr(0, 1) === firstLetterOfMediaType) {\r\n return type;\r\n }\r\n }\r\n\r\n throw new Error(`Invalid source name: ${sourceName}`);\r\n}\r\n\r\n/**\r\n * An object that carries the info about specific media type advertised by\r\n * participant in the signaling channel.\r\n * @typedef {Object} PeerMediaInfo\r\n * @property {boolean} muted indicates if the media is currently muted\r\n * @property {VideoType|undefined} videoType the type of the video if applicable\r\n */\r\n\r\n/**\r\n * Interface used to expose the information carried over the signaling channel\r\n * which is not available to the RTC module in the media SDP.\r\n *\r\n * @interface SignalingLayer\r\n */\r\nexport default class SignalingLayer extends Listenable {\r\n\r\n /**\r\n * Obtains the endpoint ID for given SSRC.\r\n * @param {number} ssrc the SSRC number.\r\n * @return {string|null} the endpoint ID for given media SSRC.\r\n */\r\n getSSRCOwner(ssrc) { // eslint-disable-line no-unused-vars\r\n throw new Error('not implemented');\r\n }\r\n\r\n /**\r\n * Obtains the info about given media advertised in the MUC presence of\r\n * the participant identified by the given MUC JID.\r\n * @param {string} owner the MUC jid of the participant for whom\r\n * {@link PeerMediaInfo} will be obtained.\r\n * @param {MediaType} mediaType the type of the media for which presence\r\n * info will be obtained.\r\n * @return {PeerMediaInfo|null} presenceInfo an object with media presence\r\n * info or null either if there is no presence available for given\r\n * JID or if the media type given is invalid.\r\n *\r\n * @deprecated This method is to be replaced with getPeerSourceInfo.\r\n */\r\n getPeerMediaInfo(owner, mediaType) { // eslint-disable-line no-unused-vars\r\n throw new Error('not implemented');\r\n }\r\n\r\n /**\r\n * Obtains the info about a source for given name and endpoint ID.\r\n * @param {EndpointId} owner - The owner's endpoint ID.\r\n * @param {SourceName} sourceName - The name of the source for which the info is to be obtained.\r\n * @returns {SourceInfo | undefined}\r\n */\r\n getPeerSourceInfo(owner, sourceName) { // eslint-disable-line no-unused-vars\r\n throw new Error('not implemented');\r\n }\r\n\r\n /**\r\n * Obtains the source name for given SSRC.\r\n * @param {number} ssrc the track's SSRC identifier.\r\n * @returns {SourceName | undefined} the track's source name.\r\n */\r\n getTrackSourceName(ssrc) { // eslint-disable-line no-unused-vars\r\n throw new Error('not implemented');\r\n }\r\n}\r\n","import * as transform from 'sdp-transform';\r\n\r\n/**\r\n * Parses the primary SSRC of given SSRC group.\r\n * @param {object} group the SSRC group object as defined by the 'sdp-transform'\r\n * @return {Number} the primary SSRC number\r\n */\r\nexport function parsePrimarySSRC(group) {\r\n return parseInt(group.ssrcs.split(' ')[0], 10);\r\n}\r\n\r\n/**\r\n * Parses the secondary SSRC of given SSRC group.\r\n * @param {object} group the SSRC group object as defined by the 'sdp-transform'\r\n * @return {Number} the secondary SSRC number\r\n */\r\nexport function parseSecondarySSRC(group) {\r\n return parseInt(group.ssrcs.split(' ')[1], 10);\r\n}\r\n\r\n/**\r\n * Tells how many distinct SSRCs are contained in given media line.\r\n * @param {Object} mLine the media line object as defined by 'sdp-transform' lib\r\n * @return {number}\r\n */\r\nfunction _getSSRCCount(mLine) {\r\n if (!mLine.ssrcs) {\r\n return 0;\r\n }\r\n\r\n return mLine.ssrcs\r\n .map(ssrcInfo => ssrcInfo.id)\r\n .filter((ssrc, index, array) => array.indexOf(ssrc) === index)\r\n .length;\r\n}\r\n\r\n/**\r\n * A wrapper around 'sdp-transform' media description object which provides\r\n * utility methods for common SDP/SSRC related operations.\r\n */\r\nclass MLineWrap {\r\n\r\n /**\r\n * Creates new MLineWrap>\r\n * @param {Object} mLine the media line object as defined by 'sdp-transform'\r\n * lib.\r\n */\r\n constructor(mLine) {\r\n if (!mLine) {\r\n throw new Error('mLine is undefined');\r\n }\r\n\r\n this.mLine = mLine;\r\n }\r\n\r\n /**\r\n * Getter for the mLine's \"ssrcs\" array. If the array was undefined an empty\r\n * one will be preassigned.\r\n *\r\n * @return {Array} an array of 'sdp-transform' SSRC attributes\r\n * objects.\r\n */\r\n get ssrcs() {\r\n if (!this.mLine.ssrcs) {\r\n this.mLine.ssrcs = [];\r\n }\r\n\r\n return this.mLine.ssrcs;\r\n }\r\n\r\n /**\r\n * Setter for the mLine's \"ssrcs\" array.\r\n *\r\n * @param {Array} ssrcs an array of 'sdp-transform' SSRC attributes\r\n * objects.\r\n */\r\n set ssrcs(ssrcs) {\r\n this.mLine.ssrcs = ssrcs;\r\n }\r\n\r\n /**\r\n * Returns the direction of the underlying media description.\r\n * @return {string} the media direction name as defined in the SDP.\r\n */\r\n get direction() {\r\n return this.mLine.direction;\r\n }\r\n\r\n /**\r\n * Modifies the direction of the underlying media description.\r\n * @param {string} direction the new direction to be set\r\n */\r\n set direction(direction) {\r\n this.mLine.direction = direction;\r\n }\r\n\r\n /**\r\n * Exposes the SSRC group array of the underlying media description object.\r\n * @return {Array.}\r\n */\r\n get ssrcGroups() {\r\n if (!this.mLine.ssrcGroups) {\r\n this.mLine.ssrcGroups = [];\r\n }\r\n\r\n return this.mLine.ssrcGroups;\r\n }\r\n\r\n /**\r\n * Modifies the SSRC groups array of the underlying media description\r\n * object.\r\n * @param {Array.} ssrcGroups\r\n */\r\n set ssrcGroups(ssrcGroups) {\r\n this.mLine.ssrcGroups = ssrcGroups;\r\n }\r\n\r\n /**\r\n * Obtains value from SSRC attribute.\r\n * @param {number} ssrcNumber the SSRC number for which attribute is to be\r\n * found\r\n * @param {string} attrName the name of the SSRC attribute to be found.\r\n * @return {string|undefined} the value of SSRC attribute or\r\n * undefined if no such attribute exists.\r\n */\r\n getSSRCAttrValue(ssrcNumber, attrName) {\r\n const attribute = this.ssrcs.find(\r\n ssrcObj => ssrcObj.id === ssrcNumber\r\n && ssrcObj.attribute === attrName);\r\n\r\n\r\n return attribute && attribute.value;\r\n }\r\n\r\n /**\r\n * Removes all attributes for given SSRC number.\r\n * @param {number} ssrcNum the SSRC number for which all attributes will be\r\n * removed.\r\n */\r\n removeSSRC(ssrcNum) {\r\n if (!this.mLine.ssrcs || !this.mLine.ssrcs.length) {\r\n return;\r\n }\r\n\r\n this.mLine.ssrcs\r\n = this.mLine.ssrcs.filter(ssrcObj => ssrcObj.id !== ssrcNum);\r\n }\r\n\r\n /**\r\n * Adds SSRC attribute\r\n * @param {object} ssrcObj the SSRC attribute object as defined in\r\n * the 'sdp-transform' lib.\r\n */\r\n addSSRCAttribute(ssrcObj) {\r\n this.ssrcs.push(ssrcObj);\r\n }\r\n\r\n /**\r\n * Finds a SSRC group matching both semantics and SSRCs in order.\r\n * @param {string} semantics the name of the semantics\r\n * @param {string} [ssrcs] group SSRCs as a string (like it's defined in\r\n * SSRC group object of the 'sdp-transform' lib) e.g. \"1232546 342344 25434\"\r\n * @return {object|undefined} the SSRC group object or undefined if\r\n * not found.\r\n */\r\n findGroup(semantics, ssrcs) {\r\n return this.ssrcGroups.find(\r\n group =>\r\n group.semantics === semantics\r\n && (!ssrcs || ssrcs === group.ssrcs));\r\n }\r\n\r\n /**\r\n * Finds all groups matching given semantic's name.\r\n * @param {string} semantics the name of the semantics\r\n * @return {Array.} an array of SSRC group objects as defined by\r\n * the 'sdp-transform' lib.\r\n */\r\n findGroups(semantics) {\r\n return this.ssrcGroups.filter(\r\n group => group.semantics === semantics);\r\n }\r\n\r\n /**\r\n * Finds all groups matching given semantic's name and group's primary SSRC.\r\n * @param {string} semantics the name of the semantics\r\n * @param {number} primarySSRC the primary SSRC number to be matched\r\n * @return {Object} SSRC group object as defined by the 'sdp-transform' lib.\r\n */\r\n findGroupByPrimarySSRC(semantics, primarySSRC) {\r\n return this.ssrcGroups.find(\r\n group => group.semantics === semantics\r\n && parsePrimarySSRC(group) === primarySSRC);\r\n }\r\n\r\n /**\r\n * @param {string|null} msid the media stream id or null to match\r\n * the first SSRC object with any 'msid' value.\r\n * @return {Object|undefined} the SSRC object as defined by 'sdp-transform'\r\n * lib.\r\n */\r\n findSSRCByMSID(msid) {\r\n return this.ssrcs.find(\r\n ssrcObj => ssrcObj.attribute === 'msid'\r\n && (msid === null || ssrcObj.value === msid));\r\n }\r\n\r\n /**\r\n * Gets the SSRC count for the underlying media description.\r\n * @return {number}\r\n */\r\n getSSRCCount() {\r\n return _getSSRCCount(this.mLine);\r\n }\r\n\r\n /**\r\n * Checks whether the underlying media description contains any SSRC groups.\r\n * @return {boolean} true if there are any SSRC groups or\r\n * false otherwise.\r\n */\r\n containsAnySSRCGroups() {\r\n return this.mLine.ssrcGroups !== undefined;\r\n }\r\n\r\n /**\r\n * Finds the primary video SSRC.\r\n * @returns {number|undefined} the primary video ssrc\r\n * @throws Error if the underlying media description is not a video\r\n */\r\n getPrimaryVideoSsrc() {\r\n const mediaType = this.mLine.type;\r\n\r\n if (mediaType !== 'video') {\r\n throw new Error(\r\n `getPrimarySsrc doesn't work with '${mediaType}'`);\r\n }\r\n\r\n const numSsrcs = _getSSRCCount(this.mLine);\r\n\r\n if (numSsrcs === 1) {\r\n // Not using \"ssrcs\" getter on purpose here\r\n return this.mLine.ssrcs[0].id;\r\n }\r\n\r\n // Look for a SIM, FID, or FEC-FR group\r\n if (this.mLine.ssrcGroups) {\r\n const simGroup = this.findGroup('SIM');\r\n\r\n if (simGroup) {\r\n return parsePrimarySSRC(simGroup);\r\n }\r\n const fidGroup = this.findGroup('FID');\r\n\r\n if (fidGroup) {\r\n return parsePrimarySSRC(fidGroup);\r\n }\r\n const fecGroup = this.findGroup('FEC-FR');\r\n\r\n if (fecGroup) {\r\n return parsePrimarySSRC(fecGroup);\r\n }\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Obtains RTX SSRC from the underlying video description (the\r\n * secondary SSRC of the first \"FID\" group found)\r\n * @param {number} primarySsrc the video ssrc for which to find the\r\n * corresponding rtx ssrc\r\n * @returns {number|undefined} the rtx ssrc (or undefined if there isn't\r\n * one)\r\n */\r\n getRtxSSRC(primarySsrc) {\r\n const fidGroup = this.findGroupByPrimarySSRC('FID', primarySsrc);\r\n\r\n\r\n return fidGroup && parseSecondarySSRC(fidGroup);\r\n }\r\n\r\n /**\r\n * Obtains all SSRCs contained in the underlying media description.\r\n * @return {Array.} an array with all SSRC as numbers.\r\n */\r\n getSSRCs() {\r\n return this.ssrcs\r\n .map(ssrcInfo => ssrcInfo.id)\r\n .filter((ssrc, index, array) => array.indexOf(ssrc) === index);\r\n }\r\n\r\n /**\r\n * Obtains primary video SSRCs.\r\n * @return {Array.} an array of all primary video SSRCs as numbers.\r\n * @throws Error if the wrapped media description is not a video.\r\n */\r\n getPrimaryVideoSSRCs() {\r\n const mediaType = this.mLine.type;\r\n\r\n if (mediaType !== 'video') {\r\n throw new Error(\r\n `getPrimaryVideoSSRCs doesn't work with ${mediaType}`);\r\n }\r\n\r\n const videoSSRCs = this.getSSRCs();\r\n\r\n for (const ssrcGroupInfo of this.ssrcGroups) {\r\n // Right now, FID and FEC-FR groups are the only ones we parse to\r\n // disqualify streams. If/when others arise we'll\r\n // need to add support for them here\r\n if (ssrcGroupInfo.semantics === 'FID'\r\n || ssrcGroupInfo.semantics === 'FEC-FR') {\r\n // secondary streams should be filtered out\r\n const secondarySsrc = parseSecondarySSRC(ssrcGroupInfo);\r\n\r\n videoSSRCs.splice(\r\n videoSSRCs.indexOf(secondarySsrc), 1);\r\n }\r\n }\r\n\r\n return videoSSRCs;\r\n }\r\n\r\n /**\r\n * Dumps all SSRC groups of this media description to JSON.\r\n */\r\n dumpSSRCGroups() {\r\n return JSON.stringify(this.mLine.ssrcGroups);\r\n }\r\n\r\n /**\r\n * Removes all SSRC groups which contain given SSRC number at any position.\r\n * @param {number} ssrc the SSRC for which all matching groups are to be\r\n * removed.\r\n */\r\n removeGroupsWithSSRC(ssrc) {\r\n if (!this.mLine.ssrcGroups) {\r\n return;\r\n }\r\n\r\n this.mLine.ssrcGroups = this.mLine.ssrcGroups\r\n .filter(groupInfo => groupInfo.ssrcs.indexOf(`${ssrc}`) === -1);\r\n }\r\n\r\n /**\r\n * Removes groups that match given semantics.\r\n * @param {string} semantics e.g. \"SIM\" or \"FID\"\r\n */\r\n removeGroupsBySemantics(semantics) {\r\n if (!this.mLine.ssrcGroups) {\r\n return;\r\n }\r\n\r\n this.mLine.ssrcGroups\r\n = this.mLine.ssrcGroups\r\n .filter(groupInfo => groupInfo.semantics !== semantics);\r\n }\r\n\r\n /**\r\n * Replaces SSRC (does not affect SSRC groups, but only attributes).\r\n * @param {number} oldSSRC the old SSRC number\r\n * @param {number} newSSRC the new SSRC number\r\n */\r\n replaceSSRC(oldSSRC, newSSRC) {\r\n if (this.mLine.ssrcs) {\r\n this.mLine.ssrcs.forEach(ssrcInfo => {\r\n if (ssrcInfo.id === oldSSRC) {\r\n ssrcInfo.id = newSSRC;\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Adds given SSRC group to this media description.\r\n * @param {object} group the SSRC group object as defined by\r\n * the 'sdp-transform' lib.\r\n */\r\n addSSRCGroup(group) {\r\n this.ssrcGroups.push(group);\r\n }\r\n}\r\n\r\n/**\r\n * Utility class for SDP manipulation using the 'sdp-transform' library.\r\n *\r\n * Typical use usage scenario:\r\n *\r\n * const transformer = new SdpTransformWrap(rawSdp);\r\n * const videoMLine = transformer.selectMedia('video);\r\n * if (videoMLine) {\r\n * videoMLiner.addSSRCAttribute({\r\n * id: 2342343,\r\n * attribute: \"cname\",\r\n * value: \"someCname\"\r\n * });\r\n * rawSdp = transformer.toRawSdp();\r\n * }\r\n */\r\nexport class SdpTransformWrap {\r\n\r\n /**\r\n * Creates new instance and parses the raw SDP into objects using\r\n * 'sdp-transform' lib.\r\n * @param {string} rawSDP the SDP in raw text format.\r\n */\r\n constructor(rawSDP) {\r\n this.parsedSDP = transform.parse(rawSDP);\r\n }\r\n\r\n /**\r\n * Selects all the m-lines from the SDP for a given media type.\r\n *\r\n * @param {string} mediaType the name of the media e.g. 'audio', 'video', 'data'.\r\n * @return {MLineWrap|null} return {@link MLineWrap} instance for the media line or null if not found. The\r\n * object returned references the underlying SDP state held by this SdpTransformWrap instance (it's not a\r\n * copy).\r\n */\r\n selectMedia(mediaType) {\r\n const selectedMLines = this.parsedSDP.media\r\n .filter(mLine => mLine.type === mediaType)\r\n .map(mLine => new MLineWrap(mLine));\r\n\r\n return selectedMLines ?? null;\r\n }\r\n\r\n /**\r\n * Converts the currently stored SDP state in this instance to raw text SDP\r\n * format.\r\n * @return {string}\r\n */\r\n toRawSDP() {\r\n return transform.write(this.parsedSDP);\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport { getSourceNameForJitsiTrack } from '../../service/RTC/SignalingLayer';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\n\r\nimport { SdpTransformWrap } from './SdpTransformUtil';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Fakes local SDP exposed to {@link JingleSessionPC} through the local\r\n * description getter. Modifies the SDP, so that it will contain muted local\r\n * video tracks description, even though their underlying {MediaStreamTrack}s\r\n * are no longer in the WebRTC peerconnection. That prevents from SSRC updates\r\n * being sent to Jicofo/remote peer and prevents sRD/sLD cycle on the remote\r\n * side.\r\n */\r\nexport default class LocalSdpMunger {\r\n\r\n /**\r\n * Creates new LocalSdpMunger instance.\r\n *\r\n * @param {TraceablePeerConnection} tpc\r\n * @param {string} localEndpointId - The endpoint id of the local user.\r\n */\r\n constructor(tpc, localEndpointId) {\r\n this.tpc = tpc;\r\n this.localEndpointId = localEndpointId;\r\n this.audioSourcesToMsidMap = new Map();\r\n this.videoSourcesToMsidMap = new Map();\r\n }\r\n\r\n /**\r\n * Makes sure that muted local video tracks associated with the parent\r\n * {@link TraceablePeerConnection} are described in the local SDP. It's done\r\n * in order to prevent from sending 'source-remove'/'source-add' Jingle\r\n * notifications when local video track is muted (MediaStream is\r\n * removed from the peerconnection).\r\n *\r\n * NOTE 1 video track is assumed\r\n *\r\n * @param {SdpTransformWrap} transformer the transformer instance which will\r\n * be used to process the SDP.\r\n * @return {boolean} true if there were any modifications to\r\n * the SDP wrapped by transformer.\r\n * @private\r\n */\r\n _addMutedLocalVideoTracksToSDP(transformer) {\r\n // Go over each video tracks and check if the SDP has to be changed\r\n const localVideos = this.tpc.getLocalTracks(MediaType.VIDEO);\r\n\r\n if (!localVideos.length) {\r\n return false;\r\n } else if (localVideos.length !== 1) {\r\n logger.error(\r\n `${this.tpc} there is more than 1 video track ! `\r\n + 'Strange things may happen !', localVideos);\r\n }\r\n\r\n const videoMLine = transformer.selectMedia(MediaType.VIDEO)?.[0];\r\n\r\n if (!videoMLine) {\r\n logger.debug(\r\n `${this.tpc} unable to hack local video track SDP`\r\n + '- no \"video\" media');\r\n\r\n return false;\r\n }\r\n\r\n let modified = false;\r\n\r\n for (const videoTrack of localVideos) {\r\n const muted = videoTrack.isMuted();\r\n const mediaStream = videoTrack.getOriginalStream();\r\n const isCamera = videoTrack.videoType === VideoType.CAMERA;\r\n\r\n // During the mute/unmute operation there are periods of time when\r\n // the track's underlying MediaStream is not added yet to\r\n // the PeerConnection. The SDP needs to be munged in such case.\r\n const isInPeerConnection\r\n = mediaStream && this.tpc.isMediaStreamInPc(mediaStream);\r\n const shouldFakeSdp = isCamera && (muted || !isInPeerConnection);\r\n\r\n if (!shouldFakeSdp) {\r\n continue; // eslint-disable-line no-continue\r\n }\r\n\r\n // Inject removed SSRCs\r\n const requiredSSRCs\r\n = this.tpc.isSimulcastOn()\r\n ? this.tpc.simulcast.ssrcCache\r\n : [ this.tpc.sdpConsistency.cachedPrimarySsrc ];\r\n\r\n if (!requiredSSRCs.length) {\r\n logger.error(`No SSRCs stored for: ${videoTrack} in ${this.tpc}`);\r\n\r\n continue; // eslint-disable-line no-continue\r\n }\r\n\r\n modified = true;\r\n\r\n // We need to fake sendrecv.\r\n // NOTE the SDP produced here goes only to Jicofo and is never set\r\n // as localDescription. That's why\r\n // TraceablePeerConnection.mediaTransferActive is ignored here.\r\n videoMLine.direction = MediaDirection.SENDRECV;\r\n\r\n // Check if the recvonly has MSID\r\n const primarySSRC = requiredSSRCs[0];\r\n\r\n // FIXME The cname could come from the stream, but may turn out to\r\n // be too complex. It is fine to come up with any value, as long as\r\n // we only care about the actual SSRC values when deciding whether\r\n // or not an update should be sent.\r\n const primaryCname = `injected-${primarySSRC}`;\r\n\r\n for (const ssrcNum of requiredSSRCs) {\r\n // Remove old attributes\r\n videoMLine.removeSSRC(ssrcNum);\r\n\r\n // Inject\r\n videoMLine.addSSRCAttribute({\r\n id: ssrcNum,\r\n attribute: 'cname',\r\n value: primaryCname\r\n });\r\n videoMLine.addSSRCAttribute({\r\n id: ssrcNum,\r\n attribute: 'msid',\r\n value: videoTrack.storedMSID\r\n });\r\n }\r\n if (requiredSSRCs.length > 1) {\r\n const group = {\r\n ssrcs: requiredSSRCs.join(' '),\r\n semantics: 'SIM'\r\n };\r\n\r\n if (!videoMLine.findGroup(group.semantics, group.ssrcs)) {\r\n // Inject the group\r\n videoMLine.addSSRCGroup(group);\r\n }\r\n }\r\n\r\n // Insert RTX\r\n // FIXME in P2P RTX is used by Chrome regardless of config option\r\n // status. Because of that 'source-remove'/'source-add'\r\n // notifications are still sent to remove/add RTX SSRC and FID group\r\n if (!this.tpc.options.disableRtx) {\r\n this.tpc.rtxModifier.modifyRtxSsrcs2(videoMLine);\r\n }\r\n }\r\n\r\n return modified;\r\n }\r\n\r\n /**\r\n * Returns a string that can be set as the MSID attribute for a source.\r\n *\r\n * @param {string} mediaType - Media type of the source.\r\n * @param {string} trackId - Id of the MediaStreamTrack associated with the source.\r\n * @param {string} streamId - Id of the MediaStream associated with the source.\r\n * @returns {string|null}\r\n */\r\n _generateMsidAttribute(mediaType, trackId, streamId = null) {\r\n if (!(mediaType && trackId)) {\r\n logger.error(`Unable to munge local MSID - track id=${trackId} or media type=${mediaType} is missing`);\r\n\r\n return null;\r\n }\r\n const pcId = this.tpc.id;\r\n\r\n // Handle a case on Firefox when the browser doesn't produce a 'a:ssrc' line with the 'msid' attribute or has\r\n // '-' for the stream id part of the msid line. Jicofo needs an unique identifier to be associated with a ssrc\r\n // and uses the msid for that.\r\n if (streamId === '-' || !streamId) {\r\n return `${this.localEndpointId}-${mediaType}-${pcId} ${trackId}-${pcId}`;\r\n }\r\n\r\n return `${streamId}-${pcId} ${trackId}-${pcId}`;\r\n }\r\n\r\n /**\r\n * Modifies 'cname', 'msid', 'label' and 'mslabel' by appending the id of {@link LocalSdpMunger#tpc} at the end,\r\n * preceding by a dash sign.\r\n *\r\n * @param {MLineWrap} mediaSection - The media part (audio or video) of the session description which will be\r\n * modified in place.\r\n * @returns {void}\r\n * @private\r\n */\r\n _transformMediaIdentifiers(mediaSection) {\r\n const mediaType = mediaSection.mLine?.type;\r\n const pcId = this.tpc.id;\r\n\r\n for (const ssrcLine of mediaSection.ssrcs) {\r\n switch (ssrcLine.attribute) {\r\n case 'cname':\r\n case 'label':\r\n case 'mslabel':\r\n ssrcLine.value = ssrcLine.value && `${ssrcLine.value}-${pcId}`;\r\n break;\r\n case 'msid': {\r\n if (ssrcLine.value) {\r\n const streamAndTrackIDs = ssrcLine.value.split(' ');\r\n\r\n let streamId = streamAndTrackIDs[0];\r\n const trackId = streamAndTrackIDs[1];\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n // Always overwrite streamId since we want the msid to be in this format even if the browser\r\n // generates one (in p2p mode).\r\n streamId = `${this.localEndpointId}-${mediaType}`;\r\n\r\n // eslint-disable-next-line max-depth\r\n if (mediaType === MediaType.VIDEO) {\r\n // eslint-disable-next-line max-depth\r\n if (!this.videoSourcesToMsidMap.has(trackId)) {\r\n streamId = `${streamId}-${this.videoSourcesToMsidMap.size}`;\r\n this.videoSourcesToMsidMap.set(trackId, streamId);\r\n }\r\n } else if (!this.audioSourcesToMsidMap.has(trackId)) {\r\n streamId = `${streamId}-${this.audioSourcesToMsidMap.size}`;\r\n this.audioSourcesToMsidMap.set(trackId, streamId);\r\n }\r\n\r\n streamId = mediaType === MediaType.VIDEO\r\n ? this.videoSourcesToMsidMap.get(trackId)\r\n : this.audioSourcesToMsidMap.get(trackId);\r\n }\r\n ssrcLine.value = this._generateMsidAttribute(mediaType, trackId, streamId);\r\n } else {\r\n logger.warn(`Unable to munge local MSID - weird format detected: ${ssrcLine.value}`);\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n // Additional transformations related to MSID are applicable to Unified-plan implementation only.\r\n if (!this.tpc.usesUnifiedPlan()) {\r\n return;\r\n }\r\n\r\n // If the msid attribute is missing, then remove the ssrc from the transformed description so that a\r\n // source-remove is signaled to Jicofo. This happens when the direction of the transceiver (or m-line)\r\n // is set to 'inactive' or 'recvonly' on Firefox, Chrome (unified) and Safari.\r\n const mediaDirection = mediaSection.mLine?.direction;\r\n\r\n if (mediaDirection === MediaDirection.RECVONLY || mediaDirection === MediaDirection.INACTIVE) {\r\n mediaSection.ssrcs = undefined;\r\n mediaSection.ssrcGroups = undefined;\r\n\r\n // Add the msid attribute if it is missing when the direction is sendrecv/sendonly. Firefox doesn't produce a\r\n // a=ssrc line with msid attribute for p2p connection.\r\n } else {\r\n const msidLine = mediaSection.mLine?.msid;\r\n const trackId = msidLine && msidLine.split(' ')[1];\r\n const sources = [ ...new Set(mediaSection.mLine?.ssrcs?.map(s => s.id)) ];\r\n\r\n for (const source of sources) {\r\n const msidExists = mediaSection.ssrcs\r\n .find(ssrc => ssrc.id === source && ssrc.attribute === 'msid');\r\n\r\n if (!msidExists && trackId) {\r\n const generatedMsid = this._generateMsidAttribute(mediaType, trackId);\r\n\r\n mediaSection.ssrcs.push({\r\n id: source,\r\n attribute: 'msid',\r\n value: generatedMsid\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Maybe modifies local description to fake local video tracks SDP when\r\n * those are muted.\r\n *\r\n * @param {object} desc the WebRTC SDP object instance for the local\r\n * description.\r\n * @returns {RTCSessionDescription}\r\n */\r\n maybeAddMutedLocalVideoTracksToSDP(desc) {\r\n if (!desc) {\r\n throw new Error('No local description passed in.');\r\n }\r\n\r\n const transformer = new SdpTransformWrap(desc.sdp);\r\n\r\n if (this._addMutedLocalVideoTracksToSDP(transformer)) {\r\n return new RTCSessionDescription({\r\n type: desc.type,\r\n sdp: transformer.toRawSDP()\r\n });\r\n }\r\n\r\n return desc;\r\n }\r\n\r\n /**\r\n * This transformation will make sure that stream identifiers are unique\r\n * across all of the local PeerConnections even if the same stream is used\r\n * by multiple instances at the same time.\r\n * Each PeerConnection assigns different SSRCs to the same local\r\n * MediaStream, but the MSID remains the same as it's used to identify\r\n * the stream by the WebRTC backend. The transformation will append\r\n * {@link TraceablePeerConnection#id} at the end of each stream's identifier\r\n * (\"cname\", \"msid\", \"label\" and \"mslabel\").\r\n *\r\n * @param {RTCSessionDescription} sessionDesc - The local session\r\n * description (this instance remains unchanged).\r\n * @return {RTCSessionDescription} - Transformed local session description\r\n * (a modified copy of the one given as the input).\r\n */\r\n transformStreamIdentifiers(sessionDesc) {\r\n // FIXME similar check is probably duplicated in all other transformers\r\n if (!sessionDesc || !sessionDesc.sdp || !sessionDesc.type) {\r\n return sessionDesc;\r\n }\r\n\r\n const transformer = new SdpTransformWrap(sessionDesc.sdp);\r\n const audioMLine = transformer.selectMedia(MediaType.AUDIO)?.[0];\r\n\r\n if (audioMLine) {\r\n this._transformMediaIdentifiers(audioMLine);\r\n this._injectSourceNames(audioMLine);\r\n }\r\n\r\n const videoMlines = transformer.selectMedia(MediaType.VIDEO);\r\n\r\n if (!FeatureFlags.isMultiStreamSupportEnabled()) {\r\n videoMlines.splice(1);\r\n }\r\n\r\n for (const videoMLine of videoMlines) {\r\n this._transformMediaIdentifiers(videoMLine);\r\n this._injectSourceNames(videoMLine);\r\n }\r\n\r\n // Plan-b clients generate new SSRCs and trackIds whenever tracks are removed and added back to the\r\n // peerconnection, therefore local track based map for msids needs to be reset after every transformation.\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && !this.tpc._usesUnifiedPlan) {\r\n this.audioSourcesToMsidMap.clear();\r\n this.videoSourcesToMsidMap.clear();\r\n }\r\n\r\n return new RTCSessionDescription({\r\n type: sessionDesc.type,\r\n sdp: transformer.toRawSDP()\r\n });\r\n }\r\n\r\n /**\r\n * Injects source names. Source names are need to for multiple streams per endpoint support. The final plan is to\r\n * use the \"mid\" attribute for source names, but because the SDP to Jingle conversion still operates in the Plan-B\r\n * semantics (one source name per media), a custom \"name\" attribute is injected into SSRC lines..\r\n *\r\n * @param {MLineWrap} mediaSection - The media part (audio or video) of the session description which will be\r\n * modified in place.\r\n * @returns {void}\r\n * @private\r\n */\r\n _injectSourceNames(mediaSection) {\r\n if (!FeatureFlags.isSourceNameSignalingEnabled()) {\r\n return;\r\n }\r\n\r\n const sources = [ ...new Set(mediaSection.mLine?.ssrcs?.map(s => s.id)) ];\r\n const mediaType = mediaSection.mLine?.type;\r\n\r\n if (!mediaType) {\r\n throw new Error('_transformMediaIdentifiers - no media type in mediaSection');\r\n }\r\n\r\n for (const source of sources) {\r\n const nameExists = mediaSection.ssrcs.find(ssrc => ssrc.id === source && ssrc.attribute === 'name');\r\n const msid = mediaSection.ssrcs.find(ssrc => ssrc.id === source && ssrc.attribute === 'msid')?.value;\r\n let trackIndex;\r\n\r\n if (msid) {\r\n const streamId = msid.split(' ')[0];\r\n\r\n trackIndex = streamId.split('-')[2];\r\n }\r\n\r\n if (!nameExists) {\r\n // Inject source names as a=ssrc:3124985624 name:endpointA-v0\r\n mediaSection.ssrcs.push({\r\n id: source,\r\n attribute: 'name',\r\n value: getSourceNameForJitsiTrack(this.localEndpointId, mediaType, trackIndex)\r\n });\r\n }\r\n }\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\n\r\nimport SDPUtil from './SDPUtil';\r\nimport { parseSecondarySSRC, SdpTransformWrap } from './SdpTransformUtil';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Begin helper functions\r\n */\r\n/**\r\n * Updates or inserts the appropriate rtx information for primarySsrc with\r\n * the given rtxSsrc. If no rtx ssrc for primarySsrc currently exists, it will\r\n * add the appropriate ssrc and ssrc group lines. If primarySsrc already has\r\n * an rtx ssrc, the appropriate ssrc and group lines will be updated\r\n * @param {MLineWrap} mLine\r\n * @param {object} primarySsrcInfo the info (ssrc, msid & cname) for the\r\n * primary ssrc\r\n * @param {number} rtxSsrc the rtx ssrc to associate with the primary ssrc\r\n */\r\nfunction updateAssociatedRtxStream(mLine, primarySsrcInfo, rtxSsrc) {\r\n const primarySsrc = primarySsrcInfo.id;\r\n const primarySsrcMsid = primarySsrcInfo.msid;\r\n const primarySsrcCname = primarySsrcInfo.cname;\r\n\r\n const previousRtxSSRC = mLine.getRtxSSRC(primarySsrc);\r\n\r\n if (previousRtxSSRC === rtxSsrc) {\r\n return;\r\n }\r\n if (previousRtxSSRC) {\r\n // Stream already had an rtx ssrc that is different than the one given,\r\n // remove all trace of the old one\r\n mLine.removeSSRC(previousRtxSSRC);\r\n mLine.removeGroupsWithSSRC(previousRtxSSRC);\r\n }\r\n mLine.addSSRCAttribute({\r\n id: rtxSsrc,\r\n attribute: 'cname',\r\n value: primarySsrcCname\r\n });\r\n mLine.addSSRCAttribute({\r\n id: rtxSsrc,\r\n attribute: 'msid',\r\n value: primarySsrcMsid\r\n });\r\n mLine.addSSRCGroup({\r\n semantics: 'FID',\r\n ssrcs: `${primarySsrc} ${rtxSsrc}`\r\n });\r\n}\r\n\r\n/**\r\n * End helper functions\r\n */\r\n\r\n/**\r\n * Adds any missing RTX streams for video streams\r\n * and makes sure that they remain consistent\r\n */\r\nexport default class RtxModifier {\r\n /**\r\n * Constructor\r\n */\r\n constructor() {\r\n /**\r\n * Map of video ssrc to corresponding RTX\r\n * ssrc\r\n */\r\n this.correspondingRtxSsrcs = new Map();\r\n }\r\n\r\n /**\r\n * Clear the cached map of primary video ssrcs to\r\n * their corresponding rtx ssrcs so that they will\r\n * not be used for the next call to modifyRtxSsrcs\r\n */\r\n clearSsrcCache() {\r\n this.correspondingRtxSsrcs.clear();\r\n }\r\n\r\n /**\r\n * Explicitly set the primary video ssrc -> rtx ssrc\r\n * mapping to be used in modifyRtxSsrcs\r\n * @param {Map} ssrcMapping a mapping of primary video\r\n * ssrcs to their corresponding rtx ssrcs\r\n */\r\n setSsrcCache(ssrcMapping) {\r\n logger.debug('Setting ssrc cache to ', ssrcMapping);\r\n this.correspondingRtxSsrcs = ssrcMapping;\r\n }\r\n\r\n /**\r\n * Adds RTX ssrcs for any video ssrcs that don't already have them. If the video ssrc has been seen before, and\r\n * already had an RTX ssrc generated, the same RTX ssrc will be used again.\r\n *\r\n * @param {string} sdpStr sdp in raw string format\r\n * @returns {string} The modified sdp in raw string format.\r\n */\r\n modifyRtxSsrcs(sdpStr) {\r\n let modified = false;\r\n const sdpTransformer = new SdpTransformWrap(sdpStr);\r\n const videoMLines = sdpTransformer.selectMedia(MediaType.VIDEO);\r\n\r\n if (!videoMLines?.length) {\r\n logger.debug(`No 'video' media found in the sdp: ${sdpStr}`);\r\n\r\n return sdpStr;\r\n }\r\n\r\n for (const videoMLine of videoMLines) {\r\n if (this.modifyRtxSsrcs2(videoMLine)) {\r\n modified = true;\r\n }\r\n }\r\n\r\n return modified ? sdpTransformer.toRawSDP() : sdpStr;\r\n }\r\n\r\n /**\r\n * Does the same thing as {@link modifyRtxSsrcs}, but takes the {@link MLineWrap} instance wrapping video media as\r\n * an argument.\r\n * @param {MLineWrap} videoMLine\r\n * @return {boolean} true if the SDP wrapped by {@link SdpTransformWrap} has been modified or\r\n * false otherwise.\r\n */\r\n modifyRtxSsrcs2(videoMLine) {\r\n if (videoMLine.direction === MediaDirection.RECVONLY) {\r\n return false;\r\n }\r\n if (videoMLine.getSSRCCount() < 1) {\r\n return false;\r\n }\r\n const primaryVideoSsrcs = videoMLine.getPrimaryVideoSSRCs();\r\n\r\n for (const ssrc of primaryVideoSsrcs) {\r\n const msid = videoMLine.getSSRCAttrValue(ssrc, 'msid');\r\n const cname = videoMLine.getSSRCAttrValue(ssrc, 'cname');\r\n let correspondingRtxSsrc = this.correspondingRtxSsrcs.get(ssrc);\r\n\r\n if (!correspondingRtxSsrc) {\r\n // If there's one in the sdp already for it, we'll just set\r\n // that as the corresponding one\r\n const previousAssociatedRtxStream = videoMLine.getRtxSSRC(ssrc);\r\n\r\n if (previousAssociatedRtxStream) {\r\n correspondingRtxSsrc = previousAssociatedRtxStream;\r\n } else {\r\n correspondingRtxSsrc = SDPUtil.generateSsrc();\r\n }\r\n this.correspondingRtxSsrcs.set(ssrc, correspondingRtxSsrc);\r\n }\r\n updateAssociatedRtxStream(\r\n videoMLine,\r\n {\r\n id: ssrc,\r\n cname,\r\n msid\r\n },\r\n correspondingRtxSsrc);\r\n }\r\n\r\n // FIXME we're not looking into much details whether the SDP has been\r\n // modified or not once the precondition requirements are met.\r\n return true;\r\n }\r\n\r\n /**\r\n * Strip all rtx streams from the given sdp.\r\n *\r\n * @param {string} sdpStr sdp in raw string format\r\n * @returns {string} sdp string with all rtx streams stripped\r\n */\r\n stripRtx(sdpStr) {\r\n const sdpTransformer = new SdpTransformWrap(sdpStr);\r\n const videoMLines = sdpTransformer.selectMedia(MediaType.VIDEO);\r\n\r\n if (!videoMLines?.length) {\r\n logger.debug(`No 'video' media found in the sdp: ${sdpStr}`);\r\n\r\n return sdpStr;\r\n }\r\n\r\n for (const videoMLine of videoMLines) {\r\n if (videoMLine.direction !== MediaDirection.RECVONLY\r\n && videoMLine.getSSRCCount()\r\n && videoMLine.containsAnySSRCGroups()) {\r\n const fidGroups = videoMLine.findGroups('FID');\r\n\r\n // Remove the fid groups from the mline\r\n videoMLine.removeGroupsBySemantics('FID');\r\n\r\n // Get the rtx ssrcs and remove them from the mline\r\n for (const fidGroup of fidGroups) {\r\n const rtxSsrc = parseSecondarySSRC(fidGroup);\r\n\r\n videoMLine.removeSSRC(rtxSsrc);\r\n }\r\n }\r\n }\r\n\r\n return sdpTransformer.toRawSDP();\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\n\r\nimport {\r\n parsePrimarySSRC,\r\n parseSecondarySSRC,\r\n SdpTransformWrap\r\n} from './SdpTransformUtil';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Handles the work of keeping video ssrcs consistent across multiple\r\n * o/a cycles, making it such that all stream operations can be\r\n * kept local and do not need to be signaled.\r\n * NOTE: This only keeps the 'primary' video ssrc consistent: meaning\r\n * the primary video stream\r\n */\r\nexport default class SdpConsistency {\r\n /**\r\n * Constructor\r\n * @param {string} logPrefix the log prefix appended to every logged\r\n * message, currently used to distinguish for which\r\n * TraceablePeerConnection the instance works.\r\n */\r\n constructor(logPrefix) {\r\n this.clearVideoSsrcCache();\r\n this.logPrefix = logPrefix;\r\n }\r\n\r\n /**\r\n * Clear the cached video primary and primary rtx ssrcs so that\r\n * they will not be used for the next call to\r\n * makeVideoPrimarySsrcsConsistent\r\n */\r\n clearVideoSsrcCache() {\r\n this.cachedPrimarySsrc = null;\r\n this.injectRecvOnly = false;\r\n }\r\n\r\n /**\r\n * Explicitly set the primary ssrc to be used in\r\n * makeVideoPrimarySsrcsConsistent\r\n * @param {number} primarySsrc the primarySsrc to be used\r\n * in future calls to makeVideoPrimarySsrcsConsistent\r\n * @throws Error if primarySsrc is not a number\r\n */\r\n setPrimarySsrc(primarySsrc) {\r\n if (typeof primarySsrc !== 'number') {\r\n throw new Error('Primary SSRC must be a number!');\r\n }\r\n this.cachedPrimarySsrc = primarySsrc;\r\n }\r\n\r\n /**\r\n * Checks whether or not there is a primary video SSRC cached already.\r\n * @return {boolean}\r\n */\r\n hasPrimarySsrcCached() {\r\n return Boolean(this.cachedPrimarySsrc);\r\n }\r\n\r\n /**\r\n * Given an sdp string, either:\r\n * 1) record the primary video and primary rtx ssrcs to be\r\n * used in future calls to makeVideoPrimarySsrcsConsistent or\r\n * 2) change the primary and primary rtx ssrcs in the given sdp\r\n * to match the ones previously cached\r\n * @param {string} sdpStr the sdp string to (potentially)\r\n * change to make the video ssrcs consistent\r\n * @returns {string} a (potentially) modified sdp string\r\n * with ssrcs consistent with this class' cache\r\n */\r\n makeVideoPrimarySsrcsConsistent(sdpStr) {\r\n const sdpTransformer = new SdpTransformWrap(sdpStr);\r\n const videoMLine = sdpTransformer.selectMedia(MediaType.VIDEO)?.[0];\r\n\r\n if (!videoMLine) {\r\n logger.debug(`${this.logPrefix} no 'video' media found in the sdp: ${sdpStr}`);\r\n\r\n return sdpStr;\r\n }\r\n\r\n if (videoMLine.direction === 'recvonly') {\r\n // If the mline is recvonly, we'll add the primary\r\n // ssrc as a recvonly ssrc\r\n if (this.cachedPrimarySsrc && this.injectRecvOnly) {\r\n videoMLine.addSSRCAttribute({\r\n id: this.cachedPrimarySsrc,\r\n attribute: 'cname',\r\n value: `recvonly-${this.cachedPrimarySsrc}`\r\n });\r\n } else {\r\n logger.info(`${this.logPrefix} no SSRC found for the recvonly video stream!`);\r\n }\r\n } else {\r\n const newPrimarySsrc = videoMLine.getPrimaryVideoSsrc();\r\n\r\n if (!newPrimarySsrc) {\r\n logger.info(`${this.logPrefix} sdp-consistency couldn't parse new primary ssrc`);\r\n\r\n return sdpStr;\r\n }\r\n if (this.cachedPrimarySsrc) {\r\n videoMLine.replaceSSRC(newPrimarySsrc, this.cachedPrimarySsrc);\r\n for (const group of videoMLine.ssrcGroups) {\r\n if (group.semantics === 'FID') {\r\n const primarySsrc = parsePrimarySSRC(group);\r\n const rtxSsrc = parseSecondarySSRC(group);\r\n\r\n // eslint-disable-next-line max-depth\r\n if (primarySsrc === newPrimarySsrc) {\r\n group.ssrcs\r\n = `${this.cachedPrimarySsrc} ${rtxSsrc}`;\r\n }\r\n }\r\n }\r\n } else {\r\n this.cachedPrimarySsrc = newPrimarySsrc;\r\n }\r\n this.injectRecvOnly = true;\r\n }\r\n\r\n return sdpTransformer.toRawSDP();\r\n }\r\n}\r\n","import { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\n\r\nimport * as transform from 'sdp-transform';\r\n\r\nconst DEFAULT_NUM_OF_LAYERS = 3;\r\n\r\ninterface Description {\r\n type: RTCSdpType;\r\n sdp: string;\r\n}\r\n\r\ninterface Options {\r\n numOfLayers?: number\r\n}\r\n\r\n/**\r\n * This class handles SDP munging for enabling simulcast for local video streams in Unified plan. A set of random SSRCs\r\n * are generated for the higher layer streams and they are cached for a given mid. The cached SSRCs are then reused on\r\n * the subsequent iterations while munging the local description. This class also handles imploding of the simulcast\r\n * SSRCs for remote endpoints into the primary FID group in remote description since Jicofo signals all SSRCs relevant\r\n * to a given endpoint.\r\n */\r\nexport default class SdpSimulcast {\r\n private _options: Options;\r\n private _ssrcCache: Map>;\r\n\r\n /**\r\n * Creates a new instance.\r\n *\r\n * @param options\r\n */\r\n constructor(options: Options) {\r\n this._options = options;\r\n this._ssrcCache = new Map();\r\n\r\n if (!this._options.numOfLayers) {\r\n this._options.numOfLayers = DEFAULT_NUM_OF_LAYERS;\r\n }\r\n }\r\n\r\n /**\r\n * Updates the given media description using the SSRCs that were cached for the mid associated\r\n * with the media description and returns the modified media description.\r\n *\r\n * @param mLine\r\n * @returns\r\n */\r\n _fillSsrcsFromCache(mLine: transform.MediaDescription) : any {\r\n const mid = mLine.mid;\r\n const cachedSsrcs = this._ssrcCache.get(mid);\r\n const newSsrcs = this._parseSimLayers(mLine);\r\n const newMsid = this._getSsrcAttribute(mLine, newSsrcs[0], 'msid');\r\n const newCname = this._getSsrcAttribute(mLine, newSsrcs[0], 'cname');\r\n\r\n mLine.ssrcs = [];\r\n mLine.ssrcGroups = [];\r\n\r\n for (const ssrc of cachedSsrcs) {\r\n mLine.ssrcs.push({\r\n id: ssrc,\r\n attribute: 'msid',\r\n value: newMsid\r\n });\r\n mLine.ssrcs.push({\r\n id: ssrc,\r\n attribute: 'cname',\r\n value: newCname\r\n });\r\n }\r\n\r\n mLine.ssrcGroups.push({\r\n semantics: 'SIM',\r\n ssrcs: cachedSsrcs.join(' ')\r\n });\r\n\r\n return mLine;\r\n }\r\n\r\n /**\r\n * Generates a new set of SSRCs for the higher simulcast layers/streams and adds the attributes and SIM group to\r\n * the given media description and returns the modified media description.\r\n *\r\n * @param mLine\r\n * @param primarySsrc\r\n * @returns\r\n */\r\n _generateNewSsrcsForSimulcast(mLine: transform.MediaDescription, primarySsrc: number) : any {\r\n const cname = this._getSsrcAttribute(mLine, primarySsrc, 'cname');\r\n let msid = this._getSsrcAttribute(mLine, primarySsrc, 'msid');\r\n const addAssociatedAttributes = (mLine: transform.MediaDescription, ssrc: number) => {\r\n mLine.ssrcs.push({\r\n id: ssrc,\r\n attribute: 'cname',\r\n value: cname\r\n });\r\n mLine.ssrcs.push({\r\n id: ssrc,\r\n attribute: 'msid',\r\n value: msid\r\n });\r\n }\r\n\r\n // In Unified-plan mode, the a=ssrc lines with the msid attribute are not present (only cname attributes are\r\n // present) in the answers that Chrome and Safari generate for an offer received from Jicofo. Generate these\r\n // a=ssrc lines using the msid values from the a=msid line.\r\n if (!msid) {\r\n msid = mLine.msid;\r\n const primarySsrcs = mLine.ssrcs;\r\n\r\n primarySsrcs.forEach(ssrc => {\r\n mLine.ssrcs.push({\r\n id: ssrc.id,\r\n attribute: 'msid',\r\n value: msid\r\n });\r\n })\r\n }\r\n\r\n // Generate SIM layers.\r\n const simSsrcs = [];\r\n\r\n for (let i = 0; i < this._options.numOfLayers - 1; ++i) {\r\n const simSsrc = this._generateSsrc();\r\n\r\n addAssociatedAttributes(mLine, simSsrc);\r\n simSsrcs.push(simSsrc);\r\n }\r\n\r\n mLine.ssrcGroups = mLine.ssrcGroups || [];\r\n mLine.ssrcGroups.push({\r\n semantics: 'SIM',\r\n ssrcs: primarySsrc + ' ' + simSsrcs.join(' ')\r\n });\r\n\r\n return mLine;\r\n }\r\n\r\n /**\r\n * Returns a random number to be used for the SSRC.\r\n *\r\n * @returns\r\n */\r\n _generateSsrc() : number {\r\n const max = 0xffffffff;\r\n\r\n return Math.floor(Math.random() * max);\r\n }\r\n\r\n /**\r\n * Returns the requested attribute value for a SSRC from a given media description.\r\n *\r\n * @param mLine\r\n * @param ssrc\r\n * @param attributeName\r\n * @returns\r\n */\r\n _getSsrcAttribute(mLine: transform.MediaDescription, ssrc: number, attributeName: string) : string | undefined {\r\n return mLine.ssrcs?.find(\r\n ssrcInfo => Number(ssrcInfo.id) === ssrc\r\n && ssrcInfo.attribute === attributeName)?.value;\r\n }\r\n\r\n /**\r\n * Returns an array of all the primary SSRCs in the SIM group for a given media description.\r\n *\r\n * @param mLine\r\n * @returns\r\n */\r\n _parseSimLayers(mLine: transform.MediaDescription) : Array | null {\r\n const simGroup = mLine.ssrcGroups?.find(group => group.semantics === 'SIM');\r\n\r\n if (simGroup) {\r\n return simGroup.ssrcs.split(' ').map(ssrc => Number(ssrc));\r\n }\r\n\r\n if (mLine.ssrcs?.length) {\r\n return [ Number(mLine.ssrcs[0].id) ];\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Munges the given media description to enable simulcast for the video media sections that are in either have\r\n * SENDRECV or SENDONLY as the media direction thereby ignoring all the RECVONLY transceivers created for remote\r\n * endpoints.\r\n * NOTE: This needs to be called only when simulcast is enabled.\r\n *\r\n * @param description\r\n * @returns\r\n */\r\n mungeLocalDescription(description: Description) : Description {\r\n if (!description || !description.sdp) {\r\n return description;\r\n }\r\n const session = transform.parse(description.sdp);\r\n\r\n for (let media of session.media) {\r\n // Ignore recvonly and inactive transceivers created for remote sources.\r\n if (media.direction === MediaDirection.RECVONLY || media.direction === MediaDirection.INACTIVE) {\r\n continue;\r\n }\r\n\r\n // Ignore audio m-lines.\r\n if (media.type !== MediaType.VIDEO) {\r\n continue;\r\n }\r\n const mid = media.mid;\r\n const numSsrcs = new Set(media.ssrcs?.map(ssrcInfo => ssrcInfo.id));\r\n const numGroups = media.ssrcGroups?.length ?? 0;\r\n let primarySsrc: number;\r\n\r\n // Do not munge if the description has no ssrcs or if simulcast is already enabled.\r\n if (numSsrcs.size === 0 || numSsrcs.size > 2 || (numSsrcs.size === 2 && numGroups === 0)) {\r\n continue;\r\n }\r\n if (numSsrcs.size === 1) {\r\n primarySsrc = Number(media.ssrcs[0]?.id);\r\n } else {\r\n const fidGroup = media.ssrcGroups.find(group => group.semantics === 'FID');\r\n\r\n if (fidGroup) {\r\n primarySsrc = Number(fidGroup.ssrcs.split(' ')[0]);\r\n }\r\n }\r\n\r\n if (this._ssrcCache.has(mid)) {\r\n media = this._fillSsrcsFromCache(media);\r\n } else {\r\n media = this._generateNewSsrcsForSimulcast(media, primarySsrc);\r\n const simulcastSsrcs = this._parseSimLayers(media);\r\n\r\n // Update the SSRCs in the cache so that they can re-used for the same mid again.\r\n this._ssrcCache.set(mid, simulcastSsrcs);\r\n }\r\n }\r\n\r\n return new RTCSessionDescription({\r\n type: description.type,\r\n sdp: transform.write(session)\r\n });\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport { JitsiConferenceEvents } from '../../JitsiConferenceEvents';\r\nimport * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport { createTrackStreamingStatusEvent } from '../../service/statistics/AnalyticsEvents';\r\nimport JitsiConference from '../../types/hand-crafted/JitsiConference';\r\nimport JitsiRemoteTrack from '../../types/hand-crafted/modules/RTC/JitsiRemoteTrack';\r\nimport RTC from '../../types/hand-crafted/modules/RTC/RTC';\r\nimport { VideoType } from '../../types/hand-crafted/service/RTC/VideoType';\r\nimport browser from '../browser';\r\nimport Statistics from '../statistics/statistics';\r\n\r\n/** Track streaming statuses. */\r\nexport enum TrackStreamingStatus {\r\n\r\n /**\r\n * Status indicating that streaming is currently active.\r\n */\r\n ACTIVE = 'active',\r\n\r\n /**\r\n * Status indicating that streaming is currently inactive.\r\n * Inactive means the streaming was stopped on purpose from the bridge, like exiting forwarded sources or\r\n * adaptivity decided to drop video because of not enough bandwidth.\r\n */\r\n INACTIVE = 'inactive',\r\n\r\n /**\r\n * Status indicating that streaming is currently interrupted.\r\n */\r\n INTERRUPTED = 'interrupted',\r\n\r\n /**\r\n * Status indicating that streaming is currently restoring.\r\n */\r\n RESTORING = 'restoring',\r\n }\r\n\r\ntype StreamingStatusMap = {\r\n // TODO: Replace this hand crafted VideoType when we convert VideoType.js to Typescript.\r\n videoType?: VideoType, \r\n startedMs?: number,\r\n p2p?: boolean,\r\n streamingStatus?: string,\r\n value?: number\r\n};\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Default value of 500 milliseconds for {@link TrackStreamingStatusImpl.outOfForwardedSourcesTimeout}.\r\n */\r\nconst DEFAULT_NOT_IN_FORWARDED_SOURCES_TIMEOUT = 500;\r\n\r\n/**\r\n * Default value of 2500 milliseconds for {@link TrackStreamingStatusImpl.p2pRtcMuteTimeout}.\r\n */\r\nconst DEFAULT_P2P_RTC_MUTE_TIMEOUT = 2500;\r\n\r\n/**\r\n * Default value of 10000 milliseconds for {@link TrackStreamingStatusImpl.rtcMuteTimeout}.\r\n */\r\nconst DEFAULT_RTC_MUTE_TIMEOUT = 10000;\r\n\r\n/**\r\n * The time to wait a track to be restored. Track which was out of forwarded sources should be inactive and when\r\n * entering forwarded sources it becomes restoring and when data is received from bridge it will become active, but if\r\n * no data is received for some time we set status of that track streaming to interrupted.\r\n */\r\nconst DEFAULT_RESTORING_TIMEOUT = 10000;\r\n\r\n/**\r\n * Class is responsible for emitting JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED events.\r\n */\r\nexport class TrackStreamingStatusImpl {\r\n rtc: RTC;\r\n conference: JitsiConference;\r\n track: JitsiRemoteTrack;\r\n\r\n /** This holds the timeout callback ID scheduled using window.setTimeout. */\r\n trackTimer: number | null;\r\n\r\n /**\r\n * If video track frozen detection through RTC mute event is supported, we wait some time until video track is\r\n * considered frozen. But because when the track falls out of forwarded sources it is expected for the video to\r\n * freeze this timeout must be significantly reduced in \"out of forwarded sources\" case.\r\n *\r\n * Basically this value is used instead of {@link rtcMuteTimeout} when track is not in forwarded sources.\r\n */\r\n outOfForwardedSourcesTimeout: number;\r\n\r\n /**\r\n * How long we are going to wait for the corresponding signaling mute event after the RTC video track muted\r\n * event is fired on the Media stream, before the connection interrupted is fired. The default value is\r\n * {@link DEFAULT_P2P_RTC_MUTE_TIMEOUT}.\r\n */\r\n p2pRtcMuteTimeout: number;\r\n\r\n /**\r\n * How long we're going to wait after the RTC video track muted event for the corresponding signalling mute\r\n * event, before the connection interrupted is fired. The default value is {@link DEFAULT_RTC_MUTE_TIMEOUT}.\r\n *\r\n * @returns amount of time in milliseconds\r\n */\r\n rtcMuteTimeout: number;\r\n\r\n /**\r\n * This holds a timestamp indicating when remote video track was RTC muted. The purpose of storing the\r\n * timestamp is to avoid the transition to disconnected status in case of legitimate video mute operation where\r\n * the signalling video muted event can arrive shortly after RTC muted event.\r\n *\r\n * The timestamp is measured in milliseconds obtained with Date.now().\r\n *\r\n * FIXME merge this logic with NO_DATA_FROM_SOURCE event implemented in JitsiLocalTrack by extending the event\r\n * to the remote track and allowing to set different timeout for local and remote tracks.\r\n */\r\n rtcMutedTimestamp: number | null;\r\n\r\n /** This holds the restoring timeout callback ID scheduled using window.setTimeout. */\r\n restoringTimer: ReturnType | null;\r\n\r\n /**\r\n * This holds the current streaming status (along with all the internal events that happen while in that\r\n * state).\r\n *\r\n * The goal is to send this information to the analytics backend for post-mortem analysis.\r\n */\r\n streamingStatusMap: StreamingStatusMap;\r\n\r\n _onP2PStatus: () => void;\r\n _onUserLeft: () => void;\r\n _onTrackRtcMuted: () => void;\r\n _onTrackRtcUnmuted: () => void;\r\n _onSignallingMuteChanged: () => void;\r\n _onTrackVideoTypeChanged: () => void;\r\n _onLastNValueChanged: () => void;\r\n _onForwardedSourcesChanged: () => void;\r\n\r\n /* eslint-disable max-params*/\r\n /**\r\n * Calculates the new {@link TrackStreamingStatus} based on the values given for some specific remote track. It is\r\n * assumed that the conference is currently in the JVB mode (in contrary to the P2P mode)\r\n * @param isInForwardedSources - indicates whether the track is in the forwarded sources set. When set to\r\n * false it means that JVB is not sending any video for the track.\r\n * @param isRestoringTimedout - if true it means that the track has been outside of forwarded sources too\r\n * long to be considered {@link TrackStreamingStatus.RESTORING}.\r\n * @param isVideoMuted - true if the track is video muted and we should not expect to receive any video.\r\n * @param isVideoTrackFrozen - if the current browser support video frozen detection then it will be set to\r\n * true when the video track is frozen. If the current browser does not support frozen detection the it's always\r\n * false.\r\n * @return {TrackStreamingStatus} the new streaming status for the track for whom the values above were provided.\r\n * @private\r\n */\r\n static _getNewStateForJvbMode(\r\n isInForwardedSources: boolean,\r\n isRestoringTimedout: boolean,\r\n isVideoMuted: boolean,\r\n isVideoTrackFrozen: boolean): TrackStreamingStatus {\r\n\r\n // We are currently not checking the endpoint connection status received from the JVB.\r\n if (isVideoMuted) {\r\n // If the connection is active according to JVB and the track is video muted there is no way for the\r\n // connection to be inactive, because the detection logic below only makes sense for video.\r\n return TrackStreamingStatus.ACTIVE;\r\n }\r\n\r\n // Logic when isVideoTrackFrozen is supported\r\n if (browser.supportsVideoMuteOnConnInterrupted()) {\r\n if (!isVideoTrackFrozen) {\r\n // If the video is playing we're good\r\n return TrackStreamingStatus.ACTIVE;\r\n } else if (isInForwardedSources) {\r\n return isRestoringTimedout ? TrackStreamingStatus.INTERRUPTED : TrackStreamingStatus.RESTORING;\r\n }\r\n\r\n return TrackStreamingStatus.INACTIVE;\r\n }\r\n\r\n // Because this browser is incapable of detecting frozen video we must rely on the forwarded sources value\r\n return isInForwardedSources ? TrackStreamingStatus.ACTIVE : TrackStreamingStatus.INACTIVE;\r\n }\r\n\r\n /* eslint-enable max-params*/\r\n\r\n /**\r\n * In P2P mode we don't care about any values coming from the JVB and the streaming status can be only active or\r\n * interrupted.\r\n * @param isVideoMuted - true if video muted\r\n * @param isVideoTrackFrozen - true if the video track for the remote track is currently frozen. If the\r\n * current browser does not support video frozen detection then it's always false.\r\n * @return {TrackStreamingStatus}\r\n * @private\r\n */\r\n static _getNewStateForP2PMode(isVideoMuted: boolean, isVideoTrackFrozen: boolean): TrackStreamingStatus {\r\n if (!browser.supportsVideoMuteOnConnInterrupted()) {\r\n // There's no way to detect problems in P2P when there's no video track frozen detection...\r\n return TrackStreamingStatus.ACTIVE;\r\n }\r\n\r\n return isVideoMuted || !isVideoTrackFrozen\r\n ? TrackStreamingStatus.ACTIVE : TrackStreamingStatus.INTERRUPTED;\r\n }\r\n\r\n /**\r\n * Creates new instance of TrackStreamingStatus.\r\n *\r\n * @constructor\r\n * @param rtc - the RTC service instance\r\n * @param conference - parent conference instance\r\n * @param {Object} options\r\n * @param {number} [options.p2pRtcMuteTimeout=2500] custom value for\r\n * {@link TrackStreamingStatusImpl.p2pRtcMuteTimeout}.\r\n * @param {number} [options.rtcMuteTimeout=2000] custom value for\r\n * {@link TrackStreamingStatusImpl.rtcMuteTimeout}.\r\n * @param {number} [options.outOfForwardedSourcesTimeout=500] custom value for\r\n * {@link TrackStreamingStatusImpl.outOfForwardedSourcesTimeout}.\r\n */\r\n constructor(rtc: RTC, conference: JitsiConference, track: JitsiRemoteTrack, options: {\r\n outOfForwardedSourcesTimeout: number,\r\n p2pRtcMuteTimeout: number,\r\n rtcMuteTimeout: number\r\n }) {\r\n this.rtc = rtc;\r\n this.conference = conference;\r\n this.track = track;\r\n\r\n this.restoringTimer = null;\r\n this.rtcMutedTimestamp = null;\r\n this.streamingStatusMap = {};\r\n this.trackTimer = null;\r\n\r\n this.outOfForwardedSourcesTimeout = typeof options.outOfForwardedSourcesTimeout === 'number'\r\n ? options.outOfForwardedSourcesTimeout : DEFAULT_NOT_IN_FORWARDED_SOURCES_TIMEOUT;\r\n\r\n this.p2pRtcMuteTimeout = typeof options.p2pRtcMuteTimeout === 'number'\r\n ? options.p2pRtcMuteTimeout : DEFAULT_P2P_RTC_MUTE_TIMEOUT;\r\n\r\n this.rtcMuteTimeout = typeof options.rtcMuteTimeout === 'number'\r\n ? options.rtcMuteTimeout : DEFAULT_RTC_MUTE_TIMEOUT;\r\n logger.info(`RtcMuteTimeout set to: ${this.rtcMuteTimeout}`);\r\n }\r\n\r\n /**\r\n * Gets the video frozen timeout for given source name.\r\n * @return how long are we going to wait since RTC video muted even, before a video track is considered\r\n * frozen.\r\n * @private\r\n */\r\n _getVideoFrozenTimeout(): number {\r\n const sourceName = this.track.getSourceName();\r\n\r\n return this.rtc.isInForwardedSources(sourceName)\r\n ? this.rtcMuteTimeout\r\n : this.conference.isP2PActive() ? this.p2pRtcMuteTimeout : this.outOfForwardedSourcesTimeout;\r\n }\r\n\r\n /**\r\n * Initializes TrackStreamingStatus and bind required event listeners.\r\n */\r\n init(): void {\r\n // Handles P2P status changes\r\n this._onP2PStatus = this.figureOutStreamingStatus.bind(this);\r\n this.conference.on(JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);\r\n\r\n // Used to send analytics events for the participant that left the call.\r\n this._onUserLeft = this.onUserLeft.bind(this);\r\n this.conference.on(JitsiConferenceEvents.USER_LEFT, this._onUserLeft);\r\n\r\n // On some browsers MediaStreamTrack trigger \"onmute\"/\"onunmute\" events for video type tracks when they stop\r\n // receiving data which is often a sign that remote user is having connectivity issues.\r\n if (browser.supportsVideoMuteOnConnInterrupted()) {\r\n\r\n this._onTrackRtcMuted = this.onTrackRtcMuted.bind(this);\r\n this.rtc.addListener(RTCEvents.REMOTE_TRACK_MUTE, this._onTrackRtcMuted);\r\n\r\n this._onTrackRtcUnmuted = this.onTrackRtcUnmuted.bind(this);\r\n this.rtc.addListener(RTCEvents.REMOTE_TRACK_UNMUTE, this._onTrackRtcUnmuted);\r\n\r\n // Listened which will be bound to JitsiRemoteTrack to listen for signalling mute/unmute events.\r\n this._onSignallingMuteChanged = this.onSignallingMuteChanged.bind(this);\r\n this.track.on(JitsiTrackEvents.TRACK_MUTE_CHANGED, this._onSignallingMuteChanged);\r\n\r\n // Used to send an analytics event when the video type changes.\r\n this._onTrackVideoTypeChanged = this.onTrackVideoTypeChanged.bind(this);\r\n this.track.on(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED, this._onTrackVideoTypeChanged);\r\n }\r\n\r\n this._onForwardedSourcesChanged = this.onForwardedSourcesChanged.bind(this);\r\n this.conference.on(JitsiConferenceEvents.FORWARDED_SOURCES_CHANGED, this._onForwardedSourcesChanged);\r\n\r\n this._onLastNValueChanged = this.figureOutStreamingStatus.bind(this);\r\n this.rtc.on(RTCEvents.LASTN_VALUE_CHANGED, this._onLastNValueChanged);\r\n }\r\n\r\n /**\r\n * Removes all event listeners and disposes of all resources held by this instance.\r\n */\r\n dispose(): void {\r\n if (browser.supportsVideoMuteOnConnInterrupted()) {\r\n this.rtc.removeListener(RTCEvents.REMOTE_TRACK_MUTE, this._onTrackRtcMuted);\r\n this.rtc.removeListener(RTCEvents.REMOTE_TRACK_UNMUTE, this._onTrackRtcUnmuted);\r\n\r\n this.track.off(JitsiTrackEvents.TRACK_MUTE_CHANGED, this._onSignallingMuteChanged);\r\n }\r\n\r\n this.conference.off(JitsiConferenceEvents.FORWARDED_SOURCES_CHANGED, this._onForwardedSourcesChanged);\r\n this.conference.off(JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);\r\n this.conference.off(JitsiConferenceEvents.USER_LEFT, this._onUserLeft);\r\n this.rtc.removeListener(RTCEvents.LASTN_VALUE_CHANGED, this._onLastNValueChanged);\r\n\r\n this.clearTimeout();\r\n this.clearRtcMutedTimestamp();\r\n this.maybeSendTrackStreamingStatusEvent(Date.now());\r\n this.figureOutStreamingStatus();\r\n }\r\n\r\n /**\r\n * Changes streaming status.\r\n * @param newStatus\r\n */\r\n _changeStreamingStatus(newStatus: TrackStreamingStatus): void {\r\n if (this.track.getTrackStreamingStatus() !== newStatus) {\r\n\r\n const sourceName = this.track.getSourceName();\r\n\r\n this.track._setTrackStreamingStatus(newStatus);\r\n\r\n logger.debug(`Emit track streaming status(${Date.now()}) ${sourceName}: ${newStatus}`);\r\n\r\n // Log the event on CallStats\r\n Statistics.sendLog(\r\n JSON.stringify({\r\n id: 'track.streaming.status',\r\n track: sourceName,\r\n status: newStatus\r\n }));\r\n\r\n // It's common for the event listeners to access the JitsiRemoteTrack. Thus pass it as a parameter here.\r\n this.track.emit(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED, this.track, newStatus);\r\n }\r\n }\r\n\r\n /**\r\n * Reset the postponed \"streaming interrupted\" event which was previously scheduled as a timeout on RTC 'onmute'\r\n * event.\r\n */\r\n clearTimeout(): void {\r\n if (this.trackTimer) {\r\n window.clearTimeout(this.trackTimer);\r\n this.trackTimer = null;\r\n }\r\n }\r\n\r\n /**\r\n * Clears the timestamp of the RTC muted event for remote video track.\r\n */\r\n clearRtcMutedTimestamp(): void {\r\n this.rtcMutedTimestamp = null;\r\n }\r\n\r\n /**\r\n * Checks if track is considered frozen.\r\n * @return true if the video has frozen or false when it's either not considered frozen\r\n * (yet) or if freeze detection is not supported by the current browser.\r\n *\r\n * FIXME merge this logic with NO_DATA_FROM_SOURCE event implemented in JitsiLocalTrack by extending the event to\r\n * the remote track and allowing to set different timeout for local and remote tracks.\r\n */\r\n isVideoTrackFrozen(): boolean {\r\n if (!browser.supportsVideoMuteOnConnInterrupted()) {\r\n return false;\r\n }\r\n\r\n const isVideoRTCMuted = this.track.isWebRTCTrackMuted();\r\n const rtcMutedTimestamp = this.rtcMutedTimestamp;\r\n const timeout = this._getVideoFrozenTimeout();\r\n\r\n return isVideoRTCMuted && typeof rtcMutedTimestamp === 'number' && (Date.now() - rtcMutedTimestamp) >= timeout;\r\n }\r\n\r\n /**\r\n * Figures out (and updates) the current streaming status for the track identified by the source name.\r\n */\r\n figureOutStreamingStatus(): void {\r\n const sourceName = this.track.getSourceName();\r\n const inP2PMode = this.conference.isP2PActive();\r\n const isRestoringTimedOut = this._isRestoringTimedout();\r\n const audioOnlyMode = this.conference.getLastN() === 0;\r\n\r\n // NOTE Overriding videoMuted to true for audioOnlyMode should disable any detection based on video playback or\r\n // forwarded sources.\r\n const isVideoMuted = this.track.isMuted() || audioOnlyMode;\r\n const isVideoTrackFrozen = this.isVideoTrackFrozen();\r\n const isInForwardedSources = this.rtc.isInForwardedSources(sourceName);\r\n\r\n const newState\r\n = inP2PMode\r\n ? TrackStreamingStatusImpl._getNewStateForP2PMode(\r\n isVideoMuted,\r\n isVideoTrackFrozen)\r\n : TrackStreamingStatusImpl._getNewStateForJvbMode(\r\n isInForwardedSources,\r\n isRestoringTimedOut,\r\n isVideoMuted,\r\n isVideoTrackFrozen);\r\n\r\n // if the new state is not restoring clear timers and timestamps that we use to track the restoring state\r\n if (newState !== TrackStreamingStatus.RESTORING) {\r\n this._clearRestoringTimer();\r\n }\r\n\r\n logger.debug(\r\n `Figure out conn status for ${sourceName}, is video muted: ${\r\n isVideoMuted} video track frozen: ${\r\n isVideoTrackFrozen} p2p mode: ${\r\n inP2PMode} is in forwarded sources: ${\r\n isInForwardedSources} currentStatus => newStatus: ${\r\n this.track.getTrackStreamingStatus()} => ${newState}`);\r\n\r\n const oldStreamingStatus = this.streamingStatusMap || {};\r\n\r\n // Send an analytics event (guard on either the p2p flag or the streaming status has changed since the last\r\n // time this code block run).\r\n if (!('p2p' in oldStreamingStatus)\r\n || !('streamingStatus' in oldStreamingStatus)\r\n || oldStreamingStatus.p2p !== inP2PMode\r\n || oldStreamingStatus.streamingStatus !== newState) {\r\n\r\n const nowMs = Date.now();\r\n\r\n this.maybeSendTrackStreamingStatusEvent(nowMs);\r\n\r\n this.streamingStatusMap = {\r\n ...oldStreamingStatus,\r\n streamingStatus: newState,\r\n p2p: inP2PMode,\r\n startedMs: nowMs\r\n };\r\n\r\n // sometimes (always?) we're late to hook the TRACK_VIDEOTYPE_CHANGED event and the video type is not in\r\n // oldStreamingStatus.\r\n if (!('videoType' in this.streamingStatusMap)) {\r\n this.streamingStatusMap.videoType = this.track.getVideoType();\r\n }\r\n }\r\n this._changeStreamingStatus(newState);\r\n }\r\n\r\n /**\r\n * Computes the duration of the current streaming status for the track (i.e. 15 seconds in the INTERRUPTED state)\r\n * and sends a track streaming status event.\r\n * @param nowMs - The current time (in millis).\r\n */\r\n maybeSendTrackStreamingStatusEvent(nowMs: number): void {\r\n const trackStreamingStatus = this.streamingStatusMap;\r\n\r\n if (trackStreamingStatus\r\n && 'startedMs' in trackStreamingStatus\r\n && 'videoType' in trackStreamingStatus\r\n && 'streamingStatus' in trackStreamingStatus\r\n && 'p2p' in trackStreamingStatus) {\r\n trackStreamingStatus.value = nowMs - trackStreamingStatus.startedMs;\r\n Statistics.sendAnalytics(createTrackStreamingStatusEvent(trackStreamingStatus));\r\n }\r\n }\r\n\r\n /**\r\n * On change in forwarded sources set check all leaving and entering track to change their corresponding statuses.\r\n *\r\n * @param leavingForwardedSources - The array of sourceName leaving forwarded sources.\r\n * @param enteringForwardedSources - The array of sourceName entering forwarded sources.\r\n * @param timestamp - The time in millis\r\n * @private\r\n */\r\n onForwardedSourcesChanged(\r\n leavingForwardedSources: string[] = [],\r\n enteringForwardedSources: string[] = [],\r\n timestamp: number): void {\r\n\r\n const sourceName = this.track.getSourceName();\r\n\r\n logger.debug(`Fowarded sources changed leaving=${leavingForwardedSources}, entering=${\r\n enteringForwardedSources} at ${timestamp}`);\r\n\r\n // If the browser doesn't fire the mute/onmute events when the remote peer stops/starts sending media,\r\n // calculate the streaming status for all the tracks since it won't get triggered automatically on the track\r\n // that has started/stopped receiving media.\r\n if (!browser.supportsVideoMuteOnConnInterrupted()) {\r\n this.figureOutStreamingStatus();\r\n }\r\n\r\n if (leavingForwardedSources.includes(sourceName)) {\r\n this.track._clearEnteredForwardedSourcesTimestamp();\r\n this._clearRestoringTimer();\r\n browser.supportsVideoMuteOnConnInterrupted() && this.figureOutStreamingStatus();\r\n }\r\n\r\n if (enteringForwardedSources.includes(sourceName)) {\r\n // store the timestamp this track is entering forwarded sources\r\n this.track._setEnteredForwardedSourcesTimestamp(timestamp);\r\n browser.supportsVideoMuteOnConnInterrupted() && this.figureOutStreamingStatus();\r\n }\r\n }\r\n\r\n /**\r\n * Clears the restoring timer for video track and the timestamp for entering forwarded sources.\r\n */\r\n _clearRestoringTimer(): void {\r\n const rTimer = this.restoringTimer;\r\n\r\n if (rTimer) {\r\n clearTimeout(rTimer);\r\n this.restoringTimer = null;\r\n }\r\n }\r\n\r\n /**\r\n * Checks whether a track had stayed enough in restoring state, compares current time and the time the track\r\n * entered in forwarded sources. If it hasn't timedout and there is no timer added, add new timer in order to give\r\n * it more time to become active or mark it as interrupted on next check.\r\n *\r\n * @returns true if the track was in restoring state more than the timeout\r\n * ({@link DEFAULT_RESTORING_TIMEOUT}.) in order to set its status to interrupted.\r\n * @private\r\n */\r\n _isRestoringTimedout(): boolean {\r\n const enteredForwardedSourcesTimestamp = this.track._getEnteredForwardedSourcesTimestamp();\r\n\r\n if (enteredForwardedSourcesTimestamp\r\n && (Date.now() - enteredForwardedSourcesTimestamp) >= DEFAULT_RESTORING_TIMEOUT) {\r\n return true;\r\n }\r\n\r\n // still haven't reached timeout, if there is no timer scheduled, schedule one so we can track the restoring\r\n // state and change it after reaching the timeout\r\n const rTimer = this.restoringTimer;\r\n\r\n if (!rTimer) {\r\n this.restoringTimer = setTimeout(() => this.figureOutStreamingStatus(), DEFAULT_RESTORING_TIMEOUT);\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /** Checks whether a track is the current track. */\r\n _isCurrentTrack(track: JitsiRemoteTrack): boolean {\r\n return track.getSourceName() === this.track.getSourceName();\r\n }\r\n\r\n /**\r\n * Sends a last/final track streaming status event for the track of the user that left the conference.\r\n * @param id - The id of the participant that left the conference.\r\n */\r\n onUserLeft(id: string): void {\r\n if (this.track.getParticipantId() === id) {\r\n this.maybeSendTrackStreamingStatusEvent(Date.now());\r\n this.streamingStatusMap = {};\r\n }\r\n }\r\n\r\n /**\r\n * Handles RTC 'onmute' event for the video track.\r\n *\r\n * @param track - The video track for which 'onmute' event will be processed.\r\n */\r\n onTrackRtcMuted(track: JitsiRemoteTrack): void {\r\n if (!this._isCurrentTrack(track)) {\r\n return;\r\n }\r\n\r\n const sourceName = track.getSourceName();\r\n\r\n logger.debug(`Detector track RTC muted: ${sourceName}`, Date.now());\r\n\r\n this.rtcMutedTimestamp = Date.now();\r\n if (!track.isMuted()) {\r\n // If the user is not muted according to the signalling we'll give it some time, before the streaming\r\n // interrupted event is triggered.\r\n this.clearTimeout();\r\n\r\n // The timeout is reduced when track is not in the forwarded sources\r\n const timeout = this._getVideoFrozenTimeout();\r\n\r\n this.trackTimer = window.setTimeout(() => {\r\n logger.debug(`Set RTC mute timeout for: ${sourceName} of ${timeout} ms`);\r\n this.clearTimeout();\r\n this.figureOutStreamingStatus();\r\n }, timeout);\r\n }\r\n }\r\n\r\n /**\r\n * Handles RTC 'onunmute' event for the video track.\r\n *\r\n * @param track - The video track for which 'onunmute' event will be processed.\r\n */\r\n onTrackRtcUnmuted(track: JitsiRemoteTrack): void {\r\n if (!this._isCurrentTrack(track)) {\r\n return;\r\n }\r\n\r\n const sourceName = this.track.getSourceName();\r\n\r\n logger.debug(`Detector track RTC unmuted: ${sourceName}`, Date.now());\r\n\r\n this.clearTimeout();\r\n this.clearRtcMutedTimestamp();\r\n\r\n this.figureOutStreamingStatus();\r\n }\r\n\r\n /**\r\n * Here the signalling \"mute\"/\"unmute\" events are processed.\r\n *\r\n * @param track - The remote video track for which the signalling mute/unmute event will be\r\n * processed.\r\n */\r\n onSignallingMuteChanged(track: JitsiRemoteTrack): void {\r\n if (!this._isCurrentTrack(track)) {\r\n return;\r\n }\r\n\r\n const sourceName = this.track.getSourceName();\r\n\r\n logger.debug(`Detector on track signalling mute changed: ${sourceName}`, track.isMuted());\r\n\r\n this.figureOutStreamingStatus();\r\n }\r\n\r\n /**\r\n * Sends a track streaming status event as a result of the video type changing.\r\n * @deprecated this will go away with full multiple streams support\r\n * @param type - The video type.\r\n */\r\n onTrackVideoTypeChanged(type: VideoType): void {\r\n const nowMs = Date.now();\r\n\r\n this.maybeSendTrackStreamingStatusEvent(nowMs);\r\n\r\n this.streamingStatusMap = {\r\n ...this.streamingStatusMap || {},\r\n videoType: type,\r\n startedMs: nowMs\r\n };\r\n }\r\n}\r\n\r\nexport default TrackStreamingStatusImpl;\r\n","import * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport { createTtfmEvent } from '../../service/statistics/AnalyticsEvents';\r\nimport TrackStreamingStatusImpl, { TrackStreamingStatus } from '../connectivity/TrackStreamingStatus';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport Statistics from '../statistics/statistics';\r\n\r\nimport JitsiTrack from './JitsiTrack';\r\n\r\nconst logger = require('@jitsi/logger').getLogger(__filename);\r\n\r\nconst RTCEvents = require('../../service/RTC/RTCEvents');\r\n\r\nlet ttfmTrackerAudioAttached = false;\r\nlet ttfmTrackerVideoAttached = false;\r\n\r\n/**\r\n * List of container events that we are going to process. _onContainerEventHandler will be added as listener to the\r\n * container for every event in the list.\r\n */\r\nconst containerEvents = [ 'abort', 'canplaythrough', 'ended', 'error' ];\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * Represents a single media track (either audio or video).\r\n */\r\nexport default class JitsiRemoteTrack extends JitsiTrack {\r\n /**\r\n * Creates new JitsiRemoteTrack instance.\r\n * @param {RTC} rtc the RTC service instance.\r\n * @param {JitsiConference} conference the conference to which this track\r\n * belongs to\r\n * @param {string} ownerEndpointId the endpoint ID of the track owner\r\n * @param {MediaStream} stream WebRTC MediaStream, parent of the track\r\n * @param {MediaStreamTrack} track underlying WebRTC MediaStreamTrack for\r\n * the new JitsiRemoteTrack\r\n * @param {MediaType} mediaType the type of the media\r\n * @param {VideoType} videoType the type of the video if applicable\r\n * @param {number} ssrc the SSRC number of the Media Stream\r\n * @param {boolean} muted the initial muted state\r\n * @param {boolean} isP2P indicates whether or not this track belongs to a\r\n * P2P session\r\n * @param {String} sourceName the source name signaled for the track\r\n * @throws {TypeError} if ssrc is not a number.\r\n * @constructor\r\n */\r\n constructor(\r\n rtc,\r\n conference,\r\n ownerEndpointId,\r\n stream,\r\n track,\r\n mediaType,\r\n videoType,\r\n ssrc,\r\n muted,\r\n isP2P,\r\n sourceName) {\r\n super(\r\n conference,\r\n stream,\r\n track,\r\n () => {\r\n // Nothing to do if the track is inactive.\r\n },\r\n mediaType,\r\n videoType);\r\n this.rtc = rtc;\r\n\r\n // Prevent from mixing up type of SSRC which should be a number\r\n if (typeof ssrc !== 'number') {\r\n throw new TypeError(`SSRC ${ssrc} is not a number`);\r\n }\r\n this.ssrc = ssrc;\r\n this.ownerEndpointId = ownerEndpointId;\r\n this.muted = muted;\r\n this.isP2P = isP2P;\r\n this._sourceName = sourceName;\r\n this._trackStreamingStatus = null;\r\n this._trackStreamingStatusImpl = null;\r\n\r\n /**\r\n * This holds the timestamp indicating when remote video track entered forwarded sources set. Track entering\r\n * forwardedSources will have streaming status restoring and when we start receiving video will become active,\r\n * but if video is not received for certain time {@link DEFAULT_RESTORING_TIMEOUT} that track streaming status\r\n * will become interrupted.\r\n */\r\n this._enteredForwardedSourcesTimestamp = null;\r\n\r\n this.addEventListener = this.on = this._addEventListener.bind(this);\r\n this.removeEventListener = this.off = this._removeEventListener.bind(this);\r\n\r\n logger.debug(`New remote track added: ${this}`);\r\n\r\n // we want to mark whether the track has been ever muted\r\n // to detect ttfm events for startmuted conferences, as it can\r\n // significantly increase ttfm values\r\n this.hasBeenMuted = muted;\r\n\r\n // Bind 'onmute' and 'onunmute' event handlers\r\n if (this.rtc && this.track) {\r\n this._bindTrackHandlers();\r\n }\r\n this._containerHandlers = {};\r\n containerEvents.forEach(event => {\r\n this._containerHandlers[event] = this._containerEventHandler.bind(this, event);\r\n });\r\n }\r\n\r\n /* eslint-enable max-params */\r\n /**\r\n * Attaches the track handlers.\r\n *\r\n * @returns {void}\r\n */\r\n _bindTrackHandlers() {\r\n this.track.addEventListener('mute', () => this._onTrackMute());\r\n this.track.addEventListener('unmute', () => this._onTrackUnmute());\r\n this.track.addEventListener('ended', () => {\r\n logger.debug(`\"onended\" event(${Date.now()}): ${this}`);\r\n });\r\n }\r\n\r\n /**\r\n * Overrides addEventListener method to init TrackStreamingStatus instance when there are listeners for the\r\n * {@link JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED} event.\r\n *\r\n * @param {string} event - event name\r\n * @param {function} handler - event handler\r\n */\r\n _addEventListener(event, handler) {\r\n super.addListener(event, handler);\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()\r\n && event === JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED\r\n && this.listenerCount(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED)\r\n && !this._trackStreamingStatusImpl\r\n ) {\r\n this._initTrackStreamingStatus();\r\n logger.debug(`Initializing track streaming status: ${this._sourceName}`);\r\n }\r\n }\r\n\r\n /**\r\n * Overrides removeEventListener method to dispose TrackStreamingStatus instance.\r\n *\r\n * @param {string} event - event name\r\n * @param {function} handler - event handler\r\n */\r\n _removeEventListener(event, handler) {\r\n super.removeListener(event, handler);\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()\r\n && event === JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED\r\n && !this.listenerCount(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED)\r\n ) {\r\n this._disposeTrackStreamingStatus();\r\n logger.debug(`Disposing track streaming status: ${this._sourceName}`);\r\n }\r\n }\r\n\r\n /**\r\n * Callback invoked when the track is muted. Emits an event notifying\r\n * listeners of the mute event.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _onTrackMute() {\r\n logger.debug(`\"onmute\" event(${Date.now()}): ${this}`);\r\n\r\n this.rtc.eventEmitter.emit(RTCEvents.REMOTE_TRACK_MUTE, this);\r\n }\r\n\r\n /**\r\n * Callback invoked when the track is unmuted. Emits an event notifying\r\n * listeners of the mute event.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _onTrackUnmute() {\r\n logger.debug(`\"onunmute\" event(${Date.now()}): ${this}`);\r\n\r\n this.rtc.eventEmitter.emit(RTCEvents.REMOTE_TRACK_UNMUTE, this);\r\n }\r\n\r\n /**\r\n * Removes attached event listeners and dispose TrackStreamingStatus .\r\n *\r\n * @returns {Promise}\r\n */\r\n dispose() {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this._disposeTrackStreamingStatus();\r\n }\r\n\r\n return super.dispose();\r\n }\r\n\r\n /**\r\n * Sets current muted status and fires an events for the change.\r\n * @param value the muted status.\r\n */\r\n setMute(value) {\r\n if (this.muted === value) {\r\n return;\r\n }\r\n\r\n if (value) {\r\n this.hasBeenMuted = true;\r\n }\r\n\r\n // we can have a fake video stream\r\n if (this.stream) {\r\n this.stream.muted = value;\r\n }\r\n\r\n this.muted = value;\r\n this.emit(JitsiTrackEvents.TRACK_MUTE_CHANGED, this);\r\n }\r\n\r\n /**\r\n * Returns the current muted status of the track.\r\n * @returns {boolean|*|JitsiRemoteTrack.muted} true if the track is\r\n * muted and false otherwise.\r\n */\r\n isMuted() {\r\n return this.muted;\r\n }\r\n\r\n /**\r\n * Returns the participant id which owns the track.\r\n *\r\n * @returns {string} the id of the participants. It corresponds to the\r\n * Colibri endpoint id/MUC nickname in case of Jitsi-meet.\r\n */\r\n getParticipantId() {\r\n return this.ownerEndpointId;\r\n }\r\n\r\n /**\r\n * Return false;\r\n */\r\n isLocal() {\r\n return false;\r\n }\r\n\r\n /**\r\n * Returns the synchronization source identifier (SSRC) of this remote\r\n * track.\r\n *\r\n * @returns {number} the SSRC of this remote track.\r\n */\r\n getSSRC() {\r\n return this.ssrc;\r\n }\r\n\r\n\r\n /**\r\n * Returns the tracks source name\r\n *\r\n * @returns {string} the track's source name\r\n */\r\n getSourceName() {\r\n return this._sourceName;\r\n }\r\n\r\n /**\r\n * Changes the video type of the track.\r\n *\r\n * @param {string} type - The new video type(\"camera\", \"desktop\").\r\n */\r\n _setVideoType(type) {\r\n if (this.videoType === type) {\r\n return;\r\n }\r\n this.videoType = type;\r\n this.emit(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED, type);\r\n }\r\n\r\n /**\r\n * Handles track play events.\r\n */\r\n _playCallback() {\r\n if (!this.conference.room) {\r\n return;\r\n }\r\n\r\n const type = this.isVideoTrack() ? 'video' : 'audio';\r\n\r\n const now = window.performance.now();\r\n\r\n console.log(`(TIME) Render ${type}:\\t`, now);\r\n this.conference.getConnectionTimes()[`${type}.render`] = now;\r\n\r\n // The conference can be started without calling GUM\r\n // FIXME if there would be a module for connection times this kind\r\n // of logic (gumDuration or ttfm) should end up there\r\n const gumStart = window.connectionTimes['obtainPermissions.start'];\r\n const gumEnd = window.connectionTimes['obtainPermissions.end'];\r\n const gumDuration\r\n = !isNaN(gumEnd) && !isNaN(gumStart) ? gumEnd - gumStart : 0;\r\n\r\n // Subtract the muc.joined-to-session-initiate duration because jicofo\r\n // waits until there are 2 participants to start Jingle sessions.\r\n const ttfm = now\r\n - (this.conference.getConnectionTimes()['session.initiate']\r\n - this.conference.getConnectionTimes()['muc.joined'])\r\n - gumDuration;\r\n\r\n this.conference.getConnectionTimes()[`${type}.ttfm`] = ttfm;\r\n console.log(`(TIME) TTFM ${type}:\\t`, ttfm);\r\n\r\n Statistics.sendAnalytics(createTtfmEvent(\r\n {\r\n 'media_type': type,\r\n muted: this.hasBeenMuted,\r\n value: ttfm\r\n }));\r\n\r\n }\r\n\r\n /**\r\n * Attach time to first media tracker only if there is conference and only\r\n * for the first element.\r\n * @param container the HTML container which can be 'video' or 'audio'\r\n * element.\r\n * @private\r\n */\r\n _attachTTFMTracker(container) {\r\n if ((ttfmTrackerAudioAttached && this.isAudioTrack())\r\n || (ttfmTrackerVideoAttached && this.isVideoTrack())) {\r\n return;\r\n }\r\n\r\n if (this.isAudioTrack()) {\r\n ttfmTrackerAudioAttached = true;\r\n }\r\n if (this.isVideoTrack()) {\r\n ttfmTrackerVideoAttached = true;\r\n }\r\n\r\n container.addEventListener('canplay', this._playCallback.bind(this));\r\n }\r\n\r\n /**\r\n * Called when the track has been attached to a new container.\r\n *\r\n * @param {HTMLElement} container the HTML container which can be 'video' or 'audio' element.\r\n * @private\r\n */\r\n _onTrackAttach(container) {\r\n containerEvents.forEach(event => {\r\n container.addEventListener(event, this._containerHandlers[event]);\r\n });\r\n }\r\n\r\n /**\r\n * Called when the track has been detached from a container.\r\n *\r\n * @param {HTMLElement} container the HTML container which can be 'video' or 'audio' element.\r\n * @private\r\n */\r\n _onTrackDetach(container) {\r\n containerEvents.forEach(event => {\r\n container.removeEventListener(event, this._containerHandlers[event]);\r\n });\r\n }\r\n\r\n /**\r\n * An event handler for events triggered by the attached container.\r\n *\r\n * @param {string} type - The type of the event.\r\n */\r\n _containerEventHandler(type) {\r\n logger.debug(`${type} handler was called for a container with attached ${this}`);\r\n }\r\n\r\n /**\r\n * Returns a string with a description of the current status of the track.\r\n *\r\n * @returns {string}\r\n */\r\n _getStatus() {\r\n const { enabled, muted, readyState } = this.track;\r\n\r\n return `readyState: ${readyState}, muted: ${muted}, enabled: ${enabled}`;\r\n }\r\n\r\n /**\r\n * Initializes trackStreamingStatusImpl.\r\n */\r\n _initTrackStreamingStatus() {\r\n const config = this.conference.options.config;\r\n\r\n this._trackStreamingStatus = TrackStreamingStatus.ACTIVE;\r\n\r\n this._trackStreamingStatusImpl = new TrackStreamingStatusImpl(\r\n this.rtc,\r\n this.conference,\r\n this,\r\n {\r\n // These options are not public API, leaving it here only as an entry point through config for\r\n // tuning up purposes. Default values should be adjusted as soon as optimal values are discovered.\r\n p2pRtcMuteTimeout: config._p2pConnStatusRtcMuteTimeout,\r\n rtcMuteTimeout: config._peerConnStatusRtcMuteTimeout,\r\n outOfForwardedSourcesTimeout: config._peerConnStatusOutOfLastNTimeout\r\n });\r\n\r\n this._trackStreamingStatusImpl.init();\r\n }\r\n\r\n /**\r\n * Disposes trackStreamingStatusImpl and clears trackStreamingStatus.\r\n */\r\n _disposeTrackStreamingStatus() {\r\n if (this._trackStreamingStatusImpl) {\r\n this._trackStreamingStatusImpl.dispose();\r\n this._trackStreamingStatusImpl = null;\r\n this._trackStreamingStatus = null;\r\n }\r\n }\r\n\r\n /**\r\n * Updates track's streaming status.\r\n *\r\n * @param {string} state the current track streaming state. {@link TrackStreamingStatus}.\r\n */\r\n _setTrackStreamingStatus(status) {\r\n this._trackStreamingStatus = status;\r\n }\r\n\r\n /**\r\n * Returns track's streaming status.\r\n *\r\n * @returns {string} the streaming status TrackStreamingStatus of the track. Returns null\r\n * if trackStreamingStatusImpl hasn't been initialized.\r\n *\r\n * {@link TrackStreamingStatus}.\r\n */\r\n getTrackStreamingStatus() {\r\n return this._trackStreamingStatus;\r\n }\r\n\r\n /**\r\n * Clears the timestamp of when the track entered forwarded sources.\r\n */\r\n _clearEnteredForwardedSourcesTimestamp() {\r\n this._enteredForwardedSourcesTimestamp = null;\r\n }\r\n\r\n /**\r\n * Updates the timestamp of when the track entered forwarded sources.\r\n *\r\n * @param {number} timestamp the time in millis\r\n */\r\n _setEnteredForwardedSourcesTimestamp(timestamp) {\r\n this._enteredForwardedSourcesTimestamp = timestamp;\r\n }\r\n\r\n /**\r\n * Returns the timestamp of when the track entered forwarded sources.\r\n *\r\n * @returns {number} the time in millis\r\n */\r\n _getEnteredForwardedSourcesTimestamp() {\r\n return this._enteredForwardedSourcesTimestamp;\r\n }\r\n\r\n /**\r\n * Creates a text representation of this remote track instance.\r\n * @return {string}\r\n */\r\n toString() {\r\n return `RemoteTrack[userID: ${this.getParticipantId()}, type: ${this.getType()}, ssrc: ${\r\n this.getSSRC()}, p2p: ${this.isP2P}, sourceName: ${this._sourceName}, status: ${this._getStatus()}]`;\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport transform from 'sdp-transform';\r\n\r\nimport { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport browser from '../browser';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\n\r\nconst logger = getLogger(__filename);\r\nconst DESKTOP_SHARE_RATE = 500000;\r\nconst LD_BITRATE = 200000;\r\nconst SD_BITRATE = 700000;\r\nconst SIM_LAYER_1_RID = '1';\r\nconst SIM_LAYER_2_RID = '2';\r\nconst SIM_LAYER_3_RID = '3';\r\n\r\nexport const HD_BITRATE = 2500000;\r\nexport const HD_SCALE_FACTOR = 1;\r\nexport const LD_SCALE_FACTOR = 4;\r\nexport const SD_SCALE_FACTOR = 2;\r\nexport const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];\r\n\r\n/**\r\n * Handles track related operations on TraceablePeerConnection when browser is\r\n * running in unified plan mode.\r\n */\r\nexport class TPCUtils {\r\n /**\r\n * Creates a new instance for a given TraceablePeerConnection\r\n *\r\n * @param peerconnection - the tpc instance for which we have utility functions.\r\n */\r\n constructor(peerconnection) {\r\n this.pc = peerconnection;\r\n const bitrateSettings = this.pc.options?.videoQuality?.maxBitratesVideo;\r\n const standardBitrates = {\r\n low: LD_BITRATE,\r\n standard: SD_BITRATE,\r\n high: HD_BITRATE\r\n };\r\n\r\n // Check if the max. bitrates for video are specified through config.js videoQuality settings.\r\n // Right now only VP8 bitrates are configured on the simulcast encodings, VP9 bitrates have to be\r\n // configured on the SDP using b:AS line.\r\n this.videoBitrates = bitrateSettings ?? standardBitrates;\r\n const encodingBitrates = this.videoBitrates.VP8 ?? this.videoBitrates;\r\n\r\n /**\r\n * The startup configuration for the stream encodings that are applicable to\r\n * the video stream when a new sender is created on the peerconnection. The initial\r\n * config takes into account the differences in browser's simulcast implementation.\r\n *\r\n * Encoding parameters:\r\n * active - determine the on/off state of a particular encoding.\r\n * maxBitrate - max. bitrate value to be applied to that particular encoding\r\n * based on the encoding's resolution and config.js videoQuality settings if applicable.\r\n * rid - Rtp Stream ID that is configured for a particular simulcast stream.\r\n * scaleResolutionDownBy - the factor by which the encoding is scaled down from the\r\n * original resolution of the captured video.\r\n */\r\n this.localStreamEncodingsConfig = [\r\n {\r\n active: true,\r\n maxBitrate: browser.isFirefox() ? encodingBitrates.high : encodingBitrates.low,\r\n rid: SIM_LAYER_1_RID,\r\n scaleResolutionDownBy: browser.isFirefox() ? HD_SCALE_FACTOR : LD_SCALE_FACTOR\r\n },\r\n {\r\n active: true,\r\n maxBitrate: encodingBitrates.standard,\r\n rid: SIM_LAYER_2_RID,\r\n scaleResolutionDownBy: SD_SCALE_FACTOR\r\n },\r\n {\r\n active: true,\r\n maxBitrate: browser.isFirefox() ? encodingBitrates.low : encodingBitrates.high,\r\n rid: SIM_LAYER_3_RID,\r\n scaleResolutionDownBy: browser.isFirefox() ? LD_SCALE_FACTOR : HD_SCALE_FACTOR\r\n }\r\n ];\r\n }\r\n\r\n /**\r\n * Obtains stream encodings that need to be configured on the given track based\r\n * on the track media type and the simulcast setting.\r\n * @param {JitsiLocalTrack} localTrack\r\n */\r\n _getStreamEncodings(localTrack) {\r\n if (this.pc.isSimulcastOn() && localTrack.isVideoTrack()) {\r\n return this.localStreamEncodingsConfig;\r\n }\r\n\r\n return localTrack.isVideoTrack()\r\n ? [ {\r\n active: true,\r\n maxBitrate: this.videoBitrates.high\r\n } ]\r\n : [ { active: true } ];\r\n }\r\n\r\n /**\r\n * Ensures that the ssrcs associated with a FID ssrc-group appear in the correct order, i.e.,\r\n * the primary ssrc first and the secondary rtx ssrc later. This is important for unified\r\n * plan since we have only one FID group per media description.\r\n * @param {Object} description the webRTC session description instance for the remote\r\n * description.\r\n * @private\r\n */\r\n ensureCorrectOrderOfSsrcs(description) {\r\n const parsedSdp = transform.parse(description.sdp);\r\n\r\n parsedSdp.media.forEach(mLine => {\r\n if (mLine.type === MediaType.AUDIO) {\r\n return;\r\n }\r\n if (!mLine.ssrcGroups || !mLine.ssrcGroups.length) {\r\n return;\r\n }\r\n let reorderedSsrcs = [];\r\n\r\n const ssrcs = new Set();\r\n\r\n mLine.ssrcGroups.map(group =>\r\n group.ssrcs\r\n .split(' ')\r\n .filter(Boolean)\r\n .forEach(ssrc => ssrcs.add(ssrc))\r\n );\r\n\r\n ssrcs.forEach(ssrc => {\r\n const sources = mLine.ssrcs.filter(source => source.id.toString() === ssrc);\r\n\r\n reorderedSsrcs = reorderedSsrcs.concat(sources);\r\n });\r\n mLine.ssrcs = reorderedSsrcs;\r\n });\r\n\r\n return new RTCSessionDescription({\r\n type: description.type,\r\n sdp: transform.write(parsedSdp)\r\n });\r\n }\r\n\r\n /**\r\n * Returns the transceiver associated with a given RTCRtpSender/RTCRtpReceiver.\r\n *\r\n * @param {string} mediaType - type of track associated with the transceiver 'audio' or 'video'.\r\n * @param {JitsiLocalTrack} localTrack - local track to be used for lookup.\r\n * @returns {RTCRtpTransceiver}\r\n */\r\n findTransceiver(mediaType, localTrack = null) {\r\n const transceiver = localTrack?.track && localTrack.getOriginalStream()\r\n ? this.pc.peerconnection.getTransceivers().find(t => t.sender?.track?.id === localTrack.getTrackId())\r\n : this.pc.peerconnection.getTransceivers().find(t => t.receiver?.track?.kind === mediaType);\r\n\r\n return transceiver;\r\n }\r\n\r\n /**\r\n * Takes in a *unified plan* offer and inserts the appropriate\r\n * parameters for adding simulcast receive support.\r\n * @param {Object} desc - A session description object\r\n * @param {String} desc.type - the type (offer/answer)\r\n * @param {String} desc.sdp - the sdp content\r\n *\r\n * @return {Object} A session description (same format as above) object\r\n * with its sdp field modified to advertise simulcast receive support\r\n */\r\n insertUnifiedPlanSimulcastReceive(desc) {\r\n // a=simulcast line is not needed on browsers where we SDP munging is used for enabling on simulcast.\r\n // Remove this check when the client switches to RID/MID based simulcast on all browsers.\r\n if (browser.usesSdpMungingForSimulcast()) {\r\n return desc;\r\n }\r\n const sdp = transform.parse(desc.sdp);\r\n const idx = sdp.media.findIndex(mline => mline.type === MediaType.VIDEO);\r\n\r\n if (sdp.media[idx].rids && (sdp.media[idx].simulcast_03 || sdp.media[idx].simulcast)) {\r\n // Make sure we don't have the simulcast recv line on video descriptions other than\r\n // the first video description.\r\n sdp.media.forEach((mline, i) => {\r\n if (mline.type === MediaType.VIDEO && i !== idx) {\r\n sdp.media[i].rids = undefined;\r\n sdp.media[i].simulcast = undefined;\r\n\r\n // eslint-disable-next-line camelcase\r\n sdp.media[i].simulcast_03 = undefined;\r\n }\r\n });\r\n\r\n return new RTCSessionDescription({\r\n type: desc.type,\r\n sdp: transform.write(sdp)\r\n });\r\n }\r\n\r\n // In order of highest to lowest spatial quality\r\n sdp.media[idx].rids = [\r\n {\r\n id: SIM_LAYER_1_RID,\r\n direction: 'recv'\r\n },\r\n {\r\n id: SIM_LAYER_2_RID,\r\n direction: 'recv'\r\n },\r\n {\r\n id: SIM_LAYER_3_RID,\r\n direction: 'recv'\r\n }\r\n ];\r\n\r\n // Firefox 72 has stopped parsing the legacy rid= parameters in simulcast attributes.\r\n // eslint-disable-next-line max-len\r\n // https://www.fxsitecompat.dev/en-CA/docs/2019/pt-and-rid-in-webrtc-simulcast-attributes-are-no-longer-supported/\r\n const simulcastLine = browser.isFirefox() && browser.isVersionGreaterThan(71)\r\n ? `recv ${SIM_LAYER_RIDS.join(';')}`\r\n : `recv rid=${SIM_LAYER_RIDS.join(';')}`;\r\n\r\n // eslint-disable-next-line camelcase\r\n sdp.media[idx].simulcast_03 = {\r\n value: simulcastLine\r\n };\r\n\r\n return new RTCSessionDescription({\r\n type: desc.type,\r\n sdp: transform.write(sdp)\r\n });\r\n }\r\n\r\n /**\r\n * Adds {@link JitsiLocalTrack} to the WebRTC peerconnection for the first time.\r\n * @param {JitsiLocalTrack} track - track to be added to the peerconnection.\r\n * @param {boolean} isInitiator - boolean that indicates if the endpoint is offerer in a p2p connection.\r\n * @returns {void}\r\n */\r\n addTrack(localTrack, isInitiator) {\r\n const track = localTrack.getTrack();\r\n\r\n if (isInitiator) {\r\n const streams = [];\r\n\r\n if (localTrack.getOriginalStream()) {\r\n streams.push(localTrack.getOriginalStream());\r\n }\r\n\r\n // Use pc.addTransceiver() for the initiator case when local tracks are getting added\r\n // to the peerconnection before a session-initiate is sent over to the peer.\r\n const transceiverInit = {\r\n direction: MediaDirection.SENDRECV,\r\n streams,\r\n sendEncodings: []\r\n };\r\n\r\n if (!browser.isFirefox()) {\r\n transceiverInit.sendEncodings = this._getStreamEncodings(localTrack);\r\n }\r\n this.pc.peerconnection.addTransceiver(track, transceiverInit);\r\n } else {\r\n // Use pc.addTrack() for responder case so that we can re-use the m-lines that were created\r\n // when setRemoteDescription was called. pc.addTrack() automatically attaches to any existing\r\n // unused \"recv-only\" transceiver.\r\n this.pc.peerconnection.addTrack(track);\r\n }\r\n }\r\n\r\n /**\r\n * Returns the calculated active state of the simulcast encodings based on the frame height requested for the send\r\n * stream. All the encodings that have a resolution lower than the frame height requested will be enabled.\r\n *\r\n * @param {JitsiLocalTrack} localVideoTrack The local video track.\r\n * @param {number} newHeight The resolution requested for the video track.\r\n * @returns {Array}\r\n */\r\n calculateEncodingsActiveState(localVideoTrack, newHeight) {\r\n const localTrack = localVideoTrack.getTrack();\r\n const { height } = localTrack.getSettings();\r\n const encodingsState = this.localStreamEncodingsConfig\r\n .map(encoding => height / encoding.scaleResolutionDownBy)\r\n .map((frameHeight, idx) => {\r\n let active = localVideoTrack.getVideoType() === VideoType.CAMERA\r\n\r\n // Keep the LD stream enabled even when the LD stream's resolution is higher than of the requested\r\n // resolution. This can happen when camera is captured at resolutions higher than 720p but the\r\n // requested resolution is 180. Since getParameters doesn't give us information about the resolutions\r\n // of the simulcast encodings, we have to rely on our initial config for the simulcast streams.\r\n ? newHeight > 0 && this.localStreamEncodingsConfig[idx]?.scaleResolutionDownBy === LD_SCALE_FACTOR\r\n ? true\r\n : frameHeight <= newHeight\r\n\r\n // Keep all the encodings for desktop track active.\r\n : true;\r\n\r\n // Disable the lower spatial layers for screensharing in Unified plan when low fps screensharing is in\r\n // progress. Sending all three streams often results in the browser suspending the high resolution in low\r\n // b/w and cpu cases, especially on the low end machines. Suspending the low resolution streams ensures\r\n // that the highest resolution stream is available always. Safari is an exception here since it does not\r\n // send the desktop stream at all if only the high resolution stream is enabled.\r\n if (this.pc.isSharingLowFpsScreen()\r\n && localVideoTrack.getVideoType() === VideoType.DESKTOP\r\n && this.pc.usesUnifiedPlan()\r\n && !browser.isWebKitBased()\r\n && this.localStreamEncodingsConfig[idx].scaleResolutionDownBy !== HD_SCALE_FACTOR) {\r\n active = false;\r\n }\r\n\r\n return active;\r\n });\r\n\r\n return encodingsState;\r\n }\r\n\r\n /**\r\n * Returns the calculates max bitrates that need to be configured on the simulcast encodings based on the video\r\n * type and other considerations associated with screenshare.\r\n *\r\n * @param {JitsiLocalTrack} localVideoTrack The local video track.\r\n * @returns {Array}\r\n */\r\n calculateEncodingsBitrates(localVideoTrack) {\r\n const videoType = localVideoTrack.getVideoType();\r\n const desktopShareBitrate = this.pc.options?.videoQuality?.desktopBitrate || DESKTOP_SHARE_RATE;\r\n const presenterEnabled = localVideoTrack._originalStream\r\n && localVideoTrack._originalStream.id !== localVideoTrack.getStreamId();\r\n\r\n const encodingsBitrates = this.localStreamEncodingsConfig\r\n .map(encoding => {\r\n const bitrate = this.pc.isSharingLowFpsScreen() && !browser.isWebKitBased()\r\n\r\n // For low fps screensharing, set a max bitrate of 500 Kbps when presenter is not turned on, 2500 Kbps\r\n // otherwise.\r\n ? presenterEnabled ? HD_BITRATE : desktopShareBitrate\r\n\r\n // For high fps screenshare, 'maxBitrate' setting must be cleared on Chrome in plan-b, because\r\n // if simulcast is enabled for screen and maxBitrates are set then Chrome will not send the\r\n // desktop stream.\r\n : videoType === VideoType.DESKTOP && browser.isChromiumBased() && !this.pc.usesUnifiedPlan()\r\n ? undefined\r\n : encoding.maxBitrate;\r\n\r\n return bitrate;\r\n });\r\n\r\n return encodingsBitrates;\r\n }\r\n\r\n /**\r\n * Replaces the existing track on a RTCRtpSender with the given track.\r\n *\r\n * @param {JitsiLocalTrack} oldTrack - existing track on the sender that needs to be removed.\r\n * @param {JitsiLocalTrack} newTrack - new track that needs to be added to the sender.\r\n * @returns {Promise} - resolved with the associated transceiver when done, rejected otherwise.\r\n */\r\n replaceTrack(oldTrack, newTrack) {\r\n const mediaType = newTrack?.getType() ?? oldTrack?.getType();\r\n const localTracks = this.pc.getLocalTracks(mediaType);\r\n const track = newTrack?.getTrack() ?? null;\r\n const isNewLocalSource = FeatureFlags.isMultiStreamSupportEnabled()\r\n && localTracks?.length\r\n && !oldTrack\r\n && newTrack\r\n && !localTracks.find(t => t === newTrack);\r\n let transceiver;\r\n\r\n // If old track exists, replace the track on the corresponding sender.\r\n if (oldTrack && !oldTrack.isMuted()) {\r\n transceiver = this.pc.peerconnection.getTransceivers().find(t => t.sender.track === oldTrack.getTrack());\r\n\r\n // Find the first recvonly transceiver when more than one track of the same media type is being added to the pc.\r\n // As part of the track addition, a new m-line was added to the remote description with direction set to\r\n // recvonly.\r\n } else if (isNewLocalSource) {\r\n transceiver = this.pc.peerconnection.getTransceivers().find(\r\n t => t.receiver.track.kind === mediaType\r\n && t.direction === MediaDirection.RECVONLY\r\n && t.currentDirection === MediaDirection.INACTIVE);\r\n\r\n // For mute/unmute operations, find the transceiver based on the track index in the source name if present,\r\n // otherwise it is assumed to be the first local track that was added to the peerconnection.\r\n } else {\r\n transceiver = this.pc.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType);\r\n const sourceName = newTrack?.getSourceName() ?? oldTrack?.getSourceName();\r\n\r\n if (sourceName) {\r\n const trackIndex = Number(sourceName.split('-')[1].substring(1));\r\n\r\n if (trackIndex) {\r\n transceiver = this.pc.peerconnection.getTransceivers()\r\n .filter(t => t.receiver.track.kind === mediaType\r\n && t.direction !== MediaDirection.RECVONLY)[trackIndex];\r\n }\r\n }\r\n }\r\n\r\n if (!transceiver) {\r\n return Promise.reject(new Error('replace track failed'));\r\n }\r\n logger.debug(`${this.pc} Replacing ${oldTrack} with ${newTrack}`);\r\n\r\n return transceiver.sender.replaceTrack(track)\r\n .then(() => Promise.resolve(transceiver));\r\n }\r\n\r\n /**\r\n * Enables/disables audio transmission on the peer connection. When\r\n * disabled the audio transceiver direction will be set to 'inactive'\r\n * which means that no data will be sent nor accepted, but\r\n * the connection should be kept alive.\r\n * @param {boolean} active - true to enable audio media transmission or\r\n * false to disable.\r\n * @returns {void}\r\n */\r\n setAudioTransferActive(active) {\r\n this.setMediaTransferActive(MediaType.AUDIO, active);\r\n }\r\n\r\n /**\r\n * Set the simulcast stream encoding properties on the RTCRtpSender.\r\n * @param {JitsiLocalTrack} track - the current track in use for which\r\n * the encodings are to be set.\r\n * @returns {Promise} - resolved when done.\r\n */\r\n setEncodings(track) {\r\n const mediaType = track.getType();\r\n const transceiver = this.findTransceiver(mediaType, track);\r\n const parameters = transceiver?.sender?.getParameters();\r\n\r\n // Resolve if the encodings are not available yet. This happens immediately after the track is added to the\r\n // peerconnection on chrome in unified-plan. It is ok to ignore and not report the error here since the\r\n // action that triggers 'addTrack' (like unmute) will also configure the encodings and set bitrates after that.\r\n if (!parameters?.encodings?.length) {\r\n return Promise.resolve();\r\n }\r\n parameters.encodings = this._getStreamEncodings(track);\r\n\r\n return transceiver.sender.setParameters(parameters);\r\n }\r\n\r\n /**\r\n * Enables/disables media transmission on the peerconnection by changing the direction\r\n * on the transceiver for the specified media type.\r\n * @param {String} mediaType - 'audio' or 'video'\r\n * @param {boolean} active - true to enable media transmission or false\r\n * to disable.\r\n * @returns {void}\r\n */\r\n setMediaTransferActive(mediaType, active) {\r\n const transceivers = this.pc.peerconnection.getTransceivers()\r\n .filter(t => t.receiver && t.receiver.track && t.receiver.track.kind === mediaType);\r\n const localTracks = this.pc.getLocalTracks(mediaType);\r\n\r\n logger.info(`${this.pc} ${active ? 'Enabling' : 'Suspending'} ${mediaType} media transfer.`);\r\n transceivers.forEach((transceiver, idx) => {\r\n if (active) {\r\n // The first transceiver is for the local track and only this one can be set to 'sendrecv'.\r\n // When multi-stream is enabled, there can be multiple transceivers with outbound streams.\r\n if (idx < localTracks.length) {\r\n transceiver.direction = MediaDirection.SENDRECV;\r\n } else {\r\n transceiver.direction = MediaDirection.RECVONLY;\r\n }\r\n } else {\r\n transceiver.direction = MediaDirection.INACTIVE;\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Enables/disables video media transmission on the peer connection. When\r\n * disabled the SDP video media direction in the local SDP will be adjusted to\r\n * 'inactive' which means that no data will be sent nor accepted, but\r\n * the connection should be kept alive.\r\n * @param {boolean} active - true to enable video media transmission or\r\n * false to disable.\r\n * @returns {void}\r\n */\r\n setVideoTransferActive(active) {\r\n this.setMediaTransferActive(MediaType.VIDEO, active);\r\n }\r\n\r\n /**\r\n * Ensures that the resolution of the stream encodings are consistent with the values\r\n * that were configured on the RTCRtpSender when the source was added to the peerconnection.\r\n * This should prevent us from overriding the default values if the browser returns\r\n * erroneous values when RTCRtpSender.getParameters is used for getting the encodings info.\r\n * @param {Object} parameters - the RTCRtpEncodingParameters obtained from the browser.\r\n * @returns {void}\r\n */\r\n updateEncodingsResolution(parameters) {\r\n if (!(browser.isWebKitBased() && parameters.encodings && Array.isArray(parameters.encodings))) {\r\n return;\r\n }\r\n const allEqualEncodings\r\n = encodings => encodings.every(encoding => typeof encoding.scaleResolutionDownBy !== 'undefined'\r\n && encoding.scaleResolutionDownBy === encodings[0].scaleResolutionDownBy);\r\n\r\n // Implement the workaround only when all the encodings report the same resolution.\r\n if (allEqualEncodings(parameters.encodings)) {\r\n parameters.encodings.forEach((encoding, idx) => {\r\n encoding.scaleResolutionDownBy = this.localStreamEncodingsConfig[idx].scaleResolutionDownBy;\r\n });\r\n }\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { Interop } from '@jitsi/sdp-interop';\r\nimport transform from 'sdp-transform';\r\n\r\nimport * as CodecMimeType from '../../service/RTC/CodecMimeType';\r\nimport { MediaDirection } from '../../service/RTC/MediaDirection';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport * as SignalingEvents from '../../service/RTC/SignalingEvents';\r\nimport { getSourceNameForJitsiTrack } from '../../service/RTC/SignalingLayer';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport { SS_DEFAULT_FRAME_RATE } from '../RTC/ScreenObtainer';\r\nimport browser from '../browser';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport LocalSdpMunger from '../sdp/LocalSdpMunger';\r\nimport RtxModifier from '../sdp/RtxModifier';\r\nimport SDP from '../sdp/SDP';\r\nimport SDPUtil from '../sdp/SDPUtil';\r\nimport SdpConsistency from '../sdp/SdpConsistency';\r\nimport SdpSimulcast from '../sdp/SdpSimulcast.ts';\r\nimport { SdpTransformWrap } from '../sdp/SdpTransformUtil';\r\nimport * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\n\r\nimport JitsiRemoteTrack from './JitsiRemoteTrack';\r\nimport RTC from './RTC';\r\nimport RTCUtils from './RTCUtils';\r\nimport {\r\n HD_BITRATE,\r\n HD_SCALE_FACTOR,\r\n SIM_LAYER_RIDS,\r\n TPCUtils\r\n} from './TPCUtils';\r\n\r\n// FIXME SDP tools should end up in some kind of util module\r\n\r\nconst logger = getLogger(__filename);\r\nconst DEGRADATION_PREFERENCE_CAMERA = 'maintain-framerate';\r\nconst DEGRADATION_PREFERENCE_DESKTOP = 'maintain-resolution';\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * Creates new instance of 'TraceablePeerConnection'.\r\n *\r\n * @param {RTC} rtc the instance of RTC service\r\n * @param {number} id the peer connection id assigned by the parent RTC module.\r\n * @param {SignalingLayer} signalingLayer the signaling layer instance\r\n * @param {object} pcConfig The {@code RTCConfiguration} to use for the WebRTC peer connection.\r\n * @param {object} constraints WebRTC 'PeerConnection' constraints\r\n * @param {boolean} isP2P indicates whether or not the new instance will be used in a peer to peer connection.\r\n * @param {object} options TracablePeerConnection config options.\r\n * @param {boolean} options.disableSimulcast if set to 'true' will disable the simulcast.\r\n * @param {boolean} options.disableRtx if set to 'true' will disable the RTX.\r\n * @param {string} options.disabledCodec the mime type of the code that should not be negotiated on the peerconnection.\r\n * @param {string} options.preferredCodec the mime type of the codec that needs to be made the preferred codec for the\r\n * peerconnection.\r\n * @param {boolean} options.startSilent If set to 'true' no audio will be sent or received.\r\n * @param {boolean} options.usesUnifiedPlan Indicates if the browser is running in unified plan mode.\r\n *\r\n * FIXME: initially the purpose of TraceablePeerConnection was to be able to\r\n * debug the peer connection. Since many other responsibilities have been added\r\n * it would make sense to extract a separate class from it and come up with\r\n * a more suitable name.\r\n *\r\n * @constructor\r\n */\r\nexport default function TraceablePeerConnection(\r\n rtc,\r\n id,\r\n signalingLayer,\r\n pcConfig,\r\n constraints,\r\n isP2P,\r\n options) {\r\n\r\n /**\r\n * Indicates whether or not this peer connection instance is actively\r\n * sending/receiving audio media. When set to false the SDP audio\r\n * media direction will be adjusted to 'inactive' in order to suspend\r\n * the transmission.\r\n * @type {boolean}\r\n * @private\r\n */\r\n this.audioTransferActive = !(options.startSilent === true);\r\n\r\n /**\r\n * The DTMF sender instance used to send DTMF tones.\r\n *\r\n * @type {RTCDTMFSender|undefined}\r\n * @private\r\n */\r\n this._dtmfSender = undefined;\r\n\r\n /**\r\n * @typedef {Object} TouchToneRequest\r\n * @property {string} tones - The DTMF tones string as defined by\r\n * {@code RTCDTMFSender.insertDTMF}, 'tones' argument.\r\n * @property {number} duration - The amount of time in milliseconds that\r\n * each DTMF should last.\r\n * @property {string} interToneGap - The length of time in miliseconds to\r\n * wait between tones.\r\n */\r\n /**\r\n * TouchToneRequests which are waiting to be played. This queue is filled\r\n * if there are touch tones currently being played.\r\n *\r\n * @type {Array}\r\n * @private\r\n */\r\n this._dtmfTonesQueue = [];\r\n\r\n /**\r\n * Indicates whether or not this peer connection instance is actively\r\n * sending/receiving video media. When set to false the SDP video\r\n * media direction will be adjusted to 'inactive' in order to suspend\r\n * the transmission.\r\n * @type {boolean}\r\n * @private\r\n */\r\n this.videoTransferActive = true;\r\n\r\n /**\r\n * The parent instance of RTC service which created this\r\n * TracablePeerConnection.\r\n * @type {RTC}\r\n */\r\n this.rtc = rtc;\r\n\r\n /**\r\n * The peer connection identifier assigned by the RTC module.\r\n * @type {number}\r\n */\r\n this.id = id;\r\n\r\n /**\r\n * Indicates whether or not this instance is used in a peer to peer\r\n * connection.\r\n * @type {boolean}\r\n */\r\n this.isP2P = isP2P;\r\n\r\n /**\r\n * The map holds remote tracks associated with this peer connection. It maps user's JID to media type and a set of\r\n * remote tracks.\r\n * @type {Map>>}\r\n */\r\n this.remoteTracks = new Map();\r\n\r\n /**\r\n * A map which stores local tracks mapped by {@link JitsiLocalTrack.rtcId}\r\n * @type {Map}\r\n */\r\n this.localTracks = new Map();\r\n\r\n /**\r\n * Keeps tracks of the WebRTC MediaStreams that have been added to\r\n * the underlying WebRTC PeerConnection.\r\n * @type {Array}\r\n * @private\r\n */\r\n this._addedStreams = [];\r\n\r\n /**\r\n * @typedef {Object} TPCGroupInfo\r\n * @property {string} semantics the SSRC groups semantics\r\n * @property {Array} ssrcs group's SSRCs in order where the first\r\n * one is group's primary SSRC, the second one is secondary (RTX) and so\r\n * on...\r\n */\r\n /**\r\n * @typedef {Object} TPCSSRCInfo\r\n * @property {Array} ssrcs an array which holds all track's SSRCs\r\n * @property {Array} groups an array stores all track's SSRC\r\n * groups\r\n */\r\n /**\r\n * Holds the info about local track's SSRCs mapped per their\r\n * {@link JitsiLocalTrack.rtcId}\r\n * @type {Map}\r\n */\r\n this.localSSRCs = new Map();\r\n\r\n /**\r\n * The local ICE username fragment for this session.\r\n */\r\n this.localUfrag = null;\r\n\r\n /**\r\n * The remote ICE username fragment for this session.\r\n */\r\n this.remoteUfrag = null;\r\n\r\n /**\r\n * The DTLS transport object for the PeerConnection.\r\n * Note: this assume only one shared transport exists because we bundled\r\n * all streams on the same underlying transport.\r\n */\r\n this._dtlsTransport = null;\r\n\r\n /**\r\n * The signaling layer which operates this peer connection.\r\n * @type {SignalingLayer}\r\n */\r\n this.signalingLayer = signalingLayer;\r\n\r\n // SignalingLayer listeners\r\n this._peerVideoTypeChanged = this._peerVideoTypeChanged.bind(this);\r\n this.signalingLayer.on(SignalingEvents.PEER_VIDEO_TYPE_CHANGED, this._peerVideoTypeChanged);\r\n\r\n this._peerMutedChanged = this._peerMutedChanged.bind(this);\r\n this.signalingLayer.on(SignalingEvents.PEER_MUTED_CHANGED, this._peerMutedChanged);\r\n this.options = options;\r\n\r\n // Setup SignalingLayer listeners for source-name based events.\r\n this.signalingLayer.on(SignalingEvents.SOURCE_MUTED_CHANGED,\r\n (sourceName, isMuted) => this._sourceMutedChanged(sourceName, isMuted));\r\n this.signalingLayer.on(SignalingEvents.SOURCE_VIDEO_TYPE_CHANGED,\r\n (sourceName, videoType) => this._sourceVideoTypeChanged(sourceName, videoType));\r\n\r\n // Make sure constraints is properly formatted in order to provide information about whether or not this\r\n // connection is P2P to rtcstats.\r\n const safeConstraints = constraints || {};\r\n\r\n safeConstraints.optional = safeConstraints.optional || [];\r\n\r\n // The `optional` parameter needs to be of type array, otherwise chrome will throw an error.\r\n // Firefox and Safari just ignore it.\r\n if (Array.isArray(safeConstraints.optional)) {\r\n safeConstraints.optional.push({ rtcStatsSFUP2P: this.isP2P });\r\n } else {\r\n logger.warn('Optional param is not an array, rtcstats p2p data is omitted.');\r\n }\r\n\r\n this.peerconnection = new RTCUtils.RTCPeerConnectionType(pcConfig, safeConstraints);\r\n\r\n this.tpcUtils = new TPCUtils(this);\r\n this.updateLog = [];\r\n this.stats = {};\r\n this.statsinterval = null;\r\n\r\n /**\r\n * Flag used to indicate if simulcast is turned off and a cap of 500 Kbps is applied on screensharing.\r\n */\r\n this._capScreenshareBitrate = this.options.capScreenshareBitrate;\r\n\r\n /**\r\n * Flag used to indicate if the browser is running in unified plan mode.\r\n */\r\n this._usesUnifiedPlan = options.usesUnifiedPlan;\r\n\r\n /**\r\n * Flag used to indicate if RTCRtpTransceiver#setCodecPreferences is to be used instead of SDP\r\n * munging for codec selection.\r\n */\r\n this._usesTransceiverCodecPreferences = browser.supportsCodecPreferences() && this._usesUnifiedPlan;\r\n this._usesTransceiverCodecPreferences\r\n && logger.info('Using RTCRtpTransceiver#setCodecPreferences for codec selection');\r\n\r\n /**\r\n * @type {number} The max number of stats to keep in this.stats. Limit to\r\n * 300 values, i.e. 5 minutes; set to 0 to disable\r\n */\r\n this.maxstats = options.maxstats;\r\n\r\n this.interop = new Interop();\r\n\r\n if (this._usesUnifiedPlan) {\r\n this.simulcast = new SdpSimulcast({ numOfLayers: SIM_LAYER_RIDS.length });\r\n } else {\r\n const Simulcast = require('@jitsi/sdp-simulcast');\r\n\r\n this.simulcast = new Simulcast(\r\n {\r\n numOfLayers: SIM_LAYER_RIDS.length,\r\n explodeRemoteSimulcast: false,\r\n usesUnifiedPlan: false\r\n });\r\n }\r\n\r\n this.sdpConsistency = new SdpConsistency(this.toString());\r\n\r\n /**\r\n * Munges local SDP provided to the Jingle Session in order to prevent from\r\n * sending SSRC updates on attach/detach and mute/unmute (for video).\r\n * @type {LocalSdpMunger}\r\n */\r\n this.localSdpMunger = new LocalSdpMunger(this, this.rtc.getLocalEndpointId());\r\n\r\n /**\r\n * TracablePeerConnection uses RTC's eventEmitter\r\n * @type {EventEmitter}\r\n */\r\n this.eventEmitter = rtc.eventEmitter;\r\n this.rtxModifier = new RtxModifier();\r\n\r\n /**\r\n * The height constraint applied on the video sender. The default value is 2160 (4K) when layer suspension is\r\n * explicitly disabled.\r\n */\r\n this._senderVideoMaxHeight = 2160;\r\n\r\n /**\r\n * The height constraints to be applied on the sender per local video source (source name as the key).\r\n * @type {Map}\r\n */\r\n this._senderMaxHeights = new Map();\r\n\r\n // override as desired\r\n this.trace = (what, info) => {\r\n logger.debug(what, info);\r\n\r\n this.updateLog.push({\r\n time: new Date(),\r\n type: what,\r\n value: info || ''\r\n });\r\n };\r\n this.onicecandidate = null;\r\n this.peerconnection.onicecandidate = event => {\r\n this.trace(\r\n 'onicecandidate',\r\n JSON.stringify(event.candidate, null, ' '));\r\n\r\n if (this.onicecandidate !== null) {\r\n this.onicecandidate(event);\r\n }\r\n };\r\n\r\n // Use track events when browser is running in unified plan mode and stream events in plan-b mode.\r\n if (this._usesUnifiedPlan) {\r\n this.onTrack = evt => {\r\n const stream = evt.streams[0];\r\n\r\n this._remoteTrackAdded(stream, evt.track, evt.transceiver);\r\n stream.addEventListener('removetrack', e => {\r\n this._remoteTrackRemoved(stream, e.track);\r\n });\r\n };\r\n this.peerconnection.addEventListener('track', this.onTrack);\r\n } else {\r\n this.peerconnection.onaddstream = event => this._remoteStreamAdded(event.stream);\r\n this.peerconnection.onremovestream = event => this._remoteStreamRemoved(event.stream);\r\n }\r\n this.onsignalingstatechange = null;\r\n this.peerconnection.onsignalingstatechange = event => {\r\n this.trace('onsignalingstatechange', this.signalingState);\r\n if (this.onsignalingstatechange !== null) {\r\n this.onsignalingstatechange(event);\r\n }\r\n };\r\n this.oniceconnectionstatechange = null;\r\n this.peerconnection.oniceconnectionstatechange = event => {\r\n this.trace('oniceconnectionstatechange', this.iceConnectionState);\r\n if (this.oniceconnectionstatechange !== null) {\r\n this.oniceconnectionstatechange(event);\r\n }\r\n };\r\n this.onnegotiationneeded = null;\r\n this.peerconnection.onnegotiationneeded = event => {\r\n this.trace('onnegotiationneeded');\r\n if (this.onnegotiationneeded !== null) {\r\n this.onnegotiationneeded(event);\r\n }\r\n };\r\n this.onconnectionstatechange = null;\r\n this.peerconnection.onconnectionstatechange = event => {\r\n this.trace('onconnectionstatechange', this.connectionState);\r\n if (this.onconnectionstatechange !== null) {\r\n this.onconnectionstatechange(event);\r\n }\r\n };\r\n this.ondatachannel = null;\r\n this.peerconnection.ondatachannel = event => {\r\n this.trace('ondatachannel');\r\n if (this.ondatachannel !== null) {\r\n this.ondatachannel(event);\r\n }\r\n };\r\n\r\n if (this.maxstats) {\r\n this.statsinterval = window.setInterval(() => {\r\n this.getStats().then(stats => {\r\n if (typeof stats?.result === 'function') {\r\n const results = stats.result();\r\n\r\n for (let i = 0; i < results.length; ++i) {\r\n const res = results[i];\r\n\r\n res.names().forEach(name => {\r\n this._processStat(res, name, res.stat(name));\r\n });\r\n }\r\n } else {\r\n stats.forEach(r => this._processStat(r, '', r));\r\n }\r\n });\r\n }, 1000);\r\n }\r\n\r\n logger.info(`Create new ${this}`);\r\n}\r\n\r\n/* eslint-enable max-params */\r\n\r\n/**\r\n * Process stat and adds it to the array of stats we store.\r\n * @param report the current stats report.\r\n * @param name the name of the report, if available\r\n * @param statValue the value to add.\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._processStat\r\n = function(report, name, statValue) {\r\n const id = `${report.id}-${name}`;\r\n let s = this.stats[id];\r\n const now = new Date();\r\n\r\n if (!s) {\r\n this.stats[id] = s = {\r\n startTime: now,\r\n endTime: now,\r\n values: [],\r\n times: []\r\n };\r\n }\r\n s.values.push(statValue);\r\n s.times.push(now.getTime());\r\n if (s.values.length > this.maxstats) {\r\n s.values.shift();\r\n s.times.shift();\r\n }\r\n s.endTime = now;\r\n };\r\n\r\n/**\r\n * Returns a string representation of a SessionDescription object.\r\n */\r\nconst dumpSDP = function(description) {\r\n if (typeof description === 'undefined' || description === null) {\r\n return '';\r\n }\r\n\r\n return `type: ${description.type}\\r\\n${description.sdp}`;\r\n};\r\n\r\n\r\n/**\r\n * Forwards the {@link peerconnection.iceConnectionState} state except that it\r\n * will convert \"completed\" into \"connected\" where both mean that the ICE has\r\n * succeeded and is up and running. We never see \"completed\" state for\r\n * the JVB connection, but it started appearing for the P2P one. This method\r\n * allows to adapt old logic to this new situation.\r\n * @return {string}\r\n */\r\nTraceablePeerConnection.prototype.getConnectionState = function() {\r\n const state = this.peerconnection.iceConnectionState;\r\n\r\n if (state === 'completed') {\r\n return 'connected';\r\n }\r\n\r\n return state;\r\n};\r\n\r\n/**\r\n * Obtains the media direction for given {@link MediaType}. The method takes\r\n * into account whether or not there are any local tracks for media and\r\n * the {@link audioTransferActive} and {@link videoTransferActive} flags.\r\n * @param {MediaType} mediaType\r\n * @param {boolean} isAddOperation whether the direction is to be calculated after a source-add action.\r\n * @return {string} one of the SDP direction constants ('sendrecv, 'recvonly'\r\n * etc.) which should be used when setting local description on the peer\r\n * connection.\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype.getDesiredMediaDirection = function(mediaType, isAddOperation = false) {\r\n const hasLocalSource = this.hasAnyTracksOfType(mediaType);\r\n\r\n if (this._usesUnifiedPlan) {\r\n return isAddOperation\r\n ? hasLocalSource ? MediaDirection.SENDRECV : MediaDirection.SENDONLY\r\n : hasLocalSource ? MediaDirection.RECVONLY : MediaDirection.INACTIVE;\r\n }\r\n\r\n const mediaTransferActive = mediaType === MediaType.AUDIO ? this.audioTransferActive : this.videoTransferActive;\r\n\r\n if (mediaTransferActive) {\r\n return hasLocalSource ? MediaDirection.SENDRECV : MediaDirection.RECVONLY;\r\n }\r\n\r\n return MediaDirection.INACTIVE;\r\n};\r\n\r\n/**\r\n * Returns the list of RTCRtpReceivers created for the source of the given media type associated with\r\n * the set of remote endpoints specified.\r\n * @param {Array} endpoints list of the endpoints\r\n * @param {string} mediaType 'audio' or 'video'\r\n * @returns {Array} list of receivers created by the peerconnection.\r\n */\r\nTraceablePeerConnection.prototype._getReceiversByEndpointIds = function(endpoints, mediaType) {\r\n let remoteTracks = [];\r\n let receivers = [];\r\n\r\n for (const endpoint of endpoints) {\r\n remoteTracks = remoteTracks.concat(this.getRemoteTracks(endpoint, mediaType));\r\n }\r\n\r\n // Get the ids of the MediaStreamTracks associated with each of these remote tracks.\r\n const remoteTrackIds = remoteTracks.map(remote => remote.track?.id);\r\n\r\n receivers = this.peerconnection.getReceivers()\r\n .filter(receiver => receiver.track\r\n && receiver.track.kind === mediaType\r\n && remoteTrackIds.find(trackId => trackId === receiver.track.id));\r\n\r\n return receivers;\r\n};\r\n\r\n/**\r\n * Tells whether or not this TPC instance is using Simulcast.\r\n * @return {boolean} true if simulcast is enabled and active or\r\n * false if it's turned off.\r\n */\r\nTraceablePeerConnection.prototype.isSimulcastOn = function() {\r\n return !this.options.disableSimulcast;\r\n};\r\n\r\n/**\r\n * Handles {@link SignalingEvents.PEER_VIDEO_TYPE_CHANGED}\r\n * @param {string} endpointId the video owner's ID (MUC nickname)\r\n * @param {VideoType} videoType the new value\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._peerVideoTypeChanged = function(endpointId, videoType) {\r\n // Check if endpointId has a value to avoid action on random track\r\n if (!endpointId) {\r\n logger.error(`${this} No endpointID on peerVideoTypeChanged`);\r\n\r\n return;\r\n }\r\n const videoTrack = this.getRemoteTracks(endpointId, MediaType.VIDEO);\r\n\r\n if (videoTrack.length) {\r\n // NOTE 1 track per media type is assumed\r\n videoTrack[0]._setVideoType(videoType);\r\n }\r\n};\r\n\r\n/**\r\n * Handles remote track mute / unmute events.\r\n * @param {string} endpointId the track owner's identifier (MUC nickname)\r\n * @param {MediaType} mediaType \"audio\" or \"video\"\r\n * @param {boolean} isMuted the new mute state\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._peerMutedChanged = function(endpointId, mediaType, isMuted) {\r\n // Check if endpointId is a value to avoid doing action on all remote tracks\r\n if (!endpointId) {\r\n logger.error(`${this} On peerMuteChanged - no endpoint ID`);\r\n\r\n return;\r\n }\r\n const track = this.getRemoteTracks(endpointId, mediaType);\r\n\r\n if (track.length) {\r\n // NOTE 1 track per media type is assumed\r\n track[0].setMute(isMuted);\r\n }\r\n};\r\n\r\n/**\r\n * Handles remote source mute and unmute changed events.\r\n *\r\n * @param {string} sourceName - The name of the remote source.\r\n * @param {boolean} isMuted - The new mute state.\r\n */\r\nTraceablePeerConnection.prototype._sourceMutedChanged = function(sourceName, isMuted) {\r\n const track = this.getRemoteTracks().find(t => t.getSourceName() === sourceName);\r\n\r\n if (!track) {\r\n return;\r\n }\r\n\r\n track.setMute(isMuted);\r\n};\r\n\r\n/**\r\n * Handles remote source videoType changed events.\r\n *\r\n * @param {string} sourceName - The name of the remote source.\r\n * @param {boolean} isMuted - The new value.\r\n */\r\nTraceablePeerConnection.prototype._sourceVideoTypeChanged = function(sourceName, videoType) {\r\n const track = this.getRemoteTracks().find(t => t.getSourceName() === sourceName);\r\n\r\n if (!track) {\r\n return;\r\n }\r\n\r\n track._setVideoType(videoType);\r\n};\r\n\r\n/**\r\n * Obtains audio levels of the remote audio tracks by getting the source information on the RTCRtpReceivers.\r\n * The information relevant to the ssrc is updated each time a RTP packet constaining the ssrc is received.\r\n * @param {Array} speakerList list of endpoint ids for which audio levels are to be gathered.\r\n * @returns {Object} containing ssrc and audio level information as a key-value pair.\r\n */\r\nTraceablePeerConnection.prototype.getAudioLevels = function(speakerList = []) {\r\n const audioLevels = {};\r\n const audioReceivers = speakerList.length\r\n ? this._getReceiversByEndpointIds(speakerList, MediaType.AUDIO)\r\n : this.peerconnection.getReceivers()\r\n .filter(receiver => receiver.track && receiver.track.kind === MediaType.AUDIO && receiver.track.enabled);\r\n\r\n audioReceivers.forEach(remote => {\r\n const ssrc = remote.getSynchronizationSources();\r\n\r\n if (ssrc && ssrc.length) {\r\n // As per spec, this audiolevel is a value between 0..1 (linear), where 1.0\r\n // represents 0 dBov, 0 represents silence, and 0.5 represents approximately\r\n // 6 dBSPL change in the sound pressure level from 0 dBov.\r\n // https://www.w3.org/TR/webrtc/#dom-rtcrtpcontributingsource-audiolevel\r\n audioLevels[ssrc[0].source] = ssrc[0].audioLevel;\r\n }\r\n });\r\n\r\n return audioLevels;\r\n};\r\n\r\n/**\r\n * Obtains local tracks for given {@link MediaType}. If the mediaType\r\n * argument is omitted the list of all local tracks will be returned.\r\n * @param {MediaType} [mediaType]\r\n * @return {Array}\r\n */\r\nTraceablePeerConnection.prototype.getLocalTracks = function(mediaType) {\r\n let tracks = Array.from(this.localTracks.values());\r\n\r\n if (mediaType !== undefined) {\r\n tracks = tracks.filter(track => track.getType() === mediaType);\r\n }\r\n\r\n return tracks;\r\n};\r\n\r\n/**\r\n * Retrieves the local video tracks.\r\n *\r\n * @returns {JitsiLocalTrack|undefined} - local video tracks.\r\n */\r\nTraceablePeerConnection.prototype.getLocalVideoTracks = function() {\r\n return this.getLocalTracks(MediaType.VIDEO);\r\n};\r\n\r\n/**\r\n * Checks whether or not this {@link TraceablePeerConnection} instance contains any local tracks for given\r\n * mediaType.\r\n *\r\n * @param {MediaType} mediaType - The media type.\r\n * @return {boolean}\r\n */\r\nTraceablePeerConnection.prototype.hasAnyTracksOfType = function(mediaType) {\r\n if (!mediaType) {\r\n throw new Error('\"mediaType\" is required');\r\n }\r\n\r\n return this.getLocalTracks(mediaType).length > 0;\r\n};\r\n\r\n/**\r\n * Obtains all remote tracks currently known to this PeerConnection instance.\r\n *\r\n * @param {string} [endpointId] - The track owner's identifier (MUC nickname)\r\n * @param {MediaType} [mediaType] - The remote tracks will be filtered by their media type if this argument is\r\n * specified.\r\n * @return {Array}\r\n */\r\nTraceablePeerConnection.prototype.getRemoteTracks = function(endpointId, mediaType) {\r\n let remoteTracks = [];\r\n const endpoints = endpointId ? [ endpointId ] : this.remoteTracks.keys();\r\n\r\n for (const endpoint of endpoints) {\r\n const endpointTracksByMediaType = this.remoteTracks.get(endpoint);\r\n\r\n if (endpointTracksByMediaType) {\r\n for (const trackMediaType of endpointTracksByMediaType.keys()) {\r\n // per media type filtering\r\n if (!mediaType || mediaType === trackMediaType) {\r\n remoteTracks = remoteTracks.concat(Array.from(endpointTracksByMediaType.get(trackMediaType)));\r\n }\r\n }\r\n }\r\n }\r\n\r\n return remoteTracks;\r\n};\r\n\r\n/**\r\n * Parses the remote description and returns the sdp lines of the sources associated with a remote participant.\r\n *\r\n * @param {string} id Endpoint id of the remote participant.\r\n * @returns {Array} The sdp lines that have the ssrc information.\r\n */\r\nTraceablePeerConnection.prototype.getRemoteSourceInfoByParticipant = function(id) {\r\n const removeSsrcInfo = [];\r\n const remoteTracks = this.getRemoteTracks(id);\r\n\r\n if (!remoteTracks?.length) {\r\n return removeSsrcInfo;\r\n }\r\n const primarySsrcs = remoteTracks.map(track => track.getSSRC());\r\n const sdp = new SDP(this.remoteDescription.sdp);\r\n\r\n primarySsrcs.forEach((ssrc, idx) => {\r\n for (const media of sdp.media) {\r\n let lines = '';\r\n let ssrcLines = SDPUtil.findLines(media, `a=ssrc:${ssrc}`);\r\n\r\n if (ssrcLines.length) {\r\n if (!removeSsrcInfo[idx]) {\r\n removeSsrcInfo[idx] = '';\r\n }\r\n\r\n // Check if there are any FID groups present for the primary ssrc.\r\n const fidLines = SDPUtil.findLines(media, `a=ssrc-group:FID ${ssrc}`);\r\n\r\n if (fidLines.length) {\r\n const secondarySsrc = fidLines[0].split(' ')[2];\r\n\r\n lines += `${fidLines[0]}\\r\\n`;\r\n ssrcLines = ssrcLines.concat(SDPUtil.findLines(media, `a=ssrc:${secondarySsrc}`));\r\n }\r\n removeSsrcInfo[idx] += `${ssrcLines.join('\\r\\n')}\\r\\n`;\r\n removeSsrcInfo[idx] += lines;\r\n }\r\n }\r\n });\r\n\r\n return removeSsrcInfo;\r\n};\r\n\r\n/**\r\n * Returns the target bitrates configured for the local video source.\r\n *\r\n * @returns {Object}\r\n */\r\nTraceablePeerConnection.prototype.getTargetVideoBitrates = function() {\r\n const currentCodec = this.getConfiguredVideoCodec();\r\n\r\n return this.tpcUtils.videoBitrates[currentCodec.toUpperCase()] || this.tpcUtils.videoBitrates;\r\n};\r\n\r\n/**\r\n * Tries to find {@link JitsiTrack} for given SSRC number. It will search both\r\n * local and remote tracks bound to this instance.\r\n * @param {number} ssrc\r\n * @return {JitsiTrack|null}\r\n */\r\nTraceablePeerConnection.prototype.getTrackBySSRC = function(ssrc) {\r\n if (typeof ssrc !== 'number') {\r\n throw new Error(`SSRC ${ssrc} is not a number`);\r\n }\r\n for (const localTrack of this.localTracks.values()) {\r\n if (this.getLocalSSRC(localTrack) === ssrc) {\r\n return localTrack;\r\n }\r\n }\r\n for (const remoteTrack of this.getRemoteTracks()) {\r\n if (remoteTrack.getSSRC() === ssrc) {\r\n return remoteTrack;\r\n }\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Tries to find SSRC number for given {@link JitsiTrack} id. It will search\r\n * both local and remote tracks bound to this instance.\r\n * @param {string} id\r\n * @return {number|null}\r\n */\r\nTraceablePeerConnection.prototype.getSsrcByTrackId = function(id) {\r\n\r\n const findTrackById = track => track.getTrack().id === id;\r\n const localTrack = this.getLocalTracks().find(findTrackById);\r\n\r\n if (localTrack) {\r\n return this.getLocalSSRC(localTrack);\r\n }\r\n\r\n const remoteTrack = this.getRemoteTracks().find(findTrackById);\r\n\r\n if (remoteTrack) {\r\n return remoteTrack.getSSRC();\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Called when new remote MediaStream is added to the PeerConnection.\r\n * @param {MediaStream} stream the WebRTC MediaStream for remote participant\r\n */\r\nTraceablePeerConnection.prototype._remoteStreamAdded = function(stream) {\r\n const streamId = RTC.getStreamID(stream);\r\n\r\n if (!RTC.isUserStreamById(streamId)) {\r\n logger.info(`${this} ignored remote 'stream added' event for non-user stream[id=${streamId}]`);\r\n\r\n return;\r\n }\r\n\r\n // Bind 'addtrack'/'removetrack' event handlers\r\n if (browser.isChromiumBased()) {\r\n stream.onaddtrack = event => {\r\n this._remoteTrackAdded(stream, event.track);\r\n };\r\n stream.onremovetrack = event => {\r\n this._remoteTrackRemoved(stream, event.track);\r\n };\r\n }\r\n\r\n // Call remoteTrackAdded for each track in the stream\r\n const streamAudioTracks = stream.getAudioTracks();\r\n\r\n for (const audioTrack of streamAudioTracks) {\r\n this._remoteTrackAdded(stream, audioTrack);\r\n }\r\n const streamVideoTracks = stream.getVideoTracks();\r\n\r\n for (const videoTrack of streamVideoTracks) {\r\n this._remoteTrackAdded(stream, videoTrack);\r\n }\r\n};\r\n\r\n\r\n/**\r\n * Called on \"track added\" and \"stream added\" PeerConnection events (because we\r\n * handle streams on per track basis). Finds the owner and the SSRC for\r\n * the track and passes that to ChatRoom for further processing.\r\n * @param {MediaStream} stream the WebRTC MediaStream instance which is\r\n * the parent of the track\r\n * @param {MediaStreamTrack} track the WebRTC MediaStreamTrack added for remote\r\n * participant.\r\n * @param {RTCRtpTransceiver} transceiver the WebRTC transceiver that is created\r\n * for the remote participant in unified plan.\r\n */\r\nTraceablePeerConnection.prototype._remoteTrackAdded = function(stream, track, transceiver = null) {\r\n const streamId = RTC.getStreamID(stream);\r\n const mediaType = track.kind;\r\n\r\n if (!this.isP2P && !RTC.isUserStreamById(streamId)) {\r\n logger.info(`${this} ignored remote 'stream added' event for non-user stream[id=${streamId}]`);\r\n\r\n return;\r\n }\r\n logger.info(`${this} adding remote track for stream[id=${streamId},type=${mediaType}]`);\r\n\r\n // look up an associated JID for a stream id\r\n if (!mediaType) {\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(\r\n `MediaType undefined for remote track, stream id: ${streamId}`\r\n ));\r\n\r\n // Abort\r\n return;\r\n }\r\n\r\n const remoteSDP = this._usesUnifiedPlan\r\n ? new SDP(this.peerconnection.remoteDescription.sdp)\r\n : new SDP(this.remoteDescription.sdp);\r\n let mediaLines;\r\n\r\n // In unified plan mode, find the matching mline using 'mid' if its availble, otherwise use the\r\n // 'msid' attribute of the stream.\r\n if (this._usesUnifiedPlan) {\r\n if (transceiver && transceiver.mid) {\r\n const mid = transceiver.mid;\r\n\r\n mediaLines = remoteSDP.media.filter(mls => SDPUtil.findLine(mls, `a=mid:${mid}`));\r\n } else {\r\n mediaLines = remoteSDP.media.filter(mls => {\r\n const msid = SDPUtil.findLine(mls, 'a=msid:');\r\n\r\n return typeof msid !== 'undefined' && streamId === msid.substring(7).split(' ')[0];\r\n });\r\n }\r\n } else {\r\n mediaLines = remoteSDP.media.filter(mls => mls.startsWith(`m=${mediaType}`));\r\n }\r\n\r\n if (!mediaLines.length) {\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(`No media lines found in remote SDP for remote stream[id=${streamId},type=${mediaType}]`));\r\n\r\n // Abort\r\n return;\r\n }\r\n\r\n let ssrcLines = SDPUtil.findLines(mediaLines[0], 'a=ssrc:');\r\n\r\n ssrcLines\r\n = ssrcLines.filter(line => line.indexOf(`msid:${streamId}`) !== -1);\r\n if (!ssrcLines.length) {\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(`No SSRC lines found in remote SDP for remote stream[msid=${streamId},type=${mediaType}]`));\r\n\r\n // Abort\r\n return;\r\n }\r\n\r\n // FIXME the length of ssrcLines[0] not verified, but it will fail\r\n // with global error handler anyway\r\n const ssrcStr = ssrcLines[0].substring(7).split(' ')[0];\r\n const trackSsrc = Number(ssrcStr);\r\n const ownerEndpointId = this.signalingLayer.getSSRCOwner(trackSsrc);\r\n\r\n if (isNaN(trackSsrc) || trackSsrc < 0) {\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(\r\n `Invalid SSRC for remote stream[ssrc=${trackSsrc},id=${streamId},type=${mediaType}]`));\r\n\r\n // Abort\r\n return;\r\n } else if (!ownerEndpointId) {\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(\r\n `No SSRC owner known for remote stream[ssrc=${trackSsrc},id=${streamId},type=${mediaType}]`));\r\n\r\n // Abort\r\n return;\r\n }\r\n\r\n\r\n let sourceName;\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n sourceName = this.signalingLayer.getTrackSourceName(trackSsrc);\r\n\r\n // If source name was not signaled, we'll generate one which allows testing signaling\r\n // when mixing legacy(mobile) with new clients.\r\n if (!sourceName) {\r\n sourceName = getSourceNameForJitsiTrack(ownerEndpointId, mediaType, 0);\r\n }\r\n }\r\n\r\n // eslint-disable-next-line no-undef\r\n logger.info(`${this} creating remote track[endpoint=${ownerEndpointId},ssrc=${trackSsrc},`\r\n + `type=${mediaType},sourceName=${sourceName}]`);\r\n\r\n const peerMediaInfo = this.signalingLayer.getPeerMediaInfo(ownerEndpointId, mediaType, sourceName);\r\n\r\n if (!peerMediaInfo) {\r\n GlobalOnErrorHandler.callErrorHandler(\r\n new Error(`${this}: no peer media info available for ${ownerEndpointId}`));\r\n\r\n return;\r\n }\r\n\r\n const muted = peerMediaInfo.muted;\r\n const videoType = peerMediaInfo.videoType; // can be undefined\r\n\r\n // eslint-disable-next-line no-undef\r\n this._createRemoteTrack(\r\n ownerEndpointId, stream, track, mediaType, videoType, trackSsrc, muted, sourceName);\r\n};\r\n\r\n// FIXME cleanup params\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * Initializes a new JitsiRemoteTrack instance with the data provided by\r\n * the signaling layer and SDP.\r\n *\r\n * @param {string} ownerEndpointId the owner's endpoint ID (MUC nickname)\r\n * @param {MediaStream} stream the WebRTC stream instance\r\n * @param {MediaStreamTrack} track the WebRTC track instance\r\n * @param {MediaType} mediaType the track's type of the media\r\n * @param {VideoType} [videoType] the track's type of the video (if applicable)\r\n * @param {number} ssrc the track's main SSRC number\r\n * @param {boolean} muted the initial muted status\r\n * @param {String} sourceName the track's source name\r\n */\r\nTraceablePeerConnection.prototype._createRemoteTrack = function(\r\n ownerEndpointId,\r\n stream,\r\n track,\r\n mediaType,\r\n videoType,\r\n ssrc,\r\n muted,\r\n sourceName) {\r\n let remoteTracksMap = this.remoteTracks.get(ownerEndpointId);\r\n\r\n if (!remoteTracksMap) {\r\n remoteTracksMap = new Map();\r\n remoteTracksMap.set(MediaType.AUDIO, new Set());\r\n remoteTracksMap.set(MediaType.VIDEO, new Set());\r\n this.remoteTracks.set(ownerEndpointId, remoteTracksMap);\r\n }\r\n\r\n const userTracksByMediaType = remoteTracksMap.get(mediaType);\r\n\r\n if (userTracksByMediaType?.size\r\n && Array.from(userTracksByMediaType).find(jitsiTrack => jitsiTrack.getTrack() === track)) {\r\n // Ignore duplicated event which can originate either from 'onStreamAdded' or 'onTrackAdded'.\r\n logger.info(`${this} ignored duplicated track event for track[endpoint=${ownerEndpointId},type=${mediaType}]`);\r\n\r\n return;\r\n } else if (userTracksByMediaType?.size && !FeatureFlags.isSourceNameSignalingEnabled()) {\r\n logger.error(`${this} received a second remote track for track[endpoint=${ownerEndpointId},type=${mediaType}]`\r\n + 'deleting the existing track');\r\n const existingTrack = Array.from(userTracksByMediaType)[0];\r\n\r\n // The exisiting track needs to be removed here. This happens on Safari sometimes when a SSRC is removed from\r\n // the remote description and the browser doesn't fire a 'removetrack' event on the associated MediaStream.\r\n this._remoteTrackRemoved(existingTrack.getOriginalStream(), existingTrack.getTrack());\r\n }\r\n\r\n const remoteTrack\r\n = new JitsiRemoteTrack(\r\n this.rtc,\r\n this.rtc.conference,\r\n ownerEndpointId,\r\n stream,\r\n track,\r\n mediaType,\r\n videoType,\r\n ssrc,\r\n muted,\r\n this.isP2P,\r\n sourceName);\r\n\r\n userTracksByMediaType.add(remoteTrack);\r\n this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_ADDED, remoteTrack, this);\r\n};\r\n\r\n/* eslint-enable max-params */\r\n\r\n/**\r\n * Handles remote stream removal.\r\n * @param stream the WebRTC MediaStream object which is being removed from the\r\n * PeerConnection\r\n */\r\nTraceablePeerConnection.prototype._remoteStreamRemoved = function(stream) {\r\n if (!RTC.isUserStream(stream)) {\r\n const id = RTC.getStreamID(stream);\r\n\r\n logger.info(`Ignored remote 'stream removed' event for stream[id=${id}]`);\r\n\r\n return;\r\n }\r\n\r\n // Call remoteTrackRemoved for each track in the stream\r\n const streamVideoTracks = stream.getVideoTracks();\r\n\r\n for (const videoTrack of streamVideoTracks) {\r\n this._remoteTrackRemoved(stream, videoTrack);\r\n }\r\n const streamAudioTracks = stream.getAudioTracks();\r\n\r\n for (const audioTrack of streamAudioTracks) {\r\n this._remoteTrackRemoved(stream, audioTrack);\r\n }\r\n};\r\n\r\n/**\r\n * Handles remote media track removal.\r\n *\r\n * @param {MediaStream} stream - WebRTC MediaStream instance which is the parent of the track.\r\n * @param {MediaStreamTrack} track - WebRTC MediaStreamTrack which has been removed from the PeerConnection.\r\n * @returns {void}\r\n */\r\nTraceablePeerConnection.prototype._remoteTrackRemoved = function(stream, track) {\r\n const streamId = RTC.getStreamID(stream);\r\n const trackId = track && RTC.getTrackID(track);\r\n\r\n if (!RTC.isUserStreamById(streamId)) {\r\n logger.info(`${this} ignored remote 'stream removed' event for non-user stream[id=${streamId}]`);\r\n\r\n return;\r\n }\r\n\r\n if (!streamId) {\r\n GlobalOnErrorHandler.callErrorHandler(new Error(`${this} remote track removal failed - no stream ID`));\r\n\r\n return;\r\n }\r\n\r\n if (!trackId) {\r\n GlobalOnErrorHandler.callErrorHandler(new Error(`${this} remote track removal failed - no track ID`));\r\n\r\n return;\r\n }\r\n\r\n const toBeRemoved = this.getRemoteTracks().find(\r\n remoteTrack => remoteTrack.getStreamId() === streamId\r\n && remoteTrack.getTrackId() === trackId);\r\n\r\n if (!toBeRemoved) {\r\n GlobalOnErrorHandler.callErrorHandler(new Error(`${this} remote track removal failed - track not found`));\r\n\r\n return;\r\n }\r\n\r\n logger.info(`${this} remote track removed stream[id=${streamId},trackId=${trackId}]`);\r\n this._removeRemoteTrack(toBeRemoved);\r\n};\r\n\r\n/**\r\n * Removes all JitsiRemoteTracks associated with given MUC nickname (resource part of the JID).\r\n *\r\n * @param {string} owner - The resource part of the MUC JID.\r\n * @returns {JitsiRemoteTrack[]} - The array of removed tracks.\r\n */\r\nTraceablePeerConnection.prototype.removeRemoteTracks = function(owner) {\r\n let removedTracks = [];\r\n const remoteTracksByMedia = this.remoteTracks.get(owner);\r\n\r\n if (remoteTracksByMedia) {\r\n removedTracks = removedTracks.concat(Array.from(remoteTracksByMedia.get(MediaType.AUDIO)));\r\n removedTracks = removedTracks.concat(Array.from(remoteTracksByMedia.get(MediaType.VIDEO)));\r\n this.remoteTracks.delete(owner);\r\n }\r\n logger.debug(`${this} removed remote tracks[endpoint=${owner},count=${removedTracks.length}`);\r\n\r\n return removedTracks;\r\n};\r\n\r\n/**\r\n * Removes and disposes given JitsiRemoteTrack instance. Emits {@link RTCEvents.REMOTE_TRACK_REMOVED}.\r\n *\r\n * @param {JitsiRemoteTrack} toBeRemoved - The remote track to be removed.\r\n * @returns {void}\r\n */\r\nTraceablePeerConnection.prototype._removeRemoteTrack = function(toBeRemoved) {\r\n toBeRemoved.dispose();\r\n const participantId = toBeRemoved.getParticipantId();\r\n const userTracksByMediaType = this.remoteTracks.get(participantId);\r\n\r\n if (!userTracksByMediaType) {\r\n logger.error(`${this} removeRemoteTrack: no remote tracks map for endpoint=${participantId}`);\r\n } else if (!userTracksByMediaType.get(toBeRemoved.getType())?.delete(toBeRemoved)) {\r\n logger.error(`${this} Failed to remove ${toBeRemoved} - type mapping messed up ?`);\r\n }\r\n this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_REMOVED, toBeRemoved);\r\n};\r\n\r\n/**\r\n * Returns a map with keys msid/mediaType and TrackSSRCInfo values.\r\n * @param {RTCSessionDescription} desc the local description.\r\n * @return {Map}\r\n */\r\nTraceablePeerConnection.prototype._extractSSRCMap = function(desc) {\r\n /**\r\n * Track SSRC infos mapped by stream ID (msid) or mediaType (unified-plan)\r\n * @type {Map}\r\n */\r\n const ssrcMap = new Map();\r\n\r\n /**\r\n * Groups mapped by primary SSRC number\r\n * @type {Map>}\r\n */\r\n const groupsMap = new Map();\r\n\r\n if (typeof desc !== 'object' || desc === null\r\n || typeof desc.sdp !== 'string') {\r\n logger.warn('An empty description was passed as an argument');\r\n\r\n return ssrcMap;\r\n }\r\n\r\n const session = transform.parse(desc.sdp);\r\n\r\n if (!Array.isArray(session.media)) {\r\n return ssrcMap;\r\n }\r\n\r\n let media = session.media;\r\n\r\n // For unified plan clients, only the first audio and video mlines will have ssrcs for the local sources.\r\n // The rest of the m-lines are for the recv-only sources, one for each remote source.\r\n if (this._usesUnifiedPlan) {\r\n if (FeatureFlags.isMultiStreamSupportEnabled()) {\r\n media = media.filter(mline => mline.direction === MediaDirection.SENDONLY\r\n || mline.direction === MediaDirection.SENDRECV);\r\n } else {\r\n media = [];\r\n [ MediaType.AUDIO, MediaType.VIDEO ].forEach(mediaType => {\r\n const mLine = session.media.find(m => m.type === mediaType);\r\n\r\n mLine && media.push(mLine);\r\n });\r\n }\r\n }\r\n\r\n let index = 0;\r\n\r\n for (const mLine of media) {\r\n if (!Array.isArray(mLine.ssrcs)) {\r\n continue; // eslint-disable-line no-continue\r\n }\r\n\r\n if (Array.isArray(mLine.ssrcGroups)) {\r\n for (const group of mLine.ssrcGroups) {\r\n if (typeof group.semantics !== 'undefined' && typeof group.ssrcs !== 'undefined') {\r\n // Parse SSRCs and store as numbers\r\n const groupSSRCs = group.ssrcs.split(' ').map(ssrcStr => parseInt(ssrcStr, 10));\r\n const primarySSRC = groupSSRCs[0];\r\n\r\n // Note that group.semantics is already present\r\n group.ssrcs = groupSSRCs;\r\n\r\n // eslint-disable-next-line max-depth\r\n if (!groupsMap.has(primarySSRC)) {\r\n groupsMap.set(primarySSRC, []);\r\n }\r\n groupsMap.get(primarySSRC).push(group);\r\n }\r\n }\r\n }\r\n\r\n let ssrcs = mLine.ssrcs;\r\n\r\n // Filter the ssrcs with 'msid' attribute for plan-b clients and 'cname' for unified-plan clients.\r\n ssrcs = this._usesUnifiedPlan\r\n ? ssrcs.filter(s => s.attribute === 'cname')\r\n : ssrcs.filter(s => s.attribute === 'msid');\r\n\r\n for (const ssrc of ssrcs) {\r\n // Use the mediaType as key for the source map for unified plan clients since msids are not part of\r\n // the standard and the unified plan SDPs do not have a proper msid attribute for the sources.\r\n // Also the ssrcs for sources do not change for Unified plan clients since RTCRtpSender#replaceTrack is\r\n // used for switching the tracks so it is safe to use the mediaType as the key for the TrackSSRCInfo map.\r\n const key = this._usesUnifiedPlan\r\n ? FeatureFlags.isMultiStreamSupportEnabled() ? `${mLine.type}-${index}` : mLine.type\r\n : ssrc.value;\r\n const ssrcNumber = ssrc.id;\r\n let ssrcInfo = ssrcMap.get(key);\r\n\r\n if (!ssrcInfo) {\r\n ssrcInfo = {\r\n ssrcs: [],\r\n groups: [],\r\n msid: key\r\n };\r\n ssrcMap.set(key, ssrcInfo);\r\n }\r\n ssrcInfo.ssrcs.push(ssrcNumber);\r\n\r\n if (groupsMap.has(ssrcNumber)) {\r\n const ssrcGroups = groupsMap.get(ssrcNumber);\r\n\r\n for (const group of ssrcGroups) {\r\n ssrcInfo.groups.push(group);\r\n }\r\n }\r\n }\r\n\r\n // Currently multi-stream is supported for video only.\r\n mLine.type === MediaType.VIDEO && index++;\r\n }\r\n\r\n return ssrcMap;\r\n};\r\n\r\n/**\r\n * Takes a SessionDescription object and returns a \"normalized\" version.\r\n * Currently it takes care of ordering the a=ssrc lines and denoting receive\r\n * only SSRCs.\r\n */\r\nconst normalizePlanB = function(desc) {\r\n if (typeof desc !== 'object' || desc === null\r\n || typeof desc.sdp !== 'string') {\r\n logger.warn('An empty description was passed as an argument');\r\n\r\n return desc;\r\n }\r\n\r\n // eslint-disable-next-line no-shadow\r\n const transform = require('sdp-transform');\r\n const session = transform.parse(desc.sdp);\r\n\r\n if (typeof session !== 'undefined'\r\n && typeof session.media !== 'undefined'\r\n && Array.isArray(session.media)) {\r\n session.media.forEach(mLine => {\r\n\r\n // Chrome appears to be picky about the order in which a=ssrc lines\r\n // are listed in an m-line when rtx is enabled (and thus there are\r\n // a=ssrc-group lines with FID semantics). Specifically if we have\r\n // \"a=ssrc-group:FID S1 S2\" and the \"a=ssrc:S2\" lines appear before\r\n // the \"a=ssrc:S1\" lines, SRD fails.\r\n // So, put SSRC which appear as the first SSRC in an FID ssrc-group\r\n // first.\r\n const firstSsrcs = [];\r\n const newSsrcLines = [];\r\n\r\n if (typeof mLine.ssrcGroups !== 'undefined'\r\n && Array.isArray(mLine.ssrcGroups)) {\r\n mLine.ssrcGroups.forEach(group => {\r\n if (typeof group.semantics !== 'undefined'\r\n && group.semantics === 'FID') {\r\n if (typeof group.ssrcs !== 'undefined') {\r\n firstSsrcs.push(Number(group.ssrcs.split(' ')[0]));\r\n }\r\n }\r\n });\r\n }\r\n\r\n if (Array.isArray(mLine.ssrcs)) {\r\n let i;\r\n\r\n for (i = 0; i < mLine.ssrcs.length; i++) {\r\n if (typeof mLine.ssrcs[i] === 'object'\r\n && typeof mLine.ssrcs[i].id !== 'undefined'\r\n && firstSsrcs.indexOf(mLine.ssrcs[i].id) >= 0) {\r\n newSsrcLines.push(mLine.ssrcs[i]);\r\n delete mLine.ssrcs[i];\r\n }\r\n }\r\n\r\n for (i = 0; i < mLine.ssrcs.length; i++) {\r\n if (typeof mLine.ssrcs[i] !== 'undefined') {\r\n newSsrcLines.push(mLine.ssrcs[i]);\r\n }\r\n }\r\n\r\n mLine.ssrcs = replaceDefaultUnifiedPlanMsid(newSsrcLines);\r\n }\r\n });\r\n }\r\n\r\n const resStr = transform.write(session);\r\n\r\n\r\n return new RTCSessionDescription({\r\n type: desc.type,\r\n sdp: resStr\r\n });\r\n};\r\n\r\n/**\r\n * Unified plan differentiates a remote track not associated with a stream using\r\n * the msid \"-\", which can incorrectly trigger an onaddstream event in plan-b.\r\n * For jitsi, these tracks are actually receive-only ssrcs. To prevent\r\n * onaddstream from firing, remove the ssrcs with msid \"-\" except the cname\r\n * line. Normally the ssrcs are not used by the client, as the bridge controls\r\n * media flow, but keep one reference to the ssrc for the p2p case.\r\n *\r\n * @param {Array} ssrcLines - The ssrc lines from a remote description.\r\n * @private\r\n * @returns {Array} ssrcLines with removed lines referencing msid \"-\".\r\n */\r\nfunction replaceDefaultUnifiedPlanMsid(ssrcLines = []) {\r\n if (!browser.isChrome() || !browser.isVersionGreaterThan(70)) {\r\n return ssrcLines;\r\n }\r\n\r\n let filteredLines = [ ...ssrcLines ];\r\n\r\n const problematicSsrcIds = ssrcLines.filter(ssrcLine =>\r\n ssrcLine.attribute === 'mslabel' && ssrcLine.value === '-')\r\n .map(ssrcLine => ssrcLine.id);\r\n\r\n problematicSsrcIds.forEach(ssrcId => {\r\n // Find the cname which is to be modified and left in.\r\n const cnameLine = filteredLines.find(line =>\r\n line.id === ssrcId && line.attribute === 'cname');\r\n\r\n cnameLine.value = `${MediaDirection.RECVONLY}-${ssrcId}`;\r\n\r\n // Remove all of lines for the ssrc.\r\n filteredLines\r\n = filteredLines.filter(line => line.id !== ssrcId);\r\n\r\n // But re-add the cname line so there is a reference kept to the ssrc\r\n // in the SDP.\r\n filteredLines.push(cnameLine);\r\n });\r\n\r\n return filteredLines;\r\n}\r\n\r\n/**\r\n * Makes sure that both audio and video directions are configured as 'sendrecv'.\r\n * @param {Object} localDescription the SDP object as defined by WebRTC.\r\n * @param {object} options TracablePeerConnection config options.\r\n */\r\nconst enforceSendRecv = function(localDescription, options) {\r\n if (!localDescription) {\r\n throw new Error('No local description passed in.');\r\n }\r\n\r\n const transformer = new SdpTransformWrap(localDescription.sdp);\r\n const audioMedia = transformer.selectMedia(MediaType.AUDIO)?.[0];\r\n let changed = false;\r\n\r\n if (audioMedia && audioMedia.direction !== MediaDirection.SENDRECV) {\r\n if (options.startSilent) {\r\n audioMedia.direction = MediaDirection.INACTIVE;\r\n } else {\r\n audioMedia.direction = MediaDirection.SENDRECV;\r\n }\r\n\r\n changed = true;\r\n }\r\n\r\n const videoMedia = transformer.selectMedia(MediaType.VIDEO)?.[0];\r\n\r\n if (videoMedia && videoMedia.direction !== MediaDirection.SENDRECV) {\r\n videoMedia.direction = MediaDirection.SENDRECV;\r\n changed = true;\r\n }\r\n\r\n if (changed) {\r\n return new RTCSessionDescription({\r\n type: localDescription.type,\r\n sdp: transformer.toRawSDP()\r\n });\r\n }\r\n\r\n return localDescription;\r\n};\r\n\r\n/**\r\n *\r\n * @param {JitsiLocalTrack} localTrack\r\n */\r\nTraceablePeerConnection.prototype.getLocalSSRC = function(localTrack) {\r\n const ssrcInfo = this._getSSRC(localTrack.rtcId);\r\n\r\n return ssrcInfo && ssrcInfo.ssrcs[0];\r\n};\r\n\r\n/**\r\n * When doing unified plan simulcast, we'll have a set of ssrcs but no ssrc-groups on Firefox. Unfortunately, Jicofo\r\n * will complain if it sees ssrcs with matching msids but no ssrc-group, so a ssrc-group line is injected to make\r\n * Jicofo happy.\r\n *\r\n * @param desc A session description object (with 'type' and 'sdp' fields)\r\n * @return A session description object with its sdp field modified to contain an inject ssrc-group for simulcast.\r\n */\r\nTraceablePeerConnection.prototype._injectSsrcGroupForUnifiedSimulcast = function(desc) {\r\n const sdp = transform.parse(desc.sdp);\r\n const video = sdp.media.find(mline => mline.type === 'video');\r\n\r\n // Check if the browser supports RTX, add only the primary ssrcs to the SIM group if that is the case.\r\n video.ssrcGroups = video.ssrcGroups || [];\r\n const fidGroups = video.ssrcGroups.filter(group => group.semantics === 'FID');\r\n\r\n if (video.simulcast || video.simulcast_03) {\r\n const ssrcs = [];\r\n\r\n if (fidGroups && fidGroups.length) {\r\n fidGroups.forEach(group => {\r\n ssrcs.push(group.ssrcs.split(' ')[0]);\r\n });\r\n } else {\r\n video.ssrcs.forEach(ssrc => {\r\n if (ssrc.attribute === 'msid') {\r\n ssrcs.push(ssrc.id);\r\n }\r\n });\r\n }\r\n if (video.ssrcGroups.find(group => group.semantics === 'SIM')) {\r\n // Group already exists, no need to do anything\r\n return desc;\r\n }\r\n\r\n // Add a SIM group for every 3 FID groups.\r\n for (let i = 0; i < ssrcs.length; i += 3) {\r\n const simSsrcs = ssrcs.slice(i, i + 3);\r\n\r\n video.ssrcGroups.push({\r\n semantics: 'SIM',\r\n ssrcs: simSsrcs.join(' ')\r\n });\r\n }\r\n }\r\n\r\n return new RTCSessionDescription({\r\n type: desc.type,\r\n sdp: transform.write(sdp)\r\n });\r\n};\r\n\r\n/* eslint-disable-next-line vars-on-top */\r\nconst getters = {\r\n signalingState() {\r\n return this.peerconnection.signalingState;\r\n },\r\n iceConnectionState() {\r\n return this.peerconnection.iceConnectionState;\r\n },\r\n connectionState() {\r\n return this.peerconnection.connectionState;\r\n },\r\n localDescription() {\r\n let desc = this.peerconnection.localDescription;\r\n\r\n if (!desc) {\r\n logger.debug(`${this} getLocalDescription no localDescription found`);\r\n\r\n return {};\r\n }\r\n\r\n this.trace('getLocalDescription::preTransform', dumpSDP(desc));\r\n\r\n // If the browser is running in unified plan mode and this is a jvb connection,\r\n // transform the SDP to Plan B first.\r\n if (this._usesUnifiedPlan && !this.isP2P) {\r\n desc = this.interop.toPlanB(desc);\r\n this.trace('getLocalDescription::postTransform (Plan B)',\r\n dumpSDP(desc));\r\n\r\n desc = this._injectSsrcGroupForUnifiedSimulcast(desc);\r\n this.trace('getLocalDescription::postTransform (inject ssrc group)',\r\n dumpSDP(desc));\r\n } else if (!this._usesUnifiedPlan) {\r\n if (browser.doesVideoMuteByStreamRemove()) {\r\n desc = this.localSdpMunger.maybeAddMutedLocalVideoTracksToSDP(desc);\r\n logger.debug(\r\n 'getLocalDescription::postTransform (munge local SDP)', desc);\r\n }\r\n\r\n // What comes out of this getter will be signalled over Jingle to\r\n // the other peer, so we need to make sure the media direction is\r\n // 'sendrecv' because we won't change the direction later and don't want\r\n // the other peer to think we can't send or receive.\r\n //\r\n // Note that the description we set in chrome does have the accurate\r\n // direction (e.g. 'recvonly'), since that is technically what is\r\n // happening (check setLocalDescription impl).\r\n desc = enforceSendRecv(desc, this.options);\r\n }\r\n\r\n // See the method's doc for more info about this transformation.\r\n desc = this.localSdpMunger.transformStreamIdentifiers(desc);\r\n\r\n return desc;\r\n },\r\n remoteDescription() {\r\n let desc = this.peerconnection.remoteDescription;\r\n\r\n if (!desc) {\r\n logger.debug(`${this} getRemoteDescription no remoteDescription found`);\r\n\r\n return {};\r\n }\r\n this.trace('getRemoteDescription::preTransform', dumpSDP(desc));\r\n\r\n if (this._usesUnifiedPlan) {\r\n if (this.isP2P) {\r\n // Adjust the media direction for p2p based on whether a local source has been added.\r\n desc = this._adjustRemoteMediaDirection(desc);\r\n } else {\r\n // If this is a jvb connection, transform the SDP to Plan B first.\r\n desc = this.interop.toPlanB(desc);\r\n this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));\r\n }\r\n }\r\n\r\n return desc;\r\n }\r\n};\r\n\r\nObject.keys(getters).forEach(prop => {\r\n Object.defineProperty(\r\n TraceablePeerConnection.prototype,\r\n prop, {\r\n get: getters[prop]\r\n }\r\n );\r\n});\r\n\r\nTraceablePeerConnection.prototype._getSSRC = function(rtcId) {\r\n return this.localSSRCs.get(rtcId);\r\n};\r\n\r\n/**\r\n * Checks if low fps screensharing is in progress.\r\n *\r\n * @private\r\n * @returns {boolean} Returns true if 5 fps screensharing is in progress, false otherwise.\r\n */\r\nTraceablePeerConnection.prototype.isSharingLowFpsScreen = function() {\r\n return this._isSharingScreen() && this._capScreenshareBitrate;\r\n};\r\n\r\n/**\r\n * Checks if screensharing is in progress.\r\n *\r\n * @returns {boolean} Returns true if a desktop track has been added to the peerconnection, false otherwise.\r\n */\r\nTraceablePeerConnection.prototype._isSharingScreen = function() {\r\n const tracks = this.getLocalVideoTracks();\r\n\r\n return Boolean(tracks.find(track => track.videoType === VideoType.DESKTOP));\r\n};\r\n\r\n/**\r\n * Munges the order of the codecs in the SDP passed based on the preference\r\n * set through config.js settings. All instances of the specified codec are\r\n * moved up to the top of the list when it is preferred. The specified codec\r\n * is deleted from the list if the configuration specifies that the codec be\r\n * disabled.\r\n * @param {RTCSessionDescription} description that needs to be munged.\r\n * @returns {RTCSessionDescription} the munged description.\r\n */\r\nTraceablePeerConnection.prototype._mungeCodecOrder = function(description) {\r\n if (!this.codecPreference) {\r\n return description;\r\n }\r\n\r\n const parsedSdp = transform.parse(description.sdp);\r\n\r\n // Only the m-line that defines the source the browser will be sending should need to change.\r\n // This is typically the first m-line with the matching media type.\r\n const mLine = parsedSdp.media.find(m => m.type === this.codecPreference.mediaType);\r\n\r\n if (!mLine) {\r\n return description;\r\n }\r\n\r\n if (this.codecPreference.enable) {\r\n SDPUtil.preferCodec(mLine, this.codecPreference.mimeType);\r\n\r\n // Strip the high profile H264 codecs on mobile clients for p2p connection.\r\n // High profile codecs give better quality at the expense of higher load which\r\n // we do not want on mobile clients.\r\n // Jicofo offers only the baseline code for the jvb connection.\r\n // TODO - add check for mobile browsers once js-utils provides that check.\r\n if (this.codecPreference.mimeType === CodecMimeType.H264 && browser.isReactNative() && this.isP2P) {\r\n SDPUtil.stripCodec(mLine, this.codecPreference.mimeType, true /* high profile */);\r\n }\r\n\r\n // Set the max bitrate here on the SDP so that the configured max. bitrate is effective\r\n // as soon as the browser switches to VP9.\r\n if (this.codecPreference.mimeType === CodecMimeType.VP9\r\n && this.getConfiguredVideoCodec() === CodecMimeType.VP9) {\r\n const bitrates = this.tpcUtils.videoBitrates.VP9 || this.tpcUtils.videoBitrates;\r\n const hdBitrate = bitrates.high ? bitrates.high : HD_BITRATE;\r\n const limit = Math.floor((this._isSharingScreen() ? HD_BITRATE : hdBitrate) / 1000);\r\n\r\n // Use only the HD bitrate for now as there is no API available yet for configuring\r\n // the bitrates on the individual SVC layers.\r\n mLine.bandwidth = [ {\r\n type: 'AS',\r\n limit\r\n } ];\r\n } else {\r\n // Clear the bandwidth limit in SDP when VP9 is no longer the preferred codec.\r\n // This is needed on react native clients as react-native-webrtc returns the\r\n // SDP that the application passed instead of returning the SDP off the native side.\r\n // This line automatically gets cleared on web on every renegotiation.\r\n mLine.bandwidth = undefined;\r\n }\r\n } else {\r\n SDPUtil.stripCodec(mLine, this.codecPreference.mimeType);\r\n }\r\n\r\n return new RTCSessionDescription({\r\n type: description.type,\r\n sdp: transform.write(parsedSdp)\r\n });\r\n};\r\n\r\n/**\r\n * Add {@link JitsiLocalTrack} to this TPC.\r\n * @param {JitsiLocalTrack} track\r\n * @param {boolean} isInitiator indicates if the endpoint is the offerer.\r\n * @returns {Promise} - resolved when done.\r\n */\r\nTraceablePeerConnection.prototype.addTrack = function(track, isInitiator = false) {\r\n const rtcId = track.rtcId;\r\n\r\n logger.info(`${this} adding ${track}`);\r\n\r\n if (this.localTracks.has(rtcId)) {\r\n\r\n return Promise.reject(new Error(`${track} is already in ${this}`));\r\n }\r\n\r\n this.localTracks.set(rtcId, track);\r\n const webrtcStream = track.getOriginalStream();\r\n\r\n if (this._usesUnifiedPlan) {\r\n logger.debug(`${this} TPC.addTrack using unified plan`);\r\n try {\r\n this.tpcUtils.addTrack(track, isInitiator);\r\n } catch (error) {\r\n logger.error(`${this} Adding track=${track} failed: ${error?.message}`);\r\n\r\n return Promise.reject(error);\r\n }\r\n } else {\r\n // Use addStream API for the plan-b case.\r\n if (webrtcStream) {\r\n this._addStream(webrtcStream);\r\n\r\n // It's not ok for a track to not have a WebRTC stream if:\r\n } else if (!browser.doesVideoMuteByStreamRemove()\r\n || track.isAudioTrack()\r\n || (track.isVideoTrack() && !track.isMuted())) {\r\n return Promise.reject(new Error(`${this} no WebRTC stream for track=${track}`));\r\n }\r\n\r\n // Muted video tracks do not have WebRTC stream\r\n if (browser.doesVideoMuteByStreamRemove() && track.isVideoTrack() && track.isMuted()) {\r\n const ssrcInfo = this.generateNewStreamSSRCInfo(track);\r\n\r\n this.sdpConsistency.setPrimarySsrc(ssrcInfo.ssrcs[0]);\r\n const simGroup\r\n = ssrcInfo.groups.find(groupInfo => groupInfo.semantics === 'SIM');\r\n\r\n if (simGroup) {\r\n this.simulcast.setSsrcCache(simGroup.ssrcs);\r\n }\r\n const fidGroups\r\n = ssrcInfo.groups.filter(\r\n groupInfo => groupInfo.semantics === 'FID');\r\n\r\n if (fidGroups) {\r\n const rtxSsrcMapping = new Map();\r\n\r\n fidGroups.forEach(fidGroup => {\r\n const primarySsrc = fidGroup.ssrcs[0];\r\n const rtxSsrc = fidGroup.ssrcs[1];\r\n\r\n rtxSsrcMapping.set(primarySsrc, rtxSsrc);\r\n });\r\n this.rtxModifier.setSsrcCache(rtxSsrcMapping);\r\n }\r\n }\r\n }\r\n let promiseChain = Promise.resolve();\r\n\r\n // On Firefox, the encodings have to be configured on the sender only after the transceiver is created.\r\n if (browser.isFirefox()) {\r\n promiseChain = promiseChain.then(() => webrtcStream && this.tpcUtils.setEncodings(track));\r\n }\r\n\r\n return promiseChain;\r\n};\r\n\r\n/**\r\n * Adds local track as part of the unmute operation.\r\n * @param {JitsiLocalTrack} track the track to be added as part of the unmute operation.\r\n *\r\n * @return {Promise} Promise that resolves to true if the underlying PeerConnection's\r\n * state has changed and renegotiation is required, false if no renegotiation is needed or\r\n * Promise is rejected when something goes wrong.\r\n */\r\nTraceablePeerConnection.prototype.addTrackUnmute = function(track) {\r\n logger.info(`${this} Adding track=${track} as unmute`);\r\n\r\n if (!this._assertTrackBelongs('addTrackUnmute', track)) {\r\n // Abort\r\n\r\n return Promise.reject('Track not found on the peerconnection');\r\n }\r\n\r\n const webRtcStream = track.getOriginalStream();\r\n\r\n if (!webRtcStream) {\r\n logger.error(`${this} Unable to add track=${track} as unmute - no WebRTC stream`);\r\n\r\n return Promise.reject('Stream not found');\r\n }\r\n\r\n if (this._usesUnifiedPlan) {\r\n return this.tpcUtils.replaceTrack(null, track).then(() => false);\r\n }\r\n\r\n this._addStream(webRtcStream);\r\n\r\n return Promise.resolve(true);\r\n};\r\n\r\n/**\r\n * Adds WebRTC media stream to the underlying PeerConnection\r\n * @param {MediaStream} mediaStream\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._addStream = function(mediaStream) {\r\n this.peerconnection.addStream(mediaStream);\r\n this._addedStreams.push(mediaStream);\r\n};\r\n\r\n/**\r\n * Removes WebRTC media stream from the underlying PeerConection\r\n * @param {MediaStream} mediaStream\r\n */\r\nTraceablePeerConnection.prototype._removeStream = function(mediaStream) {\r\n this.peerconnection.removeStream(mediaStream);\r\n this._addedStreams\r\n = this._addedStreams.filter(stream => stream !== mediaStream);\r\n};\r\n\r\n/**\r\n * This method when called will check if given localTrack belongs to\r\n * this TPC (that it has been previously added using {@link addTrack}). If the\r\n * track does not belong an error message will be logged.\r\n * @param {string} methodName the method name that will be logged in an error\r\n * message\r\n * @param {JitsiLocalTrack} localTrack\r\n * @return {boolean} true if given local track belongs to this TPC or\r\n * false otherwise.\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._assertTrackBelongs = function(\r\n methodName,\r\n localTrack) {\r\n const doesBelong = this.localTracks.has(localTrack?.rtcId);\r\n\r\n if (!doesBelong) {\r\n logger.error(`${this} ${methodName}: track=${localTrack} does not belong to pc`);\r\n }\r\n\r\n return doesBelong;\r\n};\r\n\r\n/**\r\n * Returns the codec that is configured on the client as the preferred video codec.\r\n * This takes into account the current order of codecs in the local description sdp.\r\n *\r\n * @returns {CodecMimeType} The codec that is set as the preferred codec to receive\r\n * video in the local SDP.\r\n */\r\nTraceablePeerConnection.prototype.getConfiguredVideoCodec = function() {\r\n const sdp = this.peerconnection.localDescription?.sdp;\r\n const defaultCodec = CodecMimeType.VP8;\r\n\r\n if (!sdp) {\r\n return defaultCodec;\r\n }\r\n const parsedSdp = transform.parse(sdp);\r\n const mLine = parsedSdp.media.find(m => m.type === MediaType.VIDEO);\r\n const codec = mLine.rtp[0].codec;\r\n\r\n if (codec) {\r\n return Object.values(CodecMimeType).find(value => value === codec.toLowerCase());\r\n }\r\n\r\n return defaultCodec;\r\n};\r\n\r\n/**\r\n * Enables or disables simulcast for screenshare based on the frame rate requested for desktop track capture.\r\n *\r\n * @param {number} maxFps framerate to be used for desktop track capture.\r\n */\r\nTraceablePeerConnection.prototype.setDesktopSharingFrameRate = function(maxFps) {\r\n const lowFps = maxFps <= SS_DEFAULT_FRAME_RATE;\r\n\r\n this._capScreenshareBitrate = this.isSimulcastOn() && lowFps;\r\n};\r\n\r\n/**\r\n * Sets the codec preference on the peerconnection. The codec preference goes into effect when\r\n * the next renegotiation happens.\r\n *\r\n * @param {CodecMimeType} preferredCodec the preferred codec.\r\n * @param {CodecMimeType} disabledCodec the codec that needs to be disabled.\r\n * @returns {void}\r\n */\r\nTraceablePeerConnection.prototype.setVideoCodecs = function(preferredCodec = null, disabledCodec = null) {\r\n // If both enable and disable are set, disable settings will prevail.\r\n const enable = disabledCodec === null;\r\n const mimeType = disabledCodec ? disabledCodec : preferredCodec;\r\n\r\n if (this.codecPreference && (preferredCodec || disabledCodec)) {\r\n this.codecPreference.enable = enable;\r\n this.codecPreference.mimeType = mimeType;\r\n } else if (preferredCodec || disabledCodec) {\r\n this.codecPreference = {\r\n enable,\r\n mediaType: MediaType.VIDEO,\r\n mimeType\r\n };\r\n } else {\r\n logger.warn(`${this} Invalid codec settings[preferred=${preferredCodec},disabled=${disabledCodec}],\r\n atleast one value is needed`);\r\n }\r\n};\r\n\r\n/**\r\n * Tells if the given WebRTC MediaStream has been added to\r\n * the underlying WebRTC PeerConnection.\r\n * @param {MediaStream} mediaStream\r\n * @returns {boolean}\r\n */\r\nTraceablePeerConnection.prototype.isMediaStreamInPc = function(mediaStream) {\r\n return this._addedStreams.indexOf(mediaStream) > -1;\r\n};\r\n\r\n/**\r\n * Remove local track from this TPC.\r\n * @param {JitsiLocalTrack} localTrack the track to be removed from this TPC.\r\n *\r\n * FIXME It should probably remove a boolean just like {@link removeTrackMute}\r\n * The same applies to addTrack.\r\n */\r\nTraceablePeerConnection.prototype.removeTrack = function(localTrack) {\r\n const webRtcStream = localTrack.getOriginalStream();\r\n\r\n this.trace(\r\n 'removeStream',\r\n localTrack.rtcId, webRtcStream ? webRtcStream.id : undefined);\r\n\r\n if (!this._assertTrackBelongs('removeStream', localTrack)) {\r\n // Abort - nothing to be done here\r\n return;\r\n }\r\n this.localTracks.delete(localTrack.rtcId);\r\n this.localSSRCs.delete(localTrack.rtcId);\r\n\r\n if (webRtcStream) {\r\n this.peerconnection.removeStream(webRtcStream);\r\n }\r\n};\r\n\r\n/**\r\n * Returns the sender corresponding to the given media type.\r\n * @param {MEDIA_TYPE} mediaType - The media type 'audio' or 'video' to be used for the search.\r\n * @returns {RTPSender|undefined} - The found sender or undefined if no sender\r\n * was found.\r\n */\r\nTraceablePeerConnection.prototype.findSenderByKind = function(mediaType) {\r\n if (this.peerconnection.getSenders) {\r\n return this.peerconnection.getSenders().find(s => s.track && s.track.kind === mediaType);\r\n }\r\n};\r\n\r\n/**\r\n * Returns the receiver corresponding to the given MediaStreamTrack.\r\n *\r\n * @param {MediaSreamTrack} track - The media stream track used for the search.\r\n * @returns {RTCRtpReceiver|undefined} - The found receiver or undefined if no receiver\r\n * was found.\r\n */\r\nTraceablePeerConnection.prototype.findReceiverForTrack = function(track) {\r\n return this.peerconnection.getReceivers().find(r => r.track === track);\r\n};\r\n\r\n/**\r\n * Returns the sender corresponding to the given MediaStreamTrack.\r\n *\r\n * @param {MediaSreamTrack} track - The media stream track used for the search.\r\n * @returns {RTCRtpSender|undefined} - The found sender or undefined if no sender\r\n * was found.\r\n */\r\nTraceablePeerConnection.prototype.findSenderForTrack = function(track) {\r\n if (this.peerconnection.getSenders) {\r\n return this.peerconnection.getSenders().find(s => s.track === track);\r\n }\r\n};\r\n\r\n/**\r\n * Replaces oldTrack with newTrack from the peer connection.\r\n * Either oldTrack or newTrack can be null; replacing a valid\r\n * oldTrack with a null newTrack effectively just removes\r\n * oldTrack\r\n *\r\n * @param {JitsiLocalTrack|null} oldTrack - The current track in use to be replaced on the pc.\r\n * @param {JitsiLocalTrack|null} newTrack - The new track to be used.\r\n *\r\n * @returns {Promise} - If the promise resolves with true, renegotiation will be needed.\r\n * Otherwise no renegotiation is needed.\r\n */\r\nTraceablePeerConnection.prototype.replaceTrack = function(oldTrack, newTrack) {\r\n if (!(oldTrack || newTrack)) {\r\n logger.info(`${this} replaceTrack called with no new track and no old track`);\r\n\r\n return Promise.resolve();\r\n }\r\n\r\n // If a track is being added to the peerconnection for the first time, we want the source signaling to be sent to\r\n // Jicofo before the mute state is sent over presence. Therefore, trigger a renegotiation in this case. If we\r\n // rely on \"negotiationneeded\" fired by the browser to signal new ssrcs, the mute state in presence will be sent\r\n // before the source signaling which is undesirable.\r\n // Send the presence before signaling for a new screenshare source. This is needed for multi-stream support since\r\n // videoType needs to be availble at remote track creation time so that a fake tile for screenshare can be added.\r\n // FIXME - This check needs to be removed when the client switches to the bridge based signaling for tracks.\r\n const isNewTrackScreenshare = !oldTrack\r\n && newTrack?.getVideoType() === VideoType.DESKTOP\r\n && FeatureFlags.isMultiStreamSupportEnabled()\r\n && !this.isP2P; // negotiationneeded is not fired on p2p peerconnection\r\n const negotiationNeeded = !isNewTrackScreenshare && Boolean(!oldTrack || !this.localTracks.has(oldTrack?.rtcId));\r\n\r\n if (this._usesUnifiedPlan) {\r\n logger.debug(`${this} TPC.replaceTrack using unified plan`);\r\n const mediaType = newTrack?.getType() ?? oldTrack?.getType();\r\n const stream = newTrack?.getOriginalStream();\r\n const promise = newTrack && !stream\r\n\r\n // Ignore cases when the track is replaced while the device is in a muted state.\r\n // The track will be replaced again on the peerconnection when the user unmutes.\r\n ? Promise.resolve()\r\n : this.tpcUtils.replaceTrack(oldTrack, newTrack);\r\n\r\n return promise\r\n .then(transceiver => {\r\n oldTrack && this.localTracks.delete(oldTrack.rtcId);\r\n newTrack && this.localTracks.set(newTrack.rtcId, newTrack);\r\n\r\n // Update the local SSRC cache for the case when one track gets replaced with another and no\r\n // renegotiation is triggered as a result of this.\r\n if (oldTrack && newTrack) {\r\n const oldTrackSSRC = this.localSSRCs.get(oldTrack.rtcId);\r\n\r\n if (oldTrackSSRC) {\r\n this.localSSRCs.delete(oldTrack.rtcId);\r\n this.localSSRCs.set(newTrack.rtcId, oldTrackSSRC);\r\n }\r\n }\r\n const mediaActive = mediaType === MediaType.AUDIO\r\n ? this.audioTransferActive\r\n : this.videoTransferActive;\r\n\r\n // Set the transceiver direction only if media is not suspended on the connection. This happens when\r\n // the client is using the p2p connection. Transceiver direction is updated when media is resumed on\r\n // this connection again.\r\n if (transceiver && mediaActive) {\r\n transceiver.direction = newTrack ? MediaDirection.SENDRECV : MediaDirection.RECVONLY;\r\n } else if (transceiver) {\r\n transceiver.direction = MediaDirection.INACTIVE;\r\n }\r\n\r\n // Avoid configuring the encodings on Chromium/Safari until simulcast is configured\r\n // for the newly added track using SDP munging which happens during the renegotiation.\r\n const configureEncodingsPromise = browser.usesSdpMungingForSimulcast() || !newTrack\r\n ? Promise.resolve()\r\n : this.tpcUtils.setEncodings(newTrack);\r\n\r\n // Force renegotiation only when the source is added for the first time.\r\n return configureEncodingsPromise.then(() => negotiationNeeded);\r\n });\r\n }\r\n\r\n logger.debug(`${this} TPC.replaceTrack using plan B`);\r\n\r\n let promiseChain = Promise.resolve();\r\n\r\n if (oldTrack) {\r\n this.removeTrack(oldTrack);\r\n }\r\n if (newTrack) {\r\n promiseChain = this.addTrack(newTrack);\r\n }\r\n\r\n return promiseChain.then(() => true);\r\n};\r\n\r\n/**\r\n * Removes local track as part of the mute operation.\r\n * @param {JitsiLocalTrack} localTrack the local track to be remove as part of\r\n * the mute operation.\r\n * @return {Promise} Promise that resolves to true if the underlying PeerConnection's\r\n * state has changed and renegotiation is required, false if no renegotiation is needed or\r\n * Promise is rejected when something goes wrong.\r\n */\r\nTraceablePeerConnection.prototype.removeTrackMute = function(localTrack) {\r\n const webRtcStream = localTrack.getOriginalStream();\r\n\r\n this.trace('removeTrackMute', localTrack.rtcId, webRtcStream ? webRtcStream.id : null);\r\n\r\n if (!this._assertTrackBelongs('removeTrackMute', localTrack)) {\r\n // Abort - nothing to be done here\r\n return Promise.reject('Track not found in the peerconnection');\r\n }\r\n\r\n if (this._usesUnifiedPlan) {\r\n return this.tpcUtils.replaceTrack(localTrack, null).then(() => false);\r\n }\r\n\r\n if (webRtcStream) {\r\n logger.info(`${this} Removing track=${localTrack} as mute`);\r\n this._removeStream(webRtcStream);\r\n\r\n return Promise.resolve(true);\r\n }\r\n\r\n logger.error(`${this} removeTrackMute - no WebRTC stream for track=${localTrack}`);\r\n\r\n return Promise.reject('Stream not found');\r\n};\r\n\r\nTraceablePeerConnection.prototype.createDataChannel = function(label, opts) {\r\n this.trace('createDataChannel', label, opts);\r\n\r\n return this.peerconnection.createDataChannel(label, opts);\r\n};\r\n\r\n/**\r\n * Ensures that the simulcast ssrc-group appears after any other ssrc-groups\r\n * in the SDP so that simulcast is properly activated.\r\n *\r\n * @param {Object} localSdp the WebRTC session description instance for\r\n * the local description.\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._ensureSimulcastGroupIsLast = function(localSdp) {\r\n let sdpStr = localSdp.sdp;\r\n\r\n const videoStartIndex = sdpStr.indexOf('m=video');\r\n const simStartIndex = sdpStr.indexOf('a=ssrc-group:SIM', videoStartIndex);\r\n let otherStartIndex = sdpStr.lastIndexOf('a=ssrc-group');\r\n\r\n if (simStartIndex === -1\r\n || otherStartIndex === -1\r\n || otherStartIndex === simStartIndex) {\r\n return localSdp;\r\n }\r\n\r\n const simEndIndex = sdpStr.indexOf('\\r\\n', simStartIndex);\r\n const simStr = sdpStr.substring(simStartIndex, simEndIndex + 2);\r\n\r\n sdpStr = sdpStr.replace(simStr, '');\r\n otherStartIndex = sdpStr.lastIndexOf('a=ssrc-group');\r\n const otherEndIndex = sdpStr.indexOf('\\r\\n', otherStartIndex);\r\n const sdpHead = sdpStr.slice(0, otherEndIndex);\r\n const simStrTrimmed = simStr.trim();\r\n const sdpTail = sdpStr.slice(otherEndIndex);\r\n\r\n sdpStr = `${sdpHead}\\r\\n${simStrTrimmed}${sdpTail}`;\r\n\r\n return new RTCSessionDescription({\r\n type: localSdp.type,\r\n sdp: sdpStr\r\n });\r\n};\r\n\r\n/**\r\n * Will adjust audio and video media direction in the given SDP object to\r\n * reflect the current status of the {@link audioTransferActive} and\r\n * {@link videoTransferActive} flags.\r\n * @param {RTCSessionDescription} localDescription the WebRTC session description instance for\r\n * the local description.\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._adjustLocalMediaDirection = function(localDescription) {\r\n const transformer = new SdpTransformWrap(localDescription.sdp);\r\n let modifiedDirection = false;\r\n const audioMedia = transformer.selectMedia(MediaType.AUDIO)?.[0];\r\n\r\n if (audioMedia) {\r\n const desiredAudioDirection = this.getDesiredMediaDirection(MediaType.AUDIO);\r\n\r\n if (audioMedia.direction !== desiredAudioDirection) {\r\n audioMedia.direction = desiredAudioDirection;\r\n logger.info(`${this} Adjusted local audio direction to ${desiredAudioDirection}`);\r\n modifiedDirection = true;\r\n }\r\n } else {\r\n logger.warn(`${this} No \"audio\" media found in the local description`);\r\n }\r\n\r\n const videoMedia = transformer.selectMedia(MediaType.VIDEO)?.[0];\r\n\r\n if (videoMedia) {\r\n const desiredVideoDirection = this.getDesiredMediaDirection(MediaType.VIDEO);\r\n\r\n if (videoMedia.direction !== desiredVideoDirection) {\r\n videoMedia.direction = desiredVideoDirection;\r\n logger.info(`${this} Adjusted local video direction to ${desiredVideoDirection}`);\r\n modifiedDirection = true;\r\n }\r\n } else {\r\n logger.warn(`${this} No \"video\" media found in the local description`);\r\n }\r\n\r\n if (modifiedDirection) {\r\n return new RTCSessionDescription({\r\n type: localDescription.type,\r\n sdp: transformer.toRawSDP()\r\n });\r\n }\r\n\r\n return localDescription;\r\n};\r\n\r\n/**\r\n * Adjusts the media direction on the remote description based on availability of local and remote sources in a p2p\r\n * media connection.\r\n *\r\n * @param {RTCSessionDescription} remoteDescription the WebRTC session description instance for the remote description.\r\n * @returns the transformed remoteDescription.\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._adjustRemoteMediaDirection = function(remoteDescription) {\r\n const transformer = new SdpTransformWrap(remoteDescription.sdp);\r\n\r\n [ MediaType.AUDIO, MediaType.VIDEO ].forEach(mediaType => {\r\n const media = transformer.selectMedia(mediaType)?.[0];\r\n const hasLocalSource = this.hasAnyTracksOfType(mediaType);\r\n const hasRemoteSource = this.getRemoteTracks(null, mediaType).length > 0;\r\n\r\n media.direction = hasLocalSource && hasRemoteSource\r\n ? MediaDirection.SENDRECV\r\n : hasLocalSource\r\n ? MediaDirection.RECVONLY\r\n : hasRemoteSource ? MediaDirection.SENDONLY : MediaDirection.INACTIVE;\r\n });\r\n\r\n return new RTCSessionDescription({\r\n type: remoteDescription.type,\r\n sdp: transformer.toRawSDP()\r\n });\r\n};\r\n\r\n/**\r\n * Munges the stereo flag as well as the opusMaxAverageBitrate in the SDP, based\r\n * on values set through config.js, if present.\r\n *\r\n * @param {RTCSessionDescription} description that needs to be munged.\r\n * @returns {RTCSessionDescription} the munged description.\r\n */\r\nTraceablePeerConnection.prototype._mungeOpus = function(description) {\r\n const { audioQuality } = this.options;\r\n\r\n if (!audioQuality?.stereo && !audioQuality?.opusMaxAverageBitrate) {\r\n return description;\r\n }\r\n\r\n const parsedSdp = transform.parse(description.sdp);\r\n const mLines = parsedSdp.media;\r\n\r\n for (const mLine of mLines) {\r\n if (mLine.type === 'audio') {\r\n const { payload } = mLine.rtp.find(protocol => protocol.codec === CodecMimeType.OPUS);\r\n\r\n if (!payload) {\r\n // eslint-disable-next-line no-continue\r\n continue;\r\n }\r\n\r\n let fmtpOpus = mLine.fmtp.find(protocol => protocol.payload === payload);\r\n\r\n if (!fmtpOpus) {\r\n fmtpOpus = {\r\n payload,\r\n config: ''\r\n };\r\n }\r\n\r\n const fmtpConfig = transform.parseParams(fmtpOpus.config);\r\n let sdpChanged = false;\r\n\r\n if (audioQuality?.stereo) {\r\n fmtpConfig.stereo = 1;\r\n sdpChanged = true;\r\n }\r\n\r\n if (audioQuality?.opusMaxAverageBitrate) {\r\n fmtpConfig.maxaveragebitrate = audioQuality.opusMaxAverageBitrate;\r\n sdpChanged = true;\r\n }\r\n\r\n if (!sdpChanged) {\r\n // eslint-disable-next-line no-continue\r\n continue;\r\n }\r\n\r\n let mungedConfig = '';\r\n\r\n for (const key of Object.keys(fmtpConfig)) {\r\n mungedConfig += `${key}=${fmtpConfig[key]}; `;\r\n }\r\n\r\n fmtpOpus.config = mungedConfig.trim();\r\n }\r\n }\r\n\r\n return new RTCSessionDescription({\r\n type: description.type,\r\n sdp: transform.write(parsedSdp)\r\n });\r\n};\r\n\r\n/**\r\n * Munges the SDP to set all directions to inactive and drop all ssrc and ssrc-groups.\r\n *\r\n * @param {RTCSessionDescription} description that needs to be munged.\r\n * @returns {RTCSessionDescription} the munged description.\r\n */\r\nTraceablePeerConnection.prototype._mungeInactive = function(description) {\r\n const parsedSdp = transform.parse(description.sdp);\r\n const mLines = parsedSdp.media;\r\n\r\n for (const mLine of mLines) {\r\n mLine.direction = MediaDirection.INACTIVE;\r\n mLine.ssrcs = undefined;\r\n mLine.ssrcGroups = undefined;\r\n }\r\n\r\n return new RTCSessionDescription({\r\n type: description.type,\r\n sdp: transform.write(parsedSdp)\r\n });\r\n};\r\n\r\n/**\r\n * Sets up the _dtlsTransport object and initializes callbacks for it.\r\n */\r\nTraceablePeerConnection.prototype._initializeDtlsTransport = function() {\r\n // We are assuming here that we only have one bundled transport here\r\n if (!this.peerconnection.getSenders || this._dtlsTransport) {\r\n return;\r\n }\r\n\r\n const senders = this.peerconnection.getSenders();\r\n\r\n if (senders.length !== 0 && senders[0].transport) {\r\n this._dtlsTransport = senders[0].transport;\r\n\r\n this._dtlsTransport.onerror = error => {\r\n logger.error(`${this} DtlsTransport error: ${error}`);\r\n };\r\n\r\n this._dtlsTransport.onstatechange = () => {\r\n this.trace('dtlsTransport.onstatechange', this._dtlsTransport.state);\r\n };\r\n }\r\n};\r\n\r\n\r\n/**\r\n * Configures the stream encodings depending on the video type and the bitrates configured.\r\n *\r\n * @param {JitsiLocalTrack} - The local track for which the sender encodings have to configured.\r\n * @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.\r\n */\r\nTraceablePeerConnection.prototype.configureSenderVideoEncodings = function(localVideoTrack = null) {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n if (localVideoTrack) {\r\n return this.setSenderVideoConstraints(\r\n this._senderMaxHeights.get(localVideoTrack.getSourceName()),\r\n localVideoTrack);\r\n }\r\n const promises = [];\r\n\r\n for (const track of this.getLocalVideoTracks()) {\r\n promises.push(this.setSenderVideoConstraints(this._senderMaxHeights.get(track.getSourceName()), track));\r\n }\r\n\r\n return Promise.allSettled(promises);\r\n }\r\n\r\n let localTrack = localVideoTrack;\r\n\r\n if (!localTrack) {\r\n localTrack = this.getLocalVideoTracks()[0];\r\n }\r\n\r\n return this.setSenderVideoConstraints(this._senderVideoMaxHeight, localTrack);\r\n};\r\n\r\nTraceablePeerConnection.prototype.setLocalDescription = function(description) {\r\n let localDescription = description;\r\n\r\n this.trace('setLocalDescription::preTransform', dumpSDP(localDescription));\r\n\r\n // Munge stereo flag and opusMaxAverageBitrate based on config.js\r\n localDescription = this._mungeOpus(localDescription);\r\n\r\n if (!this._usesUnifiedPlan) {\r\n localDescription = this._adjustLocalMediaDirection(localDescription);\r\n localDescription = this._ensureSimulcastGroupIsLast(localDescription);\r\n }\r\n\r\n // Munge the order of the codecs based on the preferences set through config.js if we are using SDP munging.\r\n if (!this._usesTransceiverCodecPreferences) {\r\n localDescription = this._mungeCodecOrder(localDescription);\r\n }\r\n\r\n this.trace('setLocalDescription::postTransform', dumpSDP(localDescription));\r\n\r\n return new Promise((resolve, reject) => {\r\n this.peerconnection.setLocalDescription(localDescription)\r\n .then(() => {\r\n this.trace('setLocalDescriptionOnSuccess');\r\n const localUfrag = SDPUtil.getUfrag(localDescription.sdp);\r\n\r\n if (localUfrag !== this.localUfrag) {\r\n this.localUfrag = localUfrag;\r\n this.eventEmitter.emit(RTCEvents.LOCAL_UFRAG_CHANGED, this, localUfrag);\r\n }\r\n\r\n this._initializeDtlsTransport();\r\n\r\n resolve();\r\n }, err => {\r\n this.trace('setLocalDescriptionOnFailure', err);\r\n this.eventEmitter.emit(RTCEvents.SET_LOCAL_DESCRIPTION_FAILED, err, this);\r\n reject(err);\r\n });\r\n });\r\n};\r\n\r\n/**\r\n * Enables/disables audio media transmission on this peer connection. When\r\n * disabled the SDP audio media direction in the local SDP will be adjusted to\r\n * 'inactive' which means that no data will be sent nor accepted, but\r\n * the connection should be kept alive.\r\n * @param {boolean} active true to enable audio media transmission or\r\n * false to disable. If the value is not a boolean the call will have\r\n * no effect.\r\n * @return {boolean} true if the value has changed and sRD/sLD cycle\r\n * needs to be executed in order for the changes to take effect or\r\n * false if the given value was the same as the previous one.\r\n * @public\r\n */\r\nTraceablePeerConnection.prototype.setAudioTransferActive = function(active) {\r\n logger.debug(`${this} audio transfer active: ${active}`);\r\n const changed = this.audioTransferActive !== active;\r\n\r\n this.audioTransferActive = active;\r\n\r\n if (this._usesUnifiedPlan) {\r\n this.tpcUtils.setAudioTransferActive(active);\r\n\r\n // false means no renegotiation up the chain which is not needed in the Unified mode\r\n return false;\r\n }\r\n\r\n return changed;\r\n};\r\n\r\nTraceablePeerConnection.prototype.setRemoteDescription = function(description) {\r\n let remoteDescription = description;\r\n\r\n this.trace('setRemoteDescription::preTransform', dumpSDP(description));\r\n\r\n // Munge stereo flag and opusMaxAverageBitrate based on config.js\r\n remoteDescription = this._mungeOpus(remoteDescription);\r\n\r\n if (this._usesUnifiedPlan) {\r\n // Translate the SDP to Unified plan format first for the jvb case, p2p case will only have 2 m-lines.\r\n if (!this.isP2P) {\r\n const currentDescription = this.peerconnection.remoteDescription;\r\n\r\n remoteDescription = this.interop.toUnifiedPlan(remoteDescription, currentDescription);\r\n this.trace('setRemoteDescription::postTransform (Unified)', dumpSDP(remoteDescription));\r\n\r\n if (FeatureFlags.isRunInLiteModeEnabled()) {\r\n remoteDescription = this._mungeInactive(remoteDescription);\r\n }\r\n }\r\n if (this.isSimulcastOn()) {\r\n remoteDescription = this.tpcUtils.insertUnifiedPlanSimulcastReceive(remoteDescription);\r\n this.trace('setRemoteDescription::postTransform (sim receive)', dumpSDP(remoteDescription));\r\n }\r\n remoteDescription = this.tpcUtils.ensureCorrectOrderOfSsrcs(remoteDescription);\r\n this.trace('setRemoteDescription::postTransform (correct ssrc order)', dumpSDP(remoteDescription));\r\n } else {\r\n if (this.isSimulcastOn()) {\r\n // Implode the simulcast ssrcs so that the remote sdp has only the first ssrc in the SIM group.\r\n remoteDescription = this.simulcast.mungeRemoteDescription(\r\n remoteDescription,\r\n true /* add x-google-conference flag */);\r\n this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(remoteDescription));\r\n }\r\n remoteDescription = normalizePlanB(remoteDescription);\r\n }\r\n\r\n // Munge the order of the codecs based on the preferences set through config.js.\r\n remoteDescription = this._mungeCodecOrder(remoteDescription);\r\n this.trace('setRemoteDescription::postTransform (munge codec order)', dumpSDP(remoteDescription));\r\n\r\n return new Promise((resolve, reject) => {\r\n this.peerconnection.setRemoteDescription(remoteDescription)\r\n .then(() => {\r\n this.trace('setRemoteDescriptionOnSuccess');\r\n const remoteUfrag = SDPUtil.getUfrag(remoteDescription.sdp);\r\n\r\n if (remoteUfrag !== this.remoteUfrag) {\r\n this.remoteUfrag = remoteUfrag;\r\n this.eventEmitter.emit(RTCEvents.REMOTE_UFRAG_CHANGED, this, remoteUfrag);\r\n }\r\n\r\n this._initializeDtlsTransport();\r\n\r\n resolve();\r\n }, err => {\r\n this.trace('setRemoteDescriptionOnFailure', err);\r\n this.eventEmitter.emit(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED, err, this);\r\n reject(err);\r\n });\r\n });\r\n};\r\n\r\n/**\r\n * Changes the resolution of the video stream that is sent to the peer based on the resolution requested by the peer\r\n * and user preference, sets the degradation preference on the sender based on the video type, configures the maximum\r\n * bitrates on the send stream.\r\n *\r\n * @param {number} frameHeight - The max frame height to be imposed on the outgoing video stream.\r\n * @param {JitsiLocalTrack} - The local track for which the sender constraints have to be applied.\r\n * @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.\r\n */\r\nTraceablePeerConnection.prototype.setSenderVideoConstraints = function(frameHeight, localVideoTrack) {\r\n if (frameHeight < 0) {\r\n throw new Error(`Invalid frameHeight: ${frameHeight}`);\r\n }\r\n\r\n // XXX: This is not yet supported on mobile.\r\n if (browser.isReactNative()) {\r\n return Promise.resolve();\r\n }\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this._senderMaxHeights.set(localVideoTrack.getSourceName(), frameHeight);\r\n } else {\r\n this._senderVideoMaxHeight = frameHeight;\r\n }\r\n\r\n if (!localVideoTrack || localVideoTrack.isMuted()) {\r\n return Promise.resolve();\r\n }\r\n const videoSender = this.findSenderForTrack(localVideoTrack.getTrack());\r\n\r\n if (!videoSender) {\r\n return Promise.resolve();\r\n }\r\n const parameters = videoSender.getParameters();\r\n\r\n if (!parameters?.encodings?.length) {\r\n return Promise.resolve();\r\n }\r\n\r\n // Set the degradation preference.\r\n const preference = this.isSharingLowFpsScreen()\r\n ? DEGRADATION_PREFERENCE_DESKTOP // Prefer resolution for low fps share.\r\n : DEGRADATION_PREFERENCE_CAMERA; // Prefer frame-rate for high fps share and camera.\r\n\r\n parameters.degradationPreference = preference;\r\n logger.info(`${this} Setting degradation preference [preference=${preference},track=${localVideoTrack}`);\r\n\r\n // Calculate the encodings active state based on the resolution requested by the bridge.\r\n this.encodingsEnabledState = this.tpcUtils.calculateEncodingsActiveState(localVideoTrack, frameHeight);\r\n const maxBitrates = this.tpcUtils.calculateEncodingsBitrates(localVideoTrack);\r\n const videoType = localVideoTrack.getVideoType();\r\n\r\n if (this.isSimulcastOn()) {\r\n for (const encoding in parameters.encodings) {\r\n if (parameters.encodings.hasOwnProperty(encoding)) {\r\n parameters.encodings[encoding].active = this.encodingsEnabledState[encoding];\r\n\r\n // Firefox doesn't follow the spec and lets application specify the degradation preference on the\r\n // encodings.\r\n browser.isFirefox() && (parameters.encodings[encoding].degradationPreference = preference);\r\n\r\n // Max bitrates are configured on the encodings only for VP8.\r\n if (this.getConfiguredVideoCodec() === CodecMimeType.VP8\r\n && (this.options?.videoQuality?.maxBitratesVideo\r\n || this.isSharingLowFpsScreen()\r\n || this._usesUnifiedPlan)) {\r\n parameters.encodings[encoding].maxBitrate = maxBitrates[encoding];\r\n }\r\n }\r\n }\r\n this.tpcUtils.updateEncodingsResolution(parameters);\r\n\r\n // For p2p and cases and where simulcast is explicitly disabled.\r\n } else if (frameHeight > 0) {\r\n let scaleFactor = HD_SCALE_FACTOR;\r\n\r\n // Do not scale down encodings for desktop tracks for non-simulcast case.\r\n if (videoType === VideoType.CAMERA && localVideoTrack.resolution > frameHeight) {\r\n scaleFactor = Math.floor(localVideoTrack.resolution / frameHeight);\r\n }\r\n\r\n parameters.encodings[0].active = true;\r\n parameters.encodings[0].scaleResolutionDownBy = scaleFactor;\r\n\r\n // Firefox doesn't follow the spec and lets application specify the degradation preference on the encodings.\r\n browser.isFirefox() && (parameters.encodings[0].degradationPreference = preference);\r\n\r\n // Configure the bitrate.\r\n if (this.getConfiguredVideoCodec() === CodecMimeType.VP8 && this.options?.videoQuality?.maxBitratesVideo) {\r\n let bitrate = this.getTargetVideoBitrates()?.high;\r\n\r\n if (videoType === VideoType.CAMERA) {\r\n bitrate = this.tpcUtils.localStreamEncodingsConfig\r\n .find(layer => layer.scaleResolutionDownBy === scaleFactor)?.maxBitrate ?? bitrate;\r\n }\r\n parameters.encodings[0].maxBitrate = bitrate;\r\n }\r\n } else {\r\n parameters.encodings[0].active = false;\r\n }\r\n\r\n logger.info(`${this} setting max height=${frameHeight},encodings=${JSON.stringify(parameters.encodings)}`);\r\n\r\n return videoSender.setParameters(parameters).then(() => {\r\n localVideoTrack.maxEnabledResolution = frameHeight;\r\n this.eventEmitter.emit(RTCEvents.LOCAL_TRACK_MAX_ENABLED_RESOLUTION_CHANGED, localVideoTrack);\r\n });\r\n};\r\n\r\n/**\r\n * Enables/disables video media transmission on this peer connection. When\r\n * disabled the SDP video media direction in the local SDP will be adjusted to\r\n * 'inactive' which means that no data will be sent nor accepted, but\r\n * the connection should be kept alive.\r\n * @param {boolean} active true to enable video media transmission or\r\n * false to disable. If the value is not a boolean the call will have\r\n * no effect.\r\n * @return {boolean} true if the value has changed and sRD/sLD cycle\r\n * needs to be executed in order for the changes to take effect or\r\n * false if the given value was the same as the previous one.\r\n * @public\r\n */\r\nTraceablePeerConnection.prototype.setVideoTransferActive = function(active) {\r\n logger.debug(`${this} video transfer active: ${active}`);\r\n const changed = this.videoTransferActive !== active;\r\n\r\n this.videoTransferActive = active;\r\n\r\n if (this._usesUnifiedPlan) {\r\n this.tpcUtils.setVideoTransferActive(active);\r\n\r\n // false means no renegotiation up the chain which is not needed in the Unified mode\r\n return false;\r\n }\r\n\r\n return changed;\r\n};\r\n\r\n/**\r\n * Sends DTMF tones if possible.\r\n *\r\n * @param {string} tones - The DTMF tones string as defined by {@code RTCDTMFSender.insertDTMF}, 'tones' argument.\r\n * @param {number} duration - The amount of time in milliseconds that each DTMF should last. It's 200ms by default.\r\n * @param {number} interToneGap - The length of time in miliseconds to wait between tones. It's 200ms by default.\r\n *\r\n * @returns {void}\r\n */\r\nTraceablePeerConnection.prototype.sendTones = function(tones, duration = 200, interToneGap = 200) {\r\n if (!this._dtmfSender) {\r\n if (this.peerconnection.getSenders) {\r\n const rtpSender = this.peerconnection.getSenders().find(s => s.dtmf);\r\n\r\n this._dtmfSender = rtpSender && rtpSender.dtmf;\r\n this._dtmfSender && logger.info(`${this} initialized DTMFSender using getSenders`);\r\n }\r\n\r\n if (!this._dtmfSender) {\r\n const localAudioTrack = Array.from(this.localTracks.values()).find(t => t.isAudioTrack());\r\n\r\n if (this.peerconnection.createDTMFSender && localAudioTrack) {\r\n this._dtmfSender = this.peerconnection.createDTMFSender(localAudioTrack.getTrack());\r\n }\r\n this._dtmfSender && logger.info(`${this} initialized DTMFSender using deprecated createDTMFSender`);\r\n }\r\n\r\n if (this._dtmfSender) {\r\n this._dtmfSender.ontonechange = this._onToneChange.bind(this);\r\n }\r\n }\r\n\r\n if (this._dtmfSender) {\r\n if (this._dtmfSender.toneBuffer) {\r\n this._dtmfTonesQueue.push({\r\n tones,\r\n duration,\r\n interToneGap\r\n });\r\n\r\n return;\r\n }\r\n\r\n this._dtmfSender.insertDTMF(tones, duration, interToneGap);\r\n } else {\r\n logger.warn(`${this} sendTones - failed to select DTMFSender`);\r\n }\r\n};\r\n\r\n/**\r\n * Callback ivoked by {@code this._dtmfSender} when it has finished playing\r\n * a single tone.\r\n *\r\n * @param {Object} event - The tonechange event which indicates what characters\r\n * are left to be played for the current tone.\r\n * @private\r\n * @returns {void}\r\n */\r\nTraceablePeerConnection.prototype._onToneChange = function(event) {\r\n // An empty event.tone indicates the current tones have finished playing.\r\n // Automatically start playing any queued tones on finish.\r\n if (this._dtmfSender && event.tone === '' && this._dtmfTonesQueue.length) {\r\n const { tones, duration, interToneGap } = this._dtmfTonesQueue.shift();\r\n\r\n this._dtmfSender.insertDTMF(tones, duration, interToneGap);\r\n }\r\n};\r\n\r\n/**\r\n * Makes the underlying TraceablePeerConnection generate new SSRC for\r\n * the recvonly video stream.\r\n */\r\nTraceablePeerConnection.prototype.generateRecvonlySsrc = function() {\r\n const newSSRC = SDPUtil.generateSsrc();\r\n\r\n logger.info(`${this} generated new recvonly SSRC=${newSSRC}`);\r\n this.sdpConsistency.setPrimarySsrc(newSSRC);\r\n};\r\n\r\n/**\r\n * Makes the underlying TraceablePeerConnection forget the current primary video\r\n * SSRC.\r\n */\r\nTraceablePeerConnection.prototype.clearRecvonlySsrc = function() {\r\n logger.info(`${this} Clearing primary video SSRC!`);\r\n this.sdpConsistency.clearVideoSsrcCache();\r\n};\r\n\r\n/**\r\n * Closes underlying WebRTC PeerConnection instance and removes all remote\r\n * tracks by emitting {@link RTCEvents.REMOTE_TRACK_REMOVED} for each one of\r\n * them.\r\n */\r\nTraceablePeerConnection.prototype.close = function() {\r\n this.trace('stop');\r\n\r\n // Off SignalingEvents\r\n this.signalingLayer.off(SignalingEvents.PEER_MUTED_CHANGED, this._peerMutedChanged);\r\n this.signalingLayer.off(SignalingEvents.PEER_VIDEO_TYPE_CHANGED, this._peerVideoTypeChanged);\r\n this._usesUnifiedPlan && this.peerconnection.removeEventListener('track', this.onTrack);\r\n\r\n for (const peerTracks of this.remoteTracks.values()) {\r\n for (const remoteTracks of peerTracks.values()) {\r\n for (const remoteTrack of remoteTracks) {\r\n this._removeRemoteTrack(remoteTrack);\r\n }\r\n }\r\n }\r\n this.remoteTracks.clear();\r\n\r\n this._addedStreams = [];\r\n\r\n this._dtmfSender = null;\r\n this._dtmfTonesQueue = [];\r\n\r\n if (!this.rtc._removePeerConnection(this)) {\r\n logger.error(`${this} RTC._removePeerConnection returned false`);\r\n }\r\n if (this.statsinterval !== null) {\r\n window.clearInterval(this.statsinterval);\r\n this.statsinterval = null;\r\n }\r\n logger.info(`${this} Closing peerconnection`);\r\n this.peerconnection.close();\r\n};\r\n\r\nTraceablePeerConnection.prototype.createAnswer = function(constraints) {\r\n return this._createOfferOrAnswer(false /* answer */, constraints);\r\n};\r\n\r\nTraceablePeerConnection.prototype.createOffer = function(constraints) {\r\n return this._createOfferOrAnswer(true /* offer */, constraints);\r\n};\r\n\r\nTraceablePeerConnection.prototype._createOfferOrAnswer = function(\r\n isOffer,\r\n constraints) {\r\n const logName = isOffer ? 'Offer' : 'Answer';\r\n\r\n this.trace(`create${logName}`, JSON.stringify(constraints, null, ' '));\r\n\r\n const handleSuccess = (resultSdp, resolveFn, rejectFn) => {\r\n try {\r\n this.trace(\r\n `create${logName}OnSuccess::preTransform`, dumpSDP(resultSdp));\r\n\r\n if (!this._usesUnifiedPlan) {\r\n // If there are no local video tracks, then a \"recvonly\"\r\n // SSRC needs to be generated\r\n if (!this.hasAnyTracksOfType(MediaType.VIDEO)\r\n && !this.sdpConsistency.hasPrimarySsrcCached()) {\r\n this.generateRecvonlySsrc();\r\n }\r\n\r\n // eslint-disable-next-line no-param-reassign\r\n resultSdp = new RTCSessionDescription({\r\n type: resultSdp.type,\r\n sdp: this.sdpConsistency.makeVideoPrimarySsrcsConsistent(\r\n resultSdp.sdp)\r\n });\r\n\r\n this.trace(\r\n `create${logName}OnSuccess::postTransform `\r\n + '(make primary audio/video ssrcs consistent)',\r\n dumpSDP(resultSdp));\r\n }\r\n\r\n const localVideoTrack = this.getLocalVideoTracks()[0];\r\n\r\n // Configure simulcast for camera tracks and for desktop tracks that need simulcast.\r\n if (this.isSimulcastOn() && browser.usesSdpMungingForSimulcast()\r\n && (localVideoTrack?.getVideoType() === VideoType.CAMERA\r\n || this._usesUnifiedPlan\r\n || !this.isSharingLowFpsScreen())) {\r\n // eslint-disable-next-line no-param-reassign\r\n resultSdp = this.simulcast.mungeLocalDescription(resultSdp);\r\n this.trace(`create${logName} OnSuccess::postTransform (simulcast)`, dumpSDP(resultSdp));\r\n }\r\n\r\n if (!this.options.disableRtx && browser.usesSdpMungingForSimulcast()) {\r\n // eslint-disable-next-line no-param-reassign\r\n resultSdp = new RTCSessionDescription({\r\n type: resultSdp.type,\r\n sdp: this.rtxModifier.modifyRtxSsrcs(resultSdp.sdp)\r\n });\r\n\r\n this.trace(\r\n `create${logName}`\r\n + 'OnSuccess::postTransform (rtx modifier)',\r\n dumpSDP(resultSdp));\r\n }\r\n\r\n const ssrcMap = this._extractSSRCMap(resultSdp);\r\n\r\n this._processLocalSSRCsMap(ssrcMap);\r\n\r\n resolveFn(resultSdp);\r\n } catch (e) {\r\n this.trace(`create${logName}OnError`, e);\r\n this.trace(`create${logName}OnError`, dumpSDP(resultSdp));\r\n logger.error(`${this} create${logName}OnError`, e, dumpSDP(resultSdp));\r\n\r\n rejectFn(e);\r\n }\r\n };\r\n\r\n const handleFailure = (err, rejectFn) => {\r\n this.trace(`create${logName}OnFailure`, err);\r\n const eventType\r\n = isOffer\r\n ? RTCEvents.CREATE_OFFER_FAILED\r\n : RTCEvents.CREATE_ANSWER_FAILED;\r\n\r\n this.eventEmitter.emit(eventType, err, this);\r\n\r\n rejectFn(err);\r\n };\r\n\r\n // Set the codec preference before creating an offer or answer so that the generated SDP will have\r\n // the correct preference order.\r\n if (this._usesTransceiverCodecPreferences) {\r\n const transceiver = this.peerconnection.getTransceivers()\r\n .find(t => t.receiver && t.receiver?.track?.kind === MediaType.VIDEO);\r\n\r\n if (transceiver) {\r\n let capabilities = RTCRtpReceiver.getCapabilities(MediaType.VIDEO)?.codecs;\r\n const mimeType = this.codecPreference?.mimeType;\r\n const enable = this.codecPreference?.enable;\r\n\r\n if (capabilities && mimeType && enable) {\r\n // Move the desired codec (all variations of it as well) to the beginning of the list.\r\n /* eslint-disable-next-line arrow-body-style */\r\n capabilities.sort(caps => {\r\n return caps.mimeType.toLowerCase() === `${MediaType.VIDEO}/${mimeType}` ? -1 : 1;\r\n });\r\n } else if (capabilities && mimeType) {\r\n capabilities = capabilities\r\n .filter(caps => caps.mimeType.toLowerCase() !== `${MediaType.VIDEO}/${mimeType}`);\r\n }\r\n\r\n // Disable ulpfec on Google Chrome and derivatives because\r\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1276427\r\n if (browser.isChromiumBased()) {\r\n capabilities = capabilities\r\n .filter(caps => caps.mimeType.toLowerCase() !== `${MediaType.VIDEO}/${CodecMimeType.ULPFEC}`);\r\n }\r\n\r\n try {\r\n transceiver.setCodecPreferences(capabilities);\r\n } catch (err) {\r\n logger.warn(`${this} Setting codec[preference=${mimeType},enable=${enable}] failed`, err);\r\n }\r\n }\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n let oaPromise;\r\n\r\n if (isOffer) {\r\n oaPromise = this.peerconnection.createOffer(constraints);\r\n } else {\r\n oaPromise = this.peerconnection.createAnswer(constraints);\r\n }\r\n\r\n oaPromise\r\n .then(\r\n sdp => handleSuccess(sdp, resolve, reject),\r\n error => handleFailure(error, reject));\r\n });\r\n};\r\n\r\n/**\r\n * Extract primary SSRC from given {@link TrackSSRCInfo} object.\r\n * @param {TrackSSRCInfo} ssrcObj\r\n * @return {number|null} the primary SSRC or null\r\n */\r\nTraceablePeerConnection.prototype._extractPrimarySSRC = function(ssrcObj) {\r\n if (ssrcObj && ssrcObj.groups && ssrcObj.groups.length) {\r\n return ssrcObj.groups[0].ssrcs[0];\r\n } else if (ssrcObj && ssrcObj.ssrcs && ssrcObj.ssrcs.length) {\r\n return ssrcObj.ssrcs[0];\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Goes over the SSRC map extracted from the latest local description and tries\r\n * to match them with the local tracks (by MSID). Will update the values\r\n * currently stored in the {@link TraceablePeerConnection.localSSRCs} map.\r\n * @param {Map} ssrcMap\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._processLocalSSRCsMap = function(ssrcMap) {\r\n for (const track of this.localTracks.values()) {\r\n let sourceIndex, sourceName;\r\n\r\n if (FeatureFlags.isMultiStreamSupportEnabled()) {\r\n sourceName = track.getSourceName();\r\n sourceIndex = sourceName?.indexOf('-') + 2;\r\n }\r\n\r\n const sourceIdentifier = this._usesUnifiedPlan\r\n ? FeatureFlags.isMultiStreamSupportEnabled() && sourceIndex\r\n ? `${track.getType()}-${sourceName.substr(sourceIndex, 1)}` : track.getType()\r\n : track.storedMSID;\r\n\r\n if (ssrcMap.has(sourceIdentifier)) {\r\n const newSSRC = ssrcMap.get(sourceIdentifier);\r\n\r\n if (!newSSRC) {\r\n logger.error(`${this} No SSRC found for stream=${sourceIdentifier}`);\r\n\r\n return;\r\n }\r\n const oldSSRC = this.localSSRCs.get(track.rtcId);\r\n const newSSRCNum = this._extractPrimarySSRC(newSSRC);\r\n const oldSSRCNum = this._extractPrimarySSRC(oldSSRC);\r\n\r\n // eslint-disable-next-line no-negated-condition\r\n if (newSSRCNum !== oldSSRCNum) {\r\n oldSSRCNum && logger.error(`${this} Overwriting SSRC for track=${track}] with ssrc=${newSSRC}`);\r\n this.localSSRCs.set(track.rtcId, newSSRC);\r\n this.eventEmitter.emit(RTCEvents.LOCAL_TRACK_SSRC_UPDATED, track, newSSRCNum);\r\n }\r\n } else if (!track.isVideoTrack() && !track.isMuted()) {\r\n // It is normal to find no SSRCs for a muted video track in\r\n // the local SDP as the recv-only SSRC is no longer munged in.\r\n // So log the warning only if it's not a muted video track.\r\n logger.warn(`${this} No SSRCs found in the local SDP for track=${track}, stream=${sourceIdentifier}`);\r\n }\r\n }\r\n};\r\n\r\nTraceablePeerConnection.prototype.addIceCandidate = function(candidate) {\r\n this.trace('addIceCandidate', JSON.stringify({\r\n candidate: candidate.candidate,\r\n sdpMid: candidate.sdpMid,\r\n sdpMLineIndex: candidate.sdpMLineIndex,\r\n usernameFragment: candidate.usernameFragment\r\n }, null, ' '));\r\n\r\n return this.peerconnection.addIceCandidate(candidate);\r\n};\r\n\r\n/**\r\n * Returns the number of simulcast streams that are currently enabled on the peerconnection.\r\n *\r\n * @returns {number} The number of simulcast streams currently enabled or 1 when simulcast is disabled.\r\n */\r\nTraceablePeerConnection.prototype.getActiveSimulcastStreams = function() {\r\n let activeStreams = 1;\r\n\r\n if (this.isSimulcastOn() && this.encodingsEnabledState) {\r\n activeStreams = this.encodingsEnabledState.filter(stream => Boolean(stream))?.length;\r\n } else if (this.isSimulcastOn()) {\r\n activeStreams = SIM_LAYER_RIDS.length;\r\n }\r\n\r\n return activeStreams;\r\n};\r\n\r\n/**\r\n * Obtains call-related stats from the peer connection.\r\n *\r\n * @returns {Promise} Promise which resolves with data providing statistics about\r\n * the peerconnection.\r\n */\r\nTraceablePeerConnection.prototype.getStats = function() {\r\n return this.peerconnection.getStats();\r\n};\r\n\r\n/**\r\n * Generates and stores new SSRC info object for given local track.\r\n * The method should be called only for a video track being added to this TPC\r\n * in the muted state (given that the current browser uses this strategy).\r\n * @param {JitsiLocalTrack} track\r\n * @return {TPCSSRCInfo}\r\n */\r\nTraceablePeerConnection.prototype.generateNewStreamSSRCInfo = function(track) {\r\n const rtcId = track.rtcId;\r\n let ssrcInfo = this._getSSRC(rtcId);\r\n\r\n if (ssrcInfo) {\r\n logger.error(`${this} Overwriting local SSRCs for track id=${rtcId}`);\r\n }\r\n\r\n // Configure simulcast for camera tracks and desktop tracks that need simulcast.\r\n if (this.isSimulcastOn()\r\n && (track.getVideoType() === VideoType.CAMERA || !this.isSharingLowFpsScreen())) {\r\n ssrcInfo = {\r\n ssrcs: [],\r\n groups: []\r\n };\r\n for (let i = 0; i < SIM_LAYER_RIDS.length; i++) {\r\n ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());\r\n }\r\n ssrcInfo.groups.push({\r\n ssrcs: ssrcInfo.ssrcs.slice(),\r\n semantics: 'SIM'\r\n });\r\n } else {\r\n ssrcInfo = {\r\n ssrcs: [ SDPUtil.generateSsrc() ],\r\n groups: []\r\n };\r\n }\r\n if (!this.options.disableRtx) {\r\n // Specifically use a for loop here because we'll\r\n // be adding to the list we're iterating over, so we\r\n // only want to iterate through the items originally\r\n // on the list\r\n const currNumSsrcs = ssrcInfo.ssrcs.length;\r\n\r\n for (let i = 0; i < currNumSsrcs; ++i) {\r\n const primarySsrc = ssrcInfo.ssrcs[i];\r\n const rtxSsrc = SDPUtil.generateSsrc();\r\n\r\n ssrcInfo.ssrcs.push(rtxSsrc);\r\n ssrcInfo.groups.push({\r\n ssrcs: [ primarySsrc, rtxSsrc ],\r\n semantics: 'FID'\r\n });\r\n }\r\n }\r\n ssrcInfo.msid = track.storedMSID;\r\n this.localSSRCs.set(rtcId, ssrcInfo);\r\n\r\n return ssrcInfo;\r\n};\r\n\r\n/**\r\n * Returns if the peer connection uses Unified plan implementation.\r\n *\r\n * @returns {boolean} True if the pc uses Unified plan, false otherwise.\r\n */\r\nTraceablePeerConnection.prototype.usesUnifiedPlan = function() {\r\n return this._usesUnifiedPlan;\r\n};\r\n\r\n/**\r\n * Creates a text representation of this TraceablePeerConnection\r\n * instance.\r\n * @return {string}\r\n */\r\nTraceablePeerConnection.prototype.toString = function() {\r\n return `TPC[id=${this.id},type=${this.isP2P ? 'P2P' : 'JVB'}]`;\r\n};\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport BridgeVideoType from '../../service/RTC/BridgeVideoType';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport browser from '../browser';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';\r\nimport Listenable from '../util/Listenable';\r\nimport { safeCounterIncrement } from '../util/MathUtil';\r\n\r\nimport BridgeChannel from './BridgeChannel';\r\nimport JitsiLocalTrack from './JitsiLocalTrack';\r\nimport RTCUtils from './RTCUtils';\r\nimport TraceablePeerConnection from './TraceablePeerConnection';\r\n\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The counter used to generated id numbers assigned to peer connections\r\n * @type {number}\r\n */\r\nlet peerConnectionIdCounter = 0;\r\n\r\n/**\r\n * The counter used to generate id number for the local\r\n * MediaStreamTracks.\r\n * @type {number}\r\n */\r\nlet rtcTrackIdCounter = 0;\r\n\r\n/**\r\n * Creates {@code JitsiLocalTrack} instances from the passed in meta information\r\n * about MedieaTracks.\r\n *\r\n * @param {Object[]} mediaStreamMetaData - An array of meta information with\r\n * MediaTrack instances. Each can look like:\r\n * {{\r\n * stream: MediaStream instance that holds a track with audio or video,\r\n * track: MediaTrack within the MediaStream,\r\n * videoType: \"camera\" or \"desktop\" or falsy,\r\n * sourceId: ID of the desktopsharing source,\r\n * sourceType: The desktopsharing source type,\r\n * effects: Array of effect types\r\n * }}\r\n */\r\nfunction _createLocalTracks(mediaStreamMetaData = []) {\r\n return mediaStreamMetaData.map(metaData => {\r\n const {\r\n sourceId,\r\n sourceType,\r\n stream,\r\n track,\r\n videoType,\r\n effects\r\n } = metaData;\r\n\r\n const { deviceId, facingMode } = track.getSettings();\r\n\r\n // FIXME Move rtcTrackIdCounter to a static method in JitsiLocalTrack\r\n // so RTC does not need to handle ID management. This move would be\r\n // safer to do once the old createLocalTracks is removed.\r\n rtcTrackIdCounter = safeCounterIncrement(rtcTrackIdCounter);\r\n\r\n return new JitsiLocalTrack({\r\n deviceId,\r\n facingMode,\r\n mediaType: track.kind,\r\n rtcId: rtcTrackIdCounter,\r\n sourceId,\r\n sourceType,\r\n stream,\r\n track,\r\n videoType: videoType || null,\r\n effects\r\n });\r\n });\r\n}\r\n\r\n/**\r\n *\r\n */\r\nexport default class RTC extends Listenable {\r\n /**\r\n *\r\n * @param conference\r\n * @param options\r\n */\r\n constructor(conference, options = {}) {\r\n super();\r\n this.conference = conference;\r\n\r\n /**\r\n * A map of active TraceablePeerConnection.\r\n * @type {Map.}\r\n */\r\n this.peerConnections = new Map();\r\n\r\n this.localTracks = [];\r\n\r\n this.options = options;\r\n\r\n // BridgeChannel instance.\r\n // @private\r\n // @type {BridgeChannel}\r\n this._channel = null;\r\n\r\n /**\r\n * The value specified to the last invocation of setLastN before the\r\n * channel completed opening. If non-null, the value will be sent\r\n * through a channel (once) as soon as it opens and will then be\r\n * discarded.\r\n * @private\r\n * @type {number}\r\n */\r\n this._lastN = undefined;\r\n\r\n /**\r\n * Defines the last N endpoints list. It can be null or an array once\r\n * initialised with a channel last N event.\r\n * @type {Array|null}\r\n * @private\r\n */\r\n this._lastNEndpoints = null;\r\n\r\n /**\r\n * Defines the forwarded sources list. It can be null or an array once initialised with a channel forwarded\r\n * sources event.\r\n *\r\n * @type {Array|null}\r\n * @private\r\n */\r\n this._forwardedSources = null;\r\n\r\n /**\r\n * The number representing the maximum video height the local client\r\n * should receive from the bridge.\r\n *\r\n * @type {number|undefined}\r\n * @private\r\n */\r\n this._maxFrameHeight = undefined;\r\n\r\n /**\r\n * The endpoint IDs of currently selected participants.\r\n *\r\n * @type {Array}\r\n * @private\r\n */\r\n this._selectedEndpoints = null;\r\n\r\n // The last N change listener.\r\n this._lastNChangeListener = this._onLastNChanged.bind(this);\r\n\r\n // The forwarded sources change listener.\r\n this._forwardedSourcesChangeListener = this._onForwardedSourcesChanged.bind(this);\r\n\r\n this._onDeviceListChanged = this._onDeviceListChanged.bind(this);\r\n this._updateAudioOutputForAudioTracks\r\n = this._updateAudioOutputForAudioTracks.bind(this);\r\n\r\n /**\r\n * The default video type assumed by the bridge.\r\n * @deprecated this will go away with multiple streams support\r\n * @type {BridgeVideoType}\r\n * @private\r\n */\r\n this._videoType = BridgeVideoType.NONE;\r\n\r\n // Switch audio output device on all remote audio tracks. Local audio\r\n // tracks handle this event by themselves.\r\n if (RTCUtils.isDeviceChangeAvailable('output')) {\r\n RTCUtils.addListener(\r\n RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,\r\n this._updateAudioOutputForAudioTracks\r\n );\r\n\r\n RTCUtils.addListener(\r\n RTCEvents.DEVICE_LIST_CHANGED,\r\n this._onDeviceListChanged\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Removes any listeners and stored state from this {@code RTC} instance.\r\n *\r\n * @returns {void}\r\n */\r\n destroy() {\r\n RTCUtils.removeListener(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED, this._updateAudioOutputForAudioTracks);\r\n RTCUtils.removeListener(RTCEvents.DEVICE_LIST_CHANGED, this._onDeviceListChanged);\r\n\r\n if (this._channelOpenListener) {\r\n this.removeListener(RTCEvents.DATA_CHANNEL_OPEN, this._channelOpenListener);\r\n }\r\n }\r\n\r\n /**\r\n * Exposes the private helper for converting a WebRTC MediaStream to a\r\n * JitsiLocalTrack.\r\n *\r\n * @param {Array} tracksInfo\r\n * @returns {Array}\r\n */\r\n static createLocalTracks(tracksInfo) {\r\n return _createLocalTracks(tracksInfo);\r\n }\r\n\r\n /**\r\n * Creates the local MediaStreams.\r\n * @param {object} [options] Optional parameters.\r\n * @param {array} options.devices The devices that will be requested.\r\n * @param {string} options.resolution Resolution constraints.\r\n * @param {string} options.cameraDeviceId\r\n * @param {string} options.micDeviceId\r\n * @returns {*} Promise object that will receive the new JitsiTracks\r\n */\r\n static obtainAudioAndVideoPermissions(options) {\r\n return RTCUtils.obtainAudioAndVideoPermissions(options)\r\n .then(tracksInfo => _createLocalTracks(tracksInfo));\r\n }\r\n\r\n /**\r\n * Initializes the bridge channel of this instance.\r\n * At least one of both, peerconnection or wsUrl parameters, must be\r\n * given.\r\n * @param {RTCPeerConnection} [peerconnection] WebRTC peer connection\r\n * instance.\r\n * @param {string} [wsUrl] WebSocket URL.\r\n */\r\n initializeBridgeChannel(peerconnection, wsUrl) {\r\n this._channel = new BridgeChannel(peerconnection, wsUrl, this.eventEmitter);\r\n\r\n this._channelOpenListener = () => {\r\n const logError = (error, msgType, value) => {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error(`Cannot send ${msgType}(${JSON.stringify(value)}) endpoint message`, error);\r\n };\r\n\r\n // When the channel becomes available, tell the bridge about video selections so that it can do adaptive\r\n // simulcast, we want the notification to trigger even if userJid is undefined, or null.\r\n if (this._receiverVideoConstraints) {\r\n try {\r\n this._channel.sendNewReceiverVideoConstraintsMessage(this._receiverVideoConstraints);\r\n } catch (error) {\r\n logError(error, 'ReceiverVideoConstraints', this._receiverVideoConstraints);\r\n }\r\n }\r\n if (this._selectedEndpoints) {\r\n try {\r\n this._channel.sendSelectedEndpointsMessage(this._selectedEndpoints);\r\n } catch (error) {\r\n logError(error, 'SelectedEndpointsChangedEvent', this._selectedEndpoints);\r\n }\r\n }\r\n if (typeof this._maxFrameHeight !== 'undefined') {\r\n try {\r\n this._channel.sendReceiverVideoConstraintMessage(this._maxFrameHeight);\r\n } catch (error) {\r\n logError(error, 'ReceiverVideoConstraint', this._maxFrameHeight);\r\n }\r\n }\r\n if (typeof this._lastN !== 'undefined' && this._lastN !== -1) {\r\n try {\r\n this._channel.sendSetLastNMessage(this._lastN);\r\n } catch (error) {\r\n logError(error, 'LastNChangedEvent', this._lastN);\r\n }\r\n }\r\n if (!FeatureFlags.isSourceNameSignalingEnabled()) {\r\n try {\r\n this._channel.sendVideoTypeMessage(this._videoType);\r\n } catch (error) {\r\n logError(error, 'VideoTypeMessage', this._videoType);\r\n }\r\n }\r\n };\r\n this.addListener(RTCEvents.DATA_CHANNEL_OPEN, this._channelOpenListener);\r\n\r\n // Add Last N change listener.\r\n this.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED, this._lastNChangeListener);\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n // Add forwarded sources change listener.\r\n this.addListener(RTCEvents.FORWARDED_SOURCES_CHANGED, this._forwardedSourcesChangeListener);\r\n }\r\n }\r\n\r\n /**\r\n * Callback invoked when the list of known audio and video devices has\r\n * been updated. Attempts to update the known available audio output\r\n * devices.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _onDeviceListChanged() {\r\n this._updateAudioOutputForAudioTracks(RTCUtils.getAudioOutputDevice());\r\n }\r\n\r\n /**\r\n * Receives events when Last N had changed.\r\n * @param {array} lastNEndpoints The new Last N endpoints.\r\n * @private\r\n */\r\n _onLastNChanged(lastNEndpoints = []) {\r\n const oldLastNEndpoints = this._lastNEndpoints || [];\r\n let leavingLastNEndpoints = [];\r\n let enteringLastNEndpoints = [];\r\n\r\n this._lastNEndpoints = lastNEndpoints;\r\n\r\n leavingLastNEndpoints = oldLastNEndpoints.filter(\r\n id => !this.isInLastN(id));\r\n\r\n enteringLastNEndpoints = lastNEndpoints.filter(\r\n id => oldLastNEndpoints.indexOf(id) === -1);\r\n\r\n this.conference.eventEmitter.emit(\r\n JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,\r\n leavingLastNEndpoints,\r\n enteringLastNEndpoints);\r\n }\r\n\r\n /**\r\n * Receives events when forwarded sources had changed.\r\n *\r\n * @param {array} forwardedSources The new forwarded sources.\r\n * @private\r\n */\r\n _onForwardedSourcesChanged(forwardedSources = []) {\r\n const oldForwardedSources = this._forwardedSources || [];\r\n let leavingForwardedSources = [];\r\n let enteringForwardedSources = [];\r\n\r\n this._forwardedSources = forwardedSources;\r\n\r\n leavingForwardedSources = oldForwardedSources.filter(sourceName => !this.isInForwardedSources(sourceName));\r\n\r\n enteringForwardedSources = forwardedSources.filter(\r\n sourceName => oldForwardedSources.indexOf(sourceName) === -1);\r\n\r\n this.conference.eventEmitter.emit(\r\n JitsiConferenceEvents.FORWARDED_SOURCES_CHANGED,\r\n leavingForwardedSources,\r\n enteringForwardedSources,\r\n Date.now());\r\n }\r\n\r\n /**\r\n * Should be called when current media session ends and after the\r\n * PeerConnection has been closed using PeerConnection.close() method.\r\n */\r\n onCallEnded() {\r\n if (this._channel) {\r\n // The BridgeChannel is not explicitly closed as the PeerConnection\r\n // is closed on call ended which triggers datachannel onclose\r\n // events. If using a WebSocket, the channel must be closed since\r\n // it is not managed by the PeerConnection.\r\n // The reference is cleared to disable any logic related to the\r\n // channel.\r\n if (this._channel && this._channel.mode === 'websocket') {\r\n this._channel.close();\r\n }\r\n\r\n this._channel = null;\r\n }\r\n }\r\n\r\n /**\r\n * Sets the capture frame rate to be used for desktop tracks.\r\n *\r\n * @param {number} maxFps framerate to be used for desktop track capture.\r\n */\r\n setDesktopSharingFrameRate(maxFps) {\r\n RTCUtils.setDesktopSharingFrameRate(maxFps);\r\n }\r\n\r\n /**\r\n * Sets the receiver video constraints that determine how bitrate is allocated to each of the video streams\r\n * requested from the bridge. The constraints are cached and sent through the bridge channel once the channel\r\n * is established.\r\n * @param {*} constraints\r\n */\r\n setNewReceiverVideoConstraints(constraints) {\r\n this._receiverVideoConstraints = constraints;\r\n\r\n if (this._channel && this._channel.isOpen()) {\r\n this._channel.sendNewReceiverVideoConstraintsMessage(constraints);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the maximum video size the local participant should receive from\r\n * remote participants. Will cache the value and send it through the channel\r\n * once it is created.\r\n *\r\n * @param {number} maxFrameHeightPixels the maximum frame height, in pixels,\r\n * this receiver is willing to receive.\r\n * @returns {void}\r\n */\r\n setReceiverVideoConstraint(maxFrameHeight) {\r\n this._maxFrameHeight = maxFrameHeight;\r\n\r\n if (this._channel && this._channel.isOpen()) {\r\n this._channel.sendReceiverVideoConstraintMessage(maxFrameHeight);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the video type and availability for the local video source.\r\n *\r\n * @param {string} videoType 'camera' for camera, 'desktop' for screenshare and\r\n * 'none' for when local video source is muted or removed from the peerconnection.\r\n * @returns {void}\r\n */\r\n setVideoType(videoType) {\r\n if (this._videoType !== videoType) {\r\n this._videoType = videoType;\r\n\r\n if (this._channel && this._channel.isOpen()) {\r\n this._channel.sendVideoTypeMessage(videoType);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Sends the track's video type to the JVB.\r\n * @param {SourceName} sourceName - the track's source name.\r\n * @param {BridgeVideoType} videoType - the track's video type.\r\n */\r\n sendSourceVideoType(sourceName, videoType) {\r\n if (this._channel && this._channel.isOpen()) {\r\n this._channel.sendSourceVideoTypeMessage(sourceName, videoType);\r\n }\r\n }\r\n\r\n /**\r\n * Elects the participants with the given ids to be the selected\r\n * participants in order to always receive video for this participant (even\r\n * when last n is enabled). If there is no channel we store it and send it\r\n * through the channel once it is created.\r\n *\r\n * @param {Array} ids - The user ids.\r\n * @throws NetworkError or InvalidStateError or Error if the operation\r\n * fails.\r\n * @returns {void}\r\n */\r\n selectEndpoints(ids) {\r\n this._selectedEndpoints = ids;\r\n\r\n if (this._channel && this._channel.isOpen()) {\r\n this._channel.sendSelectedEndpointsMessage(ids);\r\n }\r\n }\r\n\r\n /**\r\n *\r\n * @param eventType\r\n * @param listener\r\n */\r\n static addListener(eventType, listener) {\r\n RTCUtils.addListener(eventType, listener);\r\n }\r\n\r\n /**\r\n *\r\n * @param eventType\r\n * @param listener\r\n */\r\n static removeListener(eventType, listener) {\r\n RTCUtils.removeListener(eventType, listener);\r\n }\r\n\r\n /**\r\n *\r\n * @param options\r\n */\r\n static init(options = {}) {\r\n this.options = options;\r\n\r\n return RTCUtils.init(this.options);\r\n }\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n * Creates new TraceablePeerConnection\r\n * @param {SignalingLayer} signaling The signaling layer that will provide information about the media or\r\n * participants which is not carried over SDP.\r\n * @param {object} pcConfig The {@code RTCConfiguration} to use for the WebRTC peer connection.\r\n * @param {boolean} isP2P Indicates whether or not the new TPC will be used in a peer to peer type of session.\r\n * @param {object} options The config options.\r\n * @param {boolean} options.enableInsertableStreams - Set to true when the insertable streams constraints is to be\r\n * enabled on the PeerConnection.\r\n * @param {boolean} options.disableSimulcast If set to 'true' will disable the simulcast.\r\n * @param {boolean} options.disableRtx If set to 'true' will disable the RTX.\r\n * @param {boolean} options.startSilent If set to 'true' no audio will be sent or received.\r\n * @return {TraceablePeerConnection}\r\n */\r\n createPeerConnection(signaling, pcConfig, isP2P, options) {\r\n const pcConstraints = JSON.parse(JSON.stringify(RTCUtils.pcConstraints));\r\n\r\n if (options.enableInsertableStreams) {\r\n logger.debug('E2EE - setting insertable streams constraints');\r\n pcConfig.encodedInsertableStreams = true;\r\n }\r\n\r\n const supportsSdpSemantics = browser.isReactNative()\r\n || (browser.isChromiumBased() && !options.usesUnifiedPlan);\r\n\r\n if (supportsSdpSemantics) {\r\n logger.debug('WebRTC application is running in plan-b mode');\r\n pcConfig.sdpSemantics = 'plan-b';\r\n }\r\n\r\n if (options.forceTurnRelay) {\r\n pcConfig.iceTransportPolicy = 'relay';\r\n }\r\n\r\n // Set the RTCBundlePolicy to max-bundle so that only one set of ice candidates is generated.\r\n // The default policy generates separate ice candidates for audio and video connections.\r\n // This change is necessary for Unified plan to work properly on Chrome and Safari.\r\n pcConfig.bundlePolicy = 'max-bundle';\r\n\r\n peerConnectionIdCounter = safeCounterIncrement(peerConnectionIdCounter);\r\n\r\n const newConnection\r\n = new TraceablePeerConnection(\r\n this,\r\n peerConnectionIdCounter,\r\n signaling,\r\n pcConfig, pcConstraints,\r\n isP2P, options);\r\n\r\n this.peerConnections.set(newConnection.id, newConnection);\r\n\r\n return newConnection;\r\n }\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Removed given peer connection from this RTC module instance.\r\n * @param {TraceablePeerConnection} traceablePeerConnection\r\n * @return {boolean} true if the given peer connection was removed\r\n * successfully or false if there was no peer connection mapped in\r\n * this RTC instance.\r\n */\r\n _removePeerConnection(traceablePeerConnection) {\r\n const id = traceablePeerConnection.id;\r\n\r\n if (this.peerConnections.has(id)) {\r\n // NOTE Remote tracks are not removed here.\r\n this.peerConnections.delete(id);\r\n\r\n return true;\r\n }\r\n\r\n return false;\r\n\r\n }\r\n\r\n /**\r\n *\r\n * @param track\r\n */\r\n addLocalTrack(track) {\r\n if (!track) {\r\n throw new Error('track must not be null nor undefined');\r\n }\r\n\r\n this.localTracks.push(track);\r\n\r\n track.conference = this.conference;\r\n }\r\n\r\n /**\r\n * Get forwarded sources list.\r\n * @returns {Array|null}\r\n */\r\n getForwardedSources() {\r\n return this._forwardedSources;\r\n }\r\n\r\n /**\r\n * Get local video track.\r\n * @returns {JitsiLocalTrack|undefined}\r\n */\r\n getLocalVideoTrack() {\r\n const localVideo = this.getLocalTracks(MediaType.VIDEO);\r\n\r\n\r\n return localVideo.length ? localVideo[0] : undefined;\r\n }\r\n\r\n /**\r\n * Returns all the local video tracks.\r\n * @returns {Array}\r\n */\r\n getLocalVideoTracks() {\r\n return this.getLocalTracks(MediaType.VIDEO);\r\n }\r\n\r\n /**\r\n * Get local audio track.\r\n * @returns {JitsiLocalTrack|undefined}\r\n */\r\n getLocalAudioTrack() {\r\n const localAudio = this.getLocalTracks(MediaType.AUDIO);\r\n\r\n\r\n return localAudio.length ? localAudio[0] : undefined;\r\n }\r\n\r\n /**\r\n * Returns the endpoint id for the local user.\r\n * @returns {string}\r\n */\r\n getLocalEndpointId() {\r\n return this.conference.myUserId();\r\n }\r\n\r\n /**\r\n * Returns the local tracks of the given media type, or all local tracks if\r\n * no specific type is given.\r\n * @param {MediaType} [mediaType] Optional media type filter.\r\n * (audio or video).\r\n */\r\n getLocalTracks(mediaType) {\r\n let tracks = this.localTracks.slice();\r\n\r\n if (mediaType !== undefined) {\r\n tracks = tracks.filter(\r\n track => track.getType() === mediaType);\r\n }\r\n\r\n return tracks;\r\n }\r\n\r\n /**\r\n * Obtains all remote tracks currently known to this RTC module instance.\r\n * @param {MediaType} [mediaType] The remote tracks will be filtered\r\n * by their media type if this argument is specified.\r\n * @return {Array}\r\n */\r\n getRemoteTracks(mediaType) {\r\n let remoteTracks = [];\r\n\r\n for (const tpc of this.peerConnections.values()) {\r\n const pcRemoteTracks = tpc.getRemoteTracks(undefined, mediaType);\r\n\r\n if (pcRemoteTracks) {\r\n remoteTracks = remoteTracks.concat(pcRemoteTracks);\r\n }\r\n }\r\n\r\n return remoteTracks;\r\n }\r\n\r\n /**\r\n * Set mute for all local audio streams attached to the conference.\r\n * @param value The mute value.\r\n * @returns {Promise}\r\n */\r\n setAudioMute(value) {\r\n const mutePromises = [];\r\n\r\n this.getLocalTracks(MediaType.AUDIO).forEach(audioTrack => {\r\n // this is a Promise\r\n mutePromises.push(value ? audioTrack.mute() : audioTrack.unmute());\r\n });\r\n\r\n // We return a Promise from all Promises so we can wait for their\r\n // execution.\r\n return Promise.all(mutePromises);\r\n }\r\n\r\n /**\r\n * Set mute for all local video streams attached to the conference.\r\n * @param value The mute value.\r\n * @returns {Promise}\r\n */\r\n setVideoMute(value) {\r\n const mutePromises = [];\r\n\r\n this.getLocalTracks(MediaType.VIDEO).concat(this.getLocalTracks(MediaType.PRESENTER))\r\n .forEach(videoTrack => {\r\n // this is a Promise\r\n mutePromises.push(value ? videoTrack.mute() : videoTrack.unmute());\r\n });\r\n\r\n // We return a Promise from all Promises so we can wait for their\r\n // execution.\r\n return Promise.all(mutePromises);\r\n }\r\n\r\n /**\r\n *\r\n * @param track\r\n */\r\n removeLocalTrack(track) {\r\n const pos = this.localTracks.indexOf(track);\r\n\r\n if (pos === -1) {\r\n return;\r\n }\r\n\r\n this.localTracks.splice(pos, 1);\r\n }\r\n\r\n /**\r\n *\r\n * @param elSelector\r\n * @param stream\r\n */\r\n static attachMediaStream(elSelector, stream) {\r\n return RTCUtils.attachMediaStream(elSelector, stream);\r\n }\r\n\r\n /**\r\n * Returns the id of the given stream.\r\n * @param {MediaStream} stream\r\n */\r\n static getStreamID(stream) {\r\n return RTCUtils.getStreamID(stream);\r\n }\r\n\r\n /**\r\n * Returns the id of the given track.\r\n * @param {MediaStreamTrack} track\r\n */\r\n static getTrackID(track) {\r\n return RTCUtils.getTrackID(track);\r\n }\r\n\r\n /**\r\n * Returns true if retrieving the list of input devices is supported\r\n * and false if not.\r\n */\r\n static isDeviceListAvailable() {\r\n return RTCUtils.isDeviceListAvailable();\r\n }\r\n\r\n /**\r\n * Returns true if changing the input (camera / microphone) or output\r\n * (audio) device is supported and false if not.\r\n * @param {string} [deviceType] Type of device to change. Default is\r\n * undefined or 'input', 'output' - for audio output device change.\r\n * @returns {boolean} true if available, false otherwise.\r\n */\r\n static isDeviceChangeAvailable(deviceType) {\r\n return RTCUtils.isDeviceChangeAvailable(deviceType);\r\n }\r\n\r\n /**\r\n * Returns whether the current execution environment supports WebRTC (for\r\n * use within this library).\r\n *\r\n * @returns {boolean} {@code true} if WebRTC is supported in the current\r\n * execution environment (for use within this library); {@code false},\r\n * otherwise.\r\n */\r\n static isWebRtcSupported() {\r\n return browser.isSupported();\r\n }\r\n\r\n /**\r\n * Returns currently used audio output device id, '' stands for default\r\n * device\r\n * @returns {string}\r\n */\r\n static getAudioOutputDevice() {\r\n return RTCUtils.getAudioOutputDevice();\r\n }\r\n\r\n /**\r\n * Returns list of available media devices if its obtained, otherwise an\r\n * empty array is returned/\r\n * @returns {array} list of available media devices.\r\n */\r\n static getCurrentlyAvailableMediaDevices() {\r\n return RTCUtils.getCurrentlyAvailableMediaDevices();\r\n }\r\n\r\n /**\r\n * Returns whether available devices have permissions granted\r\n * @returns {Boolean}\r\n */\r\n static arePermissionsGrantedForAvailableDevices() {\r\n return RTCUtils.arePermissionsGrantedForAvailableDevices();\r\n }\r\n\r\n /**\r\n * Returns event data for device to be reported to stats.\r\n * @returns {MediaDeviceInfo} device.\r\n */\r\n static getEventDataForActiveDevice(device) {\r\n return RTCUtils.getEventDataForActiveDevice(device);\r\n }\r\n\r\n /**\r\n * Sets current audio output device.\r\n * @param {string} deviceId Id of 'audiooutput' device from\r\n * navigator.mediaDevices.enumerateDevices().\r\n * @returns {Promise} resolves when audio output is changed, is rejected\r\n * otherwise\r\n */\r\n static setAudioOutputDevice(deviceId) {\r\n return RTCUtils.setAudioOutputDevice(deviceId);\r\n }\r\n\r\n /**\r\n * Returns true if given WebRTC MediaStream is considered a valid\r\n * \"user\" stream which means that it's not a \"receive only\" stream nor a\r\n * \"mixed\" JVB stream.\r\n *\r\n * Clients that implement Unified Plan, such as Firefox use recvonly\r\n * \"streams/channels/tracks\" for receiving remote stream/tracks, as opposed\r\n * to Plan B where there are only 3 channels: audio, video and data.\r\n *\r\n * @param {MediaStream} stream The WebRTC MediaStream instance.\r\n * @returns {boolean}\r\n */\r\n static isUserStream(stream) {\r\n return RTC.isUserStreamById(RTCUtils.getStreamID(stream));\r\n }\r\n\r\n /**\r\n * Returns true if a WebRTC MediaStream identified by given stream\r\n * ID is considered a valid \"user\" stream which means that it's not a\r\n * \"receive only\" stream nor a \"mixed\" JVB stream.\r\n *\r\n * Clients that implement Unified Plan, such as Firefox use recvonly\r\n * \"streams/channels/tracks\" for receiving remote stream/tracks, as opposed\r\n * to Plan B where there are only 3 channels: audio, video and data.\r\n *\r\n * @param {string} streamId The id of WebRTC MediaStream.\r\n * @returns {boolean}\r\n */\r\n static isUserStreamById(streamId) {\r\n return streamId && streamId !== 'mixedmslabel'\r\n && streamId !== 'default';\r\n }\r\n\r\n /**\r\n * Allows to receive list of available cameras/microphones.\r\n * @param {function} callback Would receive array of devices as an\r\n * argument.\r\n */\r\n static enumerateDevices(callback) {\r\n RTCUtils.enumerateDevices(callback);\r\n }\r\n\r\n /**\r\n * A method to handle stopping of the stream.\r\n * One point to handle the differences in various implementations.\r\n * @param {MediaStream} mediaStream MediaStream object to stop.\r\n */\r\n static stopMediaStream(mediaStream) {\r\n RTCUtils.stopMediaStream(mediaStream);\r\n }\r\n\r\n /**\r\n * Returns whether the desktop sharing is enabled or not.\r\n * @returns {boolean}\r\n */\r\n static isDesktopSharingEnabled() {\r\n return RTCUtils.isDesktopSharingEnabled();\r\n }\r\n\r\n /**\r\n * Closes the currently opened bridge channel.\r\n */\r\n closeBridgeChannel() {\r\n if (this._channel) {\r\n this._channel.close();\r\n this._channel = null;\r\n\r\n this.removeListener(RTCEvents.LASTN_ENDPOINT_CHANGED, this._lastNChangeListener);\r\n }\r\n }\r\n\r\n /* eslint-disable max-params */\r\n /**\r\n *\r\n * @param {TraceablePeerConnection} tpc\r\n * @param {number} ssrc\r\n * @param {number} audioLevel\r\n * @param {boolean} isLocal\r\n */\r\n setAudioLevel(tpc, ssrc, audioLevel, isLocal) {\r\n const track = tpc.getTrackBySSRC(ssrc);\r\n\r\n if (!track) {\r\n return;\r\n } else if (!track.isAudioTrack()) {\r\n logger.warn(`Received audio level for non-audio track: ${ssrc}`);\r\n\r\n return;\r\n } else if (track.isLocal() !== isLocal) {\r\n logger.error(\r\n `${track} was expected to ${isLocal ? 'be' : 'not be'} local`);\r\n }\r\n\r\n track.setAudioLevel(audioLevel, tpc);\r\n }\r\n\r\n /**\r\n * Sends message via the bridge channel.\r\n * @param {string} to The id of the endpoint that should receive the\r\n * message. If \"\" the message will be sent to all participants.\r\n * @param {object} payload The payload of the message.\r\n * @throws NetworkError or InvalidStateError or Error if the operation\r\n * fails or there is no data channel created.\r\n */\r\n sendChannelMessage(to, payload) {\r\n if (this._channel) {\r\n this._channel.sendMessage(to, payload);\r\n } else {\r\n throw new Error('Channel support is disabled!');\r\n }\r\n }\r\n\r\n /**\r\n * Sends the local stats via the bridge channel.\r\n * @param {Object} payload The payload of the message.\r\n * @throws NetworkError/InvalidStateError/Error if the operation fails or if there is no data channel created.\r\n */\r\n sendEndpointStatsMessage(payload) {\r\n if (this._channel && this._channel.isOpen()) {\r\n this._channel.sendEndpointStatsMessage(payload);\r\n }\r\n }\r\n\r\n /**\r\n * Selects a new value for \"lastN\". The requested amount of videos are going\r\n * to be delivered after the value is in effect. Set to -1 for unlimited or\r\n * all available videos.\r\n * @param {number} value the new value for lastN.\r\n */\r\n setLastN(value) {\r\n if (this._lastN !== value) {\r\n this._lastN = value;\r\n if (this._channel && this._channel.isOpen()) {\r\n this._channel.sendSetLastNMessage(value);\r\n }\r\n this.eventEmitter.emit(RTCEvents.LASTN_VALUE_CHANGED, value);\r\n }\r\n }\r\n\r\n /**\r\n * Indicates if the endpoint id is currently included in the last N.\r\n * @param {string} id The endpoint id that we check for last N.\r\n * @returns {boolean} true if the endpoint id is in the last N or if we\r\n * don't have bridge channel support, otherwise we return false.\r\n */\r\n isInLastN(id) {\r\n return !this._lastNEndpoints // lastNEndpoints not initialised yet.\r\n || this._lastNEndpoints.indexOf(id) > -1;\r\n }\r\n\r\n /**\r\n * Indicates if the source name is currently included in the forwarded sources.\r\n *\r\n * @param {string} sourceName The source name that we check for forwarded sources.\r\n * @returns {boolean} true if the source name is in the forwarded sources or if we don't have bridge channel\r\n * support, otherwise we return false.\r\n */\r\n isInForwardedSources(sourceName) {\r\n return !this._forwardedSources // forwardedSources not initialised yet.\r\n || this._forwardedSources.indexOf(sourceName) > -1;\r\n }\r\n\r\n /**\r\n * Updates the target audio output device for all remote audio tracks.\r\n *\r\n * @param {string} deviceId - The device id of the audio ouput device to\r\n * use for all remote tracks.\r\n * @private\r\n * @returns {void}\r\n */\r\n _updateAudioOutputForAudioTracks(deviceId) {\r\n const remoteAudioTracks = this.getRemoteTracks(MediaType.AUDIO);\r\n\r\n for (const track of remoteAudioTracks) {\r\n track.setAudioOutput(deviceId);\r\n }\r\n }\r\n}\r\n","export enum ConnectionQualityEvents {\r\n /**\r\n * Indicates that the local connection statistics were updated.\r\n */\r\n LOCAL_STATS_UPDATED = 'cq.local_stats_updated',\r\n\r\n /**\r\n * Indicates that the connection statistics for a particular remote participant\r\n * were updated.\r\n */\r\n REMOTE_STATS_UPDATED = 'cq.remote_stats_updated'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const LOCAL_STATS_UPDATED = ConnectionQualityEvents.LOCAL_STATS_UPDATED;\r\nexport const REMOTE_STATS_UPDATED = ConnectionQualityEvents.REMOTE_STATS_UPDATED;\r\n\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as ConferenceEvents from '../../JitsiConferenceEvents';\r\nimport CodecMimeType from '../../service/RTC/CodecMimeType';\r\nimport * as RTCEvents from '../../service/RTC/RTCEvents';\r\nimport * as ConnectionQualityEvents from '../../service/connectivity/ConnectionQualityEvents';\r\nimport browser from '../browser';\r\n\r\nconst Resolutions = require('../../service/RTC/Resolutions');\r\nconst { VideoType } = require('../../service/RTC/VideoType');\r\nconst { XMPPEvents } = require('../../service/xmpp/XMPPEvents');\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The value to use for the \"type\" field for messages sent by ConnectionQuality\r\n * over the data channel.\r\n */\r\nconst STATS_MESSAGE_TYPE = 'stats';\r\n\r\n/**\r\n * The value to use for the \"type\" field for messages sent\r\n * over the data channel that contain a face landmark.\r\n */\r\nconst FACE_LANDMARK_MESSAGE_TYPE = 'face_landmark';\r\n\r\nconst kSimulcastFormats = [\r\n { width: 1920,\r\n height: 1080,\r\n layers: 3,\r\n target: 'high',\r\n targetRN: 4000000 },\r\n { width: 1280,\r\n height: 720,\r\n layers: 3,\r\n target: 'high',\r\n targetRN: 2500000 },\r\n { width: 960,\r\n height: 540,\r\n layers: 3,\r\n target: 'standard',\r\n targetRN: 900000 },\r\n { width: 640,\r\n height: 360,\r\n layers: 2,\r\n target: 'standard',\r\n targetRN: 500000 },\r\n { width: 480,\r\n height: 270,\r\n layers: 2,\r\n target: 'low',\r\n targetRN: 350000 },\r\n { width: 320,\r\n height: 180,\r\n layers: 1,\r\n target: 'low',\r\n targetRN: 150000 }\r\n];\r\n\r\n/**\r\n * The maximum bitrate to use as a measurement against the participant's current\r\n * bitrate. This cap helps in the cases where the participant's bitrate is high\r\n * but not enough to fulfill high targets, such as with 1080p.\r\n */\r\nconst MAX_TARGET_BITRATE = 2500;\r\n\r\n/**\r\n * The initial bitrate for video in kbps.\r\n */\r\nlet startBitrate = 800;\r\n\r\n/**\r\n * Gets the expected bitrate (in kbps) in perfect network conditions.\r\n * @param simulcast {boolean} whether simulcast is enabled or not.\r\n * @param resolution {Resolution} the resolution.\r\n * @param millisSinceStart {number} the number of milliseconds since sending video started.\r\n * @param videoQualitySettings {Object} the bitrate and codec settings for the local video source.\r\n */\r\nfunction getTarget(simulcast, resolution, millisSinceStart, videoQualitySettings) {\r\n let target = 0;\r\n let height = Math.min(resolution.height, resolution.width);\r\n\r\n // Find the first format with height no bigger than ours.\r\n let simulcastFormat = kSimulcastFormats.find(f => f.height <= height);\r\n\r\n if (simulcastFormat && simulcast && videoQualitySettings.codec === CodecMimeType.VP8) {\r\n // Sum the target fields from all simulcast layers for the given\r\n // resolution (e.g. 720p + 360p + 180p) for VP8 simulcast.\r\n for (height = simulcastFormat.height; height >= 180; height /= 2) {\r\n const targetHeight = height;\r\n\r\n simulcastFormat = kSimulcastFormats.find(f => f.height === targetHeight);\r\n if (simulcastFormat) {\r\n target += browser.isReactNative()\r\n ? simulcastFormat.targetRN\r\n : videoQualitySettings[simulcastFormat.target];\r\n } else {\r\n break;\r\n }\r\n }\r\n } else if (simulcastFormat) {\r\n // For VP9 SVC, H.264 (simulcast automatically disabled) and p2p, target bitrate will be\r\n // same as that of the individual stream bitrate.\r\n target = browser.isReactNative()\r\n ? simulcastFormat.targetRN\r\n : videoQualitySettings[simulcastFormat.target];\r\n }\r\n\r\n // Allow for an additional 1 second for ramp up -- delay any initial drop\r\n // of connection quality by 1 second. Convert target from bps to kbps.\r\n return Math.min(target / 1000, rampUp(Math.max(0, millisSinceStart - 1000)));\r\n}\r\n\r\n/**\r\n * Gets the bitrate to which GCC would have ramped up in perfect network\r\n * conditions after millisSinceStart milliseconds.\r\n * @param millisSinceStart {number} the number of milliseconds since sending\r\n * video was enabled.\r\n */\r\nfunction rampUp(millisSinceStart) {\r\n if (millisSinceStart > 60000) {\r\n return Number.MAX_SAFE_INTEGER;\r\n }\r\n\r\n // According to GCC the send side bandwidth estimation grows with at most\r\n // 8% per second.\r\n // https://tools.ietf.org/html/draft-ietf-rmcat-gcc-02#section-5.5\r\n return startBitrate * Math.pow(1.08, millisSinceStart / 1000);\r\n}\r\n\r\n/**\r\n * A class which monitors the local statistics coming from the RTC modules, and\r\n * calculates a \"connection quality\" value, in percent, for the media\r\n * connection. A value of 100% indicates a very good network connection, and a\r\n * value of 0% indicates a poor connection.\r\n */\r\nexport default class ConnectionQuality {\r\n /**\r\n *\r\n * @param conference\r\n * @param eventEmitter\r\n * @param options\r\n */\r\n constructor(conference, eventEmitter, options) {\r\n this.eventEmitter = eventEmitter;\r\n\r\n /**\r\n * The owning JitsiConference.\r\n */\r\n this._conference = conference;\r\n\r\n /**\r\n * Holds statistics about the local connection quality.\r\n */\r\n this._localStats = {\r\n connectionQuality: 100,\r\n jvbRTT: undefined\r\n };\r\n\r\n /**\r\n * The time this._localStats.connectionQuality was last updated.\r\n */\r\n this._lastConnectionQualityUpdate = -1;\r\n\r\n /**\r\n * Conference options.\r\n */\r\n this._options = options;\r\n\r\n /**\r\n * Maps a participant ID to an object holding connection quality\r\n * statistics received from this participant.\r\n */\r\n this._remoteStats = {};\r\n\r\n /**\r\n * The time that the ICE state last changed to CONNECTED. We use this\r\n * to calculate how much time we as a sender have had to ramp-up.\r\n */\r\n this._timeIceConnected = -1;\r\n\r\n /**\r\n * The time that local video was unmuted. We use this to calculate how\r\n * much time we as a sender have had to ramp-up.\r\n */\r\n this._timeVideoUnmuted = -1;\r\n\r\n // We assume a global startBitrate value for the sake of simplicity.\r\n if (this._options.config?.startBitrate > 0) {\r\n startBitrate = this._options.config.startBitrate;\r\n }\r\n\r\n // TODO: consider ignoring these events and letting the user of\r\n // lib-jitsi-meet handle these separately.\r\n conference.on(\r\n ConferenceEvents.CONNECTION_INTERRUPTED,\r\n () => {\r\n this._updateLocalConnectionQuality(0);\r\n this.eventEmitter.emit(\r\n ConnectionQualityEvents.LOCAL_STATS_UPDATED,\r\n this._localStats);\r\n this._broadcastLocalStats();\r\n });\r\n\r\n conference.room.addListener(\r\n XMPPEvents.ICE_CONNECTION_STATE_CHANGED,\r\n (jingleSession, newState) => {\r\n if (!jingleSession.isP2P && newState === 'connected') {\r\n this._timeIceConnected = window.performance.now();\r\n }\r\n });\r\n\r\n // Listen to DataChannel message from other participants in the\r\n // conference, and update the _remoteStats field accordingly.\r\n // TODO - Delete this when all the mobile endpoints switch to using the new Colibri\r\n // message format for sending the endpoint stats.\r\n conference.on(\r\n ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,\r\n (participant, payload) => {\r\n if (payload.type === STATS_MESSAGE_TYPE) {\r\n this._updateRemoteStats(\r\n participant.getId(), payload.values);\r\n }\r\n });\r\n\r\n conference.on(\r\n ConferenceEvents.ENDPOINT_STATS_RECEIVED,\r\n (participant, payload) => {\r\n this._updateRemoteStats(participant.getId(), payload);\r\n });\r\n\r\n conference.on(\r\n ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,\r\n (participant, payload) => {\r\n if (payload.type === FACE_LANDMARK_MESSAGE_TYPE) {\r\n this.eventEmitter.emit(\r\n ConferenceEvents.FACE_LANDMARK_ADDED,\r\n participant.getId(),\r\n payload);\r\n }\r\n });\r\n\r\n // Listen to local statistics events originating from the RTC module and update the _localStats field.\r\n conference.statistics.addConnectionStatsListener(this._updateLocalStats.bind(this));\r\n\r\n // Save the last time we were unmuted.\r\n conference.on(\r\n ConferenceEvents.TRACK_MUTE_CHANGED,\r\n track => {\r\n if (track.isVideoTrack()) {\r\n if (track.isMuted()) {\r\n this._timeVideoUnmuted = -1;\r\n } else {\r\n this._maybeUpdateUnmuteTime();\r\n }\r\n }\r\n });\r\n conference.on(\r\n ConferenceEvents.TRACK_ADDED,\r\n track => {\r\n if (track.isVideoTrack() && !track.isMuted()) {\r\n this._maybeUpdateUnmuteTime();\r\n }\r\n });\r\n conference.rtc.on(\r\n RTCEvents.LOCAL_TRACK_MAX_ENABLED_RESOLUTION_CHANGED,\r\n track => {\r\n this._localStats.maxEnabledResolution = track.maxEnabledResolution;\r\n });\r\n\r\n conference.on(\r\n ConferenceEvents.SERVER_REGION_CHANGED,\r\n serverRegion => {\r\n this._localStats.serverRegion = serverRegion;\r\n });\r\n\r\n conference.on(\r\n ConferenceEvents.PROPERTIES_CHANGED,\r\n properties => {\r\n this._localStats.bridgeCount\r\n = Number((properties || {})['bridge-count']);\r\n }\r\n );\r\n }\r\n\r\n /**\r\n * Sets _timeVideoUnmuted if it was previously unset. If it was already set,\r\n * doesn't change it.\r\n */\r\n _maybeUpdateUnmuteTime() {\r\n if (this._timeVideoUnmuted < 0) {\r\n this._timeVideoUnmuted = window.performance.now();\r\n }\r\n }\r\n\r\n /**\r\n * Calculates a new \"connection quality\" value.\r\n * @param videoType {VideoType} the type of the video source (camera or a screen capture).\r\n * @param isMuted {boolean} whether the local video is muted.\r\n * @param resolutionName {Resolution} the input resolution used by the camera.\r\n * @returns {*} the newly calculated connection quality.\r\n */\r\n _calculateConnectionQuality(videoType, isMuted, resolutionName) {\r\n\r\n // resolutionName is an index into Resolutions (where \"720\" is\r\n // \"1280x720\" and \"960\" is \"960x720\" ...).\r\n const resolution = Resolutions[resolutionName];\r\n\r\n let quality = 100;\r\n let packetLoss;\r\n\r\n // TODO: take into account packet loss for received streams\r\n\r\n if (this._localStats.packetLoss) {\r\n packetLoss = this._localStats.packetLoss.upload;\r\n\r\n // Ugly Hack Alert (UHA):\r\n // The packet loss for the upload direction is calculated based on\r\n // incoming RTCP Receiver Reports. Since we don't have RTCP\r\n // termination for audio, these reports come from the actual\r\n // receivers in the conference and therefore the reported packet\r\n // loss includes loss from the bridge to the receiver.\r\n // When we are sending video this effect is small, because the\r\n // number of video packets is much larger than the number of audio\r\n // packets (and our calculation is based on the total number of\r\n // received and lost packets).\r\n // When video is muted, however, the effect might be significant,\r\n // but we don't know what it is. We do know that it is positive, so\r\n // as a temporary solution, until RTCP termination is implemented\r\n // for the audio streams, we relax the packet loss checks here.\r\n if (isMuted) {\r\n packetLoss *= 0.5;\r\n }\r\n }\r\n\r\n if (isMuted || !resolution || videoType === VideoType.DESKTOP\r\n || this._timeIceConnected < 0\r\n || this._timeVideoUnmuted < 0) {\r\n\r\n // Calculate a value based on packet loss only.\r\n if (packetLoss === undefined) {\r\n logger.error('Cannot calculate connection quality, unknown '\r\n + 'packet loss.');\r\n quality = 100;\r\n } else if (packetLoss <= 2) {\r\n quality = 100; // Full 5 bars.\r\n } else if (packetLoss <= 4) {\r\n quality = 70; // 4 bars\r\n } else if (packetLoss <= 6) {\r\n quality = 50; // 3 bars\r\n } else if (packetLoss <= 8) {\r\n quality = 30; // 2 bars\r\n } else if (packetLoss <= 12) {\r\n quality = 10; // 1 bars\r\n } else {\r\n quality = 0; // Still 1 bar, but slower climb-up.\r\n }\r\n } else {\r\n // Calculate a value based on the send video bitrate on the active TPC.\r\n const activeTPC = this._conference.getActivePeerConnection();\r\n\r\n if (activeTPC) {\r\n const isSimulcastOn = activeTPC.isSimulcastOn();\r\n const videoQualitySettings = activeTPC.getTargetVideoBitrates();\r\n\r\n // Add the codec info as well.\r\n videoQualitySettings.codec = activeTPC.getConfiguredVideoCodec();\r\n\r\n // Time since sending of video was enabled.\r\n const millisSinceStart = window.performance.now()\r\n - Math.max(this._timeVideoUnmuted, this._timeIceConnected);\r\n const statsInterval = this._options.config?.pcStatsInterval ?? 10000;\r\n\r\n // Expected sending bitrate in perfect conditions.\r\n let target = getTarget(isSimulcastOn, resolution, millisSinceStart, videoQualitySettings);\r\n\r\n target = Math.min(target, MAX_TARGET_BITRATE);\r\n\r\n // Calculate the quality only after the stats are available (after video was enabled).\r\n if (millisSinceStart > statsInterval) {\r\n quality = 100 * this._localStats.bitrate.upload / target;\r\n }\r\n }\r\n\r\n // Whatever the bitrate, drop early if there is significant loss\r\n if (packetLoss && packetLoss >= 10) {\r\n quality = Math.min(quality, 30);\r\n }\r\n }\r\n\r\n // Make sure that the quality doesn't climb quickly\r\n if (this._lastConnectionQualityUpdate > 0) {\r\n const maxIncreasePerSecond = 2;\r\n const prevConnectionQuality = this._localStats.connectionQuality;\r\n const diffSeconds = (window.performance.now() - this._lastConnectionQualityUpdate) / 1000;\r\n\r\n quality = Math.min(quality, prevConnectionQuality + (diffSeconds * maxIncreasePerSecond));\r\n }\r\n\r\n return Math.min(100, quality);\r\n }\r\n\r\n /**\r\n * Updates the localConnectionQuality value\r\n * @param values {number} the new value. Should be in [0, 100].\r\n */\r\n _updateLocalConnectionQuality(value) {\r\n this._localStats.connectionQuality = value;\r\n this._lastConnectionQualityUpdate = window.performance.now();\r\n }\r\n\r\n /**\r\n * Broadcasts the local statistics to all other participants in the\r\n * conference.\r\n */\r\n _broadcastLocalStats() {\r\n // Send only the data that remote participants care about.\r\n const data = {\r\n bitrate: this._localStats.bitrate,\r\n packetLoss: this._localStats.packetLoss,\r\n connectionQuality: this._localStats.connectionQuality,\r\n jvbRTT: this._localStats.jvbRTT,\r\n serverRegion: this._localStats.serverRegion,\r\n maxEnabledResolution: this._localStats.maxEnabledResolution,\r\n avgAudioLevels: this._localStats.localAvgAudioLevels\r\n };\r\n\r\n try {\r\n this._conference.sendEndpointStatsMessage(data);\r\n } catch (err) {\r\n // Ignore the error as we might hit it in the beginning of the call before the channel is ready.\r\n // The statistics will be sent again after few seconds and error is logged elseware as well.\r\n }\r\n }\r\n\r\n /**\r\n * Updates the local statistics\r\n * @param {TraceablePeerConnection} tpc the peerconnection which emitted\r\n * the stats\r\n * @param data new statistics\r\n */\r\n _updateLocalStats(tpc, data) {\r\n // Update jvbRTT\r\n if (!tpc.isP2P) {\r\n const jvbRTT\r\n = data.transport\r\n && data.transport.length && data.transport[0].rtt;\r\n\r\n this._localStats.jvbRTT = jvbRTT ? jvbRTT : undefined;\r\n }\r\n\r\n // Do not continue with processing of other stats if they do not\r\n // originate from the active peerconnection\r\n if (tpc !== this._conference.getActivePeerConnection()) {\r\n return;\r\n }\r\n\r\n let key;\r\n const updateLocalConnectionQuality\r\n = !this._conference.isConnectionInterrupted();\r\n const localVideoTrack\r\n = this._conference.getLocalVideoTrack();\r\n const videoType\r\n = localVideoTrack ? localVideoTrack.videoType : undefined;\r\n const isMuted = localVideoTrack ? localVideoTrack.isMuted() : true;\r\n const resolution = localVideoTrack\r\n ? Math.min(localVideoTrack.resolution, localVideoTrack.maxEnabledResolution) : null;\r\n\r\n if (!isMuted) {\r\n this._maybeUpdateUnmuteTime();\r\n }\r\n\r\n // Copy the fields already in 'data'.\r\n for (key in data) {\r\n if (data.hasOwnProperty(key)) {\r\n this._localStats[key] = data[key];\r\n }\r\n }\r\n\r\n // And re-calculate the connectionQuality field.\r\n if (updateLocalConnectionQuality) {\r\n this._updateLocalConnectionQuality(\r\n this._calculateConnectionQuality(\r\n videoType,\r\n isMuted,\r\n resolution));\r\n }\r\n\r\n this.eventEmitter.emit(\r\n ConnectionQualityEvents.LOCAL_STATS_UPDATED,\r\n this._localStats);\r\n this._broadcastLocalStats();\r\n }\r\n\r\n /**\r\n * Updates remote statistics\r\n * @param id the id of the remote participant\r\n * @param data the statistics received\r\n */\r\n _updateRemoteStats(id, data) {\r\n // Use only the fields we need\r\n this._remoteStats[id] = {\r\n bitrate: data.bitrate,\r\n packetLoss: data.packetLoss,\r\n connectionQuality: data.connectionQuality,\r\n jvbRTT: data.jvbRTT,\r\n serverRegion: data.serverRegion,\r\n maxEnabledResolution: data.maxEnabledResolution,\r\n avgAudioLevels: data.avgAudioLevels\r\n };\r\n\r\n this.eventEmitter.emit(\r\n ConnectionQualityEvents.REMOTE_STATS_UPDATED,\r\n id,\r\n this._remoteStats[id]);\r\n }\r\n\r\n /**\r\n * Returns the local statistics.\r\n * Exported only for use in jitsi-meet-torture.\r\n */\r\n getStats() {\r\n return this._localStats;\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceErrors from '../../JitsiConferenceErrors';\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * This class deals with shenanigans around JVB media session's ICE failed status handling.\r\n *\r\n * If ICE restarts are NOT explicitly enabled by the {@code enableIceRestart} config option, then the conference will\r\n * delay emitting the {@JitsiConferenceErrors.ICE_FAILED} event by 15 seconds. If the network info module reports\r\n * the internet offline status then the time will start counting after the internet comes back online.\r\n *\r\n * If ICE restart are enabled, then a delayed ICE failed notification to Jicofo will be sent, only if the ICE connection\r\n * does not recover soon after or before the XMPP connection is restored (if it was ever broken). If ICE fails while\r\n * the XMPP connection is not broken then the notifications will be sent after 2 seconds delay.\r\n */\r\nexport default class IceFailedHandling {\r\n /**\r\n * Creates new {@code DelayedIceFailed} task.\r\n * @param {JitsiConference} conference\r\n */\r\n constructor(conference) {\r\n this._conference = conference;\r\n }\r\n\r\n /**\r\n * After making sure there's no way for the ICE connection to recover this method either sends ICE failed\r\n * notification to Jicofo or emits the ice failed conference event.\r\n * @private\r\n * @returns {void}\r\n */\r\n _actOnIceFailed() {\r\n if (!this._conference.room) {\r\n return;\r\n }\r\n\r\n const { enableForcedReload, enableIceRestart } = this._conference.options.config;\r\n const explicitlyDisabled = typeof enableIceRestart !== 'undefined' && !enableIceRestart;\r\n const supportsRestartByTerminate = this._conference.room.supportsRestartByTerminate();\r\n const useTerminateForRestart = supportsRestartByTerminate && !enableIceRestart;\r\n\r\n logger.info('ICE failed,'\r\n + ` enableForcedReload: ${enableForcedReload},`\r\n + ` enableIceRestart: ${enableIceRestart},`\r\n + ` supports restart by terminate: ${supportsRestartByTerminate}`);\r\n\r\n if (explicitlyDisabled || (!enableIceRestart && !supportsRestartByTerminate) || enableForcedReload) {\r\n logger.info('ICE failed, but ICE restarts are disabled');\r\n const reason = enableForcedReload\r\n ? JitsiConferenceErrors.CONFERENCE_RESTARTED\r\n : JitsiConferenceErrors.ICE_FAILED;\r\n\r\n this._conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, reason);\r\n\r\n return;\r\n }\r\n\r\n const jvbConnection = this._conference.jvbJingleSession;\r\n const jvbConnIceState = jvbConnection && jvbConnection.getIceConnectionState();\r\n\r\n if (!jvbConnection) {\r\n logger.warn('Not sending ICE failed - no JVB connection');\r\n } else if (jvbConnIceState === 'connected') {\r\n logger.info('ICE connection restored - not sending ICE failed');\r\n } else {\r\n logger.info('Sending ICE failed - the connection did not recover, '\r\n + `ICE state: ${jvbConnIceState}, `\r\n + `use 'session-terminate': ${useTerminateForRestart}`);\r\n if (useTerminateForRestart) {\r\n this._conference.jvbJingleSession.terminate(\r\n () => {\r\n logger.info('session-terminate for ice restart - done');\r\n },\r\n error => {\r\n logger.error(`session-terminate for ice restart - error: ${error.message}`);\r\n }, {\r\n reason: 'connectivity-error',\r\n reasonDescription: 'ICE FAILED',\r\n requestRestart: true,\r\n sendSessionTerminate: true\r\n });\r\n } else {\r\n this._conference.jvbJingleSession.sendIceFailedNotification();\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Starts the task.\r\n */\r\n start() {\r\n // Using xmpp.ping allows to handle both XMPP being disconnected and internet offline cases. The ping function\r\n // uses sendIQ2 method which is resilient to XMPP connection disconnected state and will patiently wait until it\r\n // gets reconnected.\r\n // This also handles the case about waiting for the internet to come back online, because ping\r\n // will only succeed when the internet is online and then there's a chance for the ICE to recover from FAILED to\r\n // CONNECTED which is the extra 2 second timeout after ping.\r\n // The 65 second timeout is given on purpose as there's no chance for XMPP to recover after 65 seconds of no\r\n // communication with the server. Such resume attempt will result in unrecoverable conference failed event due\r\n // to 'item-not-found' error returned by the server.\r\n this._conference.xmpp.ping(65000).then(\r\n () => {\r\n if (!this._canceled) {\r\n this._iceFailedTimeout = window.setTimeout(() => {\r\n this._iceFailedTimeout = undefined;\r\n this._actOnIceFailed();\r\n }, 2000);\r\n }\r\n },\r\n error => {\r\n logger.error('PING error/timeout - not sending ICE failed', error);\r\n });\r\n }\r\n\r\n /**\r\n * Cancels the task.\r\n */\r\n cancel() {\r\n this._canceled = true;\r\n window.clearTimeout(this._iceFailedTimeout);\r\n }\r\n}\r\n","export enum DetectionEvents {\r\n /**\r\n * Event triggered by a audio detector indicating that its active state has changed from active to inactive or vice\r\n * versa.\r\n * @event\r\n * @type {boolean} - true when service has changed to active false otherwise.\r\n */\r\n DETECTOR_STATE_CHANGE = 'detector_state_change',\r\n\r\n /** Event triggered by {@link NoAudioSignalDetector} when the local audio device associated with a JitsiConference\r\n * starts receiving audio levels with the value of 0 meaning no audio is being captured on that device, or when\r\n * it starts receiving audio levels !== 0 after being in a state of no audio.\r\n * @event\r\n * @type {boolean} - true when the current conference audio track has audio input false otherwise.\r\n */\r\n AUDIO_INPUT_STATE_CHANGE = 'audio_input_state_changed',\r\n\r\n /** Event triggered by NoAudioSignalDetector when the local audio device associated with a JitsiConference goes silent\r\n * for a period of time, meaning that the device is either broken or hardware/software muted.\r\n * @event\r\n * @type {void}\r\n */\r\n NO_AUDIO_INPUT = 'no_audio_input_detected',\r\n\r\n /**\r\n * Event generated by {@link VADNoiseDetection} when the tracked device is considered noisy.\r\n * @event\r\n * @type {Object}\r\n */\r\n VAD_NOISY_DEVICE = 'detection.vad_noise_device',\r\n\r\n /**\r\n * Event generated by VADReportingService when if finishes creating a VAD report for the monitored devices.\r\n * The generated objects are of type Array, one score for each monitored device.\r\n * @event VAD_REPORT_PUBLISHED\r\n * @type Array with the following structure:\r\n * @property {Date} timestamp - Timestamp at which the compute took place.\r\n * @property {number} avgVAD - Average VAD score over monitored period of time.\r\n * @property {string} deviceId - Associate local audio device ID.\r\n */\r\n VAD_REPORT_PUBLISHED = 'vad-report-published',\r\n\r\n /**\r\n * Event generated by {@link TrackVADEmitter} when PCM sample VAD score is available.\r\n *\r\n * @event\r\n * @type {Object}\r\n * @property {Date} timestamp - Exact time at which processed PCM sample was generated.\r\n * @property {number} score - VAD score on a scale from 0 to 1 (i.e. 0.7)\r\n * @property {Float32Array} pcmData - Raw PCM data with which the VAD score was calculated.\r\n * @property {string} deviceId - Device id of the associated track.\r\n */\r\n VAD_SCORE_PUBLISHED = 'detection.vad_score_published',\r\n\r\n /**\r\n * Event generated by {@link VADTalkMutedDetection} when a user is talking while the mic is muted.\r\n *\r\n * @event\r\n * @type {Object}\r\n */\r\n VAD_TALK_WHILE_MUTED = 'detection.vad_talk_while_muted'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const DETECTOR_STATE_CHANGE = DetectionEvents.DETECTOR_STATE_CHANGE;\r\nexport const AUDIO_INPUT_STATE_CHANGE = DetectionEvents.AUDIO_INPUT_STATE_CHANGE;\r\nexport const NO_AUDIO_INPUT = DetectionEvents.NO_AUDIO_INPUT;\r\nexport const VAD_NOISY_DEVICE = DetectionEvents.VAD_NOISY_DEVICE;\r\nexport const VAD_REPORT_PUBLISHED = DetectionEvents.VAD_REPORT_PUBLISHED;\r\nexport const VAD_SCORE_PUBLISHED = DetectionEvents.VAD_SCORE_PUBLISHED;\r\nexport const VAD_TALK_WHILE_MUTED = DetectionEvents.VAD_TALK_WHILE_MUTED;\r\n","import EventEmitter from 'events';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport browser from '../browser';\r\n\r\nimport * as DetectionEvents from './DetectionEvents';\r\n\r\n// We wait a certain time interval for constant silence input from the current device to account for\r\n// potential abnormalities and for a better use experience i.e. don't generate event the instant\r\n// an audio track is added to the tcr.\r\n// Potential improvement - add this as a configurable parameter.\r\nconst SILENCE_PERIOD_MS = 4000;\r\n\r\n/**\r\n * Detect if there is no audio input on the current TraceAblePeerConnection selected track. The no audio\r\n * state must be constant for a configured amount of time in order for the event to be triggered.\r\n * @fires DetectionEvents.AUDIO_INPUT_STATE_CHANGE\r\n * @fires DetectionEvents.NO_AUDIO_INPUT\r\n */\r\nexport default class NoAudioSignalDetection extends EventEmitter {\r\n /**\r\n * Creates new NoAudioSignalDetection.\r\n *\r\n * @param conference the JitsiConference instance that created us.\r\n * @constructor\r\n */\r\n constructor(conference) {\r\n super();\r\n\r\n this._conference = conference;\r\n this._timeoutTrigger = null;\r\n this._hasAudioInput = null;\r\n\r\n if (!browser.supportsReceiverStats()) {\r\n conference.statistics.addAudioLevelListener(this._audioLevel.bind(this));\r\n }\r\n conference.on(JitsiConferenceEvents.TRACK_ADDED, this._trackAdded.bind(this));\r\n }\r\n\r\n /**\r\n * Clear the timeout state.\r\n */\r\n _clearTriggerTimeout() {\r\n clearTimeout(this._timeoutTrigger);\r\n this._timeoutTrigger = null;\r\n }\r\n\r\n\r\n /**\r\n * Generated event triggered by a change in the current conference audio input state.\r\n *\r\n * @param {*} audioLevel - The audio level of the ssrc.\r\n * @fires DetectionEvents.AUDIO_INPUT_STATE_CHANGE\r\n */\r\n _handleAudioInputStateChange(audioLevel) {\r\n // Current audio input state of the active local track in the conference, true for audio input false for no\r\n // audio input.\r\n const status = audioLevel !== 0;\r\n\r\n // If this is the first audio event picked up or the current status is different from the previous trigger\r\n // the event.\r\n if (this._hasAudioInput === null || this._hasAudioInput !== status) {\r\n this._hasAudioInput = status;\r\n this.emit(DetectionEvents.AUDIO_INPUT_STATE_CHANGE, this._hasAudioInput);\r\n }\r\n }\r\n\r\n /**\r\n * Generate event triggered by a prolonged period of no audio input.\r\n *\r\n * @param {number} audioLevel - The audio level of the ssrc.\r\n * @fires DetectionEvents.NO_AUDIO_INPUT\r\n */\r\n _handleNoAudioInputDetection(audioLevel) {\r\n if (this._eventFired) {\r\n return;\r\n }\r\n\r\n if (audioLevel === 0 && !this._timeoutTrigger) {\r\n this._timeoutTrigger = setTimeout(() => {\r\n this._eventFired = true;\r\n\r\n this.emit(DetectionEvents.NO_AUDIO_INPUT);\r\n }, SILENCE_PERIOD_MS);\r\n } else if (audioLevel !== 0 && this._timeoutTrigger) {\r\n this._clearTriggerTimeout();\r\n }\r\n }\r\n\r\n /**\r\n * Receives audio level events for all send and receive streams on the current TraceablePeerConnection.\r\n *\r\n * @param {TraceablePeerConnection} tpc - TraceablePeerConnection of the owning conference.\r\n * @param {number} ssrc - The synchronization source identifier (SSRC) of the endpoint/participant/stream\r\n * being reported.\r\n * @param {number} audioLevel - The audio level of the ssrc.\r\n * @param {boolean} isLocal - true for local/send streams or false for remote/receive streams.\r\n */\r\n _audioLevel(tpc, ssrc, audioLevel, isLocal) {\r\n // We are interested in the local audio streams\r\n if (!isLocal || !this._audioTrack) {\r\n return;\r\n }\r\n\r\n // Get currently active local tracks from the TraceablePeerConnection\r\n const localSSRCs = tpc.localSSRCs.get(this._audioTrack.rtcId);\r\n\r\n // Only target the current active track in the tpc. For some reason audio levels for previous\r\n // devices are also picked up from the PeerConnection so we filter them out.\r\n if (!localSSRCs || !localSSRCs.ssrcs.includes(ssrc)) {\r\n return;\r\n }\r\n\r\n // First handle audio input state change. In case the state changed to no input the no audio input event\r\n // can try to fire again.\r\n this._handleAudioInputStateChange(audioLevel);\r\n this._handleNoAudioInputDetection(audioLevel);\r\n }\r\n\r\n /**\r\n * Notifies NoAudioSignalDetection that a JitsiTrack was added to the associated JitsiConference.\r\n * Only take into account local audio tracks.\r\n *\r\n * @param {JitsiTrack} track - The added JitsiTrack.\r\n */\r\n _trackAdded(track) {\r\n if (track.isLocalAudioTrack()) {\r\n // Reset state for the new track.\r\n this._audioTrack = track;\r\n this._eventFired = false;\r\n this._clearTriggerTimeout();\r\n\r\n // Listen for the audio levels on the newly added audio track\r\n if (browser.supportsReceiverStats()) {\r\n track.on(\r\n JitsiTrackEvents.NO_AUDIO_INPUT,\r\n audioLevel => {\r\n this._handleNoAudioInputDetection(audioLevel);\r\n }\r\n );\r\n track.on(\r\n JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,\r\n audioLevel => {\r\n this._handleNoAudioInputDetection(audioLevel);\r\n this._handleAudioInputStateChange(audioLevel);\r\n }\r\n );\r\n }\r\n }\r\n }\r\n}\r\n","import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\n\r\n/**\r\n * The value which we use to say, every sound over this threshold\r\n * is talking on the mic.\r\n * @type {number}\r\n */\r\nconst SPEECH_DETECT_THRESHOLD = 0.6;\r\n\r\n/**\r\n * The P2PDominantSpeakerDetection is activated only when p2p is\r\n * currently used.\r\n * Listens for changes in the audio level changes of the local p2p audio track\r\n * or remote p2p one and fires dominant speaker events to be able to use\r\n * features depending on those events (speaker stats), to make them work without\r\n * the video bridge.\r\n */\r\nexport default class P2PDominantSpeakerDetection {\r\n /**\r\n * Creates P2PDominantSpeakerDetection\r\n * @param conference the JitsiConference instance that created us.\r\n * @constructor\r\n */\r\n constructor(conference) {\r\n this.conference = conference;\r\n\r\n conference.addEventListener(\r\n JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED,\r\n this._audioLevel.bind(this));\r\n\r\n this.myUserID = this.conference.myUserId();\r\n }\r\n\r\n /**\r\n * Receives audio level events for all streams in the conference.\r\n *\r\n * @param {String} id - The participant id\r\n * @param {number} audioLevel - The audio level.\r\n */\r\n _audioLevel(id, audioLevel) {\r\n\r\n // we do not process if p2p is not active\r\n // or audio level is under certain threshold\r\n // or if the audio level is for local audio track which is muted\r\n if (!this.conference.isP2PActive()\r\n || audioLevel <= SPEECH_DETECT_THRESHOLD\r\n || (id === this.myUserID\r\n && this.conference.getLocalAudioTrack().isMuted())) {\r\n return;\r\n }\r\n\r\n this.conference.rtc.eventEmitter.emit(\r\n RTCEvents.DOMINANT_SPEAKER_CHANGED,\r\n id);\r\n }\r\n}\r\n","/**\r\n * Adapter that creates AudioContext objects depending on the browser.\r\n *\r\n * @returns {AudioContext} - Return a new AudioContext or undefined if the browser does not support it.\r\n */\r\nexport function createAudioContext(options) {\r\n const AudioContextImpl = window.AudioContext || window.webkitAudioContext;\r\n\r\n if (!AudioContextImpl) {\r\n return undefined;\r\n }\r\n\r\n return new AudioContextImpl(options);\r\n}\r\n","import EventEmitter from 'events';\r\n\r\nimport RTC from '../RTC/RTC';\r\nimport { createAudioContext } from '../webaudio/WebAudioUtils';\r\n\r\nimport { VAD_SCORE_PUBLISHED } from './DetectionEvents';\r\n\r\n/**\r\n * Connects an audio JitsiLocalTrack to a vadProcessor using WebAudio ScriptProcessorNode.\r\n * Once an object is created audio from the local track flows through the ScriptProcessorNode as raw PCM.\r\n * The PCM is processed by the injected vad module and a voice activity detection score is obtained, the\r\n * score is published to consumers via an EventEmitter.\r\n * After work is done with this service the destroy method needs to be called for a proper cleanup.\r\n *\r\n * @fires VAD_SCORE_PUBLISHED\r\n */\r\nexport default class TrackVADEmitter extends EventEmitter {\r\n /**\r\n * Constructor.\r\n *\r\n * @param {number} procNodeSampleRate - Sample rate of the ScriptProcessorNode. Possible values 256, 512, 1024,\r\n * 2048, 4096, 8192, 16384. Passing other values will default to closes neighbor.\r\n * @param {Object} vadProcessor - VAD processor that allows us to calculate VAD score for PCM samples.\r\n * @param {JitsiLocalTrack} jitsiLocalTrack - JitsiLocalTrack corresponding to micDeviceId.\r\n */\r\n constructor(procNodeSampleRate, vadProcessor, jitsiLocalTrack) {\r\n super();\r\n\r\n /**\r\n * Sample rate of the ScriptProcessorNode.\r\n */\r\n this._procNodeSampleRate = procNodeSampleRate;\r\n\r\n /**\r\n * VAD Processor that allows us to calculate VAD score for PCM samples\r\n */\r\n this._vadProcessor = vadProcessor;\r\n\r\n /**\r\n * The JitsiLocalTrack instance.\r\n */\r\n this._localTrack = jitsiLocalTrack;\r\n\r\n /**\r\n * Buffer to hold residue PCM resulting after a ScriptProcessorNode callback\r\n */\r\n this._bufferResidue = new Float32Array([]);\r\n\r\n /**\r\n * The AudioContext instance with the preferred sample frequency.\r\n */\r\n this._audioContext = createAudioContext({ sampleRate: vadProcessor.getRequiredPCMFrequency() });\r\n\r\n /**\r\n * PCM Sample size expected by the VAD Processor instance. We cache it here as this value is used extensively,\r\n * saves a couple of function calls.\r\n */\r\n this._vadSampleSize = vadProcessor.getSampleLength();\r\n\r\n /**\r\n * Event listener function that will be called by the ScriptProcessNode with raw PCM data, depending on the set\r\n * sample rate.\r\n */\r\n this._onAudioProcess = this._onAudioProcess.bind(this);\r\n\r\n this._initializeAudioContext();\r\n }\r\n\r\n /**\r\n * Factory method that sets up all the necessary components for the creation of the TrackVADEmitter.\r\n *\r\n * @param {string} micDeviceId - Target microphone device id.\r\n * @param {number} procNodeSampleRate - Sample rate of the proc node.\r\n * @param {Object} vadProcessor -Module that calculates the voice activity score for a certain audio PCM sample.\r\n * The processor needs to implement the following functions:\r\n * - getSampleLength() - Returns the sample size accepted by getSampleLength.\r\n * - getRequiredPCMFrequency() - Returns the PCM frequency at which the processor operates.\r\n * - calculateAudioFrameVAD(pcmSample) - Process a 32 float pcm sample of getSampleLength size.\r\n * @returns {Promise} - Promise resolving in a new instance of TrackVADEmitter.\r\n */\r\n static create(micDeviceId, procNodeSampleRate, vadProcessor) {\r\n return RTC.obtainAudioAndVideoPermissions({\r\n devices: [ 'audio' ],\r\n micDeviceId\r\n }).then(localTrack => {\r\n // We only expect one audio track when specifying a device id.\r\n if (!localTrack[0]) {\r\n throw new Error(`Failed to create jitsi local track for device id: ${micDeviceId}`);\r\n }\r\n\r\n return new TrackVADEmitter(procNodeSampleRate, vadProcessor, localTrack[0]);\r\n\r\n // We have no exception handling at this point as there is nothing to clean up, the vadProcessor\r\n // life cycle is handled by whoever created this instance.\r\n });\r\n }\r\n\r\n /**\r\n * Sets up the audio graph in the AudioContext.\r\n *\r\n * @returns {void}\r\n */\r\n _initializeAudioContext() {\r\n this._audioSource = this._audioContext.createMediaStreamSource(this._localTrack.stream);\r\n\r\n // TODO AudioProcessingNode is deprecated in the web audio specifications and the recommended replacement\r\n // is audio worklet, however at the point of implementation AudioProcessingNode was still de de facto way\r\n // of achieving this functionality and supported in all major browsers as opposed to audio worklet which\r\n // was only available in Chrome. This todo is just a reminder that we should replace AudioProcessingNode\r\n // with audio worklet when it's mature enough and has more browser support.\r\n // We don't need stereo for determining the VAD score so we create a single channel processing node.\r\n this._audioProcessingNode = this._audioContext.createScriptProcessor(this._procNodeSampleRate, 1, 1);\r\n }\r\n\r\n /**\r\n * ScriptProcessorNode callback, the input parameters contains the PCM audio that is then sent to rnnoise.\r\n * Rnnoise only accepts PCM samples of 480 bytes whereas the webaudio processor node can't sample at a multiple\r\n * of 480 thus after each _onAudioProcess callback there will remain and PCM buffer residue equal\r\n * to _procNodeSampleRate / 480 which will be added to the next sample buffer and so on.\\\r\n *\r\n *\r\n * @param {AudioProcessingEvent} audioEvent - Audio event.\r\n * @returns {void}\r\n * @fires VAD_SCORE_PUBLISHED\r\n */\r\n _onAudioProcess(audioEvent) {\r\n // Prepend the residue PCM buffer from the previous process callback.\r\n const inData = audioEvent.inputBuffer.getChannelData(0);\r\n const completeInData = [ ...this._bufferResidue, ...inData ];\r\n const sampleTimestamp = Date.now();\r\n\r\n let i = 0;\r\n\r\n for (; i + this._vadSampleSize < completeInData.length; i += this._vadSampleSize) {\r\n const pcmSample = completeInData.slice(i, i + this._vadSampleSize);\r\n\r\n // The VAD processor might change the values inside the array so we make a copy.\r\n const vadScore = this._vadProcessor.calculateAudioFrameVAD(pcmSample.slice());\r\n\r\n this.emit(VAD_SCORE_PUBLISHED, {\r\n timestamp: sampleTimestamp,\r\n score: vadScore,\r\n pcmData: pcmSample,\r\n deviceId: this._localTrack.getDeviceId()\r\n });\r\n }\r\n\r\n this._bufferResidue = completeInData.slice(i, completeInData.length);\r\n }\r\n\r\n /**\r\n * Connects the nodes in the AudioContext to start the flow of audio data.\r\n *\r\n * @returns {void}\r\n */\r\n _connectAudioGraph() {\r\n this._audioProcessingNode.onaudioprocess = this._onAudioProcess;\r\n this._audioSource.connect(this._audioProcessingNode);\r\n this._audioProcessingNode.connect(this._audioContext.destination);\r\n }\r\n\r\n /**\r\n * Disconnects the nodes in the AudioContext.\r\n *\r\n * @returns {void}\r\n */\r\n _disconnectAudioGraph() {\r\n // Even thought we disconnect the processing node it seems that some callbacks remain queued,\r\n // resulting in calls with and uninitialized context.\r\n // eslint-disable-next-line no-empty-function\r\n this._audioProcessingNode.onaudioprocess = () => {};\r\n this._audioProcessingNode.disconnect();\r\n this._audioSource.disconnect();\r\n }\r\n\r\n /**\r\n * Cleanup potentially acquired resources.\r\n *\r\n * @returns {void}\r\n */\r\n _cleanupResources() {\r\n this._disconnectAudioGraph();\r\n this._localTrack.stopStream();\r\n }\r\n\r\n /**\r\n * Get the associated track device ID.\r\n *\r\n * @returns {string}\r\n */\r\n getDeviceId() {\r\n return this._localTrack.getDeviceId();\r\n }\r\n\r\n\r\n /**\r\n * Get the associated track label.\r\n *\r\n * @returns {string}\r\n */\r\n getTrackLabel() {\r\n return this._localTrack.getDeviceLabel();\r\n }\r\n\r\n /**\r\n * Start the emitter by connecting the audio graph.\r\n *\r\n * @returns {void}\r\n */\r\n start() {\r\n this._connectAudioGraph();\r\n }\r\n\r\n /**\r\n * Stops the emitter by disconnecting the audio graph.\r\n *\r\n * @returns {void}\r\n */\r\n stop() {\r\n this._disconnectAudioGraph();\r\n this._bufferResidue = [];\r\n }\r\n\r\n /**\r\n * Destroy TrackVADEmitter instance (release resources and stop callbacks).\r\n *\r\n * @returns {void}\r\n */\r\n destroy() {\r\n if (this._destroyed) {\r\n return;\r\n }\r\n\r\n this._cleanupResources();\r\n this._destroyed = true;\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { EventEmitter } from 'events';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\n\r\nimport { VAD_SCORE_PUBLISHED, DETECTOR_STATE_CHANGE } from './DetectionEvents';\r\nimport TrackVADEmitter from './TrackVADEmitter';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Sample rate of TrackVADEmitter, it defines how many audio samples are processed at a time.\r\n * @type {number}\r\n */\r\nconst VAD_EMITTER_SAMPLE_RATE = 4096;\r\n\r\n/**\r\n * Connects a TrackVADEmitter to the target conference local audio track and manages various services that use\r\n * the data to produce audio analytics (VADTalkMutedDetection and VADNoiseDetection).\r\n */\r\nexport default class VADAudioAnalyser extends EventEmitter {\r\n /**\r\n * Creates VADAudioAnalyser\r\n * @param {JitsiConference} conference - JitsiConference instance that created us.\r\n * @param {Object} createVADProcessor - Function that creates a Voice activity detection processor. The processor\r\n * needs to implement the following functions:\r\n * - getSampleLength() - Returns the sample size accepted by getSampleLength.\r\n * - getRequiredPCMFrequency() - Returns the PCM frequency at which the processor operates.\r\n * - calculateAudioFrameVAD(pcmSample) - Process a 32 float pcm sample of getSampleLength size.\r\n * @constructor\r\n */\r\n constructor(conference, createVADProcessor) {\r\n super();\r\n\r\n /**\r\n * Member function that instantiates a VAD processor.\r\n */\r\n this._createVADProcessor = createVADProcessor;\r\n\r\n /**\r\n * Current {@link TrackVADEmitter}. VAD Emitter uses a {@link JitsiLocalTrack} and VAD processor to generate\r\n * period voice probability scores.\r\n */\r\n this._vadEmitter = null;\r\n\r\n /**\r\n * Current state of the _vadEmitter\r\n */\r\n this._isVADEmitterRunning = false;\r\n\r\n /**\r\n * Array of currently attached VAD processing services.\r\n */\r\n this._detectionServices = [];\r\n\r\n /**\r\n * Promise used to chain create and destroy operations associated with TRACK_ADDED and TRACK_REMOVED events\r\n * coming from the conference.\r\n * Because we have an async created component (VAD Processor) we need to make sure that it's initialized before\r\n * we destroy it ( when changing the device for instance), or when we use it from an external point of entry\r\n * i.e. (TRACK_MUTE_CHANGED event callback).\r\n */\r\n this._vadInitTracker = Promise.resolve();\r\n\r\n /**\r\n * Listens for {@link TrackVADEmitter} events and processes them.\r\n */\r\n this._processVADScore = this._processVADScore.bind(this);\r\n\r\n conference.on(JitsiConferenceEvents.TRACK_ADDED, this._trackAdded.bind(this));\r\n conference.on(JitsiConferenceEvents.TRACK_REMOVED, this._trackRemoved.bind(this));\r\n conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, this._trackMuteChanged.bind(this));\r\n }\r\n\r\n /**\r\n * Attach a VAD detector service to the analyser and handle it's state changes.\r\n *\r\n * @param {Object} vadTMDetector\r\n */\r\n addVADDetectionService(vadService) {\r\n this._detectionServices.push(vadService);\r\n vadService.on(DETECTOR_STATE_CHANGE, () => {\r\n // When the state of a detector changes check if there are any active detectors attached so that\r\n // the _vadEmitter doesn't run needlessly.\r\n const activeDetector = this._detectionServices.filter(detector => detector.isActive() === true);\r\n\r\n // If there are no active detectors running and the vadEmitter is running then stop the emitter as it is\r\n // uses a considerable amount of CPU. Otherwise start the service if it's stopped and there is a detector\r\n // that needs it.\r\n if (!activeDetector.length && this._isVADEmitterRunning) {\r\n this._stopVADEmitter();\r\n } else if (!this._isVADEmitterRunning) {\r\n this._startVADEmitter();\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Start the {@link TrackVADEmitter} and attach the event listener.\r\n * @returns {void}\r\n */\r\n _startVADEmitter() {\r\n if (this._vadEmitter) {\r\n this._vadEmitter.on(VAD_SCORE_PUBLISHED, this._processVADScore);\r\n this._vadEmitter.start();\r\n this._isVADEmitterRunning = true;\r\n }\r\n }\r\n\r\n /**\r\n * Stop the {@link TrackVADEmitter} and detach the event listener.\r\n * @returns {void}\r\n */\r\n _stopVADEmitter() {\r\n if (this._vadEmitter) {\r\n this._vadEmitter.removeListener(VAD_SCORE_PUBLISHED, this._processVADScore);\r\n this._vadEmitter.stop();\r\n }\r\n this._isVADEmitterRunning = false;\r\n }\r\n\r\n /**\r\n * Listens for {@link TrackVADEmitter} events and directs them to attached services as needed.\r\n *\r\n * @param {Object} vadScore -VAD score emitted by {@link TrackVADEmitter}\r\n * @param {Date} vadScore.timestamp - Exact time at which processed PCM sample was generated.\r\n * @param {number} vadScore.score - VAD score on a scale from 0 to 1 (i.e. 0.7)\r\n * @param {Float32Array} pcmData - Raw PCM data with which the VAD score was calculated.\r\n * @param {string} vadScore.deviceId - Device id of the associated track.\r\n * @listens VAD_SCORE_PUBLISHED\r\n */\r\n _processVADScore(vadScore) {\r\n for (const detector of this._detectionServices) {\r\n detector.processVADScore(vadScore);\r\n }\r\n }\r\n\r\n /**\r\n * Change the isMuted state of all attached detection services.\r\n *\r\n * @param {boolean} isMuted\r\n */\r\n _changeDetectorsMuteState(isMuted) {\r\n for (const detector of this._detectionServices) {\r\n detector.changeMuteState(isMuted);\r\n }\r\n }\r\n\r\n /**\r\n * Notifies the detector that a track was added to the associated {@link JitsiConference}.\r\n * Only take into account local audio tracks.\r\n * @param {JitsiTrack} track - The added track.\r\n * @returns {void}\r\n * @listens TRACK_ADDED\r\n */\r\n _trackAdded(track) {\r\n if (track.isLocalAudioTrack()) {\r\n // Keep a track promise so we take into account successive TRACK_ADD events being generated so that we\r\n // destroy/create the processing context in the proper order.\r\n this._vadInitTracker = this._vadInitTracker.then(() => this._createVADProcessor())\r\n .then(vadProcessor =>\r\n TrackVADEmitter.create(track.getDeviceId(), VAD_EMITTER_SAMPLE_RATE, vadProcessor)\r\n )\r\n .then(vadEmitter => {\r\n logger.debug('Created VAD emitter for track: ', track.getTrackLabel());\r\n\r\n this._vadEmitter = vadEmitter;\r\n\r\n // Iterate through the detection services and set their appropriate mute state, depending on\r\n // service this will trigger a DETECTOR_STATE_CHANGE which in turn might start the _vadEmitter.\r\n this._changeDetectorsMuteState(track.isMuted());\r\n })\r\n .catch(error => {\r\n logger.warn('Failed to start VADAudioAnalyser', error);\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Notifies the detector that the mute state of a {@link JitsiConference} track has changed. Only takes into account\r\n * local audio tracks.\r\n * @param {JitsiTrack} track - The track whose mute state has changed.\r\n * @returns {void}\r\n * @listens TRACK_MUTE_CHANGED\r\n */\r\n _trackMuteChanged(track) {\r\n if (track.isLocalAudioTrack()) {\r\n // On a mute toggle reset the state.\r\n this._vadInitTracker = this._vadInitTracker.then(() => {\r\n // Set mute status for the attached detection services.\r\n this._changeDetectorsMuteState(track.isMuted());\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Notifies the detector that a track associated with the {@link JitsiConference} was removed. Only takes into\r\n * account local audio tracks. Cleans up resources associated with the track and resets the processing context.\r\n *\r\n * @param {JitsiTrack} track - The removed track.\r\n * @returns {void}\r\n * @listens TRACK_REMOVED\r\n */\r\n _trackRemoved(track) {\r\n if (track.isLocalAudioTrack()) {\r\n // Use the promise to make sure operations are in sequence.\r\n this._vadInitTracker = this._vadInitTracker.then(() => {\r\n logger.debug('Removing track from VAD detection - ', track.getTrackLabel());\r\n\r\n // Track was removed, clean up and set appropriate states.\r\n if (this._vadEmitter) {\r\n this._stopVADEmitter();\r\n this._vadEmitter.destroy();\r\n this._vadEmitter = null;\r\n }\r\n\r\n // Reset state of detectors when active track is removed.\r\n for (const detector of this._detectionServices) {\r\n detector.reset();\r\n }\r\n });\r\n }\r\n }\r\n\r\n\r\n}\r\n","import { EventEmitter } from 'events';\r\n\r\nimport { calculateAverage, filterPositiveValues } from '../util/MathUtil';\r\n\r\nimport { VAD_NOISY_DEVICE, DETECTOR_STATE_CHANGE } from './DetectionEvents';\r\n\r\n/**\r\n * The average value VAD needs to be under over a period of time to be considered noise.\r\n * @type {number}\r\n */\r\nconst VAD_NOISE_AVG_THRESHOLD = 0.2;\r\n\r\n/**\r\n * The average values that audio input need to be over to be considered loud.\r\n * @type {number}\r\n */\r\nconst NOISY_AUDIO_LEVEL_THRESHOLD = 0.040;\r\n\r\n/**\r\n * The value that a VAD score needs to be under in order for processing to begin.\r\n * @type {number}\r\n */\r\nconst VAD_SCORE_TRIGGER = 0.2;\r\n\r\n/**\r\n * The value that a VAD score needs to be under in order for processing to begin.\r\n * @type {number}\r\n */\r\nconst AUDIO_LEVEL_SCORE_TRIGGER = 0.020;\r\n\r\n/**\r\n * Time span over which we calculate an average score used to determine if we trigger the event.\r\n * @type {number}\r\n */\r\nconst PROCESS_TIME_FRAME_SPAN_MS = 1500;\r\n\r\n/**\r\n * Detect if provided VAD score and PCM data is considered noise.\r\n */\r\nexport default class VADNoiseDetection extends EventEmitter {\r\n /**\r\n * Creates VADNoiseDetection\r\n *\r\n * @constructor\r\n */\r\n constructor() {\r\n super();\r\n\r\n /**\r\n * Flag which denotes the current state of the detection service i.e.if there is already a processing operation\r\n * ongoing.\r\n */\r\n this._processing = false;\r\n\r\n /**\r\n * Buffer that keeps the VAD scores for a period of time.\r\n */\r\n this._scoreArray = [];\r\n\r\n /**\r\n * Buffer that keeps audio level samples for a period of time.\r\n */\r\n this._audioLvlArray = [];\r\n\r\n /**\r\n * Current state of the service, if it's not active no processing will occur.\r\n */\r\n this._active = false;\r\n\r\n this._calculateNoisyScore = this._calculateNoisyScore.bind(this);\r\n }\r\n\r\n /**\r\n * Compute cumulative VAD score and PCM audio levels once the PROCESS_TIME_FRAME_SPAN_MS timeout has elapsed.\r\n * If the score is above the set threshold fire the event.\r\n * @returns {void}\r\n * @fires VAD_NOISY_DEVICE\r\n */\r\n _calculateNoisyScore() {\r\n const scoreAvg = calculateAverage(this._scoreArray);\r\n const audioLevelAvg = calculateAverage(this._audioLvlArray);\r\n\r\n if (scoreAvg < VAD_NOISE_AVG_THRESHOLD && audioLevelAvg > NOISY_AUDIO_LEVEL_THRESHOLD) {\r\n this.emit(VAD_NOISY_DEVICE);\r\n\r\n this._setActiveState(false);\r\n }\r\n\r\n // We reset the context in case a new process phase needs to be triggered.\r\n this.reset();\r\n }\r\n\r\n /**\r\n * Record the vad score and average volume in the appropriate buffers.\r\n *\r\n * @param {number} vadScore\r\n * @param {number} avgAudioLvl - average audio level of the PCM sample associated with the VAD score.s\r\n */\r\n _recordValues(vadScore, avgAudioLvl) {\r\n this._scoreArray.push(vadScore);\r\n this._audioLvlArray.push(avgAudioLvl);\r\n }\r\n\r\n /**\r\n * Set the active state of the detection service and notify any listeners.\r\n *\r\n * @param {boolean} active\r\n * @fires DETECTOR_STATE_CHANGE\r\n */\r\n _setActiveState(active) {\r\n this._active = active;\r\n this.emit(DETECTOR_STATE_CHANGE, this._active);\r\n }\r\n\r\n /**\r\n * Change the state according to the muted status of the tracked device.\r\n *\r\n * @param {boolean} isMuted - Is the device muted or not.\r\n */\r\n changeMuteState(isMuted) {\r\n // This service only needs to run when the microphone is not muted.\r\n this._setActiveState(!isMuted);\r\n this.reset();\r\n }\r\n\r\n /**\r\n * Check whether or not the service is active or not.\r\n *\r\n * @returns {boolean}\r\n */\r\n isActive() {\r\n return this._active;\r\n }\r\n\r\n /**\r\n * Reset the processing context, clear buffers, cancel the timeout trigger.\r\n *\r\n * @returns {void}\r\n */\r\n reset() {\r\n this._processing = false;\r\n this._scoreArray = [];\r\n this._audioLvlArray = [];\r\n clearTimeout(this._processTimeout);\r\n }\r\n\r\n /**\r\n * Listens for {@link TrackVADEmitter} events and processes them.\r\n *\r\n * @param {Object} vadScore -VAD score emitted by {@link TrackVADEmitter}\r\n * @param {Date} vadScore.timestamp - Exact time at which processed PCM sample was generated.\r\n * @param {number} vadScore.score - VAD score on a scale from 0 to 1 (i.e. 0.7)\r\n * @param {Float32Array} vadScore.pcmData - Raw PCM Data associated with the VAD score.\r\n * @param {string} vadScore.deviceId - Device id of the associated track.\r\n * @listens VAD_SCORE_PUBLISHED\r\n */\r\n processVADScore(vadScore) {\r\n if (!this._active) {\r\n return;\r\n }\r\n\r\n // There is a processing phase on going, add score to buffer array.\r\n if (this._processing) {\r\n // Filter and calculate sample average so we don't have to process one large array at a time.\r\n const posAudioLevels = filterPositiveValues(vadScore.pcmData);\r\n\r\n this._recordValues(vadScore.score, calculateAverage(posAudioLevels));\r\n\r\n return;\r\n }\r\n\r\n // If the VAD score for the sample is low and audio level has a high enough level we can start listening for\r\n // noise\r\n if (vadScore.score < VAD_SCORE_TRIGGER) {\r\n const posAudioLevels = filterPositiveValues(vadScore.pcmData);\r\n const avgAudioLvl = calculateAverage(posAudioLevels);\r\n\r\n if (avgAudioLvl > AUDIO_LEVEL_SCORE_TRIGGER) {\r\n this._processing = true;\r\n this._recordValues(vadScore.score, avgAudioLvl);\r\n\r\n // Once the preset timeout executes the final score will be calculated.\r\n this._processTimeout = setTimeout(this._calculateNoisyScore, PROCESS_TIME_FRAME_SPAN_MS);\r\n }\r\n }\r\n }\r\n}\r\n","import { EventEmitter } from 'events';\r\n\r\nimport { calculateAverage } from '../util/MathUtil';\r\n\r\nimport { VAD_TALK_WHILE_MUTED, DETECTOR_STATE_CHANGE } from './DetectionEvents';\r\n\r\n\r\n/**\r\n * The threshold which the average VAD values for a span of time needs to exceed to trigger an event.\r\n * @type {number}\r\n */\r\nconst VAD_AVG_THRESHOLD = 0.6;\r\n\r\n/**\r\n * The VAD score needed to trigger the processing algorithm, i.e. if a sample has the VAD score >= VAD_VOICE_LEVEL\r\n * we start processing all scores for a time span defined by const PROCESS_TIME_FRAME_SPAN_MS.\r\n * @type {number}\r\n */\r\nconst VAD_VOICE_LEVEL = 0.9;\r\n\r\n/**\r\n * Sample rate of TrackVADEmitter, it defines how many audio samples are processed at a time.\r\n * @type {number}\r\n */\r\n\r\n/**\r\n * Time span over which we calculate an average score used to determine if we trigger the event.\r\n * @type {number}\r\n */\r\nconst PROCESS_TIME_FRAME_SPAN_MS = 700;\r\n\r\n/**\r\n * Detect if provided VAD score which is generated on a muted device is voice and fires an event.\r\n */\r\nexport default class VADTalkMutedDetection extends EventEmitter {\r\n /**\r\n * Creates VADTalkMutedDetection\r\n * @constructor\r\n */\r\n constructor() {\r\n super();\r\n\r\n /**\r\n * Flag which denotes the current state of the detection service i.e.if there is already a processing operation\r\n * ongoing.\r\n */\r\n this._processing = false;\r\n\r\n /**\r\n * Buffer that keeps the VAD scores for a period of time.\r\n */\r\n this._scoreArray = [];\r\n\r\n /**\r\n * Current mute state of the audio track being monitored.\r\n */\r\n this._active = false;\r\n\r\n this._calculateVADScore = this._calculateVADScore.bind(this);\r\n }\r\n\r\n /**\r\n * Compute cumulative VAD score function called once the PROCESS_TIME_FRAME_SPAN_MS timeout has elapsed.\r\n * @returns {void}\r\n * @fires VAD_TALK_WHILE_MUTED\r\n */\r\n _calculateVADScore() {\r\n const score = calculateAverage(this._scoreArray);\r\n\r\n if (score > VAD_AVG_THRESHOLD) {\r\n this.emit(VAD_TALK_WHILE_MUTED);\r\n\r\n // Event was fired. Stop event emitter and remove listeners so no residue events kick off after this point\r\n // and a single VAD_TALK_WHILE_MUTED is generated per mic muted state.\r\n this._setActiveState(false);\r\n }\r\n\r\n // We reset the context in case a new process phase needs to be triggered.\r\n this.reset();\r\n }\r\n\r\n /**\r\n * Set the active state of the detection service and notify any listeners.\r\n *\r\n * @param {boolean} active\r\n * @fires DETECTOR_STATE_CHANGE\r\n */\r\n _setActiveState(active) {\r\n this._active = active;\r\n this.emit(DETECTOR_STATE_CHANGE, this._active);\r\n }\r\n\r\n /**\r\n * Change the state according to the muted status of the tracked device.\r\n *\r\n * @param {boolean} isMuted - Is the device muted or not.\r\n */\r\n changeMuteState(isMuted) {\r\n // This service only needs to run when the microphone is muted.\r\n this._setActiveState(isMuted);\r\n this.reset();\r\n }\r\n\r\n /**\r\n * Check whether or not the service is active or not.\r\n *\r\n * @returns {boolean}\r\n */\r\n isActive() {\r\n return this._active;\r\n }\r\n\r\n /**\r\n * Listens for {@link TrackVADEmitter} events and processes them.\r\n *\r\n * @param {Object} vadScore -VAD score emitted by {@link TrackVADEmitter}\r\n * @param {Date} vadScore.timestamp - Exact time at which processed PCM sample was generated.\r\n * @param {number} vadScore.score - VAD score on a scale from 0 to 1 (i.e. 0.7)\r\n * @param {string} vadScore.deviceId - Device id of the associated track.\r\n * @listens VAD_SCORE_PUBLISHED\r\n */\r\n processVADScore(vadScore) {\r\n if (!this._active) {\r\n return;\r\n }\r\n\r\n // There is a processing phase on going, add score to buffer array.\r\n if (this._processing) {\r\n this._scoreArray.push(vadScore.score);\r\n\r\n return;\r\n }\r\n\r\n // Because we remove all listeners on the vadEmitter once the main event is triggered,\r\n // there is no need to check for rogue events.\r\n if (vadScore.score > VAD_VOICE_LEVEL) {\r\n this._processing = true;\r\n this._scoreArray.push(vadScore.score);\r\n\r\n // Start gathering VAD scores for the configured period of time.\r\n this._processTimeout = setTimeout(this._calculateVADScore, PROCESS_TIME_FRAME_SPAN_MS);\r\n }\r\n }\r\n\r\n /**\r\n * Reset the processing context, clear buffer, cancel the timeout trigger.\r\n *\r\n * @returns {void}\r\n */\r\n reset() {\r\n this._processing = false;\r\n this._scoreArray = [];\r\n clearTimeout(this._processTimeout);\r\n }\r\n}\r\n","export enum E2ePingEvents {\r\n /**\r\n * Indicates that the end-to-end round-trip-time for a participant has changed.\r\n */\r\n E2E_RTT_CHANGED = 'e2eping.e2e_rtt_changed'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const E2E_RTT_CHANGED = E2ePingEvents.E2E_RTT_CHANGED;\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport * as JitsiE2EPingEvents from '../../service/e2eping/E2ePingEvents';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The 'type' of a message which designates an e2e ping request.\r\n * @type {string}\r\n */\r\nconst E2E_PING_REQUEST = 'e2e-ping-request';\r\n\r\n/**\r\n * The 'type' of a message which designates an e2e ping response.\r\n * @type {string}\r\n */\r\nconst E2E_PING_RESPONSE = 'e2e-ping-response';\r\n\r\n/**\r\n * The number of requests to wait for before emitting an RTT value.\r\n */\r\nconst DEFAULT_NUM_REQUESTS = 5;\r\n\r\n/**\r\n * The maximum number of messages per second to aim for. This is for the entire\r\n * conference, with the assumption that all endpoints join at once.\r\n */\r\nconst DEFAULT_MAX_MESSAGES_PER_SECOND = 250;\r\n\r\n/**\r\n * The conference size beyond which e2e pings will be disabled.\r\n */\r\nconst DEFAULT_MAX_CONFERENCE_SIZE = 200;\r\n\r\n/**\r\n * Saves e2e ping related state for a single JitsiParticipant.\r\n */\r\nclass ParticipantWrapper {\r\n /**\r\n * Creates a ParticipantWrapper\r\n * @param {JitsiParticipant} participant - The remote participant that this\r\n * object wraps.\r\n * @param {E2ePing} e2eping\r\n */\r\n constructor(participant, e2eping) {\r\n // The JitsiParticipant\r\n this.participant = participant;\r\n\r\n // The E2ePing\r\n this.e2eping = e2eping;\r\n\r\n // Caches the ID\r\n this.id = participant.getId();\r\n\r\n // Recently sent requests\r\n this.requests = {};\r\n\r\n // The ID of the last sent request. We just increment it for each new\r\n // request. Start at 1 so we can consider only thruthy values valid.\r\n this.lastRequestId = 1;\r\n\r\n this.sendRequest = this.sendRequest.bind(this);\r\n this.handleResponse = this.handleResponse.bind(this);\r\n this.maybeLogRttAndStop = this.maybeLogRttAndStop.bind(this);\r\n this.scheduleNext = this.scheduleNext.bind(this);\r\n this.stop = this.stop.bind(this);\r\n this.getDelay = this.getDelay.bind(this);\r\n this.timeout = this.scheduleNext();\r\n }\r\n\r\n /**\r\n * Schedule the next ping to be sent.\r\n */\r\n scheduleNext() {\r\n return window.setTimeout(this.sendRequest, this.getDelay());\r\n }\r\n\r\n /**\r\n * Stop pinging this participant, canceling a scheduled ping, if any.\r\n */\r\n stop() {\r\n if (this.timeout) {\r\n window.clearTimeout(this.timeout);\r\n }\r\n this.e2eping.removeParticipant(this.id);\r\n }\r\n\r\n /**\r\n * Get the delay until the next ping in milliseconds.\r\n */\r\n getDelay() {\r\n const conferenceSize = this.e2eping.conference.getParticipants().length;\r\n const endpointPairs = conferenceSize * (conferenceSize - 1) / 2;\r\n const totalMessages = endpointPairs * this.e2eping.numRequests;\r\n const totalSeconds = totalMessages / this.e2eping.maxMessagesPerSecond;\r\n\r\n // Randomize between .5 and 1.5\r\n const r = 1.5 - Math.random();\r\n const delayBetweenMessages = r * Math.max(1000 * (totalSeconds / this.e2eping.numRequests), 1000);\r\n\r\n return delayBetweenMessages;\r\n }\r\n\r\n /**\r\n * Sends the next ping request.\r\n * @type {*}\r\n */\r\n sendRequest() {\r\n const requestId = this.lastRequestId++;\r\n const requestMessage = {\r\n type: E2E_PING_REQUEST,\r\n id: requestId\r\n };\r\n\r\n this.e2eping.sendMessage(requestMessage, this.id);\r\n this.requests[requestId] = {\r\n id: requestId,\r\n timeSent: window.performance.now()\r\n };\r\n }\r\n\r\n /**\r\n * Handles a response from this participant.\r\n * @type {*}\r\n */\r\n handleResponse(response) {\r\n const request = this.requests[response.id];\r\n\r\n if (request) {\r\n request.rtt = window.performance.now() - request.timeSent;\r\n }\r\n this.maybeLogRttAndStop();\r\n }\r\n\r\n /**\r\n * Check if we've received the pre-configured number of responses, and if\r\n * so log the measured RTT and stop sending requests.\r\n * @type {*}\r\n */\r\n maybeLogRttAndStop() {\r\n // The RTT we'll report is the minimum RTT measured\r\n let rtt = Infinity;\r\n let request, requestId;\r\n let numRequestsWithResponses = 0;\r\n let totalNumRequests = 0;\r\n\r\n for (requestId in this.requests) {\r\n if (this.requests.hasOwnProperty(requestId)) {\r\n request = this.requests[requestId];\r\n\r\n totalNumRequests++;\r\n if (request.rtt) {\r\n numRequestsWithResponses++;\r\n rtt = Math.min(rtt, request.rtt);\r\n }\r\n }\r\n }\r\n\r\n if (numRequestsWithResponses >= this.e2eping.numRequests) {\r\n logger.info(`Measured RTT=${rtt} ms to ${this.id} (in ${this.participant.getProperty('region')})`);\r\n this.stop();\r\n\r\n this.e2eping.conference.eventEmitter.emit(\r\n JitsiE2EPingEvents.E2E_RTT_CHANGED, this.participant, rtt);\r\n\r\n return;\r\n } else if (totalNumRequests > 2 * this.e2eping.numRequests) {\r\n logger.info(`Stopping e2eping for ${this.id} because we sent ${totalNumRequests} with only `\r\n + `${numRequestsWithResponses} responses.`);\r\n this.stop();\r\n\r\n return;\r\n }\r\n\r\n this.timeout = this.scheduleNext();\r\n }\r\n}\r\n\r\n/**\r\n * Implements end-to-end ping (from one conference participant to another) via\r\n * the jitsi-videobridge channel (either WebRTC data channel or web socket).\r\n *\r\n * TODO: use a broadcast message instead of individual pings to each remote\r\n * participant.\r\n *\r\n * This class:\r\n * 1. Sends periodic ping requests to all other participants in the\r\n * conference.\r\n * 2. Responds to ping requests from other participants.\r\n * 3. Fires events with the end-to-end RTT to each participant whenever a\r\n * response is received.\r\n * 4. Fires analytics events with the end-to-end RTT periodically.\r\n */\r\nexport default class E2ePing {\r\n /**\r\n * @param {JitsiConference} conference - The conference.\r\n * @param {Function} sendMessage - The function to use to send a message.\r\n * @param {Object} options\r\n */\r\n constructor(conference, options, sendMessage) {\r\n this.conference = conference;\r\n this.eventEmitter = conference.eventEmitter;\r\n this.sendMessage = sendMessage;\r\n\r\n // Maps a participant ID to its ParticipantWrapper\r\n this.participants = {};\r\n\r\n this.numRequests = DEFAULT_NUM_REQUESTS;\r\n this.maxConferenceSize = DEFAULT_MAX_CONFERENCE_SIZE;\r\n this.maxMessagesPerSecond = DEFAULT_MAX_MESSAGES_PER_SECOND;\r\n\r\n if (options && options.e2eping) {\r\n if (typeof options.e2eping.numRequests === 'number') {\r\n this.numRequests = options.e2eping.numRequests;\r\n }\r\n if (typeof options.e2eping.maxConferenceSize === 'number') {\r\n this.maxConferenceSize = options.e2eping.maxConferenceSize;\r\n }\r\n if (typeof options.e2eping.maxMessagesPerSecond === 'number') {\r\n this.maxMessagesPerSecond = options.e2eping.maxMessagesPerSecond;\r\n }\r\n }\r\n logger.info(\r\n `Initializing e2e ping with numRequests=${this.numRequests}, maxConferenceSize=${this.maxConferenceSize}, `\r\n + `maxMessagesPerSecond=${this.maxMessagesPerSecond}.`);\r\n\r\n this.participantJoined = this.participantJoined.bind(this);\r\n\r\n this.participantLeft = this.participantLeft.bind(this);\r\n conference.on(JitsiConferenceEvents.USER_LEFT, this.participantLeft);\r\n\r\n this.messageReceived = this.messageReceived.bind(this);\r\n conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, this.messageReceived);\r\n\r\n this.conferenceJoined = this.conferenceJoined.bind(this);\r\n conference.on(JitsiConferenceEvents.CONFERENCE_JOINED, this.conferenceJoined);\r\n }\r\n\r\n /**\r\n * Delay processing USER_JOINED events until the MUC is fully joined,\r\n * otherwise the apparent conference size will be wrong.\r\n */\r\n conferenceJoined() {\r\n this.conference.getParticipants().forEach(p => this.participantJoined(p.getId(), p));\r\n this.conference.on(JitsiConferenceEvents.USER_JOINED, this.participantJoined);\r\n }\r\n\r\n /**\r\n * Handles a message that was received.\r\n *\r\n * @param participant - The message sender.\r\n * @param payload - The payload of the message.\r\n */\r\n messageReceived(participant, payload) {\r\n // Listen to E2E PING requests and responses from other participants\r\n // in the conference.\r\n if (payload.type === E2E_PING_REQUEST) {\r\n this.handleRequest(participant.getId(), payload);\r\n } else if (payload.type === E2E_PING_RESPONSE) {\r\n this.handleResponse(participant.getId(), payload);\r\n }\r\n }\r\n\r\n /**\r\n * Handles a participant joining the conference. Starts to send ping\r\n * requests to the participant.\r\n *\r\n * @param {String} id - The ID of the participant.\r\n * @param {JitsiParticipant} participant - The participant that joined.\r\n */\r\n participantJoined(id, participant) {\r\n if (this.participants[id]) {\r\n logger.info(`Participant wrapper already exists for ${id}. Clearing.`);\r\n this.participants[id].stop();\r\n }\r\n\r\n if (this.conference.getParticipants().length > this.maxConferenceSize) {\r\n return;\r\n }\r\n\r\n // We don't need to send e2eping in both directions for a pair of\r\n // endpoints. Force only one direction with just string comparison of\r\n // the IDs.\r\n if (this.conference.myUserId() > id) {\r\n logger.info(`Starting e2eping for participant ${id}`);\r\n this.participants[id] = new ParticipantWrapper(participant, this);\r\n }\r\n }\r\n\r\n /**\r\n * Remove a participant without calling \"stop\".\r\n */\r\n removeParticipant(id) {\r\n if (this.participants[id]) {\r\n delete this.participants[id];\r\n }\r\n }\r\n\r\n /**\r\n * Handles a participant leaving the conference. Stops sending requests.\r\n *\r\n * @param {String} id - The ID of the participant.\r\n */\r\n participantLeft(id) {\r\n if (this.participants[id]) {\r\n this.participants[id].stop();\r\n delete this.participants[id];\r\n }\r\n }\r\n\r\n /**\r\n * Handles a ping request coming from another participant.\r\n *\r\n * @param {string} participantId - The ID of the participant who sent the\r\n * request.\r\n * @param {Object} request - The request.\r\n */\r\n handleRequest(participantId, request) {\r\n // If it's a valid request, just send a response.\r\n if (request && request.id) {\r\n const response = {\r\n type: E2E_PING_RESPONSE,\r\n id: request.id\r\n };\r\n\r\n this.sendMessage(response, participantId);\r\n } else {\r\n logger.info(`Received an invalid e2e ping request from ${participantId}.`);\r\n }\r\n }\r\n\r\n /**\r\n * Handles a ping response coming from another participant\r\n * @param {string} participantId - The ID of the participant who sent the\r\n * response.\r\n * @param {Object} response - The response.\r\n */\r\n handleResponse(participantId, response) {\r\n const participantWrapper = this.participants[participantId];\r\n\r\n if (participantWrapper) {\r\n participantWrapper.handleResponse(response);\r\n }\r\n }\r\n\r\n /**\r\n * Stops this E2ePing (i.e. stop sending requests).\r\n */\r\n stop() {\r\n logger.info('Stopping e2eping');\r\n\r\n this.conference.off(JitsiConferenceEvents.USER_JOINED, this.participantJoined);\r\n this.conference.off(JitsiConferenceEvents.USER_LEFT, this.participantLeft);\r\n this.conference.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, this.messageReceived);\r\n\r\n for (const id in this.participants) {\r\n if (this.participants.hasOwnProperty(id)) {\r\n this.participants[id].stop();\r\n }\r\n }\r\n\r\n this.participants = {};\r\n }\r\n}\r\n\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Emits {@link JitsiConferenceEvents.JVB121_STATUS} events based on the current\r\n * P2P status and the conference participants count. See the event description\r\n * for more info.\r\n */\r\nexport default class Jvb121EventGenerator {\r\n /**\r\n * Creates new Jvb121EventGenerator for the given conference.\r\n * @param {JitsiConference} conference\r\n */\r\n constructor(conference) {\r\n this._conference = conference;\r\n\r\n /**\r\n * Indicates whether it's a one to one JVB conference (true)\r\n * or a multiparty (false). Will be also false if\r\n * the conference is currently in the P2P mode.\r\n * @type {boolean}\r\n * @private\r\n */\r\n this._jvb121 = true;\r\n\r\n this._conference.addEventListener(\r\n JitsiConferenceEvents.USER_JOINED, () => this.evaluateStatus());\r\n this._conference.addEventListener(\r\n JitsiConferenceEvents.USER_LEFT, () => this.evaluateStatus());\r\n this._conference.addEventListener(\r\n JitsiConferenceEvents.P2P_STATUS, () => this.evaluateStatus());\r\n }\r\n\r\n /**\r\n * Checks whether the JVB121 value should be updated and a new event\r\n * emitted.\r\n */\r\n evaluateStatus() {\r\n const oldStatus = this._jvb121;\r\n const newStatus\r\n = !this._conference.isP2PActive()\r\n && this._conference.getParticipantCount() <= 2;\r\n\r\n if (oldStatus !== newStatus) {\r\n this._jvb121 = newStatus;\r\n logger.debug(`JVB121 status ${oldStatus} => ${newStatus}`);\r\n this._conference.eventEmitter.emit(\r\n JitsiConferenceEvents.JVB121_STATUS, oldStatus, newStatus);\r\n }\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport isEqual from 'lodash.isequal';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\n\r\nconst logger = getLogger(__filename);\r\nconst MAX_HEIGHT_ONSTAGE = 2160;\r\nconst MAX_HEIGHT_THUMBNAIL = 180;\r\nconst LASTN_UNLIMITED = -1;\r\n\r\n/**\r\n * This class translates the legacy signaling format between the client and the bridge (that affects bandwidth\r\n * allocation) to the new format described here https://github.com/jitsi/jitsi-videobridge/blob/master/doc/allocation.md\r\n */\r\nclass ReceiverVideoConstraints {\r\n /**\r\n * Creates a new instance.\r\n */\r\n constructor() {\r\n // Default constraints used for endpoints that are not explicitly included in constraints.\r\n // These constraints are used for endpoints that are thumbnails in the stage view.\r\n this._defaultConstraints = { 'maxHeight': MAX_HEIGHT_THUMBNAIL };\r\n\r\n // The number of videos requested from the bridge.\r\n this._lastN = LASTN_UNLIMITED;\r\n\r\n // The number representing the maximum video height the local client should receive from the bridge.\r\n this._maxFrameHeight = MAX_HEIGHT_ONSTAGE;\r\n\r\n // The endpoint IDs of the participants that are currently selected.\r\n this._selectedEndpoints = [];\r\n\r\n this._receiverVideoConstraints = {\r\n constraints: {},\r\n defaultConstraints: this.defaultConstraints,\r\n lastN: this._lastN,\r\n onStageEndpoints: [],\r\n selectedEndpoints: this._selectedEndpoints\r\n };\r\n }\r\n\r\n /**\r\n * Returns the receiver video constraints that need to be sent on the bridge channel.\r\n */\r\n get constraints() {\r\n this._receiverVideoConstraints.lastN = this._lastN;\r\n\r\n if (!this._selectedEndpoints.length) {\r\n return this._receiverVideoConstraints;\r\n }\r\n\r\n // The client is assumed to be in TileView if it has selected more than one endpoint, otherwise it is\r\n // assumed to be in StageView.\r\n this._receiverVideoConstraints.constraints = {};\r\n if (this._selectedEndpoints.length > 1) {\r\n /**\r\n * Tile view.\r\n * Only the default constraints are specified here along with lastN (if it is set).\r\n * {\r\n * 'colibriClass': 'ReceiverVideoConstraints',\r\n * 'defaultConstraints': { 'maxHeight': 360 }\r\n * }\r\n */\r\n this._receiverVideoConstraints.defaultConstraints = { 'maxHeight': this._maxFrameHeight };\r\n this._receiverVideoConstraints.onStageEndpoints = [];\r\n this._receiverVideoConstraints.selectedEndpoints = [];\r\n } else {\r\n /**\r\n * Stage view.\r\n * The participant on stage is specified in onStageEndpoints and a higher maxHeight is specified\r\n * for that endpoint while a default maxHeight of 180 is applied to all the other endpoints.\r\n * {\r\n * 'colibriClass': 'ReceiverVideoConstraints',\r\n * 'onStageEndpoints': ['A'],\r\n * 'defaultConstraints': { 'maxHeight': 180 },\r\n * 'constraints': {\r\n * 'A': { 'maxHeight': 720 }\r\n * }\r\n * }\r\n */\r\n this._receiverVideoConstraints.constraints[this._selectedEndpoints[0]] = {\r\n 'maxHeight': this._maxFrameHeight\r\n };\r\n this._receiverVideoConstraints.defaultConstraints = this._defaultConstraints;\r\n this._receiverVideoConstraints.onStageEndpoints = this._selectedEndpoints;\r\n this._receiverVideoConstraints.selectedEndpoints = [];\r\n }\r\n\r\n return this._receiverVideoConstraints;\r\n }\r\n\r\n /**\r\n * Updates the lastN field of the ReceiverVideoConstraints sent to the bridge.\r\n *\r\n * @param {number} value\r\n * @returns {boolean} Returns true if the the value has been updated, false otherwise.\r\n */\r\n updateLastN(value) {\r\n const changed = this._lastN !== value;\r\n\r\n if (changed) {\r\n this._lastN = value;\r\n logger.debug(`Updating ReceiverVideoConstraints lastN(${value})`);\r\n }\r\n\r\n return changed;\r\n }\r\n\r\n /**\r\n * Updates the resolution (height requested) in the contraints field of the ReceiverVideoConstraints\r\n * sent to the bridge.\r\n *\r\n * @param {number} maxFrameHeight\r\n * @requires {boolean} Returns true if the the value has been updated, false otherwise.\r\n */\r\n updateReceiveResolution(maxFrameHeight) {\r\n const changed = this._maxFrameHeight !== maxFrameHeight;\r\n\r\n if (changed) {\r\n this._maxFrameHeight = maxFrameHeight;\r\n logger.debug(`Updating receive maxFrameHeight: ${maxFrameHeight}`);\r\n }\r\n\r\n return changed;\r\n }\r\n\r\n /**\r\n * Updates the receiver constraints sent to the bridge.\r\n *\r\n * @param {Object} videoConstraints\r\n * @returns {boolean} Returns true if the the value has been updated, false otherwise.\r\n */\r\n updateReceiverVideoConstraints(videoConstraints) {\r\n const changed = !isEqual(this._receiverVideoConstraints, videoConstraints);\r\n\r\n if (changed) {\r\n this._receiverVideoConstraints = videoConstraints;\r\n logger.debug(`Updating ReceiverVideoConstraints ${JSON.stringify(videoConstraints)}`);\r\n }\r\n\r\n return changed;\r\n }\r\n\r\n /**\r\n * Updates the list of selected endpoints.\r\n *\r\n * @param {Array} ids\r\n * @returns {void}\r\n */\r\n updateSelectedEndpoints(ids) {\r\n logger.debug(`Updating selected endpoints: ${JSON.stringify(ids)}`);\r\n this._selectedEndpoints = ids;\r\n }\r\n}\r\n\r\n/**\r\n * This class manages the receive video contraints for a given {@link JitsiConference}. These constraints are\r\n * determined by the application based on how the remote video streams need to be displayed. This class is responsible\r\n * for communicating these constraints to the bridge over the bridge channel.\r\n */\r\nexport default class ReceiveVideoController {\r\n /**\r\n * Creates a new instance for a given conference.\r\n *\r\n * @param {JitsiConference} conference the conference instance for which the new instance will be managing\r\n * the receive video quality constraints.\r\n * @param {RTC} rtc the rtc instance which is responsible for initializing the bridge channel.\r\n */\r\n constructor(conference, rtc) {\r\n this._conference = conference;\r\n this._rtc = rtc;\r\n\r\n const { config } = conference.options;\r\n\r\n // The number of videos requested from the bridge, -1 represents unlimited or all available videos.\r\n this._lastN = config?.startLastN ?? (config?.channelLastN || LASTN_UNLIMITED);\r\n\r\n // The number representing the maximum video height the local client should receive from the bridge.\r\n this._maxFrameHeight = MAX_HEIGHT_ONSTAGE;\r\n\r\n /**\r\n * The map that holds the max frame height requested for each remote source when source-name signaling is\r\n * enabled.\r\n *\r\n * @type Map\r\n */\r\n this._sourceReceiverConstraints = new Map();\r\n\r\n // Enable new receiver constraints by default unless it is explicitly disabled through config.js.\r\n const useNewReceiverConstraints = config?.useNewBandwidthAllocationStrategy ?? true;\r\n\r\n if (useNewReceiverConstraints) {\r\n this._receiverVideoConstraints = new ReceiverVideoConstraints();\r\n const lastNUpdated = this._receiverVideoConstraints.updateLastN(this._lastN);\r\n\r\n lastNUpdated && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);\r\n } else {\r\n this._rtc.setLastN(this._lastN);\r\n }\r\n\r\n // The endpoint IDs of the participants that are currently selected.\r\n this._selectedEndpoints = [];\r\n\r\n this._conference.on(\r\n JitsiConferenceEvents._MEDIA_SESSION_STARTED,\r\n session => this._onMediaSessionStarted(session));\r\n }\r\n\r\n /**\r\n * Returns a map of all the remote source names and the corresponding max frame heights.\r\n *\r\n * @param {number} maxFrameHeight\r\n * @returns\r\n */\r\n _getDefaultSourceReceiverConstraints(mediaSession, maxFrameHeight) {\r\n if (!FeatureFlags.isSourceNameSignalingEnabled()) {\r\n return null;\r\n }\r\n const remoteVideoTracks = mediaSession.peerconnection?.getRemoteTracks(null, MediaType.VIDEO) || [];\r\n const receiverConstraints = new Map();\r\n\r\n for (const track of remoteVideoTracks) {\r\n receiverConstraints.set(track.getSourceName(), maxFrameHeight);\r\n }\r\n\r\n return receiverConstraints;\r\n }\r\n\r\n /**\r\n * Handles the {@link JitsiConferenceEvents.MEDIA_SESSION_STARTED}, that is when the conference creates new media\r\n * session. The preferred receive frameHeight is applied on the media session.\r\n *\r\n * @param {JingleSessionPC} mediaSession - the started media session.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onMediaSessionStarted(mediaSession) {\r\n if (mediaSession.isP2P || !this._receiverVideoConstraints) {\r\n mediaSession.setReceiverVideoConstraint(this._maxFrameHeight, this._sourceReceiverConstraints);\r\n } else {\r\n this._receiverVideoConstraints.updateReceiveResolution(this._maxFrameHeight);\r\n this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);\r\n }\r\n }\r\n\r\n /**\r\n * Returns the lastN value for the conference.\r\n *\r\n * @returns {number}\r\n */\r\n getLastN() {\r\n return this._lastN;\r\n }\r\n\r\n /**\r\n * Elects the participants with the given ids to be the selected participants in order to always receive video\r\n * for this participant (even when last n is enabled).\r\n *\r\n * @param {Array} ids - The user ids.\r\n * @returns {void}\r\n */\r\n selectEndpoints(ids) {\r\n this._selectedEndpoints = ids;\r\n\r\n if (this._receiverVideoConstraints) {\r\n // Filter out the local endpointId from the list of selected endpoints.\r\n const remoteEndpointIds = ids.filter(id => id !== this._conference.myUserId());\r\n const oldConstraints = JSON.parse(JSON.stringify(this._receiverVideoConstraints.constraints));\r\n\r\n remoteEndpointIds.length && this._receiverVideoConstraints.updateSelectedEndpoints(remoteEndpointIds);\r\n const newConstraints = this._receiverVideoConstraints.constraints;\r\n\r\n // Send bridge message only when the constraints change.\r\n if (!isEqual(newConstraints, oldConstraints)) {\r\n this._rtc.setNewReceiverVideoConstraints(newConstraints);\r\n }\r\n\r\n return;\r\n }\r\n this._rtc.selectEndpoints(ids);\r\n }\r\n\r\n /**\r\n * Selects a new value for \"lastN\". The requested amount of videos are going to be delivered after the value is\r\n * in effect. Set to -1 for unlimited or all available videos.\r\n *\r\n * @param {number} value the new value for lastN.\r\n * @returns {void}\r\n */\r\n setLastN(value) {\r\n if (this._lastN !== value) {\r\n this._lastN = value;\r\n\r\n if (this._receiverVideoConstraints) {\r\n const lastNUpdated = this._receiverVideoConstraints.updateLastN(value);\r\n\r\n // Send out the message on the bridge channel if lastN was updated.\r\n lastNUpdated && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);\r\n\r\n return;\r\n }\r\n this._rtc.setLastN(value);\r\n }\r\n }\r\n\r\n /**\r\n * Sets the maximum video resolution the local participant should receive from remote participants.\r\n *\r\n * @param {number|undefined} maxFrameHeight - the new value.\r\n * @returns {void}\r\n */\r\n setPreferredReceiveMaxFrameHeight(maxFrameHeight) {\r\n this._maxFrameHeight = maxFrameHeight;\r\n\r\n for (const session of this._conference.getMediaSessions()) {\r\n if (session.isP2P || !this._receiverVideoConstraints) {\r\n session.setReceiverVideoConstraint(\r\n maxFrameHeight,\r\n this._getDefaultSourceReceiverConstraints(this._maxFrameHeight));\r\n } else {\r\n const resolutionUpdated = this._receiverVideoConstraints.updateReceiveResolution(maxFrameHeight);\r\n\r\n resolutionUpdated\r\n && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Sets the receiver constraints for the conference.\r\n *\r\n * @param {Object} constraints The video constraints.\r\n */\r\n setReceiverConstraints(constraints) {\r\n if (!this._receiverVideoConstraints) {\r\n this._receiverVideoConstraints = new ReceiverVideoConstraints();\r\n }\r\n\r\n const isEndpointsFormat = Object.keys(constraints).includes('onStageEndpoints', 'selectedEndpoints');\r\n const isSourcesFormat = Object.keys(constraints).includes('onStageSources', 'selectedSources');\r\n\r\n if (!FeatureFlags.isSourceNameSignalingEnabled() && isSourcesFormat) {\r\n throw new Error(\r\n '\"onStageSources\" and \"selectedSources\" are not supported when sourceNameSignaling is disabled.'\r\n );\r\n }\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && isEndpointsFormat) {\r\n throw new Error(\r\n '\"onStageEndpoints\" and \"selectedEndpoints\" are not supported when sourceNameSignaling is enabled.'\r\n );\r\n }\r\n const constraintsChanged = this._receiverVideoConstraints.updateReceiverVideoConstraints(constraints);\r\n\r\n if (constraintsChanged) {\r\n this._lastN = constraints.lastN ?? this._lastN;\r\n this._selectedEndpoints = constraints.selectedEndpoints ?? this._selectedEndpoints;\r\n this._rtc.setNewReceiverVideoConstraints(constraints);\r\n\r\n const p2pSession = this._conference.getMediaSessions().find(session => session.isP2P);\r\n\r\n if (!p2pSession) {\r\n return;\r\n }\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n const mappedConstraints = Array.from(Object.entries(constraints.constraints))\r\n .map(constraint => {\r\n constraint[1] = constraint[1].maxHeight;\r\n\r\n return constraint;\r\n });\r\n\r\n this._sourceReceiverConstraints = new Map(mappedConstraints);\r\n\r\n // Send the receiver constraints to the peer through a \"content-modify\" message.\r\n p2pSession.setReceiverVideoConstraint(null, this._sourceReceiverConstraints);\r\n } else {\r\n let maxFrameHeight = Object.values(constraints.constraints)[0]?.maxHeight;\r\n\r\n if (!maxFrameHeight) {\r\n maxFrameHeight = constraints.defaultConstraints?.maxHeight;\r\n }\r\n maxFrameHeight && p2pSession.setReceiverVideoConstraint(maxFrameHeight);\r\n }\r\n }\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\nimport MediaSessionEvents from '../xmpp/MediaSessionEvents';\r\n\r\nconst logger = getLogger(__filename);\r\nconst MAX_LOCAL_RESOLUTION = 2160;\r\n\r\n/**\r\n * The class manages send video constraints across media sessions({@link JingleSessionPC}) which belong to\r\n * {@link JitsiConference}. It finds the lowest common value, between the local user's send preference and\r\n * the remote party's receive preference. Also this module will consider only the active session's receive value,\r\n * because local tracks are shared and while JVB may have no preference, the remote p2p may have and they may be totally\r\n * different.\r\n */\r\nexport default class SendVideoController {\r\n /**\r\n * Creates new instance for a given conference.\r\n *\r\n * @param {JitsiConference} conference - the conference instance for which the new instance will be managing\r\n * the send video quality constraints.\r\n * @param {RTC} rtc - the rtc instance that is responsible for sending the messages on the bridge channel.\r\n */\r\n constructor(conference, rtc) {\r\n this._conference = conference;\r\n this._preferredSendMaxFrameHeight = MAX_LOCAL_RESOLUTION;\r\n this._rtc = rtc;\r\n\r\n /**\r\n * Source name based sender constraints.\r\n * @type {Map};\r\n */\r\n\r\n this._sourceSenderConstraints = new Map();\r\n this._conference.on(\r\n JitsiConferenceEvents._MEDIA_SESSION_STARTED,\r\n session => this._onMediaSessionStarted(session));\r\n this._conference.on(\r\n JitsiConferenceEvents._MEDIA_SESSION_ACTIVE_CHANGED,\r\n () => this._configureConstraintsForLocalSources());\r\n this._rtc.on(\r\n RTCEvents.SENDER_VIDEO_CONSTRAINTS_CHANGED,\r\n videoConstraints => this._onSenderConstraintsReceived(videoConstraints));\r\n }\r\n\r\n /**\r\n * Configures the video encodings on the local sources when a media connection is established or becomes active.\r\n *\r\n * @returns {Promise}\r\n * @private\r\n */\r\n _configureConstraintsForLocalSources() {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n for (const track of this._rtc.getLocalVideoTracks()) {\r\n const sourceName = track.getSourceName();\r\n\r\n sourceName && this._propagateSendMaxFrameHeight(sourceName);\r\n }\r\n } else {\r\n this._propagateSendMaxFrameHeight();\r\n }\r\n }\r\n\r\n /**\r\n * Handles the {@link JitsiConferenceEvents.MEDIA_SESSION_STARTED}, that is when the conference creates new media\r\n * session. It doesn't mean it's already active though. For example the JVB connection may be created after\r\n- * the conference has entered the p2p mode already.\r\n *\r\n * @param {JingleSessionPC} mediaSession - the started media session.\r\n * @private\r\n */\r\n _onMediaSessionStarted(mediaSession) {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n mediaSession.addListener(\r\n MediaSessionEvents.REMOTE_SOURCE_CONSTRAINTS_CHANGED,\r\n (session, sourceConstraints) => {\r\n session === this._conference.getActiveMediaSession()\r\n && sourceConstraints.forEach(constraint => this._onSenderConstraintsReceived(constraint));\r\n });\r\n } else {\r\n mediaSession.addListener(\r\n MediaSessionEvents.REMOTE_VIDEO_CONSTRAINTS_CHANGED,\r\n session => session === this._conference.getActiveMediaSession()\r\n && this._configureConstraintsForLocalSources());\r\n }\r\n }\r\n\r\n /**\r\n * Propagates the video constraints if they have changed.\r\n *\r\n * @param {Object} videoConstraints - The sender video constraints received from the bridge.\r\n * @returns {Promise}\r\n * @private\r\n */\r\n _onSenderConstraintsReceived(videoConstraints) {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n const { maxHeight, sourceName } = videoConstraints;\r\n const localVideoTracks = this._conference.getLocalVideoTracks() ?? [];\r\n\r\n for (const track of localVideoTracks) {\r\n // Propagate the sender constraint only if it has changed.\r\n if (track.getSourceName() === sourceName\r\n && (!this._sourceSenderConstraints.has(sourceName)\r\n || this._sourceSenderConstraints.get(sourceName) !== maxHeight)) {\r\n this._sourceSenderConstraints.set(sourceName, maxHeight);\r\n logger.debug(`Sender constraints for source:${sourceName} changed to maxHeight:${maxHeight}`);\r\n this._propagateSendMaxFrameHeight(sourceName);\r\n }\r\n }\r\n } else if (this._senderVideoConstraints?.idealHeight !== videoConstraints.idealHeight) {\r\n this._senderVideoConstraints = videoConstraints;\r\n this._propagateSendMaxFrameHeight();\r\n }\r\n }\r\n\r\n /**\r\n * Figures out the send video constraint as specified by {@link _selectSendMaxFrameHeight} and sets it on all media\r\n * sessions for the reasons mentioned in this class description.\r\n *\r\n * @param {string} sourceName - The source for which sender constraints have changed.\r\n * @returns {Promise}\r\n * @private\r\n */\r\n _propagateSendMaxFrameHeight(sourceName = null) {\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && !sourceName) {\r\n throw new Error('sourceName missing for calculating the sendMaxHeight for video tracks');\r\n }\r\n const sendMaxFrameHeight = this._selectSendMaxFrameHeight(sourceName);\r\n const promises = [];\r\n\r\n if (sendMaxFrameHeight >= 0) {\r\n for (const session of this._conference.getMediaSessions()) {\r\n promises.push(session.setSenderVideoConstraint(sendMaxFrameHeight, sourceName));\r\n }\r\n }\r\n\r\n return Promise.all(promises);\r\n }\r\n\r\n /**\r\n * Selects the lowest common value for the local video send constraint by looking at local user's preference and\r\n * the active media session's receive preference set by the remote party.\r\n *\r\n * @param {string} sourceName - The source for which sender constraints have changed.\r\n * @returns {number|undefined}\r\n * @private\r\n */\r\n _selectSendMaxFrameHeight(sourceName = null) {\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && !sourceName) {\r\n throw new Error('sourceName missing for calculating the sendMaxHeight for video tracks');\r\n }\r\n const activeMediaSession = this._conference.getActiveMediaSession();\r\n const remoteRecvMaxFrameHeight = activeMediaSession\r\n ? activeMediaSession.isP2P\r\n ? sourceName\r\n ? this._sourceSenderConstraints.get(sourceName)\r\n : activeMediaSession.getRemoteRecvMaxFrameHeight()\r\n : sourceName ? this._sourceSenderConstraints.get(sourceName) : this._senderVideoConstraints?.idealHeight\r\n : undefined;\r\n\r\n if (this._preferredSendMaxFrameHeight >= 0 && remoteRecvMaxFrameHeight >= 0) {\r\n return Math.min(this._preferredSendMaxFrameHeight, remoteRecvMaxFrameHeight);\r\n } else if (remoteRecvMaxFrameHeight >= 0) {\r\n return remoteRecvMaxFrameHeight;\r\n }\r\n\r\n return this._preferredSendMaxFrameHeight;\r\n }\r\n\r\n /**\r\n * Sets local preference for max send video frame height.\r\n *\r\n * @param {number} maxFrameHeight - the new value to set.\r\n * @returns {Promise} - resolved when the operation is complete.\r\n */\r\n setPreferredSendMaxFrameHeight(maxFrameHeight) {\r\n this._preferredSendMaxFrameHeight = maxFrameHeight;\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n const promises = [];\r\n\r\n for (const sourceName of this._sourceSenderConstraints.keys()) {\r\n promises.push(this._propagateSendMaxFrameHeight(sourceName));\r\n }\r\n\r\n return Promise.allSettled(promises);\r\n }\r\n\r\n return this._propagateSendMaxFrameHeight();\r\n }\r\n}\r\n","/**\r\n * A collection of utility functions for taking in XML and parsing it to return\r\n * certain values.\r\n */\r\nexport default {\r\n /**\r\n * Parses the presence update of the focus and returns an object with the\r\n * statuses related to recording.\r\n *\r\n * @param {Node} presence - An XMPP presence update.\r\n * @returns {Object} The current presence values related to recording.\r\n */\r\n getFocusRecordingUpdate(presence) {\r\n const jibriStatus = presence\r\n && presence.getElementsByTagName('jibri-recording-status')[0];\r\n\r\n if (!jibriStatus) {\r\n return;\r\n }\r\n\r\n return {\r\n error: jibriStatus.getAttribute('failure_reason'),\r\n initiator: jibriStatus.getAttribute('initiator'),\r\n recordingMode: jibriStatus.getAttribute('recording_mode'),\r\n sessionID: jibriStatus.getAttribute('session_id'),\r\n status: jibriStatus.getAttribute('status')\r\n };\r\n },\r\n\r\n /**\r\n * Parses the presence update from a hidden domain participant and returns\r\n * an object with the statuses related to recording.\r\n *\r\n * @param {Node} presence - An XMPP presence update.\r\n * @returns {Object} The current presence values related to recording.\r\n */\r\n getHiddenDomainUpdate(presence) {\r\n const liveStreamViewURLContainer\r\n = presence.getElementsByTagName('live-stream-view-url')[0];\r\n const liveStreamViewURL = liveStreamViewURLContainer\r\n && liveStreamViewURLContainer.textContent;\r\n const modeContainer\r\n = presence.getElementsByTagName('mode')[0];\r\n const mode = modeContainer\r\n && modeContainer.textContent\r\n && modeContainer.textContent.toLowerCase();\r\n const sessionIDContainer\r\n = presence.getElementsByTagName('session_id')[0];\r\n const sessionID\r\n = sessionIDContainer && sessionIDContainer.textContent;\r\n\r\n return {\r\n liveStreamViewURL,\r\n mode,\r\n sessionID\r\n };\r\n },\r\n\r\n /**\r\n * Returns the recording session ID from a successful IQ.\r\n *\r\n * @param {Node} response - The response from the IQ.\r\n * @returns {string} The session ID of the recording session.\r\n */\r\n getSessionIdFromIq(response) {\r\n const jibri = response && response.getElementsByTagName('jibri')[0];\r\n\r\n return jibri && jibri.getAttribute('session_id');\r\n },\r\n\r\n /**\r\n * Returns the recording session ID from a presence, if it exists.\r\n *\r\n * @param {Node} presence - An XMPP presence update.\r\n * @returns {string|undefined} The session ID of the recording session.\r\n */\r\n getSessionId(presence) {\r\n const sessionIdContainer\r\n = presence.getElementsByTagName('session_id')[0];\r\n const sessionId = sessionIdContainer && sessionIdContainer.textContent;\r\n\r\n return sessionId;\r\n },\r\n\r\n /**\r\n * Returns whether or not a presence is from the focus.\r\n *\r\n * @param {Node} presence - An XMPP presence update.\r\n * @returns {boolean} True if the presence is from the focus.\r\n */\r\n isFromFocus(presence) {\r\n return presence.getAttribute('from').includes('focus');\r\n }\r\n};\r\n","import { $iq } from 'strophe.js';\r\n\r\nimport recordingXMLUtils from './recordingXMLUtils';\r\n\r\n/**\r\n * Represents a recording session.\r\n */\r\nexport default class JibriSession {\r\n /**\r\n * Initializes a new JibriSession instance.\r\n *\r\n * @constructor\r\n */\r\n constructor(options = {}) {\r\n this._connection = options.connection;\r\n this._mode = options.mode;\r\n\r\n this._setSessionID(options.sessionID);\r\n this.setStatus(options.status);\r\n }\r\n\r\n /**\r\n * Returns the error related to the session instance, if any.\r\n *\r\n * @returns {string|undefined}\r\n */\r\n getError() {\r\n return this._error;\r\n }\r\n\r\n /**\r\n * Returns the session ID of the session instance.\r\n *\r\n * @returns {string|undefined}\r\n */\r\n getID() {\r\n return this._sessionID;\r\n }\r\n\r\n /**\r\n * Returns the initiator of the session instance.\r\n *\r\n * @returns {JitsiParticipant|string} The participant that started the session.\r\n */\r\n getInitiator() {\r\n return this._initiator;\r\n }\r\n\r\n /**\r\n * Returns the streaming URL of the session.\r\n *\r\n * @returns {string|undefined}\r\n */\r\n getLiveStreamViewURL() {\r\n return this._liveStreamViewURL;\r\n }\r\n\r\n /**\r\n * Returns the current status of the session.\r\n *\r\n * @returns {string|undefined}\r\n */\r\n getStatus() {\r\n return this._status;\r\n }\r\n\r\n /**\r\n * Returns the jid of the participant that stopped the session.\r\n *\r\n * @returns {JitsiParticipant|string} The participant that stopped the session.\r\n */\r\n getTerminator() {\r\n return this._terminator;\r\n }\r\n\r\n /**\r\n * Returns the current recording mode of the session, such as \"file\".\r\n *\r\n * @returns {string}\r\n */\r\n getMode() {\r\n return this._mode;\r\n }\r\n\r\n /**\r\n * Sets the last known error message related to the session.\r\n *\r\n * @param {string} error - The error string explaining why the session\r\n * entered an error state.\r\n * @returns {void}\r\n */\r\n setError(error) {\r\n this._error = error;\r\n }\r\n\r\n /**\r\n * Sets the last live stream URL for the session instance. Usually this is\r\n * a YouTube URL and usually this is only set for \"stream\" sessions.\r\n *\r\n * @param {string} url - The live stream URL associated with the session.\r\n * @returns {void}\r\n */\r\n setLiveStreamViewURL(url) {\r\n this._liveStreamViewURL = url;\r\n }\r\n\r\n /**\r\n * Sets the last known status for this recording session.\r\n *\r\n * @param {string} status - The new status to set.\r\n * @returns {void}\r\n */\r\n setStatus(status) {\r\n this._status = status;\r\n }\r\n\r\n /**\r\n * Sets the participant that started the session.\r\n * @param {JitsiParticipant | string} participant - The participant or resource id\r\n * if local participant.\r\n */\r\n setInitiator(participant) {\r\n this._initiator = participant;\r\n }\r\n\r\n /**\r\n * Sets the participant that stopped the session.\r\n * @param {JitsiParticipant | string} participant - The participant or the resource id\r\n * if local participant.\r\n */\r\n setTerminator(participant) {\r\n this._terminator = participant;\r\n }\r\n\r\n /**\r\n * Sends a message to start the actual recording.\r\n *\r\n * @param {Object} options - Additional arguments for starting the\r\n * recording.\r\n * @param {string} [options.appData] - Data specific to the app/service that\r\n * the result file will be uploaded.\r\n * @param {string} [options.broadcastId] - The broadcast ID of an\r\n * associated YouTube stream, used for knowing the URL from which the stream\r\n * can be viewed.\r\n * @param {string} options.focusMucJid - The JID of the focus participant\r\n * that controls recording.\r\n * @param {streamId} options.streamId - Necessary for live streaming, this\r\n * is the stream key needed to start a live streaming session with the\r\n * streaming service provider.\r\n * @returns Promise\r\n */\r\n start({ appData, broadcastId, focusMucJid, streamId }) {\r\n return new Promise((resolve, reject) => {\r\n this._connection.sendIQ(\r\n this._createIQ({\r\n action: 'start',\r\n appData,\r\n focusMucJid,\r\n broadcastId,\r\n streamId\r\n }),\r\n result => {\r\n // All users will eventually receive the 'pending' status\r\n // from the backend, but for the user initiating the session\r\n // it's better to give some instant feedback that recording\r\n // is starting so fire 'pending' here manually.\r\n this.setStatus('pending');\r\n this._setSessionID(\r\n recordingXMLUtils.getSessionIdFromIq(result));\r\n\r\n resolve();\r\n },\r\n error => {\r\n this._setErrorFromIq(error);\r\n\r\n reject(error);\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Sends a message to actually stop the recording session.\r\n *\r\n * @param {Object} options - Additional arguments for stopping the\r\n * recording.\r\n * @param {Object} options.focusMucJid - The JID of the focus participant\r\n * that controls recording.\r\n * @returns Promise\r\n */\r\n stop({ focusMucJid }) {\r\n return new Promise((resolve, reject) => {\r\n this._connection.sendIQ(\r\n this._createIQ({\r\n action: 'stop',\r\n focusMucJid\r\n }),\r\n resolve,\r\n reject);\r\n });\r\n }\r\n\r\n /**\r\n * Generates the message to change the status of the recording session.\r\n *\r\n * @param {string} status - The new status to which the recording session\r\n * should transition.\r\n * @param {string} [options.appData] - Data specific to the app/service that\r\n * the result file will be uploaded.\r\n * @param {string} [options.broadcastId] - The broadcast ID of an\r\n * associated YouTube stream, used for knowing the URL from which the stream\r\n * can be viewed.\r\n * @param {string} options.focusMucJid - The JID of the focus participant\r\n * that controls recording.\r\n * @param {streamId} options.streamId - Necessary for live streaming, this\r\n * is the stream key needed to start a live streaming session with the\r\n * streaming service provider.\r\n * @returns Object - The XMPP IQ message.\r\n */\r\n _createIQ({ action, appData, broadcastId, focusMucJid, streamId }) {\r\n return $iq({\r\n to: focusMucJid,\r\n type: 'set'\r\n })\r\n .c('jibri', {\r\n 'xmlns': 'http://jitsi.org/protocol/jibri',\r\n 'action': action,\r\n 'app_data': appData,\r\n 'recording_mode': this._mode,\r\n 'streamid': streamId,\r\n 'you_tube_broadcast_id': broadcastId\r\n })\r\n .up();\r\n }\r\n\r\n /**\r\n * Handles the error from an iq and stores the error.\r\n *\r\n * @param {Node} errorIq - The error response from an Iq.\r\n * @private\r\n * @returns {void}\r\n */\r\n _setErrorFromIq(errorIq) {\r\n const error = errorIq.getElementsByTagName('error')[0];\r\n\r\n this.setError(error.children[0].tagName);\r\n }\r\n\r\n /**\r\n * Sets the known session ID for this recording session.\r\n *\r\n * @param {string} sessionID\r\n * @private\r\n * @returns {void}\r\n */\r\n _setSessionID(sessionID) {\r\n this._sessionID = sessionID;\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\n\r\nimport JibriSession from './JibriSession';\r\nimport recordingXMLUtils from './recordingXMLUtils';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * A class responsible for starting and stopping recording sessions and emitting\r\n * state updates for them.\r\n */\r\nclass RecordingManager {\r\n /**\r\n * Initialize {@code RecordingManager} with other objects that are necessary\r\n * for starting a recording.\r\n *\r\n * @param {ChatRoom} chatRoom - The chat room to handle.\r\n * @returns {void}\r\n */\r\n constructor(chatRoom) {\r\n /**\r\n * All known recording sessions from the current conference.\r\n */\r\n this._sessions = {};\r\n\r\n this._chatRoom = chatRoom;\r\n\r\n this.onPresence = this.onPresence.bind(this);\r\n\r\n this._chatRoom.eventEmitter.addListener(\r\n XMPPEvents.PRESENCE_RECEIVED, this.onPresence);\r\n }\r\n\r\n /**\r\n * Finds an existing recording session by session ID.\r\n *\r\n * @param {string} sessionID - The session ID associated with the recording.\r\n * @returns {JibriSession|undefined}\r\n */\r\n getSession(sessionID) {\r\n return this._sessions[sessionID];\r\n }\r\n\r\n /**\r\n * Callback to invoke to parse through a presence update to find recording\r\n * related updates (from Jibri participant doing the recording and the\r\n * focus which controls recording).\r\n *\r\n * @param {Object} event - The presence data from the pubsub event.\r\n * @param {Node} event.presence - An XMPP presence update.\r\n * @param {boolean} event.fromHiddenDomain - Whether or not the update comes\r\n * from a participant that is trusted but not visible, as would be the case\r\n * with the Jibri recorder participant.\r\n * @returns {void}\r\n */\r\n onPresence({ fromHiddenDomain, presence }) {\r\n if (recordingXMLUtils.isFromFocus(presence)) {\r\n this._handleFocusPresence(presence);\r\n } else if (fromHiddenDomain) {\r\n this._handleJibriPresence(presence);\r\n }\r\n }\r\n\r\n /**\r\n * Start a recording session.\r\n *\r\n * @param {Object} options - Configuration for the recording.\r\n * @param {string} [options.appData] - Data specific to the app/service that\r\n * the result file will be uploaded.\r\n * @param {string} [optional] options.broadcastId - The channel on which a\r\n * live stream will occur.\r\n * @param {string} options.mode - The mode in which recording should be\r\n * started. Recognized values are \"file\" and \"stream\".\r\n * @param {string} [optional] options.streamId - The stream key to be used\r\n * for live stream broadcasting. Required for live streaming.\r\n * @returns {Promise} A promise for starting a recording, which will pass\r\n * back the session on success. The promise resolves after receiving an\r\n * acknowledgment of the start request success or fail.\r\n */\r\n startRecording(options) {\r\n const session = new JibriSession({\r\n ...options,\r\n connection: this._chatRoom.connection\r\n });\r\n\r\n return session.start({\r\n appData: options.appData,\r\n broadcastId: options.broadcastId,\r\n focusMucJid: this._chatRoom.focusMucJid,\r\n streamId: options.streamId\r\n })\r\n .then(() => {\r\n // Only store the session and emit if the session has not been\r\n // added already. This is a workaround for the session getting\r\n // created due to a presence update to announce a \"pending\"\r\n // recording being received before JibriSession#start finishes.\r\n if (!this.getSession(session.getID())) {\r\n this._addSession(session);\r\n this._emitSessionUpdate(session);\r\n }\r\n\r\n return session;\r\n })\r\n .catch(error => {\r\n this._emitSessionUpdate(session);\r\n\r\n return Promise.reject(error);\r\n });\r\n }\r\n\r\n /**\r\n * Stop a recording session.\r\n *\r\n * @param {string} sessionID - The ID associated with the recording session\r\n * to be stopped.\r\n * @returns {Promise} The promise resolves after receiving an\r\n * acknowledgment of the stop request success or fail.\r\n */\r\n stopRecording(sessionID) {\r\n const session = this.getSession(sessionID);\r\n\r\n if (session) {\r\n return session.stop({ focusMucJid: this._chatRoom.focusMucJid });\r\n }\r\n\r\n return Promise.reject(new Error('Could not find session'));\r\n }\r\n\r\n /**\r\n * Stores a reference to the passed in JibriSession.\r\n *\r\n * @param {string} session - The JibriSession instance to store.\r\n * @returns {void}\r\n */\r\n _addSession(session) {\r\n this._sessions[session.getID()] = session;\r\n }\r\n\r\n /**\r\n * Create a new instance of a recording session and stores a reference to\r\n * it.\r\n *\r\n * @param {string} sessionID - The session ID of the recording in progress.\r\n * @param {string} status - The current status of the recording session.\r\n * @param {string} mode - The recording mode of the session.\r\n * @returns {JibriSession}\r\n */\r\n _createSession(sessionID, status, mode) {\r\n const session = new JibriSession({\r\n connection: this._chatRoom.connection,\r\n focusMucJid: this._chatRoom.focusMucJid,\r\n mode,\r\n sessionID,\r\n status\r\n });\r\n\r\n this._addSession(session);\r\n\r\n return session;\r\n }\r\n\r\n /**\r\n * Notifies listeners of an update to a recording session.\r\n *\r\n * @param {JibriSession} session - The session that has been updated.\r\n * @param {string|undefined} initiator - The jid of the initiator of the update.\r\n */\r\n _emitSessionUpdate(session, initiator) {\r\n this._chatRoom.eventEmitter.emit(\r\n XMPPEvents.RECORDER_STATE_CHANGED, session, initiator);\r\n }\r\n\r\n /**\r\n * Parses presence to update an existing JibriSession or to create a new\r\n * JibriSession.\r\n *\r\n * @param {Node} presence - An XMPP presence update.\r\n * @returns {void}\r\n */\r\n _handleFocusPresence(presence) {\r\n const jibriStatus = recordingXMLUtils.getFocusRecordingUpdate(presence);\r\n\r\n if (!jibriStatus) {\r\n return;\r\n }\r\n\r\n const { error, initiator, recordingMode, sessionID, status } = jibriStatus;\r\n\r\n // We'll look for an existing session or create one (in case we're a\r\n // participant joining a call with an existing recording going on).\r\n let session = this.getSession(sessionID);\r\n\r\n // Handle the case where a status update is received in presence but\r\n // the local participant has joined while the JibriSession has already\r\n // ended.\r\n if (!session && status === 'off') {\r\n logger.warn(\r\n 'Ignoring recording presence update',\r\n 'Received a new session with status off.');\r\n\r\n return;\r\n }\r\n\r\n // Jicofo sends updates via presence, and any extension in presence\r\n // is sent until it is explicitly removed. It's difficult for\r\n // Jicofo to know when a presence has been sent once, so it won't\r\n // remove jibri status extension. This means we may receive the same\r\n // status update more than once, so check for that here\r\n if (session\r\n && session.getStatus() === status\r\n && session.getError() === error) {\r\n logger.warn('Ignoring duplicate presence update: ',\r\n JSON.stringify(jibriStatus));\r\n\r\n return;\r\n }\r\n\r\n if (!session) {\r\n session = this._createSession(sessionID, status, recordingMode);\r\n }\r\n\r\n session.setStatus(status);\r\n\r\n if (error) {\r\n session.setError(error);\r\n }\r\n\r\n this._emitSessionUpdate(session, initiator);\r\n }\r\n\r\n /**\r\n * Handles updates from the Jibri which can broadcast a YouTube URL that\r\n * needs to be updated in a JibriSession.\r\n *\r\n * @param {Node} presence - An XMPP presence update.\r\n * @returns {void}\r\n */\r\n _handleJibriPresence(presence) {\r\n const { liveStreamViewURL, mode, sessionID }\r\n = recordingXMLUtils.getHiddenDomainUpdate(presence);\r\n\r\n if (!sessionID) {\r\n logger.warn(\r\n 'Ignoring potential jibri presence due to no session id.');\r\n\r\n return;\r\n }\r\n\r\n let session = this.getSession(sessionID);\r\n\r\n if (!session) {\r\n session = this._createSession(sessionID, '', mode);\r\n }\r\n\r\n session.setLiveStreamViewURL(liveStreamViewURL);\r\n\r\n this._emitSessionUpdate(session);\r\n }\r\n}\r\n\r\nexport default RecordingManager;\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as ConferenceEvents from '../../JitsiConferenceEvents';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport * as ConnectionQualityEvents from '../../service/connectivity/ConnectionQualityEvents';\r\nimport { createAudioOutputProblemEvent } from '../../service/statistics/AnalyticsEvents';\r\n\r\nimport Statistics from './statistics';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Number of local samples that will be used for comparison before and after the remote sample is received.\r\n */\r\nconst NUMBER_OF_LOCAL_SAMPLES = 2;\r\n\r\n/**\r\n * Collects the average audio levels per participant from the local stats and the stats received by every remote\r\n * participant and compares them to detect potential audio problem for a participant.\r\n */\r\nexport default class AudioOutputProblemDetector {\r\n\r\n /**\r\n * Creates new AudioOutputProblemDetector instance.\r\n *\r\n * @param {JitsiCofnerence} conference - The conference instance to be monitored.\r\n */\r\n constructor(conference) {\r\n this._conference = conference;\r\n this._localAudioLevelCache = {};\r\n this._reportedParticipants = [];\r\n this._audioProblemCandidates = {};\r\n this._numberOfRemoteAudioLevelsReceived = {};\r\n this._onLocalAudioLevelsReport = this._onLocalAudioLevelsReport.bind(this);\r\n this._onRemoteAudioLevelReceived = this._onRemoteAudioLevelReceived.bind(this);\r\n this._clearUserData = this._clearUserData.bind(this);\r\n this._conference.on(ConnectionQualityEvents.REMOTE_STATS_UPDATED, this._onRemoteAudioLevelReceived);\r\n this._conference.statistics.addConnectionStatsListener(this._onLocalAudioLevelsReport);\r\n this._conference.on(ConferenceEvents.USER_LEFT, this._clearUserData);\r\n }\r\n\r\n /**\r\n * A listener for audio level data received by a remote participant.\r\n *\r\n * @param {string} userID - The user id of the participant that sent the data.\r\n * @param {number} audioLevel - The average audio level value.\r\n * @returns {void}\r\n */\r\n _onRemoteAudioLevelReceived(userID, { avgAudioLevels }) {\r\n const numberOfReports = (this._numberOfRemoteAudioLevelsReceived[userID] + 1) || 0;\r\n\r\n this._numberOfRemoteAudioLevelsReceived[userID] = numberOfReports;\r\n\r\n if (this._reportedParticipants.indexOf(userID) !== -1 || (userID in this._audioProblemCandidates)\r\n || avgAudioLevels <= 0 || numberOfReports < 3) {\r\n return;\r\n }\r\n\r\n const participant = this._conference.getParticipantById(userID);\r\n\r\n if (participant) {\r\n const tracks = participant.getTracksByMediaType(MediaType.AUDIO);\r\n\r\n if (tracks.length > 0 && participant.isAudioMuted()) {\r\n // We don't need to report an error if everything seems fine with the participant and its tracks but\r\n // the participant is audio muted. Since those are average audio levels we potentially can receive non\r\n // zero values for muted track.\r\n return;\r\n }\r\n }\r\n\r\n const localAudioLevels = this._localAudioLevelCache[userID];\r\n\r\n if (!Array.isArray(localAudioLevels) || localAudioLevels.every(audioLevel => audioLevel === 0)) {\r\n this._audioProblemCandidates[userID] = {\r\n remoteAudioLevels: avgAudioLevels,\r\n localAudioLevels: []\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * A listener for audio level data retrieved by the local stats.\r\n *\r\n * @param {TraceablePeerConnection} tpc - The TraceablePeerConnection instance used to gather the data.\r\n * @param {Object} avgAudioLevels - The average audio levels per participant.\r\n * @returns {void}\r\n */\r\n _onLocalAudioLevelsReport(tpc, { avgAudioLevels }) {\r\n if (tpc !== this._conference.getActivePeerConnection()) {\r\n return;\r\n }\r\n\r\n Object.keys(avgAudioLevels).forEach(userID => {\r\n if (this._reportedParticipants.indexOf(userID) !== -1) {\r\n return;\r\n }\r\n\r\n const localAudioLevels = this._localAudioLevelCache[userID];\r\n\r\n if (!Array.isArray(localAudioLevels)) {\r\n this._localAudioLevelCache[userID] = [ ];\r\n } else if (localAudioLevels.length >= NUMBER_OF_LOCAL_SAMPLES) {\r\n localAudioLevels.shift();\r\n }\r\n\r\n this._localAudioLevelCache[userID].push(avgAudioLevels[userID]);\r\n });\r\n\r\n\r\n Object.keys(this._audioProblemCandidates).forEach(userID => {\r\n const { localAudioLevels, remoteAudioLevels } = this._audioProblemCandidates[userID];\r\n\r\n localAudioLevels.push(avgAudioLevels[userID]);\r\n\r\n if (localAudioLevels.length === NUMBER_OF_LOCAL_SAMPLES) {\r\n if (localAudioLevels.every(audioLevel => typeof audioLevel === 'undefined' || audioLevel === 0)) {\r\n const localAudioLevelsString = JSON.stringify(localAudioLevels);\r\n\r\n Statistics.sendAnalytics(\r\n createAudioOutputProblemEvent(userID, localAudioLevelsString, remoteAudioLevels));\r\n logger.warn(`A potential problem is detected with the audio output for participant ${\r\n userID}, local audio levels: ${localAudioLevelsString}, remote audio levels: ${\r\n remoteAudioLevels}`);\r\n this._reportedParticipants.push(userID);\r\n this._clearUserData(userID);\r\n }\r\n\r\n delete this._audioProblemCandidates[userID];\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Clears the data stored for a participant.\r\n *\r\n * @param {string} userID - The id of the participant.\r\n * @returns {void}\r\n */\r\n _clearUserData(userID) {\r\n delete this._localAudioLevelCache[userID];\r\n }\r\n\r\n /**\r\n * Disposes the allocated resources.\r\n *\r\n * @returns {void}\r\n */\r\n dispose() {\r\n this._conference.off(ConnectionQualityEvents.REMOTE_STATS_UPDATED, this._onRemoteAudioLevelReceived);\r\n this._conference.off(ConferenceEvents.USER_LEFT, this._clearUserData);\r\n this._conference.statistics.removeConnectionStatsListener(this._onLocalAudioLevelsReport);\r\n this._localAudioLevelCache = undefined;\r\n this._audioProblemCandidates = undefined;\r\n this._reportedParticipants = undefined;\r\n this._numberOfRemoteAudioLevelsReceived = undefined;\r\n this._conference = undefined;\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport isEqual from 'lodash.isequal';\r\n\r\nimport * as ConferenceEvents from '../../JitsiConferenceEvents';\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport * as ConnectionQualityEvents\r\n from '../../service/connectivity/ConnectionQualityEvents';\r\nimport {\r\n createRtpStatsEvent,\r\n createTransportStatsEvent\r\n} from '../../service/statistics/AnalyticsEvents';\r\nimport browser from '../browser';\r\n\r\nimport Statistics from './statistics';\r\n\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * This will calculate an average for one, named stat and submit it to\r\n * the analytics module when requested. It automatically counts the samples.\r\n */\r\nclass AverageStatReport {\r\n /**\r\n * Creates new AverageStatReport for given name.\r\n * @param {string} name that's the name of the event that will be reported\r\n * to the analytics module.\r\n */\r\n constructor(name) {\r\n this.name = name;\r\n this.count = 0;\r\n this.sum = 0;\r\n this.samples = [];\r\n }\r\n\r\n /**\r\n * Adds the next value that will be included in the average when\r\n * {@link calculate} is called.\r\n * @param {number} nextValue\r\n */\r\n addNext(nextValue) {\r\n if (typeof nextValue !== 'number') {\r\n logger.error(\r\n `${this.name} - invalid value for idx: ${this.count}`,\r\n nextValue);\r\n } else if (!isNaN(nextValue)) {\r\n this.sum += nextValue;\r\n this.samples.push(nextValue);\r\n this.count += 1;\r\n }\r\n }\r\n\r\n /**\r\n * Calculates an average for the samples collected using {@link addNext}.\r\n * @return {number|NaN} an average of all collected samples or NaN\r\n * if no samples were collected.\r\n */\r\n calculate() {\r\n return this.sum / this.count;\r\n }\r\n\r\n /**\r\n * Appends the report to the analytics \"data\" object. The object will be\r\n * set under prefix + {@link this.name} key.\r\n * @param {Object} report the analytics \"data\" object\r\n */\r\n appendReport(report) {\r\n report[`${this.name}_avg`] = this.calculate();\r\n report[`${this.name}_samples`] = JSON.stringify(this.samples);\r\n }\r\n\r\n /**\r\n * Clears all memory of any samples collected, so that new average can be\r\n * calculated using this instance.\r\n */\r\n reset() {\r\n this.samples = [];\r\n this.sum = 0;\r\n this.count = 0;\r\n }\r\n}\r\n\r\n/**\r\n * Class gathers the stats that are calculated and reported for a\r\n * {@link TraceablePeerConnection} even if it's not currently active. For\r\n * example we want to monitor RTT for the JVB connection while in P2P mode.\r\n */\r\nclass ConnectionAvgStats {\r\n /**\r\n * Creates new ConnectionAvgStats\r\n * @param {AvgRTPStatsReporter} avgRtpStatsReporter\r\n * @param {boolean} isP2P\r\n * @param {number} n the number of samples, before arithmetic mean is to be\r\n * calculated and values submitted to the analytics module.\r\n */\r\n constructor(avgRtpStatsReporter, isP2P, n) {\r\n /**\r\n * Is this instance for JVB or P2P connection ?\r\n * @type {boolean}\r\n */\r\n this.isP2P = isP2P;\r\n\r\n /**\r\n * How many samples are to be included in arithmetic mean calculation.\r\n * @type {number}\r\n * @private\r\n */\r\n this._n = n;\r\n\r\n /**\r\n * The current sample index. Starts from 0 and goes up to {@link _n})\r\n * when analytics report will be submitted.\r\n * @type {number}\r\n * @private\r\n */\r\n this._sampleIdx = 0;\r\n\r\n /**\r\n * Average round trip time reported by the ICE candidate pair.\r\n * @type {AverageStatReport}\r\n */\r\n this._avgRTT = new AverageStatReport('rtt');\r\n\r\n /**\r\n * Map stores average RTT to the JVB reported by remote participants.\r\n * Mapped per participant id {@link JitsiParticipant.getId}.\r\n *\r\n * This is used only when {@link ConnectionAvgStats.isP2P} equals to\r\n * false.\r\n *\r\n * @type {Map}\r\n * @private\r\n */\r\n this._avgRemoteRTTMap = new Map();\r\n\r\n /**\r\n * The conference for which stats will be collected and reported.\r\n * @type {JitsiConference}\r\n * @private\r\n */\r\n this._avgRtpStatsReporter = avgRtpStatsReporter;\r\n\r\n /**\r\n * The latest average E2E RTT for the JVB connection only.\r\n *\r\n * This is used only when {@link ConnectionAvgStats.isP2P} equals to\r\n * false.\r\n *\r\n * @type {number}\r\n */\r\n this._avgEnd2EndRTT = undefined;\r\n\r\n this._onConnectionStats = (tpc, stats) => {\r\n if (this.isP2P === tpc.isP2P) {\r\n this._calculateAvgStats(stats);\r\n }\r\n };\r\n\r\n const conference = avgRtpStatsReporter._conference;\r\n\r\n conference.statistics.addConnectionStatsListener(\r\n this._onConnectionStats);\r\n\r\n if (!this.isP2P) {\r\n this._onUserLeft = id => this._avgRemoteRTTMap.delete(id);\r\n conference.on(ConferenceEvents.USER_LEFT, this._onUserLeft);\r\n\r\n this._onRemoteStatsUpdated\r\n = (id, data) => this._processRemoteStats(id, data);\r\n conference.on(\r\n ConnectionQualityEvents.REMOTE_STATS_UPDATED,\r\n this._onRemoteStatsUpdated);\r\n }\r\n }\r\n\r\n /**\r\n * Processes next batch of stats.\r\n * @param {go figure} data\r\n * @private\r\n */\r\n _calculateAvgStats(data) {\r\n if (!data) {\r\n logger.error('No stats');\r\n\r\n return;\r\n }\r\n\r\n if (browser.supportsRTTStatistics()) {\r\n if (data.transport && data.transport.length) {\r\n this._avgRTT.addNext(data.transport[0].rtt);\r\n }\r\n }\r\n\r\n this._sampleIdx += 1;\r\n\r\n if (this._sampleIdx >= this._n) {\r\n if (browser.supportsRTTStatistics()) {\r\n const conference = this._avgRtpStatsReporter._conference;\r\n\r\n const batchReport = {\r\n p2p: this.isP2P,\r\n 'conference_size': conference.getParticipantCount()\r\n };\r\n\r\n if (data.transport && data.transport.length) {\r\n Object.assign(batchReport, {\r\n 'local_candidate_type':\r\n data.transport[0].localCandidateType,\r\n 'remote_candidate_type':\r\n data.transport[0].remoteCandidateType,\r\n 'transport_type': data.transport[0].type\r\n });\r\n }\r\n\r\n this._avgRTT.appendReport(batchReport);\r\n\r\n if (this.isP2P) {\r\n // Report RTT diff only for P2P.\r\n const jvbEnd2EndRTT = this\r\n ._avgRtpStatsReporter.jvbStatsMonitor._avgEnd2EndRTT;\r\n\r\n if (!isNaN(jvbEnd2EndRTT)) {\r\n // eslint-disable-next-line dot-notation\r\n batchReport['rtt_diff']\r\n = this._avgRTT.calculate() - jvbEnd2EndRTT;\r\n }\r\n } else {\r\n // Report end to end RTT only for JVB.\r\n const avgRemoteRTT = this._calculateAvgRemoteRTT();\r\n const avgLocalRTT = this._avgRTT.calculate();\r\n\r\n this._avgEnd2EndRTT = avgLocalRTT + avgRemoteRTT;\r\n\r\n if (!isNaN(avgLocalRTT) && !isNaN(avgRemoteRTT)) {\r\n // eslint-disable-next-line dot-notation\r\n batchReport['end2end_rtt_avg'] = this._avgEnd2EndRTT;\r\n }\r\n }\r\n\r\n Statistics.sendAnalytics(createRtpStatsEvent(batchReport));\r\n }\r\n\r\n this._resetAvgStats();\r\n }\r\n }\r\n\r\n /**\r\n * Calculates arithmetic mean of all RTTs towards the JVB reported by\r\n * participants.\r\n * @return {number|NaN} NaN if not available (not enough data)\r\n * @private\r\n */\r\n _calculateAvgRemoteRTT() {\r\n let count = 0, sum = 0;\r\n\r\n // FIXME should we ignore RTT for participant\r\n // who \"is having connectivity issues\" ?\r\n for (const remoteAvg of this._avgRemoteRTTMap.values()) {\r\n const avg = remoteAvg.calculate();\r\n\r\n if (!isNaN(avg)) {\r\n sum += avg;\r\n count += 1;\r\n remoteAvg.reset();\r\n }\r\n }\r\n\r\n return sum / count;\r\n }\r\n\r\n /**\r\n * Processes {@link ConnectionQualityEvents.REMOTE_STATS_UPDATED} to analyse\r\n * RTT towards the JVB reported by each participant.\r\n * @param {string} id {@link JitsiParticipant.getId}\r\n * @param {go figure in ConnectionQuality.js} data\r\n * @private\r\n */\r\n _processRemoteStats(id, data) {\r\n const validData = typeof data.jvbRTT === 'number';\r\n let rttAvg = this._avgRemoteRTTMap.get(id);\r\n\r\n if (!rttAvg && validData) {\r\n rttAvg = new AverageStatReport(`${id}_stat_rtt`);\r\n this._avgRemoteRTTMap.set(id, rttAvg);\r\n }\r\n\r\n if (validData) {\r\n rttAvg.addNext(data.jvbRTT);\r\n } else if (rttAvg) {\r\n this._avgRemoteRTTMap.delete(id);\r\n }\r\n }\r\n\r\n /**\r\n * Reset cache of all averages and {@link _sampleIdx}.\r\n * @private\r\n */\r\n _resetAvgStats() {\r\n this._avgRTT.reset();\r\n if (this._avgRemoteRTTMap) {\r\n this._avgRemoteRTTMap.clear();\r\n }\r\n this._sampleIdx = 0;\r\n }\r\n\r\n /**\r\n *\r\n */\r\n dispose() {\r\n\r\n const conference = this._avgRtpStatsReporter._conference;\r\n\r\n conference.statistics.removeConnectionStatsListener(\r\n this._onConnectionStats);\r\n if (!this.isP2P) {\r\n conference.off(\r\n ConnectionQualityEvents.REMOTE_STATS_UPDATED,\r\n this._onRemoteStatsUpdated);\r\n conference.off(\r\n ConferenceEvents.USER_LEFT,\r\n this._onUserLeft);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Reports average RTP statistics values (arithmetic mean) to the analytics\r\n * module for things like bit rate, bandwidth, packet loss etc. It keeps track\r\n * of the P2P vs JVB conference modes and submits the values under different\r\n * namespaces (the events for P2P mode have 'p2p.' prefix). Every switch between\r\n * P2P mode resets the data collected so far and averages are calculated from\r\n * scratch.\r\n */\r\nexport default class AvgRTPStatsReporter {\r\n /**\r\n * Creates new instance of AvgRTPStatsReporter\r\n * @param {JitsiConference} conference\r\n * @param {number} n the number of samples, before arithmetic mean is to be\r\n * calculated and values submitted to the analytics module.\r\n */\r\n constructor(conference, n) {\r\n /**\r\n * How many {@link ConnectionQualityEvents.LOCAL_STATS_UPDATED} samples\r\n * are to be included in arithmetic mean calculation.\r\n * @type {number}\r\n * @private\r\n */\r\n this._n = n;\r\n\r\n if (n > 0) {\r\n logger.info(`Avg RTP stats will be calculated every ${n} samples`);\r\n } else {\r\n logger.info('Avg RTP stats reports are disabled.');\r\n\r\n // Do not initialize\r\n return;\r\n }\r\n\r\n /**\r\n * The current sample index. Starts from 0 and goes up to {@link _n})\r\n * when analytics report will be submitted.\r\n * @type {number}\r\n * @private\r\n */\r\n this._sampleIdx = 0;\r\n\r\n /**\r\n * The conference for which stats will be collected and reported.\r\n * @type {JitsiConference}\r\n * @private\r\n */\r\n this._conference = conference;\r\n\r\n /**\r\n * Average audio upload bitrate\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgAudioBitrateUp\r\n = new AverageStatReport('bitrate_audio_upload');\r\n\r\n /**\r\n * Average audio download bitrate\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgAudioBitrateDown\r\n = new AverageStatReport('bitrate_audio_download');\r\n\r\n /**\r\n * Average video upload bitrate\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgVideoBitrateUp\r\n = new AverageStatReport('bitrate_video_upload');\r\n\r\n /**\r\n * Average video download bitrate\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgVideoBitrateDown\r\n = new AverageStatReport('bitrate_video_download');\r\n\r\n /**\r\n * Average upload bandwidth\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgBandwidthUp\r\n = new AverageStatReport('bandwidth_upload');\r\n\r\n /**\r\n * Average download bandwidth\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgBandwidthDown\r\n = new AverageStatReport('bandwidth_download');\r\n\r\n /**\r\n * Average total packet loss\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgPacketLossTotal\r\n = new AverageStatReport('packet_loss_total');\r\n\r\n /**\r\n * Average upload packet loss\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgPacketLossUp\r\n = new AverageStatReport('packet_loss_upload');\r\n\r\n /**\r\n * Average download packet loss\r\n * XXX What are the units?\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgPacketLossDown\r\n = new AverageStatReport('packet_loss_download');\r\n\r\n /**\r\n * Average FPS for remote videos\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgRemoteFPS = new AverageStatReport('framerate_remote');\r\n\r\n /**\r\n * Average FPS for remote screen streaming videos (reported only if not\r\n * a NaN).\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgRemoteScreenFPS\r\n = new AverageStatReport('framerate_screen_remote');\r\n\r\n /**\r\n * Average FPS for local video (camera)\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgLocalFPS = new AverageStatReport('framerate_local');\r\n\r\n /**\r\n * Average FPS for local screen streaming video (reported only if not\r\n * a NaN).\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgLocalScreenFPS\r\n = new AverageStatReport('framerate_screen_local');\r\n\r\n /**\r\n * Average pixels for remote screen streaming videos (reported only if\r\n * not a NaN).\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgRemoteCameraPixels\r\n = new AverageStatReport('pixels_remote');\r\n\r\n /**\r\n * Average pixels for remote screen streaming videos (reported only if\r\n * not a NaN).\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgRemoteScreenPixels\r\n = new AverageStatReport('pixels_screen_remote');\r\n\r\n /**\r\n * Average pixels for local video (camera)\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgLocalCameraPixels\r\n = new AverageStatReport('pixels_local');\r\n\r\n /**\r\n * Average pixels for local screen streaming video (reported only if not\r\n * a NaN).\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgLocalScreenPixels\r\n = new AverageStatReport('pixels_screen_local');\r\n\r\n /**\r\n * Average connection quality as defined by\r\n * the {@link ConnectionQuality} module.\r\n * @type {AverageStatReport}\r\n * @private\r\n */\r\n this._avgCQ = new AverageStatReport('connection_quality');\r\n\r\n this._cachedTransportStats = undefined;\r\n\r\n this._onLocalStatsUpdated = data => {\r\n this._calculateAvgStats(data);\r\n this._maybeSendTransportAnalyticsEvent(data);\r\n };\r\n conference.on(\r\n ConnectionQualityEvents.LOCAL_STATS_UPDATED,\r\n this._onLocalStatsUpdated);\r\n\r\n this._onP2PStatusChanged = () => {\r\n logger.debug('Resetting average stats calculation');\r\n this._resetAvgStats();\r\n this.jvbStatsMonitor._resetAvgStats();\r\n this.p2pStatsMonitor._resetAvgStats();\r\n };\r\n conference.on(\r\n ConferenceEvents.P2P_STATUS,\r\n this._onP2PStatusChanged);\r\n\r\n this._onJvb121StatusChanged = (oldStatus, newStatus) => {\r\n // We want to reset only on the transition from false => true,\r\n // because otherwise those stats are resetted on JVB <=> P2P\r\n // transition.\r\n if (newStatus === true) {\r\n logger.info('Resetting JVB avg RTP stats');\r\n this._resetAvgJvbStats();\r\n }\r\n };\r\n conference.on(\r\n ConferenceEvents.JVB121_STATUS,\r\n this._onJvb121StatusChanged);\r\n\r\n this.jvbStatsMonitor\r\n = new ConnectionAvgStats(this, false /* JVB */, n);\r\n\r\n this.p2pStatsMonitor\r\n = new ConnectionAvgStats(this, true /* P2P */, n);\r\n }\r\n\r\n /**\r\n * Processes next batch of stats reported on\r\n * {@link ConnectionQualityEvents.LOCAL_STATS_UPDATED}.\r\n * @param {go figure} data\r\n * @private\r\n */\r\n _calculateAvgStats(data) {\r\n\r\n if (!data) {\r\n logger.error('No stats');\r\n\r\n return;\r\n }\r\n\r\n const isP2P = this._conference.isP2PActive();\r\n const confSize = this._conference.getParticipantCount();\r\n\r\n if (!isP2P && confSize < 2) {\r\n\r\n // There's no point in collecting stats for a JVB conference of 1.\r\n // That happens for short period of time after everyone leaves\r\n // the room, until Jicofo terminates the session.\r\n return;\r\n }\r\n\r\n /* Uncomment to figure out stats structure\r\n for (const key in data) {\r\n if (data.hasOwnProperty(key)) {\r\n logger.info(`local stat ${key}: `, data[key]);\r\n }\r\n } */\r\n\r\n const bitrate = data.bitrate;\r\n const bandwidth = data.bandwidth;\r\n const packetLoss = data.packetLoss;\r\n const frameRate = data.framerate;\r\n const resolution = data.resolution;\r\n\r\n if (!bitrate) {\r\n logger.error('No \"bitrate\"');\r\n\r\n return;\r\n } else if (!bandwidth) {\r\n logger.error('No \"bandwidth\"');\r\n\r\n return;\r\n } else if (!packetLoss) {\r\n logger.error('No \"packetloss\"');\r\n\r\n return;\r\n } else if (!frameRate) {\r\n logger.error('No \"framerate\"');\r\n\r\n return;\r\n } else if (!resolution) {\r\n logger.error('No resolution');\r\n\r\n return;\r\n }\r\n\r\n this._avgAudioBitrateUp.addNext(bitrate.audio.upload);\r\n this._avgAudioBitrateDown.addNext(bitrate.audio.download);\r\n\r\n this._avgVideoBitrateUp.addNext(bitrate.video.upload);\r\n this._avgVideoBitrateDown.addNext(bitrate.video.download);\r\n\r\n if (browser.supportsBandwidthStatistics()) {\r\n this._avgBandwidthUp.addNext(bandwidth.upload);\r\n this._avgBandwidthDown.addNext(bandwidth.download);\r\n }\r\n\r\n this._avgPacketLossUp.addNext(packetLoss.upload);\r\n this._avgPacketLossDown.addNext(packetLoss.download);\r\n this._avgPacketLossTotal.addNext(packetLoss.total);\r\n\r\n this._avgCQ.addNext(data.connectionQuality);\r\n\r\n if (frameRate) {\r\n this._avgRemoteFPS.addNext(\r\n this._calculateAvgVideoFps(\r\n frameRate, false /* remote */, VideoType.CAMERA));\r\n this._avgRemoteScreenFPS.addNext(\r\n this._calculateAvgVideoFps(\r\n frameRate, false /* remote */, VideoType.DESKTOP));\r\n\r\n this._avgLocalFPS.addNext(\r\n this._calculateAvgVideoFps(\r\n frameRate, true /* local */, VideoType.CAMERA));\r\n this._avgLocalScreenFPS.addNext(\r\n this._calculateAvgVideoFps(\r\n frameRate, true /* local */, VideoType.DESKTOP));\r\n }\r\n\r\n if (resolution) {\r\n this._avgRemoteCameraPixels.addNext(\r\n this._calculateAvgVideoPixels(\r\n resolution, false /* remote */, VideoType.CAMERA));\r\n\r\n this._avgRemoteScreenPixels.addNext(\r\n this._calculateAvgVideoPixels(\r\n resolution, false /* remote */, VideoType.DESKTOP));\r\n\r\n this._avgLocalCameraPixels.addNext(\r\n this._calculateAvgVideoPixels(\r\n resolution, true /* local */, VideoType.CAMERA));\r\n\r\n this._avgLocalScreenPixels.addNext(\r\n this._calculateAvgVideoPixels(\r\n resolution, true /* local */, VideoType.DESKTOP));\r\n }\r\n\r\n this._sampleIdx += 1;\r\n\r\n if (this._sampleIdx >= this._n) {\r\n\r\n const batchReport = {\r\n p2p: isP2P,\r\n 'conference_size': confSize\r\n };\r\n\r\n if (data.transport && data.transport.length) {\r\n Object.assign(batchReport, {\r\n 'local_candidate_type':\r\n data.transport[0].localCandidateType,\r\n 'remote_candidate_type':\r\n data.transport[0].remoteCandidateType,\r\n 'transport_type': data.transport[0].type\r\n });\r\n }\r\n\r\n this._avgAudioBitrateUp.appendReport(batchReport);\r\n this._avgAudioBitrateDown.appendReport(batchReport);\r\n\r\n this._avgVideoBitrateUp.appendReport(batchReport);\r\n this._avgVideoBitrateDown.appendReport(batchReport);\r\n\r\n if (browser.supportsBandwidthStatistics()) {\r\n this._avgBandwidthUp.appendReport(batchReport);\r\n this._avgBandwidthDown.appendReport(batchReport);\r\n }\r\n this._avgPacketLossUp.appendReport(batchReport);\r\n this._avgPacketLossDown.appendReport(batchReport);\r\n this._avgPacketLossTotal.appendReport(batchReport);\r\n\r\n this._avgRemoteFPS.appendReport(batchReport);\r\n if (!isNaN(this._avgRemoteScreenFPS.calculate())) {\r\n this._avgRemoteScreenFPS.appendReport(batchReport);\r\n }\r\n this._avgLocalFPS.appendReport(batchReport);\r\n if (!isNaN(this._avgLocalScreenFPS.calculate())) {\r\n this._avgLocalScreenFPS.appendReport(batchReport);\r\n }\r\n\r\n this._avgRemoteCameraPixels.appendReport(batchReport);\r\n if (!isNaN(this._avgRemoteScreenPixels.calculate())) {\r\n this._avgRemoteScreenPixels.appendReport(batchReport);\r\n }\r\n this._avgLocalCameraPixels.appendReport(batchReport);\r\n if (!isNaN(this._avgLocalScreenPixels.calculate())) {\r\n this._avgLocalScreenPixels.appendReport(batchReport);\r\n }\r\n\r\n this._avgCQ.appendReport(batchReport);\r\n\r\n Statistics.sendAnalytics(createRtpStatsEvent(batchReport));\r\n\r\n this._resetAvgStats();\r\n }\r\n }\r\n\r\n /**\r\n * Calculates average number of pixels for the report\r\n *\r\n * @param {map} peerResolutions a map of peer resolutions\r\n * @param {boolean} isLocal if the average is to be calculated for the local\r\n * video or false if for remote videos.\r\n * @param {VideoType} videoType\r\n * @return {number|NaN} average number of pixels or NaN if there\r\n * are no samples.\r\n * @private\r\n */\r\n _calculateAvgVideoPixels(peerResolutions, isLocal, videoType) {\r\n let peerPixelsSum = 0;\r\n let peerCount = 0;\r\n const myID = this._conference.myUserId();\r\n\r\n for (const peerID of Object.keys(peerResolutions)) {\r\n if (isLocal ? peerID === myID : peerID !== myID) {\r\n const participant\r\n = isLocal\r\n ? null\r\n : this._conference.getParticipantById(peerID);\r\n const videosResolution = peerResolutions[peerID];\r\n\r\n // Do not continue without participant for non local peerID\r\n if ((isLocal || participant) && videosResolution) {\r\n const peerAvgPixels = this._calculatePeerAvgVideoPixels(\r\n videosResolution, participant, videoType);\r\n\r\n if (!isNaN(peerAvgPixels)) {\r\n peerPixelsSum += peerAvgPixels;\r\n peerCount += 1;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return peerPixelsSum / peerCount;\r\n }\r\n\r\n /**\r\n * Calculate average pixels for either remote or local participant\r\n * @param {object} videos maps resolution per video SSRC\r\n * @param {JitsiParticipant|null} participant remote participant or\r\n * null for local video pixels calculation.\r\n * @param {VideoType} videoType the type of the video for which an average\r\n * will be calculated.\r\n * @return {number|NaN} average video pixels of all participant's videos or\r\n * NaN if currently not available\r\n * @private\r\n */\r\n _calculatePeerAvgVideoPixels(videos, participant, videoType) {\r\n let ssrcs = Object.keys(videos).map(ssrc => Number(ssrc));\r\n let videoTracks = null;\r\n\r\n // NOTE that this method is supposed to be called for the stats\r\n // received from the current peerconnection.\r\n const tpc = this._conference.getActivePeerConnection();\r\n\r\n if (participant) {\r\n videoTracks = participant.getTracksByMediaType(MediaType.VIDEO);\r\n if (videoTracks) {\r\n ssrcs\r\n = ssrcs.filter(\r\n ssrc => videoTracks.find(\r\n track =>\r\n !track.isMuted()\r\n && track.getSSRC() === ssrc\r\n && track.videoType === videoType));\r\n }\r\n } else {\r\n videoTracks = this._conference.getLocalTracks(MediaType.VIDEO);\r\n ssrcs\r\n = ssrcs.filter(\r\n ssrc => videoTracks.find(\r\n track =>\r\n !track.isMuted()\r\n && tpc.getLocalSSRC(track) === ssrc\r\n && track.videoType === videoType));\r\n }\r\n\r\n let peerPixelsSum = 0;\r\n let peerSsrcCount = 0;\r\n\r\n for (const ssrc of ssrcs) {\r\n const peerSsrcPixels\r\n = Number(videos[ssrc].height) * Number(videos[ssrc].width);\r\n\r\n // FPS is reported as 0 for users with no video\r\n if (!isNaN(peerSsrcPixels) && peerSsrcPixels > 0) {\r\n peerPixelsSum += peerSsrcPixels;\r\n peerSsrcCount += 1;\r\n }\r\n }\r\n\r\n return peerPixelsSum / peerSsrcCount;\r\n }\r\n\r\n\r\n /**\r\n * Calculates average FPS for the report\r\n * @param {go figure} frameRate\r\n * @param {boolean} isLocal if the average is to be calculated for the local\r\n * video or false if for remote videos.\r\n * @param {VideoType} videoType\r\n * @return {number|NaN} average FPS or NaN if there are no samples.\r\n * @private\r\n */\r\n _calculateAvgVideoFps(frameRate, isLocal, videoType) {\r\n let peerFpsSum = 0;\r\n let peerCount = 0;\r\n const myID = this._conference.myUserId();\r\n\r\n for (const peerID of Object.keys(frameRate)) {\r\n if (isLocal ? peerID === myID : peerID !== myID) {\r\n const participant\r\n = isLocal\r\n ? null : this._conference.getParticipantById(peerID);\r\n const videosFps = frameRate[peerID];\r\n\r\n // Do not continue without participant for non local peerID\r\n if ((isLocal || participant) && videosFps) {\r\n const peerAvgFPS\r\n = this._calculatePeerAvgVideoFps(\r\n videosFps, participant, videoType);\r\n\r\n if (!isNaN(peerAvgFPS)) {\r\n peerFpsSum += peerAvgFPS;\r\n peerCount += 1;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return peerFpsSum / peerCount;\r\n }\r\n\r\n /**\r\n * Calculate average FPS for either remote or local participant\r\n * @param {object} videos maps FPS per video SSRC\r\n * @param {JitsiParticipant|null} participant remote participant or\r\n * null for local FPS calculation.\r\n * @param {VideoType} videoType the type of the video for which an average\r\n * will be calculated.\r\n * @return {number|NaN} average FPS of all participant's videos or\r\n * NaN if currently not available\r\n * @private\r\n */\r\n _calculatePeerAvgVideoFps(videos, participant, videoType) {\r\n let ssrcs = Object.keys(videos).map(ssrc => Number(ssrc));\r\n let videoTracks = null;\r\n\r\n // NOTE that this method is supposed to be called for the stats\r\n // received from the current peerconnection.\r\n const tpc = this._conference.getActivePeerConnection();\r\n\r\n if (participant) {\r\n videoTracks = participant.getTracksByMediaType(MediaType.VIDEO);\r\n if (videoTracks) {\r\n ssrcs\r\n = ssrcs.filter(\r\n ssrc => videoTracks.find(\r\n track => !track.isMuted()\r\n && track.getSSRC() === ssrc\r\n && track.videoType === videoType));\r\n }\r\n } else {\r\n videoTracks = this._conference.getLocalTracks(MediaType.VIDEO);\r\n ssrcs\r\n = ssrcs.filter(\r\n ssrc => videoTracks.find(\r\n track => !track.isMuted()\r\n && tpc.getLocalSSRC(track) === ssrc\r\n && track.videoType === videoType));\r\n }\r\n\r\n let peerFpsSum = 0;\r\n let peerSsrcCount = 0;\r\n\r\n for (const ssrc of ssrcs) {\r\n const peerSsrcFps = Number(videos[ssrc]);\r\n\r\n // FPS is reported as 0 for users with no video\r\n if (!isNaN(peerSsrcFps) && peerSsrcFps > 0) {\r\n peerFpsSum += peerSsrcFps;\r\n peerSsrcCount += 1;\r\n }\r\n }\r\n\r\n return peerFpsSum / peerSsrcCount;\r\n }\r\n\r\n /**\r\n * Sends the 'transport.stats' analytics event whenever we detect that\r\n * there is a change in the local or remote candidate type on the transport\r\n * that is currently selected.\r\n * @param {*} data\r\n * @private\r\n */\r\n _maybeSendTransportAnalyticsEvent(data) {\r\n if (!data || !data.transport || !data.transport.length) {\r\n return;\r\n }\r\n const transportStats = {\r\n p2p: data.transport[0].p2p,\r\n 'local_candidate_type': data.transport[0].localCandidateType,\r\n 'remote_candidate_type': data.transport[0].remoteCandidateType,\r\n 'transport_type': data.transport[0].type\r\n };\r\n\r\n if (!this._cachedTransportStats || !isEqual(transportStats, this._cachedTransportStats)) {\r\n this._cachedTransportStats = transportStats;\r\n Statistics.sendAnalytics(createTransportStatsEvent(transportStats));\r\n }\r\n }\r\n\r\n /**\r\n * Resets the stats related to JVB connection. Must not be called when in\r\n * P2P mode, because then the {@link AverageStatReport} instances are\r\n * tracking P2P stats. Note that this should never happen unless something\r\n * is wrong with the P2P and JVB121 events.\r\n * @private\r\n */\r\n _resetAvgJvbStats() {\r\n this._resetAvgStats();\r\n this.jvbStatsMonitor._resetAvgStats();\r\n }\r\n\r\n /**\r\n * Reset cache of all averages and {@link _sampleIdx}.\r\n * @private\r\n */\r\n _resetAvgStats() {\r\n this._avgAudioBitrateUp.reset();\r\n this._avgAudioBitrateDown.reset();\r\n\r\n this._avgVideoBitrateUp.reset();\r\n this._avgVideoBitrateDown.reset();\r\n\r\n this._avgBandwidthUp.reset();\r\n this._avgBandwidthDown.reset();\r\n\r\n this._avgPacketLossUp.reset();\r\n this._avgPacketLossDown.reset();\r\n this._avgPacketLossTotal.reset();\r\n\r\n this._avgRemoteFPS.reset();\r\n this._avgRemoteScreenFPS.reset();\r\n this._avgLocalFPS.reset();\r\n this._avgLocalScreenFPS.reset();\r\n\r\n this._avgRemoteCameraPixels.reset();\r\n this._avgRemoteScreenPixels.reset();\r\n this._avgLocalCameraPixels.reset();\r\n this._avgLocalScreenPixels.reset();\r\n\r\n this._avgCQ.reset();\r\n\r\n this._sampleIdx = 0;\r\n }\r\n\r\n /**\r\n * Unregisters all event listeners and stops working.\r\n */\r\n dispose() {\r\n this._conference.off(\r\n ConferenceEvents.P2P_STATUS,\r\n this._onP2PStatusChanged);\r\n this._conference.off(\r\n ConnectionQualityEvents.LOCAL_STATS_UPDATED,\r\n this._onLocalStatsUpdated);\r\n this._conference.off(\r\n ConferenceEvents.JVB121_STATUS,\r\n this._onJvb121StatusChanged);\r\n this.jvbStatsMonitor.dispose();\r\n this.p2pStatsMonitor.dispose();\r\n }\r\n}\r\n","import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\n\r\nimport SpeakerStats from './SpeakerStats';\r\n\r\n/**\r\n * A collection for tracking speaker stats. Attaches listeners\r\n * to the conference to automatically update on tracked events.\r\n */\r\nexport default class SpeakerStatsCollector {\r\n /**\r\n * Initializes a new SpeakerStatsCollector instance.\r\n *\r\n * @constructor\r\n * @param {JitsiConference} conference - The conference to track.\r\n * @returns {void}\r\n */\r\n constructor(conference) {\r\n this.stats = {\r\n users: {\r\n\r\n // userId: SpeakerStats\r\n },\r\n dominantSpeakerId: null\r\n };\r\n\r\n const userId = conference.myUserId();\r\n\r\n this.stats.users[userId] = new SpeakerStats(userId, null, true);\r\n this.conference = conference;\r\n\r\n conference.addEventListener(\r\n JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,\r\n this._onDominantSpeaker.bind(this));\r\n conference.addEventListener(\r\n JitsiConferenceEvents.USER_JOINED,\r\n this._onUserJoin.bind(this));\r\n conference.addEventListener(\r\n JitsiConferenceEvents.USER_LEFT,\r\n this._onUserLeave.bind(this));\r\n conference.addEventListener(\r\n JitsiConferenceEvents.DISPLAY_NAME_CHANGED,\r\n this._onDisplayNameChange.bind(this));\r\n conference.addEventListener(\r\n JitsiConferenceEvents.FACE_LANDMARK_ADDED,\r\n this._onFaceLandmarkAdd.bind(this));\r\n if (conference.xmpp) {\r\n conference.xmpp.addListener(\r\n XMPPEvents.SPEAKER_STATS_RECEIVED,\r\n this._updateStats.bind(this));\r\n }\r\n }\r\n\r\n /**\r\n * Reacts to dominant speaker change events by changing its speaker stats\r\n * models to reflect the current dominant speaker.\r\n *\r\n * @param {string} dominantSpeakerId - The user id of the new\r\n * dominant speaker.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onDominantSpeaker(dominantSpeakerId) {\r\n const oldDominantSpeaker\r\n = this.stats.users[this.stats.dominantSpeakerId];\r\n const newDominantSpeaker = this.stats.users[dominantSpeakerId];\r\n\r\n oldDominantSpeaker && oldDominantSpeaker.setDominantSpeaker(false);\r\n newDominantSpeaker && newDominantSpeaker.setDominantSpeaker(true);\r\n this.stats.dominantSpeakerId = dominantSpeakerId;\r\n }\r\n\r\n /**\r\n * Reacts to user join events by creating a new SpeakerStats model.\r\n *\r\n * @param {string} userId - The user id of the new user.\r\n * @param {JitsiParticipant} - The JitsiParticipant model for the new user.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onUserJoin(userId, participant) {\r\n if (participant.isHidden()) {\r\n return;\r\n }\r\n\r\n if (!this.stats.users[userId]) {\r\n this.stats.users[userId] = new SpeakerStats(userId, participant.getDisplayName());\r\n }\r\n }\r\n\r\n /**\r\n * Reacts to user leave events by updating the associated user's\r\n * SpeakerStats model.\r\n *\r\n * @param {string} userId - The user id of the user that left.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onUserLeave(userId) {\r\n const savedUser = this.stats.users[userId];\r\n\r\n if (savedUser) {\r\n savedUser.markAsHasLeft();\r\n }\r\n }\r\n\r\n /**\r\n * Reacts to user name change events by updating the last known name\r\n * tracked in the associated SpeakerStats model.\r\n *\r\n * @param {string} userId - The user id of the user that left.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onDisplayNameChange(userId, newName) {\r\n const savedUser = this.stats.users[userId];\r\n\r\n if (savedUser) {\r\n savedUser.setDisplayName(newName);\r\n }\r\n }\r\n\r\n /**\r\n * Processes a new face landmark object of a remote user.\r\n *\r\n * @param {string} userId - The user id of the user that left.\r\n * @param {Object} data - The face landmark object.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onFaceLandmarkAdd(userId, data) {\r\n const savedUser = this.stats.users[userId];\r\n\r\n if (savedUser && data.faceExpression) {\r\n savedUser.addFaceExpression(data.faceExpression, data.duration);\r\n }\r\n }\r\n\r\n /**\r\n * Return a copy of the tracked SpeakerStats models.\r\n *\r\n * @returns {Object} The keys are the user ids and the values are the\r\n * associated user's SpeakerStats model.\r\n */\r\n getStats() {\r\n return this.stats.users;\r\n }\r\n\r\n /**\r\n * Updates of the current stats is requested, passing the new values.\r\n *\r\n * @param {Object} newStats - The new values used to update current one.\r\n * @private\r\n */\r\n _updateStats(newStats) {\r\n for (const userId in newStats) { // eslint-disable-line guard-for-in\r\n let speakerStatsToUpdate;\r\n const newParticipant = this.conference.getParticipantById(userId);\r\n\r\n // we want to ignore hidden participants\r\n if (!newParticipant || !newParticipant.isHidden()) {\r\n if (this.stats.users[userId]) {\r\n speakerStatsToUpdate = this.stats.users[userId];\r\n\r\n if (!speakerStatsToUpdate.getDisplayName()) {\r\n speakerStatsToUpdate\r\n .setDisplayName(newStats[userId].displayName);\r\n }\r\n } else {\r\n speakerStatsToUpdate = new SpeakerStats(\r\n userId, newStats[userId].displayName);\r\n this.stats.users[userId] = speakerStatsToUpdate;\r\n speakerStatsToUpdate.markAsHasLeft();\r\n }\r\n }\r\n\r\n speakerStatsToUpdate.totalDominantSpeakerTime\r\n = newStats[userId].totalDominantSpeakerTime;\r\n\r\n speakerStatsToUpdate.setFaceExpressions(newStats[userId].faceExpressions);\r\n }\r\n }\r\n}\r\n","/* eslint-disable max-params */\r\n\r\n/**\r\n * This object stores variables needed around the recording of an audio stream\r\n * and passing this recording along with additional information along to\r\n * different processes\r\n */\r\nexport default class RecordingResult {\r\n /**\r\n * @param blob the recording audio stream as a single blob\r\n * @param name the name of the person of the audio stream\r\n * @param startTime the time in UTC when recording of the audiostream started\r\n * @param wordArray the recorder audio stream transcribed as an array of Word objects\r\n */\r\n constructor(blob, name, startTime, wordArray) {\r\n this.blob = blob;\r\n this.name = name;\r\n this.startTime = startTime;\r\n this.wordArray = wordArray;\r\n }\r\n}\r\n","/**\r\n * A TrackRecorder object holds all the information needed for recording a\r\n * single JitsiTrack (either remote or local)\r\n * @param track The JitsiTrack the object is going to hold\r\n */\r\nexport default class TrackRecorder {\r\n /**\r\n * @param track The JitsiTrack the object is going to hold\r\n */\r\n constructor(track) {\r\n // The JitsiTrack holding the stream\r\n this.track = track;\r\n\r\n // The MediaRecorder recording the stream\r\n this.recorder = null;\r\n\r\n // The array of data chunks recorded from the stream\r\n // acts as a buffer until the data is stored on disk\r\n this.data = null;\r\n\r\n // the name of the person of the JitsiTrack. This can be undefined and/or\r\n // not unique\r\n this.name = null;\r\n\r\n // the time of the start of the recording\r\n this.startTime = null;\r\n }\r\n}\r\n","import RecordingResult from './recordingResult';\r\nimport TrackRecorder from './trackRecorder';\r\n\r\n/**\r\n * Possible audio formats MIME types\r\n */\r\nconst AUDIO_WEBM = 'audio/webm'; // Supported in chrome\r\nconst AUDIO_OGG = 'audio/ogg'; // Supported in firefox\r\n\r\n/**\r\n * Starts the recording of a JitsiTrack in a TrackRecorder object.\r\n * This will also define the timestamp and try to update the name\r\n * @param trackRecorder the TrackRecorder to start\r\n */\r\nfunction startRecorder(trackRecorder) {\r\n if (trackRecorder.recorder === undefined) {\r\n throw new Error('Passed an object to startRecorder which is not a '\r\n + 'TrackRecorder object');\r\n }\r\n trackRecorder.recorder.start();\r\n trackRecorder.startTime = new Date();\r\n}\r\n\r\n/**\r\n * Stops the recording of a JitsiTrack in a TrackRecorder object.\r\n * This will also try to update the name\r\n * @param trackRecorder the TrackRecorder to stop\r\n */\r\nfunction stopRecorder(trackRecorder) {\r\n if (trackRecorder.recorder === undefined) {\r\n throw new Error('Passed an object to stopRecorder which is not a '\r\n + 'TrackRecorder object');\r\n }\r\n trackRecorder.recorder.stop();\r\n}\r\n\r\n/**\r\n * Determines which kind of audio recording the browser supports\r\n * chrome supports \"audio/webm\" and firefox supports \"audio/ogg\"\r\n */\r\nfunction determineCorrectFileType() {\r\n if (MediaRecorder.isTypeSupported(AUDIO_WEBM)) {\r\n return AUDIO_WEBM;\r\n } else if (MediaRecorder.isTypeSupported(AUDIO_OGG)) {\r\n return AUDIO_OGG;\r\n }\r\n throw new Error(\r\n 'unable to create a MediaRecorder with the right mimetype!');\r\n}\r\n\r\n/**\r\n * main exported object of the file, holding all\r\n * relevant functions and variables for the outside world\r\n * @param jitsiConference the jitsiConference which this object\r\n * is going to record\r\n */\r\nfunction AudioRecorder(jitsiConference) {\r\n // array of TrackRecorders, where each trackRecorder\r\n // holds the JitsiTrack, MediaRecorder and recorder data\r\n this.recorders = [];\r\n\r\n // get which file type is supported by the current browser\r\n this.fileType = determineCorrectFileType();\r\n\r\n // boolean flag for active recording\r\n this.isRecording = false;\r\n\r\n // the jitsiconference the object is recording\r\n this.jitsiConference = jitsiConference;\r\n}\r\n\r\n/**\r\n * Add the exported module so that it can be accessed by other files\r\n */\r\nAudioRecorder.determineCorrectFileType = determineCorrectFileType;\r\n\r\n/**\r\n * Adds a new TrackRecorder object to the array.\r\n *\r\n * @param track the track potentially holding an audio stream\r\n */\r\nAudioRecorder.prototype.addTrack = function(track) {\r\n if (track.isAudioTrack()) {\r\n // create the track recorder\r\n const trackRecorder = this.instantiateTrackRecorder(track);\r\n\r\n // push it to the local array of all recorders\r\n\r\n this.recorders.push(trackRecorder);\r\n\r\n // update the name of the trackRecorders\r\n this.updateNames();\r\n\r\n // If we're already recording, immediately start recording this new\r\n // track.\r\n if (this.isRecording) {\r\n startRecorder(trackRecorder);\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Creates a TrackRecorder object. Also creates the MediaRecorder and\r\n * data array for the trackRecorder.\r\n * @param track the JitsiTrack holding the audio MediaStream(s)\r\n */\r\nAudioRecorder.prototype.instantiateTrackRecorder = function(track) {\r\n const trackRecorder = new TrackRecorder(track);\r\n\r\n // Create a new stream which only holds the audio track\r\n const originalStream = trackRecorder.track.getOriginalStream();\r\n const stream = new MediaStream();\r\n\r\n originalStream.getAudioTracks().forEach(t => stream.addTrack(t));\r\n\r\n // Create the MediaRecorder\r\n trackRecorder.recorder = new MediaRecorder(stream,\r\n { mimeType: this.fileType });\r\n\r\n // array for holding the recorder data. Resets it when\r\n // audio already has been recorder once\r\n trackRecorder.data = [];\r\n\r\n // function handling a dataEvent, e.g the stream gets new data\r\n trackRecorder.recorder.ondataavailable = function(dataEvent) {\r\n if (dataEvent.data.size > 0) {\r\n trackRecorder.data.push(dataEvent.data);\r\n }\r\n };\r\n\r\n return trackRecorder;\r\n};\r\n\r\n/**\r\n * Notifies the module that a specific track has stopped, e.g participant left\r\n * the conference.\r\n * if the recording has not started yet, the TrackRecorder will be removed from\r\n * the array. If the recording has started, the recorder will stop recording\r\n * but not removed from the array so that the recorded stream can still be\r\n * accessed\r\n *\r\n * @param {JitsiTrack} track the JitsiTrack to remove from the recording session\r\n */\r\nAudioRecorder.prototype.removeTrack = function(track) {\r\n if (track.isVideoTrack()) {\r\n return;\r\n }\r\n\r\n const array = this.recorders;\r\n let i;\r\n\r\n for (i = 0; i < array.length; i++) {\r\n if (array[i].track.getParticipantId() === track.getParticipantId()) {\r\n const recorderToRemove = array[i];\r\n\r\n if (this.isRecording) {\r\n stopRecorder(recorderToRemove);\r\n } else {\r\n // remove the TrackRecorder from the array\r\n array.splice(i, 1);\r\n }\r\n }\r\n }\r\n\r\n // make sure the names are up to date\r\n this.updateNames();\r\n};\r\n\r\n/**\r\n * Tries to update the name value of all TrackRecorder in the array.\r\n * If it hasn't changed,it will keep the exiting name. If it changes to a\r\n * undefined value, the old value will also be kept.\r\n */\r\nAudioRecorder.prototype.updateNames = function() {\r\n const conference = this.jitsiConference;\r\n\r\n this.recorders.forEach(trackRecorder => {\r\n if (trackRecorder.track.isLocal()) {\r\n trackRecorder.name = 'the transcriber';\r\n } else {\r\n const id = trackRecorder.track.getParticipantId();\r\n const participant = conference.getParticipantById(id);\r\n const newName = participant.getDisplayName();\r\n\r\n if (newName !== 'undefined') {\r\n trackRecorder.name = newName;\r\n }\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * Starts the audio recording of every local and remote track\r\n */\r\nAudioRecorder.prototype.start = function() {\r\n if (this.isRecording) {\r\n throw new Error('audiorecorder is already recording');\r\n }\r\n\r\n // set boolean isRecording flag to true so if new participants join the\r\n // conference, that track can instantly start recording as well\r\n this.isRecording = true;\r\n\r\n // start all the mediaRecorders\r\n this.recorders.forEach(trackRecorder => startRecorder(trackRecorder));\r\n\r\n // log that recording has started\r\n console.log(\r\n `Started the recording of the audio. There are currently ${\r\n this.recorders.length} recorders active.`);\r\n};\r\n\r\n/**\r\n * Stops the audio recording of every local and remote track\r\n */\r\nAudioRecorder.prototype.stop = function() {\r\n // set the boolean flag to false\r\n this.isRecording = false;\r\n\r\n // stop all recorders\r\n this.recorders.forEach(trackRecorder => stopRecorder(trackRecorder));\r\n console.log('stopped recording');\r\n};\r\n\r\n/**\r\n * link hacking to download all recorded audio streams\r\n */\r\nAudioRecorder.prototype.download = function() {\r\n this.recorders.forEach(trackRecorder => {\r\n const blob = new Blob(trackRecorder.data, { type: this.fileType });\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement('a');\r\n\r\n document.body.appendChild(a);\r\n a.style = 'display: none';\r\n a.href = url;\r\n a.download = `test.${this.fileType.split('/')[1]}`;\r\n a.click();\r\n window.URL.revokeObjectURL(url);\r\n });\r\n};\r\n\r\n/**\r\n * returns the audio files of all recorders as an array of objects,\r\n * which include the name of the owner of the track and the starting time stamp\r\n * @returns {Array} an array of RecordingResult objects\r\n */\r\nAudioRecorder.prototype.getRecordingResults = function() {\r\n if (this.isRecording) {\r\n throw new Error(\r\n 'cannot get blobs because the AudioRecorder is still recording!');\r\n }\r\n\r\n // make sure the names are up to date before sending them off\r\n this.updateNames();\r\n\r\n const array = [];\r\n\r\n this.recorders.forEach(\r\n recorder =>\r\n array.push(\r\n new RecordingResult(\r\n new Blob(recorder.data, { type: this.fileType }),\r\n recorder.name,\r\n recorder.startTime)));\r\n\r\n return array;\r\n};\r\n\r\n/**\r\n * Gets the mime type of the recorder audio\r\n * @returns {String} the mime type of the recorder audio\r\n */\r\nAudioRecorder.prototype.getFileType = function() {\r\n return this.fileType;\r\n};\r\n\r\n/**\r\n * export the main object AudioRecorder\r\n */\r\nexport default AudioRecorder;\r\n","/**\r\n * An object representing a transcribed word, with some additional information\r\n * @param word the word\r\n * @param begin the time the word was started being uttered\r\n * @param end the time the word stopped being uttered\r\n */\r\nexport default class Word {\r\n /**\r\n * @param word the word\r\n * @param begin the time the word was started being uttered\r\n * @param end the time the word stopped being uttered\r\n */\r\n constructor(word, begin, end) {\r\n this.word = word;\r\n this.begin = begin;\r\n this.end = end;\r\n }\r\n\r\n /**\r\n * Get the string representation of the word\r\n * @returns {*} the word as a string\r\n */\r\n getWord() {\r\n return this.word;\r\n }\r\n\r\n /**\r\n * Get the time the word started being uttered\r\n * @returns {*} the start time as an integer\r\n */\r\n getBeginTime() {\r\n return this.begin;\r\n }\r\n\r\n /**\r\n * Get the time the word stopped being uttered\r\n * @returns {*} the end time as an integer\r\n */\r\n getEndTime() {\r\n return this.end;\r\n }\r\n}\r\n","/* global config */\r\n\r\nimport Word from '../word';\r\n\r\nimport audioRecorder from './../audioRecorder';\r\nimport AbstractTranscriptionService from './AbstractTranscriptionService';\r\n\r\n/**\r\n * Implements a TranscriptionService for a Sphinx4 http server\r\n */\r\nexport default class SphinxService extends AbstractTranscriptionService {\r\n /**\r\n * Implements a TranscriptionService for a Sphinx4 http server\r\n */\r\n constructor() {\r\n super();\r\n\r\n // set the correct url\r\n this.url = getURL();\r\n }\r\n\r\n /**\r\n * Overrides the sendRequest method from AbstractTranscriptionService\r\n * it will send the audio stream the a Sphinx4 server to get the transcription\r\n *\r\n * @param audioFileBlob the recorder audio stream an a single Blob\r\n * @param callback the callback function retrieving the server response\r\n */\r\n sendRequest(audioFileBlob, callback) {\r\n console.log(`sending an audio file to ${this.url}`);\r\n console.log(`the audio file being sent: ${audioFileBlob}`);\r\n const request = new XMLHttpRequest();\r\n\r\n request.onreadystatechange = function() {\r\n if (request.readyState === XMLHttpRequest.DONE\r\n && request.status === 200) {\r\n callback(request.responseText);\r\n } else if (request.readyState === XMLHttpRequest.DONE) {\r\n throw new Error(\r\n `unable to accept response from sphinx server. status: ${request.status}`);\r\n }\r\n\r\n // if not ready no point to throw an error\r\n };\r\n request.open('POST', this.url);\r\n request.setRequestHeader('Content-Type',\r\n audioRecorder.determineCorrectFileType());\r\n request.send(audioFileBlob);\r\n console.log(`send ${audioFileBlob}`);\r\n }\r\n\r\n /**\r\n * Overrides the formatResponse method from AbstractTranscriptionService\r\n * It will parse the answer from the server in the expected format\r\n *\r\n * @param response the JSON body retrieved from the Sphinx4 server\r\n */\r\n formatResponse(response) {\r\n const result = JSON.parse(response).objects;\r\n\r\n // make sure to delete the session id object, which is always\r\n // the first value in the JSON array\r\n result.shift();\r\n const array = [];\r\n\r\n result.forEach(\r\n word => word.filler\r\n || array.push(new Word(word.word, word.start, word.end)));\r\n\r\n return array;\r\n }\r\n\r\n /**\r\n * checks wether the reply is empty, or doesn't contain a correct JSON object\r\n * @param response the server response\r\n * @return {boolean} whether the response is valid\r\n */\r\n verify(response) {\r\n console.log(`response from server:${response.toString()}`);\r\n\r\n // test if server responded with a string object\r\n if (typeof response !== 'string') {\r\n return false;\r\n }\r\n\r\n // test if the string can be parsed into valid JSON\r\n let json;\r\n\r\n try {\r\n json = JSON.parse(response);\r\n } catch (error) {\r\n console.log(error);\r\n\r\n return false;\r\n }\r\n\r\n // check if the JSON has a \"objects\" value\r\n if (json.objects === undefined) {\r\n return false;\r\n }\r\n\r\n // get the \"objects\" value and check for a session ID\r\n const array = json.objects;\r\n\r\n if (!(array[0] && array[0]['session-id'])) {\r\n return false;\r\n }\r\n\r\n // everything seems to be in order\r\n return true;\r\n }\r\n}\r\n\r\n/**\r\n * Gets the URL to the Sphinx4 server from the config file. If it's not there,\r\n * it will throw an error\r\n *\r\n * @returns {string} the URL to the sphinx4 server\r\n */\r\nfunction getURL() {\r\n const message = 'config does not contain an url to a Sphinx4 https server';\r\n\r\n if (config.sphinxURL === undefined) {\r\n console.log(message);\r\n } else {\r\n const toReturn = config.sphinxURL;\r\n\r\n if (toReturn.includes !== undefined && toReturn.includes('https://')) {\r\n return toReturn;\r\n }\r\n console.log(message);\r\n\r\n }\r\n}\r\n","/**\r\n * Abstract class representing an interface to implement a speech-to-text\r\n * service on.\r\n */\r\nexport default class TranscriptionService {\r\n /**\r\n * Abstract class representing an interface to implement a speech-to-text\r\n * service on.\r\n */\r\n constructor() {\r\n throw new Error('TranscriptionService is abstract and cannot be created');\r\n }\r\n\r\n /**\r\n * This method can be used to send the recorder audio stream and\r\n * retrieve the answer from the transcription service from the callback\r\n *\r\n * @param {RecordingResult} recordingResult a recordingResult object which\r\n * includes the recorded audio stream as a blob\r\n * @param {Function} callback which will retrieve the a RecordingResult with\r\n * the answer as a WordArray\r\n */\r\n send(recordingResult, callback) {\r\n this.sendRequest(recordingResult.blob, response => {\r\n if (this.verify(response)) {\r\n recordingResult.wordArray = this.formatResponse(response);\r\n } else {\r\n console.log('the retrieved response from the server is not valid!');\r\n recordingResult.wordArray = [];\r\n }\r\n callback(recordingResult);\r\n });\r\n }\r\n\r\n /**\r\n * Abstract method which will rend the recorder audio stream to the implemented\r\n * transcription service and will retrieve an answer, which will be\r\n * called on the given callback method\r\n *\r\n * @param {Blob} audioBlob the recorded audio stream as a single Blob\r\n * @param {function} callback function which will retrieve the answer\r\n * from the service\r\n */\r\n sendRequest(audioBlob, callback) { // eslint-disable-line no-unused-vars\r\n throw new Error('TranscriptionService.sendRequest is abstract');\r\n }\r\n\r\n /**\r\n * Abstract method which will parse the output from the implemented\r\n * transcription service to the expected format\r\n *\r\n * The transcriber class expect an array of word objects, where each word\r\n * object is one transcribed word by the service.\r\n *\r\n * The expected output of this method is an array of word objects, in\r\n * the correct order. That is, the first object in the array is the first word\r\n * being said, and the last word in the array is the last word being said\r\n *\r\n * @param response the answer from the speech-to-text server which needs to be\r\n * formatted\r\n * @return {Array} an array of Word objects\r\n */\r\n formatResponse(response) { // eslint-disable-line no-unused-vars\r\n throw new Error('TranscriptionService.format is abstract');\r\n }\r\n\r\n /**\r\n * Abstract method which will verify that the response from the server is valid\r\n *\r\n * @param response the response from the server\r\n * @return {boolean} true if response is valid, false otherwise\r\n */\r\n verify(response) { // eslint-disable-line no-unused-vars\r\n throw new Error('TranscriptionService.verify is abstract');\r\n }\r\n}\r\n","import AudioRecorder from './audioRecorder';\r\nimport SphinxService from './transcriptionServices/SphinxTranscriptionService';\r\n\r\nconst BEFORE_STATE = 'before';\r\nconst RECORDING_STATE = 'recording';\r\nconst TRANSCRIBING_STATE = 'transcribing';\r\nconst FINISHED_STATE = 'finished';\r\n\r\n// the amount of characters each line in the transcription will have\r\nconst MAXIMUM_SENTENCE_LENGTH = 80;\r\n\r\n/**\r\n * This is the main object for handing the Transcription. It interacts with\r\n * the audioRecorder to record every person in a conference and sends the\r\n * recorder audio to a transcriptionService. The returned speech-to-text result\r\n * will be merged to create a transcript\r\n * @param {AudioRecorder} audioRecorder An audioRecorder recording a conference\r\n */\r\nfunction Transcriber() {\r\n // the object which can record all audio in the conference\r\n this.audioRecorder = new AudioRecorder();\r\n\r\n // this object can send the recorder audio to a speech-to-text service\r\n this.transcriptionService = new SphinxService();\r\n\r\n // holds a counter to keep track if merging can start\r\n this.counter = null;\r\n\r\n // holds the date when transcription started which makes it possible\r\n // to calculate the offset between recordings\r\n this.startTime = null;\r\n\r\n // will hold the transcription once it is completed\r\n this.transcription = null;\r\n\r\n // this will be a method which will be called once the transcription is done\r\n // with the transcription as parameter\r\n this.callback = null;\r\n\r\n // stores all the retrieved speech-to-text results to merge together\r\n // this value will store an Array object\r\n this.results = [];\r\n\r\n // Stores the current state of the transcription process\r\n this.state = BEFORE_STATE;\r\n\r\n // Used in the updateTranscription method to add a new line when the\r\n // sentence becomes to long\r\n this.lineLength = 0;\r\n}\r\n\r\n/**\r\n * Method to start the transcription process. It will tell the audioRecorder\r\n * to start storing all audio streams and record the start time for merging\r\n * purposes\r\n */\r\nTranscriber.prototype.start = function start() {\r\n if (this.state !== BEFORE_STATE) {\r\n throw new Error(\r\n `The transcription can only start when it's in the \"${\r\n BEFORE_STATE}\" state. It's currently in the \"${\r\n this.state}\" state`);\r\n }\r\n this.state = RECORDING_STATE;\r\n this.audioRecorder.start();\r\n this.startTime = new Date();\r\n};\r\n\r\n/**\r\n * Method to stop the transcription process. It will tell the audioRecorder to\r\n * stop, and get all the recorded audio to send it to the transcription service\r\n\r\n * @param callback a callback which will receive the transcription\r\n */\r\nTranscriber.prototype.stop = function stop(callback) {\r\n if (this.state !== RECORDING_STATE) {\r\n throw new Error(\r\n `The transcription can only stop when it's in the \"${\r\n RECORDING_STATE}\" state. It's currently in the \"${\r\n this.state}\" state`);\r\n }\r\n\r\n // stop the recording\r\n console.log('stopping recording and sending audio files');\r\n this.audioRecorder.stop();\r\n\r\n // and send all recorded audio to the transcription service\r\n const callBack = blobCallBack.bind(null, this);\r\n\r\n this.audioRecorder.getRecordingResults().forEach(recordingResult => {\r\n this.transcriptionService.send(recordingResult, callBack);\r\n this.counter++;\r\n });\r\n\r\n // set the state to \"transcribing\" so that maybeMerge() functions correctly\r\n this.state = TRANSCRIBING_STATE;\r\n\r\n // and store the callback for later\r\n this.callback = callback;\r\n};\r\n\r\n/**\r\n * This method gets the answer from the transcription service, calculates the\r\n * offset and adds is to every Word object. It will also start the merging\r\n * when every send request has been received\r\n *\r\n * note: Make sure to bind this as a Transcription object\r\n * @param {Transcriber} transcriber the transcriber instance\r\n * @param {RecordingResult} answer a RecordingResult object with a defined\r\n * WordArray\r\n */\r\nfunction blobCallBack(transcriber, answer) {\r\n console.log(\r\n 'retrieved an answer from the transcription service. The answer has an'\r\n + ` array of length: ${answer.wordArray.length}`);\r\n\r\n // first add the offset between the start of the transcription and\r\n // the start of the recording to all start and end times\r\n if (answer.wordArray.length > 0) {\r\n let offset = answer.startTime.getUTCMilliseconds()\r\n - transcriber.startTime.getUTCMilliseconds();\r\n\r\n // transcriber time will always be earlier\r\n\r\n if (offset < 0) {\r\n offset = 0; // presume 0 if it somehow not earlier\r\n }\r\n\r\n let array = '[';\r\n\r\n answer.wordArray.forEach(wordObject => {\r\n wordObject.begin += offset;\r\n wordObject.end += offset;\r\n array += `${wordObject.word},`;\r\n });\r\n array += ']';\r\n console.log(array);\r\n\r\n // give a name value to the Array object so that the merging can access\r\n // the name value without having to use the whole recordingResult object\r\n // in the algorithm\r\n answer.wordArray.name = answer.name;\r\n }\r\n\r\n // then store the array and decrease the counter\r\n transcriber.results.push(answer.wordArray);\r\n transcriber.counter--;\r\n console.log(`current counter: ${transcriber.counter}`);\r\n\r\n // and check if all results have been received.\r\n transcriber.maybeMerge();\r\n}\r\n\r\n/**\r\n * this method will check if the counter is zero. If it is, it will call\r\n * the merging method\r\n */\r\nTranscriber.prototype.maybeMerge = function() {\r\n if (this.state === TRANSCRIBING_STATE && this.counter === 0) {\r\n // make sure to include the events in the result arrays before\r\n // merging starts\r\n this.merge();\r\n }\r\n};\r\n\r\n/**\r\n * This method will merge all speech-to-text arrays together in one\r\n * readable transcription string\r\n */\r\nTranscriber.prototype.merge = function() {\r\n console.log(\r\n `starting merge process!\\n The length of the array: ${\r\n this.results.length}`);\r\n this.transcription = '';\r\n\r\n // the merging algorithm will look over all Word objects who are at pos 0 in\r\n // every array. It will then select the one closest in time to the\r\n // previously placed word, while removing the selected word from its array\r\n // note: words can be skipped the skipped word's begin and end time somehow\r\n // end up between the closest word start and end time\r\n const arrays = this.results;\r\n\r\n // arrays of Word objects\r\n const potentialWords = []; // array of the first Word objects\r\n // check if any arrays are already empty and remove them\r\n\r\n hasPopulatedArrays(arrays);\r\n\r\n // populate all the potential Words for a first time\r\n arrays.forEach(array => pushWordToSortedArray(potentialWords, array));\r\n\r\n // keep adding words to transcription until all arrays are exhausted\r\n while (hasPopulatedArrays(arrays)) {\r\n // first select the lowest array;\r\n let lowestWordArray = arrays[0];\r\n\r\n arrays.forEach(wordArray => {\r\n if (wordArray[0].begin < lowestWordArray[0].begin) {\r\n lowestWordArray = wordArray;\r\n }\r\n });\r\n\r\n // put the word in the transcription\r\n let wordToAdd = lowestWordArray.shift();\r\n\r\n this.updateTranscription(wordToAdd, lowestWordArray.name);\r\n\r\n // keep going until a word in another array has a smaller time\r\n // or the array is empty\r\n while (lowestWordArray.length > 0) {\r\n let foundSmaller = false;\r\n const wordToCompare = lowestWordArray[0].begin;\r\n\r\n arrays.forEach(wordArray => {\r\n if (wordArray[0].begin < wordToCompare) {\r\n foundSmaller = true;\r\n }\r\n });\r\n\r\n // add next word if no smaller time has been found\r\n if (foundSmaller) {\r\n break;\r\n }\r\n\r\n wordToAdd = lowestWordArray.shift();\r\n this.updateTranscription(wordToAdd, null);\r\n }\r\n\r\n }\r\n\r\n // set the state to finished and do the necessary left-over tasks\r\n this.state = FINISHED_STATE;\r\n if (this.callback) {\r\n this.callback(this.transcription);\r\n }\r\n};\r\n\r\n/**\r\n * Appends a word object to the transcription. It will make a new line with a\r\n * name if a name is specified\r\n * @param {Word} word the Word object holding the word to append\r\n * @param {String|null} name the name of a new speaker. Null if not applicable\r\n */\r\nTranscriber.prototype.updateTranscription = function(word, name) {\r\n if (name !== undefined && name !== null) {\r\n this.transcription += `\\n${name}:`;\r\n this.lineLength = name.length + 1; // +1 for the semi-colon\r\n }\r\n if (this.lineLength + word.word.length > MAXIMUM_SENTENCE_LENGTH) {\r\n this.transcription += '\\n ';\r\n this.lineLength = 4; // because of the 4 spaces after the new line\r\n }\r\n this.transcription += ` ${word.word}`;\r\n this.lineLength += word.word.length + 1; // +1 for the space\r\n};\r\n\r\n/**\r\n * Check if the given 2 dimensional array has any non-zero Word-arrays in them.\r\n * All zero-element arrays inside will be removed\r\n * If any non-zero-element arrays are found, the method will return true.\r\n * otherwise it will return false\r\n * @param {Array} twoDimensionalArray the array to check\r\n * @returns {boolean} true if any non-zero arrays inside, otherwise false\r\n */\r\nfunction hasPopulatedArrays(twoDimensionalArray) {\r\n for (let i = 0; i < twoDimensionalArray.length; i++) {\r\n if (twoDimensionalArray[i].length === 0) {\r\n twoDimensionalArray.splice(i, 1);\r\n }\r\n }\r\n\r\n return twoDimensionalArray.length > 0;\r\n}\r\n\r\n/**\r\n * Push a word to the right location in a sorted array. The array is sorted\r\n * from lowest to highest start time. Every word is stored in an object which\r\n * includes the name of the person saying the word.\r\n *\r\n * @param {Array} array the sorted array to push to\r\n * @param {Word} word the word to push into the array\r\n */\r\nfunction pushWordToSortedArray(array, word) {\r\n if (array.length === 0) {\r\n array.push(word);\r\n } else {\r\n if (array[array.length - 1].begin <= word.begin) {\r\n array.push(word);\r\n\r\n return;\r\n }\r\n\r\n for (let i = 0; i < array.length; i++) {\r\n if (word.begin < array[i].begin) {\r\n array.splice(i, 0, word);\r\n\r\n return;\r\n }\r\n }\r\n array.push(word); // fail safe\r\n }\r\n}\r\n\r\n/**\r\n * Gives the transcriber a JitsiTrack holding an audioStream to transcribe.\r\n * The JitsiTrack is given to the audioRecorder. If it doesn't hold an\r\n * audiostream, it will not be added by the audioRecorder\r\n * @param {JitsiTrack} track the track to give to the audioRecorder\r\n */\r\nTranscriber.prototype.addTrack = function(track) {\r\n this.audioRecorder.addTrack(track);\r\n};\r\n\r\n/**\r\n * Remove the given track from the auioRecorder\r\n * @param track\r\n */\r\nTranscriber.prototype.removeTrack = function(track) {\r\n this.audioRecorder.removeTrack(track);\r\n};\r\n\r\n/**\r\n * Will return the created transcription if it's avialable or throw an error\r\n * when it's not done yet\r\n * @returns {String} the transcription as a String\r\n */\r\nTranscriber.prototype.getTranscription = function() {\r\n if (this.state !== FINISHED_STATE) {\r\n throw new Error(\r\n `The transcription can only be retrieved when it's in the \"${\r\n FINISHED_STATE}\" state. It's currently in the \"${\r\n this.state}\" state`);\r\n }\r\n\r\n return this.transcription;\r\n};\r\n\r\n/**\r\n * Returns the current state of the transcription process\r\n */\r\nTranscriber.prototype.getState = function() {\r\n return this.state;\r\n};\r\n\r\n/**\r\n * Resets the state to the \"before\" state, such that it's again possible to\r\n * call the start method\r\n */\r\nTranscriber.prototype.reset = function() {\r\n this.state = BEFORE_STATE;\r\n this.counter = null;\r\n this.transcription = null;\r\n this.startTime = null;\r\n this.callback = null;\r\n this.results = [];\r\n this.lineLength = 0;\r\n};\r\n\r\nexport default Transcriber;\r\n","import Statistics from '../statistics/statistics';\r\n\r\nconst logger = require('@jitsi/logger').getLogger(__filename);\r\n\r\n/**\r\n * Creates new instance of ComponentsVersions which will be discovering\r\n * the versions of conferencing system components in given\r\n * JitsiConference.\r\n * @param conference JitsiConference instance which will be used to\r\n * listen for focus presence updates.\r\n * @constructor\r\n */\r\nexport default function ComponentsVersions(conference) {\r\n\r\n this.versions = {};\r\n\r\n this.conference = conference;\r\n this.conference.addCommandListener(\r\n 'versions', this.processVersions.bind(this));\r\n}\r\n\r\nComponentsVersions.prototype.processVersions\r\n = function(versions, mucResource, mucJid) {\r\n if (!this.conference.isFocus(mucJid)) {\r\n logger.warn(\r\n `Received versions not from the focus user: ${versions}`,\r\n mucJid);\r\n\r\n return;\r\n }\r\n\r\n const log = [];\r\n\r\n versions.children.forEach(component => {\r\n\r\n const name = component.attributes.name;\r\n const version = component.value;\r\n\r\n if (this.versions[name] !== version) {\r\n this.versions[name] = version;\r\n logger.info(`Got ${name} version: ${version}`);\r\n\r\n log.push({\r\n id: 'component_version',\r\n component: name,\r\n version\r\n });\r\n }\r\n });\r\n\r\n // logs versions to stats\r\n if (log.length > 0) {\r\n Statistics.sendLog(JSON.stringify(log));\r\n }\r\n };\r\n\r\n/**\r\n * Obtains the version of conferencing system component.\r\n * @param componentName the name of the component for which we want to obtain\r\n * the version.\r\n * @returns {String} which describes the version of the component identified by\r\n * given componentName or undefined if not found.\r\n */\r\nComponentsVersions.prototype.getComponentVersion = function(componentName) {\r\n return this.versions[componentName];\r\n};\r\n","export enum VideoSIPGWStatusConstants {\r\n /**\r\n * Status that video SIP GW service is available.\r\n */\r\n STATUS_AVAILABLE = 'available',\r\n\r\n /**\r\n * Status that video SIP GW service is not available.\r\n */\r\n STATUS_UNDEFINED = 'undefined',\r\n\r\n /**\r\n * Status that video SIP GW service is available but there are no free nodes\r\n * at the moment to serve new requests.\r\n */\r\n STATUS_BUSY = 'busy'\r\n};\r\n\r\nexport enum VideoSIPGWStateConstants {\r\n /**\r\n * Video SIP GW session state, currently running.\r\n */\r\n STATE_ON = 'on',\r\n\r\n /**\r\n * Video SIP GW session state, currently stopped and not running.\r\n */\r\n STATE_OFF = 'off',\r\n\r\n /**\r\n * Video SIP GW session state, currently is starting.\r\n */\r\n STATE_PENDING = 'pending',\r\n\r\n /**\r\n * Video SIP GW session state, has observed some issues and is retrying at the\r\n * moment.\r\n */\r\n STATE_RETRYING = 'retrying',\r\n\r\n /**\r\n * Video SIP GW session state, tried to start but it failed.\r\n */\r\n STATE_FAILED = 'failed'\r\n};\r\n\r\nexport enum VideoSIPGWErrorConstants {\r\n /**\r\n * Error on trying to create video SIP GW session in conference where\r\n * there is no room connection (hasn't joined or has left the room).\r\n */\r\n ERROR_NO_CONNECTION = 'error_no_connection',\r\n\r\n /**\r\n * Error on trying to create video SIP GW session with address for which\r\n * there is an already created session.\r\n */\r\n ERROR_SESSION_EXISTS = 'error_session_already_exists'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const STATUS_AVAILABLE = VideoSIPGWStatusConstants.STATUS_AVAILABLE;\r\nexport const STATUS_UNDEFINED = VideoSIPGWStatusConstants.STATUS_UNDEFINED;\r\nexport const STATUS_BUSY = VideoSIPGWStatusConstants.STATUS_BUSY;\r\n\r\nexport const STATE_ON = VideoSIPGWStateConstants.STATE_ON;\r\nexport const STATE_OFF = VideoSIPGWStateConstants.STATE_OFF;\r\nexport const STATE_PENDING = VideoSIPGWStateConstants.STATE_PENDING;\r\nexport const STATE_RETRYING = VideoSIPGWStateConstants.STATE_RETRYING;\r\nexport const STATE_FAILED = VideoSIPGWStateConstants.STATE_FAILED;\r\n\r\nexport const ERROR_NO_CONNECTION = VideoSIPGWErrorConstants.ERROR_NO_CONNECTION;\r\nexport const ERROR_SESSION_EXISTS = VideoSIPGWErrorConstants.ERROR_SESSION_EXISTS;\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { $iq } from 'strophe.js';\r\n\r\nimport Listenable from '../util/Listenable';\r\n\r\nimport * as VideoSIPGWConstants from './VideoSIPGWConstants';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The event name for current sip video session state changed.\r\n * @type {string} event name for sip video session state changed.\r\n */\r\nconst STATE_CHANGED = 'STATE_CHANGED';\r\n\r\n/**\r\n * Jitsi video SIP GW session. Holding its state and able to start/stop it.\r\n * When session is in OFF or FAILED stated it cannot be used anymore.\r\n */\r\nexport default class JitsiVideoSIPGWSession extends Listenable {\r\n\r\n /**\r\n * Creates new session with the desired sip address and display name.\r\n *\r\n * @param {string} sipAddress - The sip address to use when\r\n * starting the session.\r\n * @param {string} displayName - The display name to use for\r\n * that participant.\r\n * @param {ChatRoom} chatRoom - The chat room this session is bound to.\r\n */\r\n constructor(sipAddress, displayName, chatRoom) {\r\n super();\r\n\r\n this.sipAddress = sipAddress;\r\n this.displayName = displayName;\r\n this.chatRoom = chatRoom;\r\n\r\n /*\r\n * The initial state is undefined. Initial state cannot be STATE_OFF,\r\n * the session enters this state when it was in STATE_ON and was stopped\r\n * and such session cannot be used anymore.\r\n *\r\n * @type {VideoSIPGWConstants|undefined}\r\n */\r\n this.state = undefined;\r\n }\r\n\r\n /**\r\n * Stops the current session.\r\n */\r\n stop() {\r\n if (this.state === VideoSIPGWConstants.STATE_OFF\r\n || this.state === VideoSIPGWConstants.STATE_FAILED) {\r\n logger.warn('Video SIP GW session already stopped or failed!');\r\n\r\n return;\r\n }\r\n\r\n this._sendJibriIQ('stop');\r\n }\r\n\r\n /**\r\n * Starts a new session. Sends an iq to the focus.\r\n */\r\n start() {\r\n // if state is off, this session was active for some reason\r\n // and we should create new one, rather than reusing it\r\n if (this.state === VideoSIPGWConstants.STATE_ON\r\n || this.state === VideoSIPGWConstants.STATE_OFF\r\n || this.state === VideoSIPGWConstants.STATE_PENDING\r\n || this.state === VideoSIPGWConstants.STATE_RETRYING) {\r\n logger.warn('Video SIP GW session already started!');\r\n\r\n return;\r\n }\r\n\r\n this._sendJibriIQ('start');\r\n }\r\n\r\n /**\r\n * Changes the state of this session.\r\n *\r\n * @param {string} newState - The new {VideoSIPGWConstants} state to set.\r\n * @param {string} [optional] failureReason - The reason why a failure state\r\n * was entered.\r\n * @returns {void}\r\n */\r\n setState(newState, failureReason) {\r\n if (newState === this.state) {\r\n return;\r\n }\r\n\r\n const oldState = this.state;\r\n\r\n this.state = newState;\r\n this.eventEmitter.emit(STATE_CHANGED,\r\n {\r\n address: this.sipAddress,\r\n failureReason,\r\n oldState,\r\n newState: this.state,\r\n displayName: this.displayName\r\n }\r\n );\r\n }\r\n\r\n /**\r\n * Subscribes the passed listener to the event for state change of this\r\n * session.\r\n *\r\n * @param {Function} listener - The function that will receive the event.\r\n */\r\n addStateListener(listener) {\r\n this.addListener(STATE_CHANGED, listener);\r\n }\r\n\r\n /**\r\n * Unsubscribes the passed handler.\r\n *\r\n * @param {Function} listener - The function to be removed.\r\n */\r\n removeStateListener(listener) {\r\n this.removeListener(STATE_CHANGED, listener);\r\n }\r\n\r\n /**\r\n * Sends a jibri command using an iq.\r\n *\r\n * @private\r\n * @param {string} action - The action to send ('start' or 'stop').\r\n */\r\n _sendJibriIQ(action) {\r\n const attributes = {\r\n 'xmlns': 'http://jitsi.org/protocol/jibri',\r\n 'action': action,\r\n sipaddress: this.sipAddress\r\n };\r\n\r\n attributes.displayname = this.displayName;\r\n\r\n const iq = $iq({\r\n to: this.chatRoom.focusMucJid,\r\n type: 'set' })\r\n .c('jibri', attributes)\r\n .up();\r\n\r\n logger.debug(`${action} video SIP GW session`, iq.nodeTree);\r\n this.chatRoom.connection.sendIQ(\r\n iq,\r\n () => {}, // eslint-disable-line no-empty-function\r\n error => {\r\n logger.error(\r\n `Failed to ${action} video SIP GW session, error: `, error);\r\n this.setState(VideoSIPGWConstants.STATE_FAILED);\r\n });\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nconst logger = getLogger(__filename);\r\n\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\n\r\nimport JitsiVideoSIPGWSession from './JitsiVideoSIPGWSession';\r\nimport * as Constants from './VideoSIPGWConstants';\r\n\r\n/**\r\n * Main video SIP GW handler. Stores references of all created sessions.\r\n */\r\nexport default class VideoSIPGW {\r\n\r\n /**\r\n * Creates new handler.\r\n *\r\n * @param {ChatRoom} chatRoom - Tha chat room to handle.\r\n */\r\n constructor(chatRoom) {\r\n this.chatRoom = chatRoom;\r\n this.eventEmitter = chatRoom.eventEmitter;\r\n logger.debug('creating VideoSIPGW');\r\n this.sessions = {};\r\n\r\n this.sessionStateChangeListener = this.sessionStateChanged.bind(this);\r\n\r\n // VideoSIPGW, JitsiConference and ChatRoom are not reusable and no\r\n // more than one VideoSIPGW can be created per JitsiConference,\r\n // so we don't bother to cleanup\r\n chatRoom.addPresenceListener('jibri-sip-call-state',\r\n this.handleJibriSIPState.bind(this));\r\n }\r\n\r\n /**\r\n * Handles presence nodes with name: jibri-sip-call-state.\r\n *\r\n * @param {Object} node the presence node Object to handle.\r\n * Object representing part of the presence received over xmpp.\r\n */\r\n handleJibriSIPState(node) {\r\n const attributes = node.attributes;\r\n\r\n if (!attributes) {\r\n return;\r\n }\r\n\r\n logger.debug('Handle video sip gw state : ', attributes);\r\n\r\n const newState = attributes.state;\r\n\r\n if (newState === this.state) {\r\n return;\r\n }\r\n\r\n switch (newState) {\r\n case Constants.STATE_ON:\r\n case Constants.STATE_OFF:\r\n case Constants.STATE_PENDING:\r\n case Constants.STATE_RETRYING:\r\n case Constants.STATE_FAILED: {\r\n const address = attributes.sipaddress;\r\n\r\n if (!address) {\r\n return;\r\n }\r\n\r\n // find the corresponding session and set its state\r\n const session = this.sessions[address];\r\n\r\n if (session) {\r\n session.setState(newState, attributes.failure_reason);\r\n } else {\r\n logger.warn('Video SIP GW session not found:', address);\r\n }\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Creates new session and stores its reference if it does not exist or\r\n * returns an error otherwise.\r\n *\r\n * @param {string} sipAddress - The sip address to use.\r\n * @param {string} displayName - The display name to use.\r\n * @returns {JitsiVideoSIPGWSession|Error}\r\n */\r\n createVideoSIPGWSession(sipAddress, displayName) {\r\n if (this.sessions[sipAddress]) {\r\n logger.warn('There was already a Video SIP GW session for address',\r\n sipAddress);\r\n\r\n return new Error(Constants.ERROR_SESSION_EXISTS);\r\n }\r\n\r\n const session = new JitsiVideoSIPGWSession(\r\n sipAddress, displayName, this.chatRoom);\r\n\r\n session.addStateListener(this.sessionStateChangeListener);\r\n\r\n this.sessions[sipAddress] = session;\r\n\r\n return session;\r\n }\r\n\r\n /**\r\n * Listener for session state changed. When a session goes to off or failed\r\n * we delete its reference.\r\n *\r\n * @param {options} event - { address, oldState, newState, displayName }\r\n */\r\n sessionStateChanged(event) {\r\n const address = event.address;\r\n\r\n if (event.newState === Constants.STATE_OFF\r\n || event.newState === Constants.STATE_FAILED) {\r\n const session = this.sessions[address];\r\n\r\n if (!session) {\r\n logger.error('Missing Video SIP GW session with address:',\r\n address);\r\n\r\n return;\r\n }\r\n\r\n session.removeStateListener(this.sessionStateChangeListener);\r\n delete this.sessions[address];\r\n }\r\n\r\n this.eventEmitter.emit(\r\n XMPPEvents.VIDEO_SIP_GW_SESSION_STATE_CHANGED,\r\n event);\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { Strophe } from 'strophe.js';\r\n\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport * as SignalingEvents from '../../service/RTC/SignalingEvents';\r\nimport SignalingLayer, { getMediaTypeFromSourceName } from '../../service/RTC/SignalingLayer';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\nimport FeatureFlags from '../flags/FeatureFlags';\r\n\r\nimport { filterNodeFromPresenceJSON } from './ChatRoom';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\nexport const SOURCE_INFO_PRESENCE_ELEMENT = 'SourceInfo';\r\n\r\n/**\r\n * Default XMPP implementation of the {@link SignalingLayer} interface. Obtains\r\n * the data from the MUC presence.\r\n */\r\nexport default class SignalingLayerImpl extends SignalingLayer {\r\n /**\r\n * Creates new instance.\r\n */\r\n constructor() {\r\n super();\r\n\r\n /**\r\n * A map that stores SSRCs of remote streams. And is used only locally\r\n * We store the mapping when jingle is received, and later is used\r\n * onaddstream webrtc event where we have only the ssrc\r\n * FIXME: This map got filled and never cleaned and can grow during long\r\n * conference\r\n * @type {Map} maps SSRC number to jid\r\n */\r\n this.ssrcOwners = new Map();\r\n\r\n /**\r\n *\r\n * @type {ChatRoom|null}\r\n */\r\n this.chatRoom = null;\r\n\r\n /**\r\n * @type {Map}\r\n * @private\r\n */\r\n this._localSourceState = { };\r\n\r\n /**\r\n * @type {Map>}\r\n * @private\r\n */\r\n this._remoteSourceState = { };\r\n\r\n /**\r\n * A map that stores the source name of a track identified by it's ssrc.\r\n * We store the mapping when jingle is received, and later is used\r\n * onaddstream webrtc event where we have only the ssrc\r\n * FIXME: This map got filled and never cleaned and can grow during long\r\n * conference\r\n * @type {Map} maps SSRC number to source name\r\n */\r\n this._sourceNames = new Map();\r\n }\r\n\r\n /**\r\n * Adds element to the local presence.\r\n *\r\n * @returns {void}\r\n * @private\r\n */\r\n _addLocalSourceInfoToPresence() {\r\n if (this.chatRoom) {\r\n return this.chatRoom.addOrReplaceInPresence(\r\n SOURCE_INFO_PRESENCE_ELEMENT,\r\n { value: JSON.stringify(this._localSourceState) });\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Check is given endpoint has advertised in it's presence which means that the source name signaling\r\n * is used by this endpoint.\r\n *\r\n * @param {EndpointId} endpointId\r\n * @returns {boolean}\r\n */\r\n _doesEndpointSendNewSourceInfo(endpointId) {\r\n const presence = this.chatRoom?.getLastPresence(endpointId);\r\n\r\n return Boolean(presence && presence.find(node => node.tagName === SOURCE_INFO_PRESENCE_ELEMENT));\r\n }\r\n\r\n /**\r\n * Sets the ChatRoom instance used and binds presence listeners.\r\n * @param {ChatRoom} room\r\n */\r\n setChatRoom(room) {\r\n const oldChatRoom = this.chatRoom;\r\n\r\n this.chatRoom = room;\r\n if (oldChatRoom) {\r\n oldChatRoom.removePresenceListener(\r\n 'audiomuted', this._audioMuteHandler);\r\n oldChatRoom.removePresenceListener(\r\n 'videomuted', this._videoMuteHandler);\r\n oldChatRoom.removePresenceListener(\r\n 'videoType', this._videoTypeHandler);\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this._sourceInfoHandler\r\n && oldChatRoom.removePresenceListener(\r\n SOURCE_INFO_PRESENCE_ELEMENT, this._sourceInfoHandler);\r\n this._memberLeftHandler\r\n && oldChatRoom.removeEventListener(\r\n XMPPEvents.MUC_MEMBER_LEFT, this._memberLeftHandler);\r\n }\r\n }\r\n if (room) {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this._bindChatRoomEventHandlers(room);\r\n this._addLocalSourceInfoToPresence();\r\n } else {\r\n // TODO the logic below has been duplicated in _bindChatRoomEventHandlers, clean this up once\r\n // the new impl has been tested well enough\r\n // SignalingEvents\r\n this._audioMuteHandler = (node, from) => {\r\n this.eventEmitter.emit(\r\n SignalingEvents.PEER_MUTED_CHANGED,\r\n from, MediaType.AUDIO, node.value === 'true');\r\n };\r\n room.addPresenceListener('audiomuted', this._audioMuteHandler);\r\n\r\n this._videoMuteHandler = (node, from) => {\r\n this.eventEmitter.emit(\r\n SignalingEvents.PEER_MUTED_CHANGED,\r\n from, MediaType.VIDEO, node.value === 'true');\r\n };\r\n room.addPresenceListener('videomuted', this._videoMuteHandler);\r\n\r\n this._videoTypeHandler = (node, from) => {\r\n this.eventEmitter.emit(\r\n SignalingEvents.PEER_VIDEO_TYPE_CHANGED,\r\n from, node.value);\r\n };\r\n room.addPresenceListener('videoType', this._videoTypeHandler);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Binds event listeners to the chat room instance.\r\n * @param {ChatRoom} room\r\n * @private\r\n * @returns {void}\r\n */\r\n _bindChatRoomEventHandlers(room) {\r\n const emitAudioMutedEvent = (endpointId, muted) => {\r\n this.eventEmitter.emit(\r\n SignalingEvents.PEER_MUTED_CHANGED,\r\n endpointId,\r\n MediaType.AUDIO,\r\n muted);\r\n };\r\n const emitVideoMutedEvent = (endpointId, muted) => {\r\n this.eventEmitter.emit(\r\n SignalingEvents.PEER_MUTED_CHANGED,\r\n endpointId,\r\n MediaType.VIDEO,\r\n muted);\r\n };\r\n\r\n // SignalingEvents\r\n this._audioMuteHandler = (node, from) => {\r\n if (!this._doesEndpointSendNewSourceInfo(from)) {\r\n emitAudioMutedEvent(from, node.value === 'true');\r\n }\r\n };\r\n room.addPresenceListener('audiomuted', this._audioMuteHandler);\r\n\r\n this._videoMuteHandler = (node, from) => {\r\n if (!this._doesEndpointSendNewSourceInfo(from)) {\r\n emitVideoMutedEvent(from, node.value === 'true');\r\n }\r\n };\r\n room.addPresenceListener('videomuted', this._videoMuteHandler);\r\n\r\n const emitVideoTypeEvent = (endpointId, videoType) => {\r\n this.eventEmitter.emit(\r\n SignalingEvents.PEER_VIDEO_TYPE_CHANGED,\r\n endpointId, videoType);\r\n };\r\n\r\n this._videoTypeHandler = (node, from) => {\r\n if (!this._doesEndpointSendNewSourceInfo(from)) {\r\n emitVideoTypeEvent(from, node.value);\r\n }\r\n };\r\n\r\n if (!FeatureFlags.isMultiStreamSupportEnabled()) {\r\n room.addPresenceListener('videoType', this._videoTypeHandler);\r\n }\r\n\r\n this._sourceInfoHandler = (node, mucNick) => {\r\n const endpointId = mucNick;\r\n const { value } = node;\r\n const sourceInfoJSON = JSON.parse(value);\r\n const emitEventsFromHere = this._doesEndpointSendNewSourceInfo(endpointId);\r\n const endpointSourceState\r\n = this._remoteSourceState[endpointId] || (this._remoteSourceState[endpointId] = {});\r\n\r\n for (const sourceName of Object.keys(sourceInfoJSON)) {\r\n const mediaType = getMediaTypeFromSourceName(sourceName);\r\n const newMutedState = Boolean(sourceInfoJSON[sourceName].muted);\r\n const oldSourceState = endpointSourceState[sourceName]\r\n || (endpointSourceState[sourceName] = { sourceName });\r\n\r\n if (oldSourceState.muted !== newMutedState) {\r\n oldSourceState.muted = newMutedState;\r\n if (emitEventsFromHere && !this._localSourceState[sourceName]) {\r\n this.eventEmitter.emit(SignalingEvents.SOURCE_MUTED_CHANGED, sourceName, newMutedState);\r\n }\r\n }\r\n\r\n // Assume a default videoType of 'camera' for video sources.\r\n const newVideoType = mediaType === MediaType.VIDEO\r\n ? sourceInfoJSON[sourceName].videoType ?? VideoType.CAMERA\r\n : undefined;\r\n\r\n if (oldSourceState.videoType !== newVideoType) {\r\n oldSourceState.videoType = newVideoType;\r\n\r\n // Since having a mix of eps that do/don't support multi-stream in the same call is supported, emit\r\n // SOURCE_VIDEO_TYPE_CHANGED event when the remote source changes videoType.\r\n if (emitEventsFromHere && !this._localSourceState[sourceName]) {\r\n this.eventEmitter.emit(SignalingEvents.SOURCE_VIDEO_TYPE_CHANGED, sourceName, newVideoType);\r\n }\r\n }\r\n }\r\n\r\n // Cleanup removed source names\r\n const newSourceNames = Object.keys(sourceInfoJSON);\r\n\r\n for (const sourceName of Object.keys(endpointSourceState)) {\r\n if (newSourceNames.indexOf(sourceName) === -1) {\r\n delete endpointSourceState[sourceName];\r\n }\r\n }\r\n };\r\n room.addPresenceListener('SourceInfo', this._sourceInfoHandler);\r\n\r\n // Cleanup when participant leaves\r\n this._memberLeftHandler = jid => {\r\n const endpointId = Strophe.getResourceFromJid(jid);\r\n\r\n delete this._remoteSourceState[endpointId];\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n for (const [ key, value ] of this.ssrcOwners.entries()) {\r\n if (value === endpointId) {\r\n delete this._sourceNames[key];\r\n }\r\n }\r\n }\r\n };\r\n\r\n room.addEventListener(XMPPEvents.MUC_MEMBER_LEFT, this._memberLeftHandler);\r\n }\r\n\r\n /**\r\n * Finds the first source of given media type for the given endpoint.\r\n * @param endpointId\r\n * @param mediaType\r\n * @returns {SourceInfo|null}\r\n * @private\r\n */\r\n _findEndpointSourceInfoForMediaType(endpointId, mediaType) {\r\n const remoteSourceState = this._remoteSourceState[endpointId];\r\n\r\n if (!remoteSourceState) {\r\n return null;\r\n }\r\n\r\n for (const sourceInfo of Object.values(remoteSourceState)) {\r\n const _mediaType = getMediaTypeFromSourceName(sourceInfo.sourceName);\r\n\r\n if (_mediaType === mediaType) {\r\n return sourceInfo;\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * @inheritDoc\r\n */\r\n getPeerMediaInfo(owner, mediaType, sourceName) {\r\n const legacyGetPeerMediaInfo = () => {\r\n if (this.chatRoom) {\r\n return this.chatRoom.getMediaPresenceInfo(owner, mediaType);\r\n }\r\n logger.warn('Requested peer media info, before room was set');\r\n };\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n const lastPresence = this.chatRoom?.getLastPresence(owner);\r\n\r\n if (!lastPresence) {\r\n logger.warn(`getPeerMediaInfo - no presence stored for: ${owner}`);\r\n\r\n return;\r\n }\r\n if (!this._doesEndpointSendNewSourceInfo(owner)) {\r\n return legacyGetPeerMediaInfo();\r\n }\r\n\r\n if (sourceName) {\r\n return this.getPeerSourceInfo(owner, sourceName);\r\n }\r\n\r\n /**\r\n * @type {PeerMediaInfo}\r\n */\r\n const mediaInfo = {};\r\n const endpointMediaSource = this._findEndpointSourceInfoForMediaType(owner, mediaType);\r\n\r\n // The defaults are provided only, because getPeerMediaInfo is a legacy method. This will be eventually\r\n // changed into a getSourceInfo method which returns undefined if there's no source. Also there will be\r\n // no mediaType argument there.\r\n if (mediaType === MediaType.AUDIO) {\r\n mediaInfo.muted = endpointMediaSource ? endpointMediaSource.muted : true;\r\n } else if (mediaType === MediaType.VIDEO) {\r\n mediaInfo.muted = endpointMediaSource ? endpointMediaSource.muted : true;\r\n mediaInfo.videoType = endpointMediaSource ? endpointMediaSource.videoType : undefined;\r\n\r\n const codecTypeNode = filterNodeFromPresenceJSON(lastPresence, 'jitsi_participant_codecType');\r\n\r\n if (codecTypeNode.length > 0) {\r\n mediaInfo.codecType = codecTypeNode[0].value;\r\n }\r\n } else {\r\n throw new Error(`Unsupported media type: ${mediaType}`);\r\n }\r\n\r\n return mediaInfo;\r\n }\r\n\r\n return legacyGetPeerMediaInfo();\r\n }\r\n\r\n /**\r\n * @inheritDoc\r\n */\r\n getPeerSourceInfo(owner, sourceName) {\r\n const mediaInfo = {\r\n muted: true, // muted by default\r\n videoType: VideoType.CAMERA // 'camera' by default\r\n };\r\n\r\n return this._remoteSourceState[owner]\r\n ? this._remoteSourceState[owner][sourceName] ?? mediaInfo\r\n : undefined;\r\n }\r\n\r\n /**\r\n * @inheritDoc\r\n */\r\n getSSRCOwner(ssrc) {\r\n return this.ssrcOwners.get(ssrc);\r\n }\r\n\r\n /**\r\n * Set an SSRC owner.\r\n * @param {number} ssrc an SSRC to be owned\r\n * @param {string} endpointId owner's ID (MUC nickname)\r\n * @throws TypeError if ssrc is not a number\r\n */\r\n setSSRCOwner(ssrc, endpointId) {\r\n if (typeof ssrc !== 'number') {\r\n throw new TypeError(`SSRC(${ssrc}) must be a number`);\r\n }\r\n\r\n // Now signaling layer instance is shared between different JingleSessionPC instances, so although very unlikely\r\n // an SSRC conflict could potentially occur. Log a message to make debugging easier.\r\n const existingOwner = this.ssrcOwners.get(ssrc);\r\n\r\n if (existingOwner && existingOwner !== endpointId) {\r\n logger.error(`SSRC owner re-assigned from ${existingOwner} to ${endpointId}`);\r\n }\r\n this.ssrcOwners.set(ssrc, endpointId);\r\n }\r\n\r\n /**\r\n * Adjusts muted status of given track.\r\n *\r\n * @param {SourceName} sourceName - the name of the track's source.\r\n * @param {boolean} muted - the new muted status.\r\n * @returns {boolean}\r\n */\r\n setTrackMuteStatus(sourceName, muted) {\r\n if (!this._localSourceState[sourceName]) {\r\n this._localSourceState[sourceName] = {};\r\n }\r\n\r\n this._localSourceState[sourceName].muted = muted;\r\n\r\n if (this.chatRoom) {\r\n // FIXME This only adjusts the presence, but doesn't actually send it. Here we temporarily rely on\r\n // the legacy signaling part to send the presence. Remember to add \"send presence\" here when the legacy\r\n // signaling is removed.\r\n return this._addLocalSourceInfoToPresence();\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Sets track's video type.\r\n * @param {SourceName} sourceName - the track's source name.\r\n * @param {VideoType} videoType - the new video type.\r\n * @returns {boolean}\r\n */\r\n setTrackVideoType(sourceName, videoType) {\r\n if (!this._localSourceState[sourceName]) {\r\n this._localSourceState[sourceName] = {};\r\n }\r\n\r\n if (this._localSourceState[sourceName].videoType !== videoType) {\r\n // Include only if not a camera (default)\r\n this._localSourceState[sourceName].videoType = videoType === VideoType.CAMERA ? undefined : videoType;\r\n\r\n // NOTE this doesn't send the actual presence, because is called from the same place where the legacy video\r\n // type is emitted which does the actual sending. A send presence statement needs to be added when\r\n // the legacy part is removed.\r\n return this._addLocalSourceInfoToPresence();\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * @inheritDoc\r\n */\r\n getTrackSourceName(ssrc) {\r\n return this._sourceNames.get(ssrc);\r\n }\r\n\r\n /**\r\n * Saves the source name for a track identified by it's ssrc.\r\n * @param {number} ssrc the ssrc of the target track.\r\n * @param {SourceName} sourceName the track's source name to save.\r\n * @throws TypeError if ssrc is not a number\r\n */\r\n setTrackSourceName(ssrc, sourceName) {\r\n if (typeof ssrc !== 'number') {\r\n throw new TypeError(`SSRC(${ssrc}) must be a number`);\r\n }\r\n\r\n // Now signaling layer instance is shared between different JingleSessionPC instances, so although very unlikely\r\n // an SSRC conflict could potentially occur. Log a message to make debugging easier.\r\n const existingName = this._sourceNames.get(ssrc);\r\n\r\n if (existingName && existingName !== sourceName) {\r\n logger.error(`SSRC(${ssrc}) sourceName re-assigned from ${existingName} to ${sourceName}`);\r\n }\r\n\r\n this._sourceNames.set(ssrc, sourceName);\r\n }\r\n\r\n}\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport EventEmitter from 'events';\r\nimport isEqual from 'lodash.isequal';\r\nimport { Strophe } from 'strophe.js';\r\n\r\nimport * as JitsiConferenceErrors from './JitsiConferenceErrors';\r\nimport JitsiConferenceEventManager from './JitsiConferenceEventManager';\r\nimport * as JitsiConferenceEvents from './JitsiConferenceEvents';\r\nimport JitsiParticipant from './JitsiParticipant';\r\nimport JitsiTrackError from './JitsiTrackError';\r\nimport * as JitsiTrackErrors from './JitsiTrackErrors';\r\nimport * as JitsiTrackEvents from './JitsiTrackEvents';\r\nimport authenticateAndUpgradeRole from './authenticateAndUpgradeRole';\r\nimport { CodecSelection } from './modules/RTC/CodecSelection';\r\nimport RTC from './modules/RTC/RTC';\r\nimport { SS_DEFAULT_FRAME_RATE } from './modules/RTC/ScreenObtainer';\r\nimport browser from './modules/browser';\r\nimport ConnectionQuality from './modules/connectivity/ConnectionQuality';\r\nimport IceFailedHandling\r\n from './modules/connectivity/IceFailedHandling';\r\nimport ParticipantConnectionStatusHandler\r\n from './modules/connectivity/ParticipantConnectionStatus';\r\nimport * as DetectionEvents from './modules/detection/DetectionEvents';\r\nimport NoAudioSignalDetection from './modules/detection/NoAudioSignalDetection';\r\nimport P2PDominantSpeakerDetection from './modules/detection/P2PDominantSpeakerDetection';\r\nimport VADAudioAnalyser from './modules/detection/VADAudioAnalyser';\r\nimport VADNoiseDetection from './modules/detection/VADNoiseDetection';\r\nimport VADTalkMutedDetection from './modules/detection/VADTalkMutedDetection';\r\nimport { E2EEncryption } from './modules/e2ee/E2EEncryption';\r\nimport E2ePing from './modules/e2eping/e2eping';\r\nimport Jvb121EventGenerator from './modules/event/Jvb121EventGenerator';\r\nimport FeatureFlags from './modules/flags/FeatureFlags';\r\nimport ReceiveVideoController from './modules/qualitycontrol/ReceiveVideoController';\r\nimport SendVideoController from './modules/qualitycontrol/SendVideoController';\r\nimport RecordingManager from './modules/recording/RecordingManager';\r\nimport Settings from './modules/settings/Settings';\r\nimport AudioOutputProblemDetector from './modules/statistics/AudioOutputProblemDetector';\r\nimport AvgRTPStatsReporter from './modules/statistics/AvgRTPStatsReporter';\r\nimport SpeakerStatsCollector from './modules/statistics/SpeakerStatsCollector';\r\nimport Statistics from './modules/statistics/statistics';\r\nimport Transcriber from './modules/transcription/transcriber';\r\nimport GlobalOnErrorHandler from './modules/util/GlobalOnErrorHandler';\r\nimport RandomUtil from './modules/util/RandomUtil';\r\nimport ComponentsVersions from './modules/version/ComponentsVersions';\r\nimport VideoSIPGW from './modules/videosipgw/VideoSIPGW';\r\nimport * as VideoSIPGWConstants from './modules/videosipgw/VideoSIPGWConstants';\r\nimport SignalingLayerImpl from './modules/xmpp/SignalingLayerImpl';\r\nimport {\r\n FEATURE_E2EE,\r\n FEATURE_JIGASI,\r\n JITSI_MEET_MUC_TYPE\r\n} from './modules/xmpp/xmpp';\r\nimport BridgeVideoType from './service/RTC/BridgeVideoType';\r\nimport CodecMimeType from './service/RTC/CodecMimeType';\r\nimport { MediaType } from './service/RTC/MediaType';\r\nimport RTCEvents from './service/RTC/RTCEvents';\r\nimport { getSourceNameForJitsiTrack } from './service/RTC/SignalingLayer';\r\nimport { VideoType } from './service/RTC/VideoType';\r\nimport {\r\n ACTION_JINGLE_RESTART,\r\n ACTION_JINGLE_SI_RECEIVED,\r\n ACTION_JINGLE_SI_TIMEOUT,\r\n ACTION_JINGLE_TERMINATE,\r\n ACTION_P2P_DECLINED,\r\n ACTION_P2P_ESTABLISHED,\r\n ACTION_P2P_FAILED,\r\n ACTION_P2P_SWITCH_TO_JVB,\r\n ICE_ESTABLISHMENT_DURATION_DIFF,\r\n createConferenceEvent,\r\n createJingleEvent,\r\n createP2PEvent\r\n} from './service/statistics/AnalyticsEvents';\r\nimport { XMPPEvents } from './service/xmpp/XMPPEvents';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * How long since Jicofo is supposed to send a session-initiate, before\r\n * {@link ACTION_JINGLE_SI_TIMEOUT} analytics event is sent (in ms).\r\n * @type {number}\r\n */\r\nconst JINGLE_SI_TIMEOUT = 5000;\r\n\r\n/**\r\n * Creates a JitsiConference object with the given name and properties.\r\n * Note: this constructor is not a part of the public API (objects should be\r\n * created using JitsiConnection.createConference).\r\n * @param options.config properties / settings related to the conference that\r\n * will be created.\r\n * @param options.name the name of the conference\r\n * @param options.connection the JitsiConnection object for this\r\n * JitsiConference.\r\n * @param {number} [options.config.avgRtpStatsN=15] how many samples are to be\r\n * collected by {@link AvgRTPStatsReporter}, before arithmetic mean is\r\n * calculated and submitted to the analytics module.\r\n * @param {boolean} [options.config.enableIceRestart=false] - enables the ICE\r\n * restart logic.\r\n * @param {boolean} [options.config.p2p.enabled] when set to true\r\n * the peer to peer mode will be enabled. It means that when there are only 2\r\n * participants in the conference an attempt to make direct connection will be\r\n * made. If the connection succeeds the conference will stop sending data\r\n * through the JVB connection and will use the direct one instead.\r\n * @param {number} [options.config.p2p.backToP2PDelay=5] a delay given in\r\n * seconds, before the conference switches back to P2P, after the 3rd\r\n * participant has left the room.\r\n * @param {number} [options.config.channelLastN=-1] The requested amount of\r\n * videos are going to be delivered after the value is in effect. Set to -1 for\r\n * unlimited or all available videos.\r\n * @param {number} [options.config.forceJVB121Ratio]\r\n * \"Math.random() < forceJVB121Ratio\" will determine whether a 2 people\r\n * conference should be moved to the JVB instead of P2P. The decision is made on\r\n * the responder side, after ICE succeeds on the P2P connection.\r\n * @constructor\r\n *\r\n * FIXME Make all methods which are called from lib-internal classes\r\n * to non-public (use _). To name a few:\r\n * {@link JitsiConference.onLocalRoleChanged}\r\n * {@link JitsiConference.onUserRoleChanged}\r\n * {@link JitsiConference.onMemberLeft}\r\n * and so on...\r\n */\r\nexport default function JitsiConference(options) {\r\n if (!options.name || options.name.toLowerCase() !== options.name.toString()) {\r\n const errmsg\r\n = 'Invalid conference name (no conference name passed or it '\r\n + 'contains invalid characters like capital letters)!';\r\n\r\n logger.error(errmsg);\r\n throw new Error(errmsg);\r\n }\r\n this.connection = options.connection;\r\n this.xmpp = this.connection?.xmpp;\r\n\r\n if (this.xmpp.isRoomCreated(options.name, options.customDomain)) {\r\n const errmsg = 'A conference with the same name has already been created!';\r\n\r\n delete this.connection;\r\n delete this.xmpp;\r\n logger.error(errmsg);\r\n throw new Error(errmsg);\r\n }\r\n this.eventEmitter = new EventEmitter();\r\n this.options = options;\r\n this.eventManager = new JitsiConferenceEventManager(this);\r\n this.participants = {};\r\n\r\n /**\r\n * The signaling layer instance.\r\n * @type {SignalingLayerImpl}\r\n * @private\r\n */\r\n this._signalingLayer = new SignalingLayerImpl();\r\n\r\n this._init(options);\r\n this.componentsVersions = new ComponentsVersions(this);\r\n\r\n /**\r\n * Jingle session instance for the JVB connection.\r\n * @type {JingleSessionPC}\r\n */\r\n this.jvbJingleSession = null;\r\n this.lastDominantSpeaker = null;\r\n this.dtmfManager = null;\r\n this.somebodySupportsDTMF = false;\r\n this.authEnabled = false;\r\n this.startAudioMuted = false;\r\n this.startVideoMuted = false;\r\n this.startMutedPolicy = {\r\n audio: false,\r\n video: false\r\n };\r\n this.isMutedByFocus = false;\r\n\r\n // when muted by focus we receive the jid of the initiator of the mute\r\n this.mutedByFocusActor = null;\r\n\r\n this.isVideoMutedByFocus = false;\r\n\r\n // when video muted by focus we receive the jid of the initiator of the mute\r\n this.mutedVideoByFocusActor = null;\r\n\r\n // Flag indicates if the 'onCallEnded' method was ever called on this\r\n // instance. Used to log extra analytics event for debugging purpose.\r\n // We need to know if the potential issue happened before or after\r\n // the restart.\r\n this.wasStopped = false;\r\n\r\n // Conference properties, maintained by jicofo.\r\n this.properties = {};\r\n\r\n /**\r\n * The object which monitors local and remote connection statistics (e.g.\r\n * sending bitrate) and calculates a number which represents the connection\r\n * quality.\r\n */\r\n this.connectionQuality\r\n = new ConnectionQuality(this, this.eventEmitter, options);\r\n\r\n /**\r\n * Reports average RTP statistics to the analytics module.\r\n * @type {AvgRTPStatsReporter}\r\n */\r\n this.avgRtpStatsReporter\r\n = new AvgRTPStatsReporter(this, options.config.avgRtpStatsN || 15);\r\n\r\n /**\r\n * Detects issues with the audio of remote participants.\r\n * @type {AudioOutputProblemDetector}\r\n */\r\n if (!options.config.disableAudioLevels) {\r\n this._audioOutputProblemDetector = new AudioOutputProblemDetector(this);\r\n }\r\n\r\n /**\r\n * Indicates whether the connection is interrupted or not.\r\n */\r\n this.isJvbConnectionInterrupted = false;\r\n\r\n /**\r\n * The object which tracks active speaker times\r\n */\r\n this.speakerStatsCollector = new SpeakerStatsCollector(this);\r\n\r\n /* P2P related fields below: */\r\n\r\n /**\r\n * Stores reference to deferred start P2P task. It's created when 3rd\r\n * participant leaves the room in order to avoid ping pong effect (it\r\n * could be just a page reload).\r\n * @type {number|null}\r\n */\r\n this.deferredStartP2PTask = null;\r\n\r\n const delay\r\n = parseInt(options.config.p2p && options.config.p2p.backToP2PDelay, 10);\r\n\r\n /**\r\n * A delay given in seconds, before the conference switches back to P2P\r\n * after the 3rd participant has left.\r\n * @type {number}\r\n */\r\n this.backToP2PDelay = isNaN(delay) ? 5 : delay;\r\n logger.info(`backToP2PDelay: ${this.backToP2PDelay}`);\r\n\r\n /**\r\n * If set to true it means the P2P ICE is no longer connected.\r\n * When false it means that P2P ICE (media) connection is up\r\n * and running.\r\n * @type {boolean}\r\n */\r\n this.isP2PConnectionInterrupted = false;\r\n\r\n /**\r\n * Flag set to true when P2P session has been established\r\n * (ICE has been connected) and this conference is currently in the peer to\r\n * peer mode (P2P connection is the active one).\r\n * @type {boolean}\r\n */\r\n this.p2p = false;\r\n\r\n /**\r\n * A JingleSession for the direct peer to peer connection.\r\n * @type {JingleSessionPC}\r\n */\r\n this.p2pJingleSession = null;\r\n\r\n this.videoSIPGWHandler = new VideoSIPGW(this.room);\r\n this.recordingManager = new RecordingManager(this.room);\r\n\r\n /**\r\n * If the conference.joined event has been sent this will store the timestamp when it happened.\r\n *\r\n * @type {undefined|number}\r\n * @private\r\n */\r\n this._conferenceJoinAnalyticsEventSent = undefined;\r\n\r\n /**\r\n * End-to-End Encryption. Make it available if supported.\r\n */\r\n if (this.isE2EESupported()) {\r\n logger.info('End-to-End Encryption is supported');\r\n\r\n this._e2eEncryption = new E2EEncryption(this);\r\n }\r\n\r\n /**\r\n * Flag set to true when Jicofo sends a presence message indicating that the max audio sender limit has\r\n * been reached for the call. Once this is set, unmuting audio will be disabled from the client until it gets reset\r\n * again by Jicofo.\r\n */\r\n this._audioSenderLimitReached = undefined;\r\n\r\n /**\r\n * Flag set to true when Jicofo sends a presence message indicating that the max video sender limit has\r\n * been reached for the call. Once this is set, unmuting video will be disabled from the client until it gets reset\r\n * again by Jicofo.\r\n */\r\n this._videoSenderLimitReached = undefined;\r\n}\r\n\r\n// FIXME convert JitsiConference to ES6 - ASAP !\r\nJitsiConference.prototype.constructor = JitsiConference;\r\n\r\n/**\r\n * Create a resource for the a jid. We use the room nickname (the resource part\r\n * of the occupant JID, see XEP-0045) as the endpoint ID in colibri. We require\r\n * endpoint IDs to be 8 hex digits because in some cases they get serialized\r\n * into a 32bit field.\r\n *\r\n * @param {string} jid - The id set onto the XMPP connection.\r\n * @param {boolean} isAuthenticatedUser - Whether or not the user has connected\r\n * to the XMPP service with a password.\r\n * @returns {string}\r\n * @static\r\n */\r\nJitsiConference.resourceCreator = function(jid, isAuthenticatedUser) {\r\n let mucNickname;\r\n\r\n if (isAuthenticatedUser) {\r\n // For authenticated users generate a random ID.\r\n mucNickname = RandomUtil.randomHexString(8).toLowerCase();\r\n } else {\r\n // We try to use the first part of the node (which for anonymous users\r\n // on prosody is a UUID) to match the previous behavior (and maybe make\r\n // debugging easier).\r\n mucNickname = Strophe.getNodeFromJid(jid)?.substr(0, 8)\r\n .toLowerCase();\r\n\r\n // But if this doesn't have the required format we just generate a new\r\n // random nickname.\r\n const re = /[0-9a-f]{8}/g;\r\n\r\n if (!mucNickname || !re.test(mucNickname)) {\r\n mucNickname = RandomUtil.randomHexString(8).toLowerCase();\r\n }\r\n }\r\n\r\n return mucNickname;\r\n};\r\n\r\n/**\r\n * Initializes the conference object properties\r\n * @param options {object}\r\n * @param options.connection {JitsiConnection} overrides this.connection\r\n */\r\nJitsiConference.prototype._init = function(options = {}) {\r\n this.eventManager.setupXMPPListeners();\r\n\r\n const { config } = this.options;\r\n\r\n // Get the codec preference settings from config.js.\r\n // 'preferH264' and 'disableH264' settings have been deprecated for a while,\r\n // 'preferredCodec' and 'disabledCodec' will have precedence over them.\r\n const codecSettings = {\r\n disabledCodec: config.videoQuality\r\n ? config.videoQuality.disabledCodec\r\n : config.p2p && config.p2p.disableH264 && CodecMimeType.H264,\r\n enforcePreferredCodec: config.videoQuality && config.videoQuality.enforcePreferredCodec,\r\n jvbCodec: (config.videoQuality && config.videoQuality.preferredCodec)\r\n || (config.preferH264 && CodecMimeType.H264),\r\n p2pCodec: config.p2p\r\n ? config.p2p.preferredCodec || (config.p2p.preferH264 && CodecMimeType.H264)\r\n : CodecMimeType.VP8\r\n };\r\n\r\n this.codecSelection = new CodecSelection(this, codecSettings);\r\n this._statsCurrentId = config.statisticsId ? config.statisticsId : Settings.callStatsUserName;\r\n this.room = this.xmpp.createRoom(\r\n this.options.name, {\r\n ...config,\r\n statsId: this._statsCurrentId\r\n },\r\n JitsiConference.resourceCreator\r\n );\r\n\r\n this._signalingLayer.setChatRoom(this.room);\r\n\r\n // Connection interrupted/restored listeners\r\n this._onIceConnectionInterrupted\r\n = this._onIceConnectionInterrupted.bind(this);\r\n this.room.addListener(\r\n XMPPEvents.CONNECTION_INTERRUPTED, this._onIceConnectionInterrupted);\r\n\r\n this._onIceConnectionRestored = this._onIceConnectionRestored.bind(this);\r\n this.room.addListener(\r\n XMPPEvents.CONNECTION_RESTORED, this._onIceConnectionRestored);\r\n\r\n this._onIceConnectionEstablished\r\n = this._onIceConnectionEstablished.bind(this);\r\n this.room.addListener(\r\n XMPPEvents.CONNECTION_ESTABLISHED, this._onIceConnectionEstablished);\r\n\r\n this._updateProperties = this._updateProperties.bind(this);\r\n this.room.addListener(XMPPEvents.CONFERENCE_PROPERTIES_CHANGED,\r\n this._updateProperties);\r\n\r\n this._sendConferenceJoinAnalyticsEvent = this._sendConferenceJoinAnalyticsEvent.bind(this);\r\n this.room.addListener(XMPPEvents.MEETING_ID_SET, this._sendConferenceJoinAnalyticsEvent);\r\n\r\n this._removeLocalSourceOnReject = this._removeLocalSourceOnReject.bind(this);\r\n this._updateRoomPresence = this._updateRoomPresence.bind(this);\r\n this.room.addListener(XMPPEvents.SESSION_ACCEPT, this._updateRoomPresence);\r\n this.room.addListener(XMPPEvents.SOURCE_ADD, this._updateRoomPresence);\r\n this.room.addListener(XMPPEvents.SOURCE_ADD_ERROR, this._removeLocalSourceOnReject);\r\n this.room.addListener(XMPPEvents.SOURCE_REMOVE, this._updateRoomPresence);\r\n\r\n if (config.e2eping?.enabled) {\r\n this.e2eping = new E2ePing(\r\n this,\r\n config,\r\n (message, to) => {\r\n try {\r\n this.sendMessage(message, to, true /* sendThroughVideobridge */);\r\n } catch (error) {\r\n logger.warn('Failed to send E2E ping request or response.', error && error.msg);\r\n }\r\n });\r\n }\r\n\r\n if (!this.rtc) {\r\n this.rtc = new RTC(this, options);\r\n this.eventManager.setupRTCListeners();\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this._registerRtcListeners(this.rtc);\r\n }\r\n }\r\n\r\n this.receiveVideoController = new ReceiveVideoController(this, this.rtc);\r\n this.sendVideoController = new SendVideoController(this, this.rtc);\r\n\r\n // Do not initialize ParticipantConnectionStatusHandler when source-name signaling is enabled.\r\n if (!FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this.participantConnectionStatus\r\n = new ParticipantConnectionStatusHandler(\r\n this.rtc,\r\n this,\r\n {\r\n // These options are not public API, leaving it here only as an entry point through config for tuning\r\n // up purposes. Default values should be adjusted as soon as optimal values are discovered.\r\n p2pRtcMuteTimeout: config._p2pConnStatusRtcMuteTimeout,\r\n rtcMuteTimeout: config._peerConnStatusRtcMuteTimeout,\r\n outOfLastNTimeout: config._peerConnStatusOutOfLastNTimeout\r\n });\r\n this.participantConnectionStatus.init();\r\n }\r\n\r\n // Add the ability to enable callStats only on a percentage of users based on config.js settings.\r\n let enableCallStats = true;\r\n\r\n if (config.testing && config.testing.callStatsThreshold) {\r\n enableCallStats = (Math.random() * 100) <= config.testing.callStatsThreshold;\r\n }\r\n\r\n if (!this.statistics) {\r\n this.statistics = new Statistics(this.xmpp, {\r\n aliasName: this._statsCurrentId,\r\n userName: config.statisticsDisplayName ? config.statisticsDisplayName : this.myUserId(),\r\n confID: config.confID || `${this.connection.options.hosts.domain}/${this.options.name}`,\r\n siteID: config.siteID,\r\n customScriptUrl: config.callStatsCustomScriptUrl,\r\n callStatsID: config.callStatsID,\r\n callStatsSecret: config.callStatsSecret,\r\n callStatsApplicationLogsDisabled: config.callStatsApplicationLogsDisabled,\r\n enableCallStats,\r\n roomName: this.options.name,\r\n applicationName: config.applicationName,\r\n getWiFiStatsMethod: config.getWiFiStatsMethod,\r\n configParams: config.callStatsConfigParams\r\n });\r\n Statistics.analytics.addPermanentProperties({\r\n 'callstats_name': this._statsCurrentId\r\n });\r\n\r\n // Start performance observer for monitoring long tasks\r\n if (config.longTasksStatsInterval) {\r\n this.statistics.attachLongTasksStats(this);\r\n }\r\n }\r\n\r\n this.eventManager.setupChatRoomListeners();\r\n\r\n // Always add listeners because on reload we are executing leave and the\r\n // listeners are removed from statistics module.\r\n this.eventManager.setupStatisticsListeners();\r\n\r\n // Disable VAD processing on Safari since it causes audio input to\r\n // fail on some of the mobile devices.\r\n if (config.enableTalkWhileMuted && browser.supportsVADDetection()) {\r\n // If VAD processor factory method is provided uses VAD based detection, otherwise fallback to audio level\r\n // based detection.\r\n if (config.createVADProcessor) {\r\n logger.info('Using VAD detection for generating talk while muted events');\r\n\r\n if (!this._audioAnalyser) {\r\n this._audioAnalyser = new VADAudioAnalyser(this, config.createVADProcessor);\r\n }\r\n\r\n const vadTalkMutedDetection = new VADTalkMutedDetection();\r\n\r\n vadTalkMutedDetection.on(DetectionEvents.VAD_TALK_WHILE_MUTED, () =>\r\n this.eventEmitter.emit(JitsiConferenceEvents.TALK_WHILE_MUTED));\r\n\r\n this._audioAnalyser.addVADDetectionService(vadTalkMutedDetection);\r\n } else {\r\n logger.warn('No VAD Processor was provided. Talk while muted detection service was not initialized!');\r\n }\r\n }\r\n\r\n // Disable noisy mic detection on safari since it causes the audio input to\r\n // fail on Safari on iPadOS.\r\n if (config.enableNoisyMicDetection && browser.supportsVADDetection()) {\r\n if (config.createVADProcessor) {\r\n if (!this._audioAnalyser) {\r\n this._audioAnalyser = new VADAudioAnalyser(this, config.createVADProcessor);\r\n }\r\n\r\n const vadNoiseDetection = new VADNoiseDetection();\r\n\r\n vadNoiseDetection.on(DetectionEvents.VAD_NOISY_DEVICE, () =>\r\n this.eventEmitter.emit(JitsiConferenceEvents.NOISY_MIC));\r\n\r\n this._audioAnalyser.addVADDetectionService(vadNoiseDetection);\r\n } else {\r\n logger.warn('No VAD Processor was provided. Noisy microphone detection service was not initialized!');\r\n }\r\n }\r\n\r\n // Generates events based on no audio input detector.\r\n if (config.enableNoAudioDetection) {\r\n this._noAudioSignalDetection = new NoAudioSignalDetection(this);\r\n this._noAudioSignalDetection.on(DetectionEvents.NO_AUDIO_INPUT, () => {\r\n this.eventEmitter.emit(JitsiConferenceEvents.NO_AUDIO_INPUT);\r\n });\r\n this._noAudioSignalDetection.on(DetectionEvents.AUDIO_INPUT_STATE_CHANGE, hasAudioSignal => {\r\n this.eventEmitter.emit(JitsiConferenceEvents.AUDIO_INPUT_STATE_CHANGE, hasAudioSignal);\r\n });\r\n }\r\n\r\n\r\n if ('channelLastN' in config) {\r\n this.setLastN(config.channelLastN);\r\n }\r\n\r\n /**\r\n * Emits {@link JitsiConferenceEvents.JVB121_STATUS}.\r\n * @type {Jvb121EventGenerator}\r\n */\r\n this.jvb121Status = new Jvb121EventGenerator(this);\r\n\r\n // creates dominant speaker detection that works only in p2p mode\r\n this.p2pDominantSpeakerDetection = new P2PDominantSpeakerDetection(this);\r\n\r\n if (config && config.deploymentInfo && config.deploymentInfo.userRegion) {\r\n this.setLocalParticipantProperty(\r\n 'region', config.deploymentInfo.userRegion);\r\n }\r\n\r\n // Publish the codec type to presence.\r\n this.setLocalParticipantProperty('codecType', this.codecSelection.getPreferredCodec());\r\n\r\n // Set transcription language presence extension.\r\n // In case the language config is undefined or has the default value that the transcriber uses\r\n // (in our case Jigasi uses 'en-US'), don't set the participant property in order to avoid\r\n // needlessly polluting the presence stanza.\r\n if (config && config.transcriptionLanguage && config.transcriptionLanguage !== 'en-US') {\r\n this.setLocalParticipantProperty('transcription_language', config.transcriptionLanguage);\r\n }\r\n};\r\n\r\n/**\r\n * Joins the conference.\r\n * @param password {string} the password\r\n * @param replaceParticipant {boolean} whether the current join replaces\r\n * an existing participant with same jwt from the meeting.\r\n */\r\nJitsiConference.prototype.join = function(password, replaceParticipant = false) {\r\n if (this.room) {\r\n this.room.join(password, replaceParticipant).then(() => this._maybeSetSITimeout());\r\n }\r\n};\r\n\r\n/**\r\n * Authenticates and upgrades the role of the local participant/user.\r\n *\r\n * @returns {Object} A thenable which (1) settles when the process of\r\n * authenticating and upgrading the role of the local participant/user finishes\r\n * and (2) has a cancel method that allows the caller to interrupt the\r\n * process.\r\n */\r\nJitsiConference.prototype.authenticateAndUpgradeRole = function(options) {\r\n return authenticateAndUpgradeRole.call(this, {\r\n ...options,\r\n onCreateResource: JitsiConference.resourceCreator\r\n });\r\n};\r\n\r\n/**\r\n * Check if joined to the conference.\r\n */\r\nJitsiConference.prototype.isJoined = function() {\r\n return this.room && this.room.joined;\r\n};\r\n\r\n/**\r\n * Tells whether or not the P2P mode is enabled in the configuration.\r\n * @return {boolean}\r\n */\r\nJitsiConference.prototype.isP2PEnabled = function() {\r\n return Boolean(this.options.config.p2p && this.options.config.p2p.enabled)\r\n\r\n // FIXME: remove once we have a default config template. -saghul\r\n || typeof this.options.config.p2p === 'undefined';\r\n};\r\n\r\n/**\r\n * When in P2P test mode, the conference will not automatically switch to P2P\r\n * when there 2 participants.\r\n * @return {boolean}\r\n */\r\nJitsiConference.prototype.isP2PTestModeEnabled = function() {\r\n return Boolean(this.options.config.testing\r\n && this.options.config.testing.p2pTestMode);\r\n};\r\n\r\n/**\r\n * Leaves the conference.\r\n * @returns {Promise}\r\n */\r\nJitsiConference.prototype.leave = async function() {\r\n if (this.participantConnectionStatus) {\r\n this.participantConnectionStatus.dispose();\r\n this.participantConnectionStatus = null;\r\n }\r\n if (this.avgRtpStatsReporter) {\r\n this.avgRtpStatsReporter.dispose();\r\n this.avgRtpStatsReporter = null;\r\n }\r\n\r\n if (this._audioOutputProblemDetector) {\r\n this._audioOutputProblemDetector.dispose();\r\n this._audioOutputProblemDetector = null;\r\n }\r\n\r\n if (this.e2eping) {\r\n this.e2eping.stop();\r\n this.e2eping = null;\r\n }\r\n\r\n this.getLocalTracks().forEach(track => this.onLocalTrackRemoved(track));\r\n\r\n this.rtc.closeBridgeChannel();\r\n\r\n this._sendConferenceLeftAnalyticsEvent();\r\n\r\n if (this.statistics) {\r\n this.statistics.dispose();\r\n }\r\n\r\n this._delayedIceFailed && this._delayedIceFailed.cancel();\r\n\r\n this._maybeClearSITimeout();\r\n\r\n // Close both JVb and P2P JingleSessions\r\n if (this.jvbJingleSession) {\r\n this.jvbJingleSession.close();\r\n this.jvbJingleSession = null;\r\n }\r\n if (this.p2pJingleSession) {\r\n this.p2pJingleSession.close();\r\n this.p2pJingleSession = null;\r\n }\r\n\r\n // Leave the conference. If this.room == null we are calling second time leave().\r\n if (!this.room) {\r\n throw new Error('The conference is has been already left');\r\n }\r\n\r\n const room = this.room;\r\n\r\n // Unregister connection state listeners\r\n room.removeListener(\r\n XMPPEvents.CONNECTION_INTERRUPTED,\r\n this._onIceConnectionInterrupted);\r\n room.removeListener(\r\n XMPPEvents.CONNECTION_RESTORED,\r\n this._onIceConnectionRestored);\r\n room.removeListener(\r\n XMPPEvents.CONNECTION_ESTABLISHED,\r\n this._onIceConnectionEstablished);\r\n\r\n room.removeListener(\r\n XMPPEvents.CONFERENCE_PROPERTIES_CHANGED,\r\n this._updateProperties);\r\n\r\n room.removeListener(XMPPEvents.MEETING_ID_SET, this._sendConferenceJoinAnalyticsEvent);\r\n room.removeListener(XMPPEvents.SESSION_ACCEPT, this._updateRoomPresence);\r\n room.removeListener(XMPPEvents.SOURCE_ADD, this._updateRoomPresence);\r\n room.removeListener(XMPPEvents.SOURCE_ADD_ERROR, this._removeLocalSourceOnReject);\r\n room.removeListener(XMPPEvents.SOURCE_REMOVE, this._updateRoomPresence);\r\n\r\n this.eventManager.removeXMPPListeners();\r\n\r\n this._signalingLayer.setChatRoom(null);\r\n\r\n this.room = null;\r\n\r\n let leaveError;\r\n\r\n try {\r\n await room.leave();\r\n } catch (err) {\r\n leaveError = err;\r\n\r\n // Remove all participants because currently the conference\r\n // won't be usable anyway. This is done on success automatically\r\n // by the ChatRoom instance.\r\n this.getParticipants().forEach(\r\n participant => this.onMemberLeft(participant.getJid()));\r\n }\r\n\r\n if (this.rtc) {\r\n this.rtc.destroy();\r\n }\r\n\r\n if (leaveError) {\r\n throw leaveError;\r\n }\r\n};\r\n\r\n/**\r\n * Returns the currently active media session if any.\r\n *\r\n * @returns {JingleSessionPC|undefined}\r\n */\r\nJitsiConference.prototype.getActiveMediaSession = function() {\r\n return this.isP2PActive() ? this.p2pJingleSession : this.jvbJingleSession;\r\n};\r\n\r\n/**\r\n * Returns an array containing all media sessions existing in this conference.\r\n *\r\n * @returns {Array}\r\n */\r\nJitsiConference.prototype.getMediaSessions = function() {\r\n const sessions = [];\r\n\r\n this.jvbJingleSession && sessions.push(this.jvbJingleSession);\r\n this.p2pJingleSession && sessions.push(this.p2pJingleSession);\r\n\r\n return sessions;\r\n};\r\n\r\n/**\r\n * Registers event listeners on the RTC instance.\r\n * @param {RTC} rtc - the RTC module instance used by this conference.\r\n * @private\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype._registerRtcListeners = function(rtc) {\r\n rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, () => {\r\n for (const localTrack of this.rtc.localTracks) {\r\n localTrack.isVideoTrack() && this._sendBridgeVideoTypeMessage(localTrack);\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * Sends the 'VideoTypeMessage' to the bridge on the bridge channel so that the bridge can make bitrate allocation\r\n * decisions based on the video type of the local source.\r\n *\r\n * @param {JitsiLocalTrack} localtrack - The track associated with the local source signaled to the bridge.\r\n * @returns {void}\r\n * @private\r\n */\r\nJitsiConference.prototype._sendBridgeVideoTypeMessage = function(localtrack) {\r\n let videoType = !localtrack || localtrack.isMuted() ? BridgeVideoType.NONE : localtrack.getVideoType();\r\n\r\n if (videoType === BridgeVideoType.DESKTOP && this._desktopSharingFrameRate > SS_DEFAULT_FRAME_RATE) {\r\n videoType = BridgeVideoType.DESKTOP_HIGH_FPS;\r\n }\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && localtrack) {\r\n this.rtc.sendSourceVideoType(localtrack.getSourceName(), videoType);\r\n } else if (!FeatureFlags.isSourceNameSignalingEnabled()) {\r\n this.rtc.setVideoType(videoType);\r\n }\r\n};\r\n\r\n/**\r\n * Returns name of this conference.\r\n */\r\nJitsiConference.prototype.getName = function() {\r\n return this.options.name.toString();\r\n};\r\n\r\n/**\r\n * Returns the {@link JitsiConnection} used by this this conference.\r\n */\r\nJitsiConference.prototype.getConnection = function() {\r\n return this.connection;\r\n};\r\n\r\n/**\r\n * Check if authentication is enabled for this conference.\r\n */\r\nJitsiConference.prototype.isAuthEnabled = function() {\r\n return this.authEnabled;\r\n};\r\n\r\n/**\r\n * Check if user is logged in.\r\n */\r\nJitsiConference.prototype.isLoggedIn = function() {\r\n return Boolean(this.authIdentity);\r\n};\r\n\r\n/**\r\n * Get authorized login.\r\n */\r\nJitsiConference.prototype.getAuthLogin = function() {\r\n return this.authIdentity;\r\n};\r\n\r\n/**\r\n * Check if external authentication is enabled for this conference.\r\n */\r\nJitsiConference.prototype.isExternalAuthEnabled = function() {\r\n return this.room && this.room.moderator.isExternalAuthEnabled();\r\n};\r\n\r\n/**\r\n * Get url for external authentication.\r\n * @param {boolean} [urlForPopup] if true then return url for login popup,\r\n * else url of login page.\r\n * @returns {Promise}\r\n */\r\nJitsiConference.prototype.getExternalAuthUrl = function(urlForPopup) {\r\n return new Promise((resolve, reject) => {\r\n if (!this.isExternalAuthEnabled()) {\r\n reject();\r\n\r\n return;\r\n }\r\n if (urlForPopup) {\r\n this.room.moderator.getPopupLoginUrl(resolve, reject);\r\n } else {\r\n this.room.moderator.getLoginUrl(resolve, reject);\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * Returns the local tracks of the given media type, or all local tracks if no\r\n * specific type is given.\r\n * @param {MediaType} [mediaType] Optional media type (audio or video).\r\n */\r\nJitsiConference.prototype.getLocalTracks = function(mediaType) {\r\n let tracks = [];\r\n\r\n if (this.rtc) {\r\n tracks = this.rtc.getLocalTracks(mediaType);\r\n }\r\n\r\n return tracks;\r\n};\r\n\r\n/**\r\n * Obtains local audio track.\r\n * @return {JitsiLocalTrack|null}\r\n */\r\nJitsiConference.prototype.getLocalAudioTrack = function() {\r\n return this.rtc ? this.rtc.getLocalAudioTrack() : null;\r\n};\r\n\r\n/**\r\n * Obtains local video track.\r\n * @return {JitsiLocalTrack|null}\r\n */\r\nJitsiConference.prototype.getLocalVideoTrack = function() {\r\n return this.rtc ? this.rtc.getLocalVideoTrack() : null;\r\n};\r\n\r\n/**\r\n * Returns all the local video tracks.\r\n * @returns {Array}\r\n */\r\nJitsiConference.prototype.getLocalVideoTracks = function() {\r\n return this.rtc ? this.rtc.getLocalVideoTracks() : null;\r\n};\r\n\r\n/**\r\n * Obtains the performance statistics.\r\n * @returns {Object|null}\r\n */\r\nJitsiConference.prototype.getPerformanceStats = function() {\r\n return {\r\n longTasksStats: this.statistics.getLongTasksStats()\r\n };\r\n};\r\n\r\n/**\r\n * Attaches a handler for events(For example - \"participant joined\".) in the\r\n * conference. All possible event are defined in JitsiConferenceEvents.\r\n * @param eventId the event ID.\r\n * @param handler handler for the event.\r\n *\r\n * Note: consider adding eventing functionality by extending an EventEmitter\r\n * impl, instead of rolling ourselves\r\n */\r\nJitsiConference.prototype.on = function(eventId, handler) {\r\n if (this.eventEmitter) {\r\n this.eventEmitter.on(eventId, handler);\r\n }\r\n};\r\n\r\n/**\r\n * Removes event listener\r\n * @param eventId the event ID.\r\n * @param [handler] optional, the specific handler to unbind\r\n *\r\n * Note: consider adding eventing functionality by extending an EventEmitter\r\n * impl, instead of rolling ourselves\r\n */\r\nJitsiConference.prototype.off = function(eventId, handler) {\r\n if (this.eventEmitter) {\r\n this.eventEmitter.removeListener(eventId, handler);\r\n }\r\n};\r\n\r\n// Common aliases for event emitter\r\nJitsiConference.prototype.addEventListener = JitsiConference.prototype.on;\r\nJitsiConference.prototype.removeEventListener = JitsiConference.prototype.off;\r\n\r\n/**\r\n * Receives notifications from other participants about commands / custom events\r\n * (sent by sendCommand or sendCommandOnce methods).\r\n * @param command {String} the name of the command\r\n * @param handler {Function} handler for the command\r\n */\r\nJitsiConference.prototype.addCommandListener = function(command, handler) {\r\n if (this.room) {\r\n this.room.addPresenceListener(command, handler);\r\n }\r\n};\r\n\r\n/**\r\n * Removes command listener\r\n * @param command {String} the name of the command\r\n * @param handler {Function} handler to remove for the command\r\n */\r\nJitsiConference.prototype.removeCommandListener = function(command, handler) {\r\n if (this.room) {\r\n this.room.removePresenceListener(command, handler);\r\n }\r\n};\r\n\r\n/**\r\n * Sends text message to the other participants in the conference\r\n * @param message the text message.\r\n * @param elementName the element name to encapsulate the message.\r\n * @deprecated Use 'sendMessage' instead. TODO: this should be private.\r\n */\r\nJitsiConference.prototype.sendTextMessage = function(message, elementName = 'body') {\r\n if (this.room) {\r\n this.room.sendMessage(message, elementName);\r\n }\r\n};\r\n\r\n/**\r\n * Send private text message to another participant of the conference\r\n * @param id the id of the participant to send a private message.\r\n * @param message the text message.\r\n * @param elementName the element name to encapsulate the message.\r\n * @deprecated Use 'sendMessage' instead. TODO: this should be private.\r\n */\r\nJitsiConference.prototype.sendPrivateTextMessage = function(id, message, elementName = 'body') {\r\n if (this.room) {\r\n this.room.sendPrivateMessage(id, message, elementName);\r\n }\r\n};\r\n\r\n/**\r\n * Send presence command.\r\n * @param name {String} the name of the command.\r\n * @param values {Object} with keys and values that will be sent.\r\n **/\r\nJitsiConference.prototype.sendCommand = function(name, values) {\r\n if (this.room) {\r\n this.room.addOrReplaceInPresence(name, values) && this.room.sendPresence();\r\n } else {\r\n logger.warn('Not sending a command, room not initialized.');\r\n }\r\n\r\n};\r\n\r\n/**\r\n * Send presence command one time.\r\n * @param name {String} the name of the command.\r\n * @param values {Object} with keys and values that will be sent.\r\n **/\r\nJitsiConference.prototype.sendCommandOnce = function(name, values) {\r\n this.sendCommand(name, values);\r\n this.removeCommand(name);\r\n};\r\n\r\n/**\r\n * Removes presence command.\r\n * @param name {String} the name of the command.\r\n **/\r\nJitsiConference.prototype.removeCommand = function(name) {\r\n if (this.room) {\r\n this.room.removeFromPresence(name);\r\n }\r\n};\r\n\r\n/**\r\n * Sets the display name for this conference.\r\n * @param name the display name to set\r\n */\r\nJitsiConference.prototype.setDisplayName = function(name) {\r\n if (this.room) {\r\n const nickKey = 'nick';\r\n\r\n // if there is no display name already set, avoid setting an empty one\r\n if (!name && !this.room.getFromPresence(nickKey)) {\r\n return;\r\n }\r\n\r\n this.room.addOrReplaceInPresence(nickKey, {\r\n attributes: { xmlns: 'http://jabber.org/protocol/nick' },\r\n value: name\r\n }) && this.room.sendPresence();\r\n }\r\n};\r\n\r\n/**\r\n * Set new subject for this conference. (available only for moderator)\r\n * @param {string} subject new subject\r\n */\r\nJitsiConference.prototype.setSubject = function(subject) {\r\n if (this.room && this.isModerator()) {\r\n this.room.setSubject(subject);\r\n } else {\r\n logger.warn(`Failed to set subject, ${this.room ? '' : 'not in a room, '}${\r\n this.isModerator() ? '' : 'participant is not a moderator'}`);\r\n }\r\n};\r\n\r\n/**\r\n * Get a transcriber object for all current participants in this conference\r\n * @return {Transcriber} the transcriber object\r\n */\r\nJitsiConference.prototype.getTranscriber = function() {\r\n if (this.transcriber === undefined) {\r\n this.transcriber = new Transcriber();\r\n\r\n // add all existing local audio tracks to the transcriber\r\n const localAudioTracks = this.getLocalTracks(MediaType.AUDIO);\r\n\r\n for (const localAudio of localAudioTracks) {\r\n this.transcriber.addTrack(localAudio);\r\n }\r\n\r\n // and all remote audio tracks\r\n const remoteAudioTracks = this.rtc.getRemoteTracks(MediaType.AUDIO);\r\n\r\n for (const remoteTrack of remoteAudioTracks) {\r\n this.transcriber.addTrack(remoteTrack);\r\n }\r\n }\r\n\r\n return this.transcriber;\r\n};\r\n\r\n/**\r\n * Returns the transcription status.\r\n *\r\n * @returns {String} \"on\" or \"off\".\r\n */\r\nJitsiConference.prototype.getTranscriptionStatus = function() {\r\n return this.room.transcriptionStatus;\r\n};\r\n\r\n/**\r\n * Adds JitsiLocalTrack object to the conference.\r\n * @param {JitsiLocalTrack} track the JitsiLocalTrack object.\r\n * @returns {Promise}\r\n * @throws {Error} if the specified track is a video track and there is already\r\n * another video track in the conference.\r\n */\r\nJitsiConference.prototype.addTrack = function(track) {\r\n const mediaType = track.getType();\r\n const localTracks = this.rtc.getLocalTracks(mediaType);\r\n\r\n // Ensure there's exactly 1 local track of each media type in the conference.\r\n if (localTracks.length > 0) {\r\n // Don't be excessively harsh and severe if the API client happens to attempt to add the same local track twice.\r\n if (track === localTracks[0]) {\r\n return Promise.resolve(track);\r\n }\r\n\r\n if (FeatureFlags.isMultiStreamSupportEnabled() && mediaType === MediaType.VIDEO) {\r\n const sourceName = getSourceNameForJitsiTrack(\r\n this.myUserId(),\r\n mediaType,\r\n this.getLocalTracks(mediaType)?.length);\r\n\r\n track.setSourceName(sourceName);\r\n const addTrackPromises = [];\r\n\r\n this.p2pJingleSession && addTrackPromises.push(this.p2pJingleSession.addTracks([ track ]));\r\n this.jvbJingleSession && addTrackPromises.push(this.jvbJingleSession.addTracks([ track ]));\r\n\r\n return Promise.all(addTrackPromises)\r\n .then(() => {\r\n this._setupNewTrack(track);\r\n this._sendBridgeVideoTypeMessage(track);\r\n this._updateRoomPresence(this.getActiveMediaSession());\r\n\r\n if (this.isMutedByFocus || this.isVideoMutedByFocus) {\r\n this._fireMuteChangeEvent(track);\r\n }\r\n });\r\n }\r\n\r\n return Promise.reject(new Error(`Cannot add second ${mediaType} track to the conference`));\r\n }\r\n\r\n return this.replaceTrack(null, track)\r\n .then(() => {\r\n // Presence needs to be sent here for desktop track since we need the presence to reach the remote peer\r\n // before signaling so that a fake participant tile is created for screenshare. Otherwise, presence will\r\n // only be sent after a session-accept or source-add is ack'ed.\r\n if (track.getVideoType() === VideoType.DESKTOP && FeatureFlags.isMultiStreamSupportEnabled()) {\r\n this._updateRoomPresence(this.getActiveMediaSession());\r\n }\r\n });\r\n};\r\n\r\n/**\r\n * Fires TRACK_AUDIO_LEVEL_CHANGED change conference event (for local tracks).\r\n * @param {number} audioLevel the audio level\r\n * @param {TraceablePeerConnection} [tpc]\r\n */\r\nJitsiConference.prototype._fireAudioLevelChangeEvent = function(audioLevel, tpc) {\r\n const activeTpc = this.getActivePeerConnection();\r\n\r\n // There will be no TraceablePeerConnection if audio levels do not come from\r\n // a peerconnection. LocalStatsCollector.js measures audio levels using Web\r\n // Audio Analyser API and emits local audio levels events through\r\n // JitsiTrack.setAudioLevel, but does not provide TPC instance which is\r\n // optional.\r\n if (!tpc || activeTpc === tpc) {\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED,\r\n this.myUserId(), audioLevel);\r\n }\r\n};\r\n\r\n/**\r\n * Fires TRACK_MUTE_CHANGED change conference event.\r\n * @param track the JitsiTrack object related to the event.\r\n */\r\nJitsiConference.prototype._fireMuteChangeEvent = function(track) {\r\n // check if track was muted by focus and now is unmuted by user\r\n if (this.isMutedByFocus && track.isAudioTrack() && !track.isMuted()) {\r\n this.isMutedByFocus = false;\r\n\r\n // unmute local user on server\r\n this.room.muteParticipant(this.room.myroomjid, false, MediaType.AUDIO);\r\n } else if (this.isVideoMutedByFocus && track.isVideoTrack() && !track.isMuted()) {\r\n this.isVideoMutedByFocus = false;\r\n\r\n // unmute local user on server\r\n this.room.muteParticipant(this.room.myroomjid, false, MediaType.VIDEO);\r\n }\r\n\r\n let actorParticipant;\r\n\r\n if (this.mutedByFocusActor && track.isAudioTrack()) {\r\n const actorId = Strophe.getResourceFromJid(this.mutedByFocusActor);\r\n\r\n actorParticipant = this.participants[actorId];\r\n } else if (this.mutedVideoByFocusActor && track.isVideoTrack()) {\r\n const actorId = Strophe.getResourceFromJid(this.mutedVideoByFocusActor);\r\n\r\n actorParticipant = this.participants[actorId];\r\n }\r\n\r\n // Send the video type message to the bridge if the track is not removed/added to the pc as part of\r\n // the mute/unmute operation. This currently happens only on Firefox.\r\n if (track.isVideoTrack() && !browser.doesVideoMuteByStreamRemove()) {\r\n this._sendBridgeVideoTypeMessage(track);\r\n }\r\n\r\n this.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track, actorParticipant);\r\n};\r\n\r\n/**\r\n * Returns the list of local tracks that need to be added to the peerconnection on join.\r\n * This takes the startAudioMuted/startVideoMuted flags into consideration since we do not\r\n * want to add the tracks if the user joins the call audio/video muted. The tracks will be\r\n * added when the user unmutes for the first time.\r\n * @returns {Array} - list of local tracks that are unmuted.\r\n */\r\nJitsiConference.prototype._getInitialLocalTracks = function() {\r\n // Always add the audio track on certain platforms:\r\n // * Safari / WebKit: because of a known issue where audio playout doesn't happen\r\n // if the user joins audio and video muted.\r\n // * React Native: after iOS 15, if a user joins muted they won't be able to unmute.\r\n return this.getLocalTracks()\r\n .filter(track => {\r\n const trackType = track.getType();\r\n\r\n if (trackType === MediaType.AUDIO\r\n && (!this.isStartAudioMuted() || browser.isWebKitBased() || browser.isReactNative())) {\r\n return true;\r\n } else if (trackType === MediaType.VIDEO && !this.isStartVideoMuted()) {\r\n return true;\r\n }\r\n\r\n return false;\r\n });\r\n};\r\n\r\n/**\r\n * Clear JitsiLocalTrack properties and listeners.\r\n * @param track the JitsiLocalTrack object.\r\n */\r\nJitsiConference.prototype.onLocalTrackRemoved = function(track) {\r\n track.setConference(null);\r\n this.rtc.removeLocalTrack(track);\r\n track.removeEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, track.muteHandler);\r\n if (track.isAudioTrack()) {\r\n track.removeEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, track.audioLevelHandler);\r\n }\r\n\r\n // send event for stopping screen sharing\r\n // FIXME: we assume we have only one screen sharing track\r\n // if we change this we need to fix this check\r\n if (track.isVideoTrack() && track.videoType === VideoType.DESKTOP) {\r\n this.statistics.sendScreenSharingEvent(false);\r\n }\r\n\r\n this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);\r\n};\r\n\r\n/**\r\n * Removes JitsiLocalTrack from the conference and performs\r\n * a new offer/answer cycle.\r\n * @param {JitsiLocalTrack} track\r\n * @returns {Promise}\r\n */\r\nJitsiConference.prototype.removeTrack = function(track) {\r\n return this.replaceTrack(track, null);\r\n};\r\n\r\n/**\r\n * Replaces oldTrack with newTrack and performs a single offer/answer\r\n * cycle after both operations are done. Either oldTrack or newTrack\r\n * can be null; replacing a valid 'oldTrack' with a null 'newTrack'\r\n * effectively just removes 'oldTrack'\r\n * @param {JitsiLocalTrack} oldTrack the current stream in use to be replaced\r\n * @param {JitsiLocalTrack} newTrack the new stream to use\r\n * @returns {Promise} resolves when the replacement is finished\r\n */\r\nJitsiConference.prototype.replaceTrack = function(oldTrack, newTrack) {\r\n const oldVideoType = oldTrack?.getVideoType();\r\n const mediaType = oldTrack?.getType() || newTrack?.getType();\r\n const newVideoType = newTrack?.getVideoType();\r\n\r\n if (FeatureFlags.isMultiStreamSupportEnabled() && oldTrack && newTrack && oldVideoType !== newVideoType) {\r\n throw new Error(`Replacing a track of videoType=${oldVideoType} with a track of videoType=${newVideoType} is`\r\n + ' not supported in this mode.');\r\n }\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && newTrack) {\r\n if (oldTrack) {\r\n newTrack.setSourceName(oldTrack.getSourceName());\r\n } else {\r\n const sourceName = getSourceNameForJitsiTrack(\r\n this.myUserId(),\r\n mediaType,\r\n this.getLocalTracks(mediaType)?.length);\r\n\r\n newTrack.setSourceName(sourceName);\r\n }\r\n }\r\n const oldTrackBelongsToConference = this === oldTrack?.conference;\r\n\r\n if (oldTrackBelongsToConference && oldTrack.disposed) {\r\n return Promise.reject(new JitsiTrackError(JitsiTrackErrors.TRACK_IS_DISPOSED));\r\n }\r\n if (newTrack?.disposed) {\r\n return Promise.reject(new JitsiTrackError(JitsiTrackErrors.TRACK_IS_DISPOSED));\r\n }\r\n\r\n if (oldTrack && !oldTrackBelongsToConference) {\r\n logger.warn(`JitsiConference.replaceTrack oldTrack (${oldTrack} does not belong to this conference`);\r\n }\r\n\r\n // Now replace the stream at the lower levels\r\n return this._doReplaceTrack(oldTrackBelongsToConference ? oldTrack : null, newTrack)\r\n .then(() => {\r\n oldTrackBelongsToConference && this.onLocalTrackRemoved(oldTrack);\r\n newTrack && this._setupNewTrack(newTrack);\r\n\r\n // Send 'VideoTypeMessage' on the bridge channel when a video track is added/removed.\r\n if ((oldTrackBelongsToConference && oldTrack?.isVideoTrack()) || newTrack?.isVideoTrack()) {\r\n this._sendBridgeVideoTypeMessage(newTrack);\r\n }\r\n\r\n // We do not want to send presence update during setEffect switching, which removes and then adds the same\r\n // track back to the conference.\r\n if (!(oldTrack?._setEffectInProgress || newTrack?._setEffectInProgress)) {\r\n this._updateRoomPresence(this.getActiveMediaSession());\r\n }\r\n\r\n if (newTrack !== null && (this.isMutedByFocus || this.isVideoMutedByFocus)) {\r\n this._fireMuteChangeEvent(newTrack);\r\n }\r\n\r\n return Promise.resolve();\r\n })\r\n .catch(error => {\r\n logger.error(`replaceTrack failed: ${error?.stack}`);\r\n\r\n return Promise.reject(error);\r\n });\r\n};\r\n\r\n/**\r\n * Replaces the tracks at the lower level by going through the Jingle session\r\n * and WebRTC peer connection. The method will resolve immediately if there is\r\n * currently no JingleSession started.\r\n * @param {JitsiLocalTrack|null} oldTrack the track to be removed during\r\n * the process or null if the method should act as \"add track\"\r\n * @param {JitsiLocalTrack|null} newTrack the new track to be added or\r\n * null if the method should act as \"remove track\"\r\n * @return {Promise} resolved when the process is done or rejected with a string\r\n * which describes the error.\r\n * @private\r\n */\r\nJitsiConference.prototype._doReplaceTrack = function(oldTrack, newTrack) {\r\n const replaceTrackPromises = [];\r\n\r\n if (this.jvbJingleSession) {\r\n replaceTrackPromises.push(this.jvbJingleSession.replaceTrack(oldTrack, newTrack));\r\n } else {\r\n logger.info('_doReplaceTrack - no JVB JingleSession');\r\n }\r\n\r\n if (this.p2pJingleSession) {\r\n replaceTrackPromises.push(this.p2pJingleSession.replaceTrack(oldTrack, newTrack));\r\n } else {\r\n logger.info('_doReplaceTrack - no P2P JingleSession');\r\n }\r\n\r\n return Promise.all(replaceTrackPromises);\r\n};\r\n\r\n/**\r\n * Handler for when a source-add for a local source is rejected by Jicofo.\r\n *\r\n * @param {JingleSessionPC} jingleSession - The media session.\r\n * @param {Error} error - The error message.\r\n * @param {MediaType} mediaType - The media type of the track associated with the source that was rejected.\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype._removeLocalSourceOnReject = function(jingleSession, error, mediaType) {\r\n if (!jingleSession) {\r\n return;\r\n }\r\n logger.warn(`Source-add rejected on ${jingleSession}, reason=\"${error?.reason}\", message=\"${error?.msg}\"`);\r\n const track = this.getLocalTracks(mediaType)[0];\r\n\r\n this.eventEmitter.emit(JitsiConferenceEvents.TRACK_UNMUTE_REJECTED, track);\r\n};\r\n\r\n/**\r\n * Operations related to creating a new track\r\n * @param {JitsiLocalTrack} newTrack the new track being created\r\n */\r\nJitsiConference.prototype._setupNewTrack = function(newTrack) {\r\n const mediaType = newTrack.getType();\r\n\r\n if (newTrack.isAudioTrack() || (newTrack.isVideoTrack() && newTrack.videoType !== VideoType.DESKTOP)) {\r\n // Report active device to statistics\r\n const devices = RTC.getCurrentlyAvailableMediaDevices();\r\n const device = devices\r\n .find(d => d.kind === `${newTrack.getTrack().kind}input` && d.label === newTrack.getTrack().label);\r\n\r\n if (device) {\r\n Statistics.sendActiveDeviceListEvent(RTC.getEventDataForActiveDevice(device));\r\n }\r\n }\r\n\r\n // Create a source name for this track if it doesn't exist.\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && !newTrack.getSourceName()) {\r\n const sourceName = getSourceNameForJitsiTrack(\r\n this.myUserId(),\r\n mediaType,\r\n this.getLocalTracks(mediaType)?.length);\r\n\r\n newTrack.setSourceName(sourceName);\r\n }\r\n\r\n this.rtc.addLocalTrack(newTrack);\r\n newTrack.setConference(this);\r\n\r\n // Add event handlers.\r\n newTrack.muteHandler = this._fireMuteChangeEvent.bind(this, newTrack);\r\n newTrack.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, newTrack.muteHandler);\r\n\r\n if (newTrack.isAudioTrack()) {\r\n newTrack.audioLevelHandler = this._fireAudioLevelChangeEvent.bind(this);\r\n newTrack.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, newTrack.audioLevelHandler);\r\n }\r\n\r\n this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, newTrack);\r\n};\r\n\r\n/**\r\n * Sets the video type.\r\n * @param track\r\n * @return true if video type was changed in presence.\r\n * @private\r\n */\r\nJitsiConference.prototype._setNewVideoType = function(track) {\r\n let videoTypeChanged = false;\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && track) {\r\n videoTypeChanged = this._signalingLayer.setTrackVideoType(track.getSourceName(), track.videoType);\r\n }\r\n\r\n if (!FeatureFlags.isMultiStreamSupportEnabled()) {\r\n const videoTypeTagName = 'videoType';\r\n\r\n // If track is missing we revert to default type Camera, the case where we screenshare and\r\n // we return to be video muted.\r\n const trackVideoType = track ? track.videoType : VideoType.CAMERA;\r\n\r\n // If video type is camera and there is no videoType in presence, we skip adding it, as this is the default one\r\n if (trackVideoType !== VideoType.CAMERA || this.room.getFromPresence(videoTypeTagName)) {\r\n // We will not use this.sendCommand here to avoid sending the presence immediately, as later we may also\r\n // set the mute status.\r\n const legacyTypeChanged = this.room.addOrReplaceInPresence(videoTypeTagName, { value: trackVideoType });\r\n\r\n videoTypeChanged = videoTypeChanged || legacyTypeChanged;\r\n }\r\n }\r\n\r\n return videoTypeChanged;\r\n};\r\n\r\n/**\r\n * Sets mute status.\r\n * @param mediaType\r\n * @param localTrack\r\n * @param isMuted\r\n * @param true when presence was changed, false otherwise.\r\n * @private\r\n */\r\nJitsiConference.prototype._setTrackMuteStatus = function(mediaType, localTrack, isMuted) {\r\n let presenceChanged = false;\r\n\r\n if (FeatureFlags.isSourceNameSignalingEnabled() && localTrack) {\r\n presenceChanged = this._signalingLayer.setTrackMuteStatus(localTrack.getSourceName(), isMuted);\r\n }\r\n\r\n // Add the 'audioMuted' and 'videoMuted' tags when source name signaling is enabled for backward compatibility.\r\n // It won't be used anymore when multiple stream support is enabled.\r\n if (!FeatureFlags.isMultiStreamSupportEnabled()) {\r\n let audioMuteChanged, videoMuteChanged;\r\n\r\n if (!this.room) {\r\n return false;\r\n }\r\n\r\n if (mediaType === MediaType.AUDIO) {\r\n audioMuteChanged = this.room.addAudioInfoToPresence(isMuted);\r\n } else {\r\n videoMuteChanged = this.room.addVideoInfoToPresence(isMuted);\r\n }\r\n\r\n presenceChanged = presenceChanged || audioMuteChanged || videoMuteChanged;\r\n }\r\n\r\n return presenceChanged;\r\n};\r\n\r\n/**\r\n * Method called by the {@link JitsiLocalTrack} (a video one) in order to add\r\n * back the underlying WebRTC MediaStream to the PeerConnection (which has\r\n * removed on video mute).\r\n * @param {JitsiLocalTrack} track the local track that will be added as part of\r\n * the unmute operation.\r\n * @return {Promise} resolved when the process is done or rejected with a string\r\n * which describes the error.\r\n */\r\nJitsiConference.prototype._addLocalTrackAsUnmute = function(track) {\r\n const addAsUnmutePromises = [];\r\n\r\n if (this.jvbJingleSession) {\r\n addAsUnmutePromises.push(this.jvbJingleSession.addTrackAsUnmute(track));\r\n } else {\r\n logger.debug('Add local MediaStream as unmute - no JVB Jingle session started yet');\r\n }\r\n\r\n if (this.p2pJingleSession) {\r\n addAsUnmutePromises.push(this.p2pJingleSession.addTrackAsUnmute(track));\r\n } else {\r\n logger.debug('Add local MediaStream as unmute - no P2P Jingle session started yet');\r\n }\r\n\r\n return Promise.allSettled(addAsUnmutePromises);\r\n};\r\n\r\n/**\r\n * Method called by the {@link JitsiLocalTrack} (a video one) in order to remove\r\n * the underlying WebRTC MediaStream from the PeerConnection. The purpose of\r\n * that is to stop sending any data and turn off the HW camera device.\r\n * @param {JitsiLocalTrack} track the local track that will be removed.\r\n * @return {Promise}\r\n */\r\nJitsiConference.prototype._removeLocalTrackAsMute = function(track) {\r\n const removeAsMutePromises = [];\r\n\r\n if (this.jvbJingleSession) {\r\n removeAsMutePromises.push(this.jvbJingleSession.removeTrackAsMute(track));\r\n } else {\r\n logger.debug('Remove local MediaStream - no JVB JingleSession started yet');\r\n }\r\n if (this.p2pJingleSession) {\r\n removeAsMutePromises.push(this.p2pJingleSession.removeTrackAsMute(track));\r\n } else {\r\n logger.debug('Remove local MediaStream - no P2P JingleSession started yet');\r\n }\r\n\r\n return Promise.allSettled(removeAsMutePromises);\r\n};\r\n\r\n/**\r\n * Get role of the local user.\r\n * @returns {string} user role: 'moderator' or 'none'\r\n */\r\nJitsiConference.prototype.getRole = function() {\r\n return this.room.role;\r\n};\r\n\r\n/**\r\n * Returns whether or not the current conference has been joined as a hidden\r\n * user.\r\n *\r\n * @returns {boolean|null} True if hidden, false otherwise. Will return null if\r\n * no connection is active.\r\n */\r\nJitsiConference.prototype.isHidden = function() {\r\n if (!this.connection) {\r\n return null;\r\n }\r\n\r\n return Strophe.getDomainFromJid(this.connection.getJid())\r\n === this.options.config.hiddenDomain;\r\n};\r\n\r\n/**\r\n * Check if local user is moderator.\r\n * @returns {boolean|null} true if local user is moderator, false otherwise. If\r\n * we're no longer in the conference room then null is returned.\r\n */\r\nJitsiConference.prototype.isModerator = function() {\r\n return this.room ? this.room.isModerator() : null;\r\n};\r\n\r\n/**\r\n * Set password for the room.\r\n * @param {string} password new password for the room.\r\n * @returns {Promise}\r\n */\r\nJitsiConference.prototype.lock = function(password) {\r\n if (!this.isModerator()) {\r\n return Promise.reject(new Error('You are not moderator.'));\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.room.lockRoom(\r\n password || '',\r\n () => resolve(),\r\n err => reject(err),\r\n () => reject(JitsiConferenceErrors.PASSWORD_NOT_SUPPORTED));\r\n });\r\n};\r\n\r\n/**\r\n * Remove password from the room.\r\n * @returns {Promise}\r\n */\r\nJitsiConference.prototype.unlock = function() {\r\n return this.lock();\r\n};\r\n\r\n/**\r\n * Elects the participant with the given id to be the selected participant in\r\n * order to receive higher video quality (if simulcast is enabled).\r\n * Or cache it if channel is not created and send it once channel is available.\r\n * @param participantId the identifier of the participant\r\n * @throws NetworkError or InvalidStateError or Error if the operation fails.\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.selectParticipant = function(participantId) {\r\n this.selectParticipants([ participantId ]);\r\n};\r\n\r\n/*\r\n * Elects participants with given ids to be the selected participants in order\r\n * to receive higher video quality (if simulcast is enabled). The argument\r\n * should be an array of participant id strings or an empty array; an error will\r\n * be thrown if a non-array is passed in. The error is thrown as a layer of\r\n * protection against passing an invalid argument, as the error will happen in\r\n * the bridge and may not be visible in the client.\r\n *\r\n * @param {Array} participantIds - An array of identifiers for\r\n * participants.\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.selectParticipants = function(participantIds) {\r\n if (!Array.isArray(participantIds)) {\r\n throw new Error('Invalid argument; participantIds must be an array.');\r\n }\r\n\r\n this.receiveVideoController.selectEndpoints(participantIds);\r\n};\r\n\r\n/**\r\n * Obtains the current value for \"lastN\". See {@link setLastN} for more info.\r\n * @returns {number}\r\n */\r\nJitsiConference.prototype.getLastN = function() {\r\n return this.receiveVideoController.getLastN();\r\n};\r\n\r\n/**\r\n * Obtains the forwarded sources list in this conference.\r\n * @return {Array|null}\r\n */\r\nJitsiConference.prototype.getForwardedSources = function() {\r\n return this.rtc.getForwardedSources();\r\n};\r\n\r\n/**\r\n * Selects a new value for \"lastN\". The requested amount of videos are going\r\n * to be delivered after the value is in effect. Set to -1 for unlimited or\r\n * all available videos.\r\n * @param lastN the new number of videos the user would like to receive.\r\n * @throws Error or RangeError if the given value is not a number or is smaller\r\n * than -1.\r\n */\r\nJitsiConference.prototype.setLastN = function(lastN) {\r\n if (!Number.isInteger(lastN) && !Number.parseInt(lastN, 10)) {\r\n throw new Error(`Invalid value for lastN: ${lastN}`);\r\n }\r\n const n = Number(lastN);\r\n\r\n if (n < -1) {\r\n throw new RangeError('lastN cannot be smaller than -1');\r\n }\r\n this.receiveVideoController.setLastN(n);\r\n\r\n // If the P2P session is not fully established yet, we wait until it gets\r\n // established.\r\n if (this.p2pJingleSession) {\r\n const isVideoActive = n !== 0;\r\n\r\n this.p2pJingleSession\r\n .setMediaTransferActive(true, isVideoActive)\r\n .catch(error => {\r\n logger.error(\r\n `Failed to adjust video transfer status (${isVideoActive})`,\r\n error);\r\n });\r\n }\r\n};\r\n\r\n/**\r\n * Checks if the participant given by participantId is currently included in\r\n * the last N.\r\n * @param {string} participantId the identifier of the participant we would\r\n * like to check.\r\n * @return {boolean} true if the participant with id is in the last N set or\r\n * if there's no last N set, false otherwise.\r\n * @deprecated this method should never be used to figure out the UI, but\r\n * {@link ParticipantConnectionStatus} should be used instead.\r\n */\r\nJitsiConference.prototype.isInLastN = function(participantId) {\r\n return this.rtc.isInLastN(participantId);\r\n};\r\n\r\n/**\r\n * @return Array an array of all participants in this\r\n * conference.\r\n */\r\nJitsiConference.prototype.getParticipants = function() {\r\n return Object.values(this.participants);\r\n};\r\n\r\n/**\r\n * Returns the number of participants in the conference, including the local\r\n * participant.\r\n * @param countHidden {boolean} Whether or not to include hidden participants\r\n * in the count. Default: false.\r\n **/\r\nJitsiConference.prototype.getParticipantCount = function(countHidden = false) {\r\n let participants = this.getParticipants();\r\n\r\n if (!countHidden) {\r\n participants = participants.filter(p => !p.isHidden());\r\n }\r\n\r\n // Add one for the local participant.\r\n return participants.length + 1;\r\n};\r\n\r\n/**\r\n * @returns {JitsiParticipant} the participant in this conference with the\r\n * specified id (or undefined if there isn't one).\r\n * @param id the id of the participant.\r\n */\r\nJitsiConference.prototype.getParticipantById = function(id) {\r\n return this.participants[id];\r\n};\r\n\r\n/**\r\n * Grant owner rights to the participant.\r\n * @param {string} id id of the participant to grant owner rights to.\r\n */\r\nJitsiConference.prototype.grantOwner = function(id) {\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n this.room.setAffiliation(participant.getConnectionJid(), 'owner');\r\n};\r\n\r\n/**\r\n * Revoke owner rights to the participant or local Participant as\r\n * the user might want to refuse to be a moderator.\r\n * @param {string} id id of the participant to revoke owner rights to.\r\n */\r\nJitsiConference.prototype.revokeOwner = function(id) {\r\n const participant = this.getParticipantById(id);\r\n const isMyself = this.myUserId() === id;\r\n const role = this.isMembersOnly() ? 'member' : 'none';\r\n\r\n if (isMyself) {\r\n this.room.setAffiliation(this.connection.getJid(), role);\r\n } else if (participant) {\r\n this.room.setAffiliation(participant.getConnectionJid(), role);\r\n }\r\n};\r\n\r\n/**\r\n * Kick participant from this conference.\r\n * @param {string} id id of the participant to kick\r\n * @param {string} reason reason of the participant to kick\r\n */\r\nJitsiConference.prototype.kickParticipant = function(id, reason) {\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n this.room.kick(participant.getJid(), reason);\r\n};\r\n\r\n/**\r\n * Maybe clears the timeout which emits {@link ACTION_JINGLE_SI_TIMEOUT}\r\n * analytics event.\r\n * @private\r\n */\r\nJitsiConference.prototype._maybeClearSITimeout = function() {\r\n if (this._sessionInitiateTimeout\r\n && (this.jvbJingleSession || this.getParticipantCount() < 2)) {\r\n window.clearTimeout(this._sessionInitiateTimeout);\r\n this._sessionInitiateTimeout = null;\r\n }\r\n};\r\n\r\n/**\r\n * Sets a timeout which will emit {@link ACTION_JINGLE_SI_TIMEOUT} analytics\r\n * event.\r\n * @private\r\n */\r\nJitsiConference.prototype._maybeSetSITimeout = function() {\r\n // Jicofo is supposed to invite if there are at least 2 participants\r\n if (!this.jvbJingleSession\r\n && this.getParticipantCount() >= 2\r\n && !this._sessionInitiateTimeout) {\r\n this._sessionInitiateTimeout = window.setTimeout(() => {\r\n this._sessionInitiateTimeout = null;\r\n Statistics.sendAnalytics(createJingleEvent(\r\n ACTION_JINGLE_SI_TIMEOUT,\r\n {\r\n p2p: false,\r\n value: JINGLE_SI_TIMEOUT\r\n }));\r\n }, JINGLE_SI_TIMEOUT);\r\n }\r\n};\r\n\r\n/**\r\n * Mutes a participant.\r\n * @param {string} id The id of the participant to mute.\r\n */\r\nJitsiConference.prototype.muteParticipant = function(id, mediaType) {\r\n const muteMediaType = mediaType ? mediaType : MediaType.AUDIO;\r\n\r\n if (muteMediaType !== MediaType.AUDIO && muteMediaType !== MediaType.VIDEO) {\r\n logger.error(`Unsupported media type: ${muteMediaType}`);\r\n\r\n return;\r\n }\r\n\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n this.room.muteParticipant(participant.getJid(), true, muteMediaType);\r\n};\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * Notifies this JitsiConference that a new member has joined its chat room.\r\n *\r\n * FIXME This should NOT be exposed!\r\n *\r\n * @param jid the jid of the participant in the MUC\r\n * @param nick the display name of the participant\r\n * @param role the role of the participant in the MUC\r\n * @param isHidden indicates if this is a hidden participant (system\r\n * participant for example a recorder).\r\n * @param statsID the participant statsID (optional)\r\n * @param status the initial status if any\r\n * @param identity the member identity, if any\r\n * @param botType the member botType, if any\r\n * @param fullJid the member full jid, if any\r\n * @param features the member botType, if any\r\n * @param isReplaceParticipant whether this join replaces a participant with\r\n * the same jwt.\r\n */\r\nJitsiConference.prototype.onMemberJoined = function(\r\n jid, nick, role, isHidden, statsID, status, identity, botType, fullJid, features, isReplaceParticipant) {\r\n const id = Strophe.getResourceFromJid(jid);\r\n\r\n if (id === 'focus' || this.myUserId() === id) {\r\n return;\r\n }\r\n\r\n const participant\r\n = new JitsiParticipant(jid, this, nick, isHidden, statsID, status, identity);\r\n\r\n participant.setConnectionJid(fullJid);\r\n participant.setRole(role);\r\n participant.setBotType(botType);\r\n participant.setFeatures(features);\r\n participant.setIsReplacing(isReplaceParticipant);\r\n\r\n this.participants[id] = participant;\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.USER_JOINED,\r\n id,\r\n participant);\r\n\r\n this._updateFeatures(participant);\r\n\r\n // maybeStart only if we had finished joining as then we will have information for the number of participants\r\n if (this.isJoined()) {\r\n this._maybeStartOrStopP2P();\r\n }\r\n\r\n this._maybeSetSITimeout();\r\n};\r\n\r\n/* eslint-enable max-params */\r\n\r\n/**\r\n * Get notified when we joined the room.\r\n *\r\n * FIXME This should NOT be exposed!\r\n *\r\n * @private\r\n */\r\nJitsiConference.prototype._onMucJoined = function() {\r\n this._maybeStartOrStopP2P();\r\n};\r\n\r\n/**\r\n * Updates features for a participant.\r\n * @param {JitsiParticipant} participant - The participant to query for features.\r\n * @returns {void}\r\n * @private\r\n */\r\nJitsiConference.prototype._updateFeatures = function(participant) {\r\n participant.getFeatures()\r\n .then(features => {\r\n participant._supportsDTMF = features.has('urn:xmpp:jingle:dtmf:0');\r\n this.updateDTMFSupport();\r\n\r\n if (features.has(FEATURE_JIGASI)) {\r\n participant.setProperty('features_jigasi', true);\r\n }\r\n\r\n if (features.has(FEATURE_E2EE)) {\r\n participant.setProperty('features_e2ee', true);\r\n }\r\n })\r\n .catch(() => false);\r\n};\r\n\r\n/**\r\n * Get notified when member bot type had changed.\r\n * @param jid the member jid\r\n * @param botType the new botType value\r\n * @private\r\n */\r\nJitsiConference.prototype._onMemberBotTypeChanged = function(jid, botType) {\r\n\r\n // find the participant and mark it as non bot, as the real one will join\r\n // in a moment\r\n const peers = this.getParticipants();\r\n const botParticipant = peers.find(p => p.getJid() === jid);\r\n\r\n if (botParticipant) {\r\n botParticipant.setBotType(botType);\r\n const id = Strophe.getResourceFromJid(jid);\r\n\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.BOT_TYPE_CHANGED,\r\n id,\r\n botType);\r\n }\r\n\r\n // if botType changed to undefined, botType was removed, in case of\r\n // poltergeist mode this is the moment when the poltergeist had exited and\r\n // the real participant had already replaced it.\r\n // In this case we can check and try p2p\r\n if (!botParticipant.getBotType()) {\r\n this._maybeStartOrStopP2P();\r\n }\r\n};\r\n\r\nJitsiConference.prototype.onMemberLeft = function(jid) {\r\n const id = Strophe.getResourceFromJid(jid);\r\n\r\n if (id === 'focus' || this.myUserId() === id) {\r\n return;\r\n }\r\n\r\n const participant = this.participants[id];\r\n const mediaSessions = this.getMediaSessions();\r\n let tracksToBeRemoved = [];\r\n\r\n for (const session of mediaSessions) {\r\n const remoteTracks = session.peerconnection.getRemoteTracks(id);\r\n\r\n remoteTracks && (tracksToBeRemoved = [ ...tracksToBeRemoved, ...remoteTracks ]);\r\n\r\n // Remove the ssrcs from the remote description and renegotiate.\r\n session.removeRemoteStreamsOnLeave(id);\r\n }\r\n\r\n // Fire the event before renegotiation is done so that the thumbnails can be removed immediately.\r\n tracksToBeRemoved.forEach(track => {\r\n this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);\r\n });\r\n\r\n if (participant) {\r\n delete this.participants[id];\r\n this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id, participant);\r\n }\r\n\r\n if (this.room !== null) { // Skip if we have left the room already.\r\n this._maybeStartOrStopP2P(true /* triggered by user left event */);\r\n this._maybeClearSITimeout();\r\n }\r\n};\r\n\r\n/* eslint-disable max-params */\r\n\r\n/**\r\n * Designates an event indicating that we were kicked from the XMPP MUC.\r\n * @param {boolean} isSelfPresence - whether it is for local participant\r\n * or another participant.\r\n * @param {string} actorId - the id of the participant who was initiator\r\n * of the kick.\r\n * @param {string?} kickedParticipantId - when it is not a kick for local participant,\r\n * this is the id of the participant which was kicked.\r\n * @param {string} reason - reason of the participant to kick\r\n * @param {boolean?} isReplaceParticipant - whether this is a server initiated kick in order\r\n * to replace it with a participant with same jwt.\r\n */\r\nJitsiConference.prototype.onMemberKicked = function(\r\n isSelfPresence,\r\n actorId,\r\n kickedParticipantId,\r\n reason,\r\n isReplaceParticipant) {\r\n // This check which be true when we kick someone else. With the introduction of lobby\r\n // the ChatRoom KICKED event is now also emitted for ourselves (the kicker) so we want to\r\n // avoid emitting an event where `undefined` kicked someone.\r\n if (actorId === this.myUserId()) {\r\n return;\r\n }\r\n\r\n const actorParticipant = this.participants[actorId];\r\n\r\n if (isSelfPresence) {\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.KICKED, actorParticipant, reason, isReplaceParticipant);\r\n\r\n this.leave();\r\n\r\n return;\r\n }\r\n\r\n const kickedParticipant = this.participants[kickedParticipantId];\r\n\r\n kickedParticipant.setIsReplaced(isReplaceParticipant);\r\n\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.PARTICIPANT_KICKED, actorParticipant, kickedParticipant, reason);\r\n};\r\n\r\n/**\r\n * Method called on local MUC role change.\r\n * @param {string} role the name of new user's role as defined by XMPP MUC.\r\n */\r\nJitsiConference.prototype.onLocalRoleChanged = function(role) {\r\n // Emit role changed for local JID\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.USER_ROLE_CHANGED, this.myUserId(), role);\r\n};\r\n\r\nJitsiConference.prototype.onUserRoleChanged = function(jid, role) {\r\n const id = Strophe.getResourceFromJid(jid);\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n participant.setRole(role);\r\n this.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, id, role);\r\n};\r\n\r\nJitsiConference.prototype.onDisplayNameChanged = function(jid, displayName) {\r\n const id = Strophe.getResourceFromJid(jid);\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n\r\n if (participant._displayName === displayName) {\r\n return;\r\n }\r\n\r\n participant._displayName = displayName;\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.DISPLAY_NAME_CHANGED,\r\n id,\r\n displayName);\r\n};\r\n\r\n/**\r\n * Notifies this JitsiConference that a JitsiRemoteTrack was added to the conference.\r\n *\r\n * @param {JitsiRemoteTrack} track the JitsiRemoteTrack which was added to this JitsiConference.\r\n */\r\nJitsiConference.prototype.onRemoteTrackAdded = function(track) {\r\n if (track.isP2P && !this.isP2PActive()) {\r\n logger.info('Trying to add remote P2P track, when not in P2P - IGNORED');\r\n\r\n return;\r\n } else if (!track.isP2P && this.isP2PActive()) {\r\n logger.info('Trying to add remote JVB track, when in P2P - IGNORED');\r\n\r\n return;\r\n }\r\n\r\n const id = track.getParticipantId();\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n logger.error(`No participant found for id: ${id}`);\r\n\r\n return;\r\n }\r\n\r\n // Add track to JitsiParticipant.\r\n participant._tracks.push(track);\r\n\r\n if (this.transcriber) {\r\n this.transcriber.addTrack(track);\r\n }\r\n\r\n const emitter = this.eventEmitter;\r\n\r\n track.addEventListener(\r\n JitsiTrackEvents.TRACK_MUTE_CHANGED,\r\n () => emitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track));\r\n track.isAudioTrack() && track.addEventListener(\r\n JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,\r\n (audioLevel, tpc) => {\r\n const activeTPC = this.getActivePeerConnection();\r\n\r\n if (activeTPC === tpc) {\r\n emitter.emit(JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, id, audioLevel);\r\n }\r\n }\r\n );\r\n\r\n emitter.emit(JitsiConferenceEvents.TRACK_ADDED, track);\r\n};\r\n\r\n/**\r\n * Callback called by the Jingle plugin when 'session-answer' is received.\r\n * @param {JingleSessionPC} session the Jingle session for which an answer was\r\n * received.\r\n * @param {jQuery} answer a jQuery selector pointing to 'jingle' IQ element\r\n */\r\n// eslint-disable-next-line no-unused-vars\r\nJitsiConference.prototype.onCallAccepted = function(session, answer) {\r\n if (this.p2pJingleSession === session) {\r\n logger.info('P2P setAnswer');\r\n\r\n this.p2pJingleSession.setAnswer(answer);\r\n this.eventEmitter.emit(JitsiConferenceEvents._MEDIA_SESSION_STARTED, this.p2pJingleSession);\r\n }\r\n};\r\n\r\n/**\r\n * Callback called by the Jingle plugin when 'transport-info' is received.\r\n * @param {JingleSessionPC} session the Jingle session for which the IQ was\r\n * received\r\n * @param {jQuery} transportInfo a jQuery selector pointing to 'jingle' IQ\r\n * element\r\n */\r\n// eslint-disable-next-line no-unused-vars\r\nJitsiConference.prototype.onTransportInfo = function(session, transportInfo) {\r\n if (this.p2pJingleSession === session) {\r\n logger.info('P2P addIceCandidates');\r\n this.p2pJingleSession.addIceCandidates(transportInfo);\r\n }\r\n};\r\n\r\n/**\r\n * Notifies this JitsiConference that a JitsiRemoteTrack was removed from\r\n * the conference.\r\n *\r\n * @param {JitsiRemoteTrack} removedTrack\r\n */\r\nJitsiConference.prototype.onRemoteTrackRemoved = function(removedTrack) {\r\n this.getParticipants().forEach(participant => {\r\n const tracks = participant.getTracks();\r\n\r\n for (let i = 0; i < tracks.length; i++) {\r\n if (tracks[i] === removedTrack) {\r\n // Since the tracks have been compared and are\r\n // considered equal the result of splice can be ignored.\r\n participant._tracks.splice(i, 1);\r\n\r\n this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, removedTrack);\r\n\r\n if (this.transcriber) {\r\n this.transcriber.removeTrack(removedTrack);\r\n }\r\n\r\n break;\r\n }\r\n }\r\n }, this);\r\n};\r\n\r\n/**\r\n * Handles an incoming call event for the P2P jingle session.\r\n */\r\nJitsiConference.prototype._onIncomingCallP2P = function(jingleSession, jingleOffer) {\r\n let rejectReason;\r\n const usesUnifiedPlan = browser.supportsUnifiedPlan()\r\n && (!browser.isChromiumBased() || (this.options.config.enableUnifiedOnChrome ?? true));\r\n const contentName = jingleOffer.find('>content').attr('name');\r\n const peerUsesUnifiedPlan = contentName === '0' || contentName === '1';\r\n\r\n // Reject P2P between endpoints that are not running in the same mode w.r.t to SDPs (plan-b and unified plan).\r\n if (usesUnifiedPlan !== peerUsesUnifiedPlan) {\r\n rejectReason = {\r\n reason: 'decline',\r\n reasonDescription: 'P2P disabled',\r\n errorMsg: 'P2P across two endpoints in different SDP modes is disabled'\r\n };\r\n } else if ((!this.isP2PEnabled() && !this.isP2PTestModeEnabled())\r\n || browser.isFirefox()\r\n || browser.isWebKitBased()) {\r\n rejectReason = {\r\n reason: 'decline',\r\n reasonDescription: 'P2P disabled',\r\n errorMsg: 'P2P mode disabled in the configuration or browser unsupported'\r\n };\r\n } else if (this.p2pJingleSession) {\r\n // Reject incoming P2P call (already in progress)\r\n rejectReason = {\r\n reason: 'busy',\r\n reasonDescription: 'P2P already in progress',\r\n errorMsg: 'Duplicated P2P \"session-initiate\"'\r\n };\r\n } else if (!this._shouldBeInP2PMode()) {\r\n rejectReason = {\r\n reason: 'decline',\r\n reasonDescription: 'P2P requirements not met',\r\n errorMsg: 'Received P2P \"session-initiate\" when should not be in P2P mode'\r\n };\r\n Statistics.sendAnalytics(createJingleEvent(ACTION_P2P_DECLINED));\r\n }\r\n\r\n if (rejectReason) {\r\n this._rejectIncomingCall(jingleSession, rejectReason);\r\n } else {\r\n this._acceptP2PIncomingCall(jingleSession, jingleOffer);\r\n }\r\n};\r\n\r\n/**\r\n * Handles an incoming call event.\r\n */\r\nJitsiConference.prototype.onIncomingCall = function(jingleSession, jingleOffer, now) {\r\n // Handle incoming P2P call\r\n if (jingleSession.isP2P) {\r\n this._onIncomingCallP2P(jingleSession, jingleOffer);\r\n } else {\r\n if (!this.isFocus(jingleSession.remoteJid)) {\r\n const description = 'Rejecting session-initiate from non-focus.';\r\n\r\n this._rejectIncomingCall(\r\n jingleSession, {\r\n reason: 'security-error',\r\n reasonDescription: description,\r\n errorMsg: description\r\n });\r\n\r\n return;\r\n }\r\n this._acceptJvbIncomingCall(jingleSession, jingleOffer, now);\r\n }\r\n};\r\n\r\n/**\r\n * Accepts an incoming call event for the JVB jingle session.\r\n */\r\nJitsiConference.prototype._acceptJvbIncomingCall = function(jingleSession, jingleOffer, now) {\r\n\r\n // Accept incoming call\r\n this.jvbJingleSession = jingleSession;\r\n this.room.connectionTimes['session.initiate'] = now;\r\n this._sendConferenceJoinAnalyticsEvent();\r\n\r\n if (this.wasStopped) {\r\n Statistics.sendAnalyticsAndLog(createJingleEvent(ACTION_JINGLE_RESTART, { p2p: false }));\r\n }\r\n\r\n const serverRegion\r\n = $(jingleOffer)\r\n .find('>bridge-session[xmlns=\"http://jitsi.org/protocol/focus\"]')\r\n .attr('region');\r\n\r\n this.eventEmitter.emit(JitsiConferenceEvents.SERVER_REGION_CHANGED, serverRegion);\r\n\r\n this._maybeClearSITimeout();\r\n Statistics.sendAnalytics(createJingleEvent(\r\n ACTION_JINGLE_SI_RECEIVED,\r\n {\r\n p2p: false,\r\n value: now\r\n }));\r\n\r\n try {\r\n jingleSession.initialize(\r\n this.room,\r\n this.rtc,\r\n this._signalingLayer,\r\n {\r\n ...this.options.config,\r\n enableInsertableStreams: this.isE2EEEnabled()\r\n });\r\n } catch (error) {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error(error);\r\n\r\n return;\r\n }\r\n\r\n // Open a channel with the videobridge.\r\n this._setBridgeChannel(jingleOffer, jingleSession.peerconnection);\r\n\r\n const localTracks = this._getInitialLocalTracks();\r\n\r\n try {\r\n jingleSession.acceptOffer(\r\n jingleOffer,\r\n () => {\r\n // If for any reason invite for the JVB session arrived after\r\n // the P2P has been established already the media transfer needs\r\n // to be turned off here.\r\n if (this.isP2PActive() && this.jvbJingleSession) {\r\n this._suspendMediaTransferForJvbConnection();\r\n }\r\n\r\n this.eventEmitter.emit(JitsiConferenceEvents._MEDIA_SESSION_STARTED, jingleSession);\r\n if (!this.isP2PActive()) {\r\n this.eventEmitter.emit(JitsiConferenceEvents._MEDIA_SESSION_ACTIVE_CHANGED, jingleSession);\r\n }\r\n },\r\n error => {\r\n GlobalOnErrorHandler.callErrorHandler(error);\r\n logger.error('Failed to accept incoming Jingle session', error);\r\n },\r\n localTracks\r\n );\r\n\r\n // Enable or disable simulcast for plan-b screensharing based on the capture fps if it is set through the UI.\r\n this._desktopSharingFrameRate\r\n && jingleSession.peerconnection.setDesktopSharingFrameRate(this._desktopSharingFrameRate);\r\n\r\n // Start callstats as soon as peerconnection is initialized,\r\n // do not wait for XMPPEvents.PEERCONNECTION_READY, as it may never\r\n // happen in case if user doesn't have or denied permission to\r\n // both camera and microphone.\r\n logger.info('Starting CallStats for JVB connection...');\r\n this.statistics.startCallStats(\r\n this.jvbJingleSession.peerconnection,\r\n 'jitsi' /* Remote user ID for JVB is 'jitsi' */);\r\n this.statistics.startRemoteStats(this.jvbJingleSession.peerconnection);\r\n } catch (e) {\r\n GlobalOnErrorHandler.callErrorHandler(e);\r\n logger.error(e);\r\n }\r\n};\r\n\r\n/**\r\n * Sets the BridgeChannel.\r\n *\r\n * @param {jQuery} offerIq a jQuery selector pointing to the jingle element of\r\n * the offer IQ which may carry the WebSocket URL for the 'websocket'\r\n * BridgeChannel mode.\r\n * @param {TraceablePeerConnection} pc the peer connection which will be used\r\n * to listen for new WebRTC Data Channels (in the 'datachannel' mode).\r\n */\r\nJitsiConference.prototype._setBridgeChannel = function(offerIq, pc) {\r\n let wsUrl = null;\r\n const webSocket\r\n = $(offerIq)\r\n .find('>content>transport>web-socket')\r\n .first();\r\n\r\n if (webSocket.length === 1) {\r\n wsUrl = webSocket[0].getAttribute('url');\r\n }\r\n\r\n if (wsUrl) {\r\n // If the offer contains a websocket use it.\r\n this.rtc.initializeBridgeChannel(null, wsUrl);\r\n } else {\r\n // Otherwise, fall back to an attempt to use SCTP.\r\n this.rtc.initializeBridgeChannel(pc, null);\r\n }\r\n};\r\n\r\n/**\r\n * Rejects incoming Jingle call.\r\n * @param {JingleSessionPC} jingleSession the session instance to be rejected.\r\n * @param {object} [options]\r\n * @param {string} options.reason the name of the reason element as defined\r\n * by Jingle\r\n * @param {string} options.reasonDescription the reason description which will\r\n * be included in Jingle 'session-terminate' message.\r\n * @param {string} options.errorMsg an error message to be logged on global\r\n * error handler\r\n * @private\r\n */\r\nJitsiConference.prototype._rejectIncomingCall = function(jingleSession, options) {\r\n if (options?.errorMsg) {\r\n logger.warn(options.errorMsg);\r\n }\r\n\r\n // Terminate the jingle session with a reason\r\n jingleSession.terminate(\r\n null /* success callback => we don't care */,\r\n error => {\r\n logger.warn(\r\n 'An error occurred while trying to terminate'\r\n + ' invalid Jingle session', error);\r\n }, {\r\n reason: options && options.reason,\r\n reasonDescription: options && options.reasonDescription,\r\n sendSessionTerminate: true\r\n });\r\n};\r\n\r\n/**\r\n * Handles the call ended event.\r\n * XXX is this due to the remote side terminating the Jingle session?\r\n *\r\n * @param {JingleSessionPC} jingleSession the jingle session which has been\r\n * terminated.\r\n * @param {String} reasonCondition the Jingle reason condition.\r\n * @param {String|null} reasonText human readable reason text which may provide\r\n * more details about why the call has been terminated.\r\n */\r\nJitsiConference.prototype.onCallEnded = function(jingleSession, reasonCondition, reasonText) {\r\n logger.info(\r\n `Call ended: ${reasonCondition} - ${reasonText} P2P ?${\r\n jingleSession.isP2P}`);\r\n if (jingleSession === this.jvbJingleSession) {\r\n this.wasStopped = true;\r\n\r\n Statistics.sendAnalytics(\r\n createJingleEvent(ACTION_JINGLE_TERMINATE, { p2p: false }));\r\n\r\n // Stop the stats\r\n if (this.statistics) {\r\n this.statistics.stopRemoteStats(\r\n this.jvbJingleSession.peerconnection);\r\n logger.info('Stopping JVB CallStats');\r\n this.statistics.stopCallStats(\r\n this.jvbJingleSession.peerconnection);\r\n }\r\n\r\n // Current JVB JingleSession is no longer valid, so set it to null\r\n this.jvbJingleSession = null;\r\n\r\n // Let the RTC service do any cleanups\r\n this.rtc.onCallEnded();\r\n } else if (jingleSession === this.p2pJingleSession) {\r\n const stopOptions = {};\r\n\r\n // It's the responder who decides to enforce JVB mode, so that both\r\n // initiator and responder are aware if it was intentional.\r\n if (reasonCondition === 'decline' && reasonText === 'force JVB121') {\r\n logger.info('In forced JVB 121 mode...');\r\n Statistics.analytics.addPermanentProperties({ forceJvb121: true });\r\n } else if (reasonCondition === 'connectivity-error'\r\n && reasonText === 'ICE FAILED') {\r\n // It can happen that the other peer detects ICE failed and\r\n // terminates the session, before we get the event on our side.\r\n // But we are able to parse the reason and mark it here.\r\n Statistics.analytics.addPermanentProperties({ p2pFailed: true });\r\n } else if (reasonCondition === 'success' && reasonText === 'restart') {\r\n // When we are restarting media sessions we don't want to switch the tracks\r\n // to the JVB just yet.\r\n stopOptions.requestRestart = true;\r\n }\r\n this._stopP2PSession(stopOptions);\r\n } else {\r\n logger.error(\r\n 'Received onCallEnded for invalid session',\r\n jingleSession.sid,\r\n jingleSession.remoteJid,\r\n reasonCondition,\r\n reasonText);\r\n }\r\n};\r\n\r\n/**\r\n * Handles the suspend detected event. Leaves the room and fires suspended.\r\n * @param {JingleSessionPC} jingleSession\r\n */\r\nJitsiConference.prototype.onSuspendDetected = function(jingleSession) {\r\n if (!jingleSession.isP2P) {\r\n this.leave();\r\n this.eventEmitter.emit(JitsiConferenceEvents.SUSPEND_DETECTED);\r\n }\r\n};\r\n\r\nJitsiConference.prototype.updateDTMFSupport = function() {\r\n let somebodySupportsDTMF = false;\r\n const participants = this.getParticipants();\r\n\r\n // check if at least 1 participant supports DTMF\r\n for (let i = 0; i < participants.length; i += 1) {\r\n if (participants[i].supportsDTMF()) {\r\n somebodySupportsDTMF = true;\r\n break;\r\n }\r\n }\r\n if (somebodySupportsDTMF !== this.somebodySupportsDTMF) {\r\n this.somebodySupportsDTMF = somebodySupportsDTMF;\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.DTMF_SUPPORT_CHANGED,\r\n somebodySupportsDTMF);\r\n }\r\n};\r\n\r\n/**\r\n * Allows to check if there is at least one user in the conference\r\n * that supports DTMF.\r\n * @returns {boolean} true if somebody supports DTMF, false otherwise\r\n */\r\nJitsiConference.prototype.isDTMFSupported = function() {\r\n return this.somebodySupportsDTMF;\r\n};\r\n\r\n/**\r\n * Returns the local user's ID\r\n * @return {string} local user's ID\r\n */\r\nJitsiConference.prototype.myUserId = function() {\r\n return (\r\n this.room && this.room.myroomjid\r\n ? Strophe.getResourceFromJid(this.room.myroomjid)\r\n : null);\r\n};\r\n\r\nJitsiConference.prototype.sendTones = function(tones, duration, pause) {\r\n const peerConnection = this.getActivePeerConnection();\r\n\r\n if (peerConnection) {\r\n peerConnection.sendTones(tones, duration, pause);\r\n } else {\r\n logger.warn('cannot sendTones: no peer connection');\r\n }\r\n};\r\n\r\n/**\r\n * Starts recording the current conference.\r\n *\r\n * @param {Object} options - Configuration for the recording. See\r\n * {@link Chatroom#startRecording} for more info.\r\n * @returns {Promise} See {@link Chatroom#startRecording} for more info.\r\n */\r\nJitsiConference.prototype.startRecording = function(options) {\r\n if (this.room) {\r\n return this.recordingManager.startRecording(options);\r\n }\r\n\r\n return Promise.reject(new Error('The conference is not created yet!'));\r\n};\r\n\r\n/**\r\n * Stop a recording session.\r\n *\r\n * @param {string} sessionID - The ID of the recording session that\r\n * should be stopped.\r\n * @returns {Promise} See {@link Chatroom#stopRecording} for more info.\r\n */\r\nJitsiConference.prototype.stopRecording = function(sessionID) {\r\n if (this.room) {\r\n return this.recordingManager.stopRecording(sessionID);\r\n }\r\n\r\n return Promise.reject(new Error('The conference is not created yet!'));\r\n};\r\n\r\n/**\r\n * Returns true if the SIP calls are supported and false otherwise\r\n */\r\nJitsiConference.prototype.isSIPCallingSupported = function() {\r\n if (this.room) {\r\n return this.room.isSIPCallingSupported();\r\n }\r\n\r\n return false;\r\n};\r\n\r\n/**\r\n * Dials a number.\r\n * @param number the number\r\n */\r\nJitsiConference.prototype.dial = function(number) {\r\n if (this.room) {\r\n return this.room.dial(number);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n reject(new Error('The conference is not created yet!'));\r\n });\r\n};\r\n\r\n/**\r\n * Hangup an existing call\r\n */\r\nJitsiConference.prototype.hangup = function() {\r\n if (this.room) {\r\n return this.room.hangup();\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n reject(new Error('The conference is not created yet!'));\r\n });\r\n};\r\n\r\n/**\r\n * Starts the transcription service.\r\n */\r\nJitsiConference.prototype.startTranscriber = function() {\r\n return this.dial('jitsi_meet_transcribe');\r\n};\r\n\r\n\r\n/**\r\n * Stops the transcription service.\r\n */\r\nJitsiConference.prototype.stopTranscriber = JitsiConference.prototype.hangup;\r\n\r\n/**\r\n * Returns the phone number for joining the conference.\r\n */\r\nJitsiConference.prototype.getPhoneNumber = function() {\r\n if (this.room) {\r\n return this.room.getPhoneNumber();\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Returns the pin for joining the conference with phone.\r\n */\r\nJitsiConference.prototype.getPhonePin = function() {\r\n if (this.room) {\r\n return this.room.getPhonePin();\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Returns the meeting unique ID if any.\r\n *\r\n * @returns {string|undefined}\r\n */\r\nJitsiConference.prototype.getMeetingUniqueId = function() {\r\n if (this.room) {\r\n return this.room.getMeetingId();\r\n }\r\n};\r\n\r\n/**\r\n * Will return P2P or JVB TraceablePeerConnection depending on\r\n * which connection is currently active.\r\n *\r\n * @return {TraceablePeerConnection|null} null if there isn't any active\r\n * TraceablePeerConnection currently available.\r\n * @public (FIXME how to make package local ?)\r\n */\r\nJitsiConference.prototype.getActivePeerConnection = function() {\r\n const session = this.isP2PActive() ? this.p2pJingleSession : this.jvbJingleSession;\r\n\r\n return session ? session.peerconnection : null;\r\n};\r\n\r\n/**\r\n * Returns the connection state for the current room. Its ice connection state\r\n * for its session.\r\n * NOTE that \"completed\" ICE state which can appear on the P2P connection will\r\n * be converted to \"connected\".\r\n * @return {string|null} ICE state name or null if there is no active\r\n * peer connection at this time.\r\n */\r\nJitsiConference.prototype.getConnectionState = function() {\r\n const peerConnection = this.getActivePeerConnection();\r\n\r\n return peerConnection ? peerConnection.getConnectionState() : null;\r\n};\r\n\r\n/**\r\n * Make all new participants mute their audio/video on join.\r\n * @param policy {Object} object with 2 boolean properties for video and audio:\r\n * @param {boolean} audio if audio should be muted.\r\n * @param {boolean} video if video should be muted.\r\n */\r\nJitsiConference.prototype.setStartMutedPolicy = function(policy) {\r\n if (!this.isModerator()) {\r\n logger.warn(`Failed to set start muted policy, ${this.room ? '' : 'not in a room, '}${\r\n this.isModerator() ? '' : 'participant is not a moderator'}`);\r\n\r\n return;\r\n }\r\n this.startMutedPolicy = policy;\r\n this.room.addOrReplaceInPresence('startmuted', {\r\n attributes: {\r\n audio: policy.audio,\r\n video: policy.video,\r\n xmlns: 'http://jitsi.org/jitmeet/start-muted'\r\n }\r\n }) && this.room.sendPresence();\r\n};\r\n\r\n/**\r\n * Returns current start muted policy\r\n * @returns {Object} with 2 properties - audio and video.\r\n */\r\nJitsiConference.prototype.getStartMutedPolicy = function() {\r\n return this.startMutedPolicy;\r\n};\r\n\r\n/**\r\n * Check if audio is muted on join.\r\n */\r\nJitsiConference.prototype.isStartAudioMuted = function() {\r\n return this.startAudioMuted;\r\n};\r\n\r\n/**\r\n * Check if video is muted on join.\r\n */\r\nJitsiConference.prototype.isStartVideoMuted = function() {\r\n return this.startVideoMuted;\r\n};\r\n\r\n/**\r\n * Returns measured connectionTimes.\r\n */\r\nJitsiConference.prototype.getConnectionTimes = function() {\r\n return this.room.connectionTimes;\r\n};\r\n\r\n/**\r\n * Sets a property for the local participant.\r\n */\r\nJitsiConference.prototype.setLocalParticipantProperty = function(name, value) {\r\n this.sendCommand(`jitsi_participant_${name}`, { value });\r\n};\r\n\r\n/**\r\n * Removes a property for the local participant and sends the updated presence.\r\n */\r\nJitsiConference.prototype.removeLocalParticipantProperty = function(name) {\r\n this.removeCommand(`jitsi_participant_${name}`);\r\n this.room.sendPresence();\r\n};\r\n\r\n/**\r\n * Gets a local participant property.\r\n *\r\n * @return value of the local participant property if the tagName exists in the\r\n * list of properties, otherwise returns undefined.\r\n */\r\nJitsiConference.prototype.getLocalParticipantProperty = function(name) {\r\n const property = this.room.presMap.nodes.find(prop =>\r\n prop.tagName === `jitsi_participant_${name}`\r\n );\r\n\r\n return property ? property.value : undefined;\r\n};\r\n\r\n/**\r\n * Sends the given feedback through CallStats if enabled.\r\n *\r\n * @param overallFeedback an integer between 1 and 5 indicating the\r\n * user feedback\r\n * @param detailedFeedback detailed feedback from the user. Not yet used\r\n * @returns {Promise} Resolves if feedback is submitted successfully.\r\n */\r\nJitsiConference.prototype.sendFeedback = function(overallFeedback, detailedFeedback) {\r\n return this.statistics.sendFeedback(overallFeedback, detailedFeedback);\r\n};\r\n\r\n/**\r\n * Returns true if the callstats integration is enabled, otherwise returns\r\n * false.\r\n *\r\n * @returns true if the callstats integration is enabled, otherwise returns\r\n * false.\r\n */\r\nJitsiConference.prototype.isCallstatsEnabled = function() {\r\n return this.statistics.isCallstatsEnabled();\r\n};\r\n\r\n/**\r\n * Finds the SSRC of a given track\r\n *\r\n * @param track\r\n * @returns {number|undefined} the SSRC of the specificed track, otherwise undefined.\r\n */\r\nJitsiConference.prototype.getSsrcByTrack = function(track) {\r\n return track.isLocal() ? this.getActivePeerConnection()?.getLocalSSRC(track) : track.getSSRC();\r\n};\r\n\r\n/**\r\n * Handles track attached to container (Calls associateStreamWithVideoTag method\r\n * from statistics module)\r\n * @param {JitsiLocalTrack|JitsiRemoteTrack} track the track\r\n * @param container the container\r\n */\r\nJitsiConference.prototype._onTrackAttach = function(track, container) {\r\n const isLocal = track.isLocal();\r\n let ssrc = null;\r\n const isP2P = track.isP2P;\r\n const remoteUserId = isP2P ? track.getParticipantId() : 'jitsi';\r\n const peerConnection\r\n = isP2P\r\n ? this.p2pJingleSession && this.p2pJingleSession.peerconnection\r\n : this.jvbJingleSession && this.jvbJingleSession.peerconnection;\r\n\r\n if (isLocal) {\r\n // Local tracks have SSRC stored on per peer connection basis.\r\n if (peerConnection) {\r\n ssrc = peerConnection.getLocalSSRC(track);\r\n }\r\n } else {\r\n ssrc = track.getSSRC();\r\n }\r\n if (!container.id || !ssrc || !peerConnection) {\r\n return;\r\n }\r\n\r\n this.statistics.associateStreamWithVideoTag(\r\n peerConnection,\r\n ssrc,\r\n isLocal,\r\n remoteUserId,\r\n track.getUsageLabel(),\r\n container.id);\r\n};\r\n\r\n/**\r\n * Logs an \"application log\" message.\r\n * @param message {string} The message to log. Note that while this can be a\r\n * generic string, the convention used by lib-jitsi-meet and jitsi-meet is to\r\n * log valid JSON strings, with an \"id\" field used for distinguishing between\r\n * message types. E.g.: {id: \"recorder_status\", status: \"off\"}\r\n */\r\nJitsiConference.prototype.sendApplicationLog = function(message) {\r\n Statistics.sendLog(message);\r\n};\r\n\r\n/**\r\n * Checks if the user identified by given mucJid is the conference focus.\r\n * @param mucJid the full MUC address of the user to be checked.\r\n * @returns {boolean|null} true if MUC user is the conference focus,\r\n * false when is not. null if we're not in the MUC anymore and\r\n * are unable to figure out the status or if given mucJid is invalid.\r\n */\r\nJitsiConference.prototype.isFocus = function(mucJid) {\r\n return this.room ? this.room.isFocus(mucJid) : null;\r\n};\r\n\r\n/**\r\n * Fires CONFERENCE_FAILED event with INCOMPATIBLE_SERVER_VERSIONS parameter\r\n */\r\nJitsiConference.prototype._fireIncompatibleVersionsEvent = function() {\r\n this.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,\r\n JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS);\r\n};\r\n\r\n/**\r\n * Sends a message via the data channel.\r\n * @param to {string} the id of the endpoint that should receive the message.\r\n * If \"\" the message will be sent to all participants.\r\n * @param payload {object} the payload of the message.\r\n * @throws NetworkError or InvalidStateError or Error if the operation fails.\r\n * @deprecated Use 'sendMessage' instead. TODO: this should be private.\r\n */\r\nJitsiConference.prototype.sendEndpointMessage = function(to, payload) {\r\n this.rtc.sendChannelMessage(to, payload);\r\n};\r\n\r\n/**\r\n * Sends local stats via the bridge channel which then forwards to other endpoints selectively.\r\n * @param {Object} payload The payload of the message.\r\n * @throws NetworkError/InvalidStateError/Error if the operation fails or if there is no data channel created.\r\n */\r\nJitsiConference.prototype.sendEndpointStatsMessage = function(payload) {\r\n this.rtc.sendEndpointStatsMessage(payload);\r\n};\r\n\r\n/**\r\n * Sends a broadcast message via the data channel.\r\n * @param payload {object} the payload of the message.\r\n * @throws NetworkError or InvalidStateError or Error if the operation fails.\r\n * @deprecated Use 'sendMessage' instead. TODO: this should be private.\r\n */\r\nJitsiConference.prototype.broadcastEndpointMessage = function(payload) {\r\n this.sendEndpointMessage('', payload);\r\n};\r\n\r\n/**\r\n * Sends a message to a given endpoint (if 'to' is a non-empty string), or\r\n * broadcasts it to all endpoints in the conference.\r\n * @param {string} to The ID of the endpoint/participant which is to receive\r\n * the message, or '' to broadcast the message to all endpoints in the\r\n * conference.\r\n * @param {string|object} message the message to send. If this is of type\r\n * 'string' it will be sent as a chat message. If it is of type 'object', it\r\n * will be encapsulated in a format recognized by jitsi-meet and converted to\r\n * JSON before being sent.\r\n * @param {boolean} sendThroughVideobridge Whether to send the message through\r\n * jitsi-videobridge (via the COLIBRI data channel or web socket), or through\r\n * the XMPP MUC. Currently only objects can be sent through jitsi-videobridge.\r\n */\r\nJitsiConference.prototype.sendMessage = function(message, to = '', sendThroughVideobridge = false) {\r\n const messageType = typeof message;\r\n\r\n // Through videobridge we support only objects. Through XMPP we support\r\n // objects (encapsulated in a specific JSON format) and strings (i.e.\r\n // regular chat messages).\r\n if (messageType !== 'object'\r\n && (sendThroughVideobridge || messageType !== 'string')) {\r\n logger.error(`Can not send a message of type ${messageType}`);\r\n\r\n return;\r\n }\r\n\r\n if (sendThroughVideobridge) {\r\n this.sendEndpointMessage(to, message);\r\n } else {\r\n let messageToSend = message;\r\n\r\n // Name of packet extension of message stanza to send the required\r\n // message in.\r\n let elementName = 'body';\r\n\r\n if (messageType === 'object') {\r\n elementName = 'json-message';\r\n\r\n // Mark as valid JSON message if not already\r\n if (!messageToSend.hasOwnProperty(JITSI_MEET_MUC_TYPE)) {\r\n messageToSend[JITSI_MEET_MUC_TYPE] = '';\r\n }\r\n\r\n try {\r\n messageToSend = JSON.stringify(messageToSend);\r\n } catch (e) {\r\n logger.error('Can not send a message, stringify failed: ', e);\r\n\r\n return;\r\n }\r\n }\r\n\r\n if (to) {\r\n this.sendPrivateTextMessage(to, messageToSend, elementName);\r\n } else {\r\n // Broadcast\r\n this.sendTextMessage(messageToSend, elementName);\r\n }\r\n }\r\n\r\n};\r\n\r\nJitsiConference.prototype.isConnectionInterrupted = function() {\r\n return this.isP2PActive()\r\n ? this.isP2PConnectionInterrupted : this.isJvbConnectionInterrupted;\r\n};\r\n\r\n/**\r\n * Handles {@link XMPPEvents.CONNECTION_RESTARTED} event. This happens when the bridge goes down\r\n * and Jicofo moves conferences away to a different bridge.\r\n * @param {JingleSessionPC} session\r\n * @private\r\n */\r\nJitsiConference.prototype._onConferenceRestarted = function(session) {\r\n if (!session.isP2P && this.options.config.enableForcedReload) {\r\n this.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.CONFERENCE_RESTARTED);\r\n }\r\n};\r\n\r\n/**\r\n * Handles {@link XMPPEvents.CONNECTION_INTERRUPTED}\r\n * @param {JingleSessionPC} session\r\n * @private\r\n */\r\nJitsiConference.prototype._onIceConnectionInterrupted = function(session) {\r\n if (session.isP2P) {\r\n this.isP2PConnectionInterrupted = true;\r\n } else {\r\n this.isJvbConnectionInterrupted = true;\r\n }\r\n if (session.isP2P === this.isP2PActive()) {\r\n this.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED);\r\n }\r\n};\r\n\r\n/**\r\n * Handles {@link XMPPEvents.CONNECTION_ICE_FAILED}\r\n * @param {JingleSessionPC} session\r\n * @private\r\n */\r\nJitsiConference.prototype._onIceConnectionFailed = function(session) {\r\n // We do nothing for the JVB connection, because it's up to the Jicofo to\r\n // eventually come up with the new offer (at least for the time being).\r\n if (session.isP2P) {\r\n // Add p2pFailed property to analytics to distinguish, between \"good\"\r\n // and \"bad\" connection\r\n Statistics.analytics.addPermanentProperties({ p2pFailed: true });\r\n\r\n if (this.p2pJingleSession) {\r\n Statistics.sendAnalyticsAndLog(\r\n createP2PEvent(\r\n ACTION_P2P_FAILED,\r\n {\r\n initiator: this.p2pJingleSession.isInitiator\r\n }));\r\n\r\n }\r\n this._stopP2PSession({\r\n reason: 'connectivity-error',\r\n reasonDescription: 'ICE FAILED'\r\n });\r\n } else if (session && this.jvbJingleSession === session) {\r\n this._delayedIceFailed = new IceFailedHandling(this);\r\n this._delayedIceFailed.start(session);\r\n }\r\n};\r\n\r\n/**\r\n * Handles {@link XMPPEvents.CONNECTION_RESTORED}\r\n * @param {JingleSessionPC} session\r\n * @private\r\n */\r\nJitsiConference.prototype._onIceConnectionRestored = function(session) {\r\n if (session.isP2P) {\r\n this.isP2PConnectionInterrupted = false;\r\n } else {\r\n this.isJvbConnectionInterrupted = false;\r\n this._delayedIceFailed && this._delayedIceFailed.cancel();\r\n }\r\n\r\n if (session.isP2P === this.isP2PActive()) {\r\n this.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED);\r\n }\r\n};\r\n\r\n/**\r\n * Accept incoming P2P Jingle call.\r\n * @param {JingleSessionPC} jingleSession the session instance\r\n * @param {jQuery} jingleOffer a jQuery selector pointing to 'jingle' IQ element\r\n * @private\r\n */\r\nJitsiConference.prototype._acceptP2PIncomingCall = function(jingleSession, jingleOffer) {\r\n this.isP2PConnectionInterrupted = false;\r\n\r\n // Accept the offer\r\n this.p2pJingleSession = jingleSession;\r\n this._sendConferenceJoinAnalyticsEvent();\r\n\r\n this.p2pJingleSession.initialize(\r\n this.room,\r\n this.rtc,\r\n this._signalingLayer,\r\n {\r\n ...this.options.config,\r\n enableInsertableStreams: this.isE2EEEnabled()\r\n });\r\n\r\n logger.info('Starting CallStats for P2P connection...');\r\n\r\n let remoteID = Strophe.getResourceFromJid(this.p2pJingleSession.remoteJid);\r\n\r\n const participant = this.participants[remoteID];\r\n\r\n if (participant) {\r\n remoteID = participant.getStatsID() || remoteID;\r\n }\r\n\r\n this.statistics.startCallStats(\r\n this.p2pJingleSession.peerconnection,\r\n remoteID);\r\n\r\n const localTracks = this.getLocalTracks();\r\n\r\n this.p2pJingleSession.acceptOffer(\r\n jingleOffer,\r\n () => {\r\n logger.debug('Got RESULT for P2P \"session-accept\"');\r\n\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents._MEDIA_SESSION_STARTED,\r\n jingleSession);\r\n },\r\n error => {\r\n logger.error(\r\n 'Failed to accept incoming P2P Jingle session', error);\r\n },\r\n localTracks);\r\n};\r\n\r\n/**\r\n * Adds remote tracks to the conference associated with the JVB session.\r\n * @private\r\n */\r\nJitsiConference.prototype._addRemoteJVBTracks = function() {\r\n this._addRemoteTracks('JVB', this.jvbJingleSession.peerconnection.getRemoteTracks());\r\n};\r\n\r\n/**\r\n * Adds remote tracks to the conference associated with the P2P session.\r\n * @private\r\n */\r\nJitsiConference.prototype._addRemoteP2PTracks = function() {\r\n this._addRemoteTracks('P2P', this.p2pJingleSession.peerconnection.getRemoteTracks());\r\n};\r\n\r\n/**\r\n * Generates fake \"remote track added\" events for given Jingle session.\r\n * @param {string} logName the session's nickname which will appear in log\r\n * messages.\r\n * @param {Array} remoteTracks the tracks that will be added\r\n * @private\r\n */\r\nJitsiConference.prototype._addRemoteTracks = function(logName, remoteTracks) {\r\n for (const track of remoteTracks) {\r\n logger.info(`Adding remote ${logName} track: ${track}`);\r\n this.onRemoteTrackAdded(track);\r\n }\r\n};\r\n\r\n/**\r\n * Called when {@link XMPPEvents.CONNECTION_ESTABLISHED} event is\r\n * triggered for a {@link JingleSessionPC}. Switches the conference to use\r\n * the P2P connection if the event comes from the P2P session.\r\n * @param {JingleSessionPC} jingleSession the session instance.\r\n * @private\r\n */\r\nJitsiConference.prototype._onIceConnectionEstablished = function(jingleSession) {\r\n if (this.p2pJingleSession !== null) {\r\n // store the establishment time of the p2p session as a field of the\r\n // JitsiConference because the p2pJingleSession might get disposed (thus\r\n // the value is lost).\r\n this.p2pEstablishmentDuration\r\n = this.p2pJingleSession.establishmentDuration;\r\n }\r\n\r\n if (this.jvbJingleSession !== null) {\r\n this.jvbEstablishmentDuration\r\n = this.jvbJingleSession.establishmentDuration;\r\n }\r\n\r\n let done = false;\r\n const forceJVB121Ratio = this.options.config.forceJVB121Ratio;\r\n\r\n // We don't care about the JVB case, there's nothing to be done\r\n if (!jingleSession.isP2P) {\r\n done = true;\r\n } else if (this.p2pJingleSession !== jingleSession) {\r\n logger.error('CONNECTION_ESTABLISHED - wrong P2P session instance ?!');\r\n\r\n done = true;\r\n } else if (!jingleSession.isInitiator\r\n && typeof forceJVB121Ratio === 'number'\r\n && Math.random() < forceJVB121Ratio) {\r\n logger.info(`Forcing JVB 121 mode (ratio=${forceJVB121Ratio})...`);\r\n Statistics.analytics.addPermanentProperties({ forceJvb121: true });\r\n this._stopP2PSession({\r\n reason: 'decline',\r\n reasonDescription: 'force JVB121'\r\n });\r\n\r\n done = true;\r\n }\r\n\r\n if (!isNaN(this.p2pEstablishmentDuration)\r\n && !isNaN(this.jvbEstablishmentDuration)) {\r\n const establishmentDurationDiff\r\n = this.p2pEstablishmentDuration - this.jvbEstablishmentDuration;\r\n\r\n Statistics.sendAnalytics(\r\n ICE_ESTABLISHMENT_DURATION_DIFF,\r\n { value: establishmentDurationDiff });\r\n }\r\n\r\n if (jingleSession.isP2P === this.isP2PActive()) {\r\n this.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_ESTABLISHED);\r\n }\r\n\r\n if (done) {\r\n\r\n return;\r\n }\r\n\r\n // Update P2P status and emit events\r\n this._setP2PStatus(true);\r\n\r\n // Remove remote tracks\r\n if (this.jvbJingleSession) {\r\n this._removeRemoteJVBTracks();\r\n } else {\r\n logger.info('Not removing remote JVB tracks - no session yet');\r\n }\r\n\r\n this._addRemoteP2PTracks();\r\n\r\n // Stop media transfer over the JVB connection\r\n if (this.jvbJingleSession) {\r\n this._suspendMediaTransferForJvbConnection();\r\n }\r\n\r\n logger.info('Starting remote stats with p2p connection');\r\n this.statistics.startRemoteStats(this.p2pJingleSession.peerconnection);\r\n\r\n Statistics.sendAnalyticsAndLog(\r\n createP2PEvent(\r\n ACTION_P2P_ESTABLISHED,\r\n {\r\n initiator: this.p2pJingleSession.isInitiator\r\n }));\r\n\r\n};\r\n\r\n/**\r\n * Called when the chat room reads a new list of properties from jicofo's\r\n * presence. The properties may have changed, but they don't have to.\r\n *\r\n * @param {Object} properties - The properties keyed by the property name\r\n * ('key').\r\n * @private\r\n */\r\nJitsiConference.prototype._updateProperties = function(properties = {}) {\r\n const changed = !isEqual(properties, this.properties);\r\n\r\n this.properties = properties;\r\n if (changed) {\r\n this.eventEmitter.emit(JitsiConferenceEvents.PROPERTIES_CHANGED, this.properties);\r\n\r\n const audioLimitReached = this.properties['audio-limit-reached'] === 'true';\r\n const videoLimitReached = this.properties['video-limit-reached'] === 'true';\r\n\r\n if (this._audioSenderLimitReached !== audioLimitReached) {\r\n this._audioSenderLimitReached = audioLimitReached;\r\n this.eventEmitter.emit(JitsiConferenceEvents.AUDIO_UNMUTE_PERMISSIONS_CHANGED, audioLimitReached);\r\n logger.info(`Audio unmute permissions set by Jicofo to ${audioLimitReached}`);\r\n }\r\n\r\n if (this._videoSenderLimitReached !== videoLimitReached) {\r\n this._videoSenderLimitReached = videoLimitReached;\r\n this.eventEmitter.emit(JitsiConferenceEvents.VIDEO_UNMUTE_PERMISSIONS_CHANGED, videoLimitReached);\r\n logger.info(`Video unmute permissions set by Jicofo to ${videoLimitReached}`);\r\n }\r\n\r\n // Some of the properties need to be added to analytics events.\r\n const analyticsKeys = [\r\n\r\n // The number of jitsi-videobridge instances currently used for the\r\n // conference.\r\n 'bridge-count',\r\n\r\n // The conference creation time (set by jicofo).\r\n 'created-ms'\r\n ];\r\n\r\n analyticsKeys.forEach(key => {\r\n if (properties[key] !== undefined) {\r\n Statistics.analytics.addPermanentProperties({\r\n [key.replace('-', '_')]: properties[key]\r\n });\r\n }\r\n });\r\n }\r\n};\r\n\r\n/**\r\n * Gets a conference property with a given key.\r\n *\r\n * @param {string} key - The key.\r\n * @returns {*} The value\r\n */\r\nJitsiConference.prototype.getProperty = function(key) {\r\n return this.properties[key];\r\n};\r\n\r\n/**\r\n * Clears the deferred start P2P task if it has been scheduled.\r\n * @private\r\n */\r\nJitsiConference.prototype._maybeClearDeferredStartP2P = function() {\r\n if (this.deferredStartP2PTask) {\r\n logger.info('Cleared deferred start P2P task');\r\n clearTimeout(this.deferredStartP2PTask);\r\n this.deferredStartP2PTask = null;\r\n }\r\n};\r\n\r\n/**\r\n * Removes from the conference remote tracks associated with the JVB\r\n * connection.\r\n * @private\r\n */\r\nJitsiConference.prototype._removeRemoteJVBTracks = function() {\r\n this._removeRemoteTracks(\r\n 'JVB', this.jvbJingleSession.peerconnection.getRemoteTracks());\r\n};\r\n\r\n/**\r\n * Removes from the conference remote tracks associated with the P2P\r\n * connection.\r\n * @private\r\n */\r\nJitsiConference.prototype._removeRemoteP2PTracks = function() {\r\n this._removeRemoteTracks(\r\n 'P2P', this.p2pJingleSession.peerconnection.getRemoteTracks());\r\n};\r\n\r\n/**\r\n * Generates fake \"remote track removed\" events for given Jingle session.\r\n * @param {string} sessionNickname the session's nickname which will appear in\r\n * log messages.\r\n * @param {Array} remoteTracks the tracks that will be removed\r\n * @private\r\n */\r\nJitsiConference.prototype._removeRemoteTracks = function(sessionNickname, remoteTracks) {\r\n for (const track of remoteTracks) {\r\n logger.info(`Removing remote ${sessionNickname} track: ${track}`);\r\n this.onRemoteTrackRemoved(track);\r\n }\r\n};\r\n\r\n/**\r\n * Resumes media transfer over the JVB connection.\r\n * @private\r\n */\r\nJitsiConference.prototype._resumeMediaTransferForJvbConnection = function() {\r\n logger.info('Resuming media transfer over the JVB connection...');\r\n this.jvbJingleSession.setMediaTransferActive(true, true).then(\r\n () => {\r\n logger.info('Resumed media transfer over the JVB connection!');\r\n },\r\n error => {\r\n logger.error(\r\n 'Failed to resume media transfer over the JVB connection:',\r\n error);\r\n });\r\n};\r\n\r\n/**\r\n * Sets new P2P status and updates some events/states hijacked from\r\n * the JitsiConference.\r\n * @param {boolean} newStatus the new P2P status value, true means that\r\n * P2P is now in use, false means that the JVB connection is now in use\r\n * @private\r\n */\r\nJitsiConference.prototype._setP2PStatus = function(newStatus) {\r\n if (this.p2p === newStatus) {\r\n logger.debug(`Called _setP2PStatus with the same status: ${newStatus}`);\r\n\r\n return;\r\n }\r\n this.p2p = newStatus;\r\n if (newStatus) {\r\n logger.info('Peer to peer connection established!');\r\n\r\n // When we end up in a valid P2P session need to reset the properties\r\n // in case they have persisted, after session with another peer.\r\n Statistics.analytics.addPermanentProperties({\r\n p2pFailed: false,\r\n forceJvb121: false\r\n });\r\n\r\n // Sync up video transfer active in case p2pJingleSession not existed\r\n // when the lastN value was being adjusted.\r\n const isVideoActive = this.getLastN() !== 0;\r\n\r\n this.p2pJingleSession\r\n .setMediaTransferActive(true, isVideoActive)\r\n .catch(error => {\r\n logger.error(\r\n 'Failed to sync up P2P video transfer status'\r\n + `(${isVideoActive})`, error);\r\n });\r\n } else {\r\n logger.info('Peer to peer connection closed!');\r\n }\r\n\r\n // Put the JVB connection on hold/resume\r\n if (this.jvbJingleSession) {\r\n this.statistics.sendConnectionResumeOrHoldEvent(\r\n this.jvbJingleSession.peerconnection, !newStatus);\r\n }\r\n\r\n // Clear dtmfManager, so that it can be recreated with new connection\r\n this.dtmfManager = null;\r\n\r\n // Update P2P status\r\n this.eventEmitter.emit(\r\n JitsiConferenceEvents.P2P_STATUS,\r\n this,\r\n this.p2p);\r\n this.eventEmitter.emit(JitsiConferenceEvents._MEDIA_SESSION_ACTIVE_CHANGED, this.getActiveMediaSession());\r\n\r\n // Refresh connection interrupted/restored\r\n this.eventEmitter.emit(\r\n this.isConnectionInterrupted()\r\n ? JitsiConferenceEvents.CONNECTION_INTERRUPTED\r\n : JitsiConferenceEvents.CONNECTION_RESTORED);\r\n};\r\n\r\n/**\r\n * Starts new P2P session.\r\n * @param {string} remoteJid the JID of the remote participant\r\n * @private\r\n */\r\nJitsiConference.prototype._startP2PSession = function(remoteJid) {\r\n this._maybeClearDeferredStartP2P();\r\n if (this.p2pJingleSession) {\r\n logger.error('P2P session already started!');\r\n\r\n return;\r\n }\r\n\r\n this.isP2PConnectionInterrupted = false;\r\n this.p2pJingleSession\r\n = this.xmpp.connection.jingle.newP2PJingleSession(\r\n this.room.myroomjid,\r\n remoteJid);\r\n logger.info(\r\n 'Created new P2P JingleSession', this.room.myroomjid, remoteJid);\r\n this._sendConferenceJoinAnalyticsEvent();\r\n\r\n this.p2pJingleSession.initialize(\r\n this.room,\r\n this.rtc,\r\n this._signalingLayer,\r\n {\r\n ...this.options.config,\r\n enableInsertableStreams: this.isE2EEEnabled()\r\n });\r\n\r\n logger.info('Starting CallStats for P2P connection...');\r\n\r\n let remoteID = Strophe.getResourceFromJid(this.p2pJingleSession.remoteJid);\r\n\r\n const participant = this.participants[remoteID];\r\n\r\n if (participant) {\r\n remoteID = participant.getStatsID() || remoteID;\r\n }\r\n\r\n this.statistics.startCallStats(\r\n this.p2pJingleSession.peerconnection,\r\n remoteID);\r\n\r\n const localTracks = this.getLocalTracks();\r\n\r\n this.p2pJingleSession.invite(localTracks);\r\n};\r\n\r\n/**\r\n * Suspends media transfer over the JVB connection.\r\n * @private\r\n */\r\nJitsiConference.prototype._suspendMediaTransferForJvbConnection = function() {\r\n logger.info('Suspending media transfer over the JVB connection...');\r\n this.jvbJingleSession.setMediaTransferActive(false, false).then(\r\n () => {\r\n logger.info('Suspended media transfer over the JVB connection !');\r\n },\r\n error => {\r\n logger.error(\r\n 'Failed to suspend media transfer over the JVB connection:',\r\n error);\r\n });\r\n};\r\n\r\n/**\r\n * Method when called will decide whether it's the time to start or stop\r\n * the P2P session.\r\n * @param {boolean} userLeftEvent if true it means that the call\r\n * originates from the user left event.\r\n * @private\r\n */\r\nJitsiConference.prototype._maybeStartOrStopP2P = function(userLeftEvent) {\r\n if (!this.isP2PEnabled()\r\n || this.isP2PTestModeEnabled()\r\n || browser.isFirefox()\r\n || browser.isWebKitBased()\r\n || this.isE2EEEnabled()) {\r\n logger.info('Auto P2P disabled');\r\n\r\n return;\r\n }\r\n const peers = this.getParticipants();\r\n const peerCount = peers.length;\r\n\r\n // FIXME 1 peer and it must *support* P2P switching\r\n const shouldBeInP2P = this._shouldBeInP2PMode();\r\n\r\n // Clear deferred \"start P2P\" task\r\n if (!shouldBeInP2P && this.deferredStartP2PTask) {\r\n this._maybeClearDeferredStartP2P();\r\n }\r\n\r\n // Start peer to peer session\r\n if (!this.p2pJingleSession && shouldBeInP2P) {\r\n const peer = peerCount && peers[0];\r\n\r\n\r\n const myId = this.myUserId();\r\n const peersId = peer.getId();\r\n\r\n if (myId > peersId) {\r\n logger.debug(\r\n 'I\\'m the bigger peersId - '\r\n + 'the other peer should start P2P', myId, peersId);\r\n\r\n return;\r\n } else if (myId === peersId) {\r\n logger.error('The same IDs ? ', myId, peersId);\r\n\r\n return;\r\n }\r\n\r\n const jid = peer.getJid();\r\n\r\n if (userLeftEvent) {\r\n if (this.deferredStartP2PTask) {\r\n logger.error('Deferred start P2P task\\'s been set already!');\r\n\r\n return;\r\n }\r\n logger.info(\r\n `Will start P2P with: ${jid} after ${\r\n this.backToP2PDelay} seconds...`);\r\n this.deferredStartP2PTask = setTimeout(\r\n this._startP2PSession.bind(this, jid),\r\n this.backToP2PDelay * 1000);\r\n } else {\r\n logger.info(`Will start P2P with: ${jid}`);\r\n this._startP2PSession(jid);\r\n }\r\n } else if (this.p2pJingleSession && !shouldBeInP2P) {\r\n logger.info(`Will stop P2P with: ${this.p2pJingleSession.remoteJid}`);\r\n\r\n // Log that there will be a switch back to the JVB connection\r\n if (this.p2pJingleSession.isInitiator && peerCount > 1) {\r\n Statistics.sendAnalyticsAndLog(\r\n createP2PEvent(ACTION_P2P_SWITCH_TO_JVB));\r\n }\r\n this._stopP2PSession();\r\n }\r\n};\r\n\r\n/**\r\n * Tells whether or not this conference should be currently in the P2P mode.\r\n *\r\n * @private\r\n * @returns {boolean}\r\n */\r\nJitsiConference.prototype._shouldBeInP2PMode = function() {\r\n const peers = this.getParticipants();\r\n const peerCount = peers.length;\r\n const hasBotPeer = peers.find(p => p.getBotType() === 'poltergeist' || p.hasFeature(FEATURE_JIGASI)) !== undefined;\r\n const shouldBeInP2P = peerCount === 1 && !hasBotPeer;\r\n\r\n logger.debug(`P2P? peerCount: ${peerCount}, hasBotPeer: ${hasBotPeer} => ${shouldBeInP2P}`);\r\n\r\n return shouldBeInP2P;\r\n};\r\n\r\n/**\r\n * Stops the current P2P session.\r\n * @param {Object} options - Options for stopping P2P.\r\n * @param {string} options.reason - One of the Jingle \"reason\" element\r\n * names as defined by https://xmpp.org/extensions/xep-0166.html#def-reason\r\n * @param {string} options.reasonDescription - Text\r\n * description that will be included in the session terminate message\r\n * @param {boolean} requestRestart - Whether this is due to a session restart, in which case\r\n * media will not be resumed on the JVB.\r\n * @private\r\n */\r\nJitsiConference.prototype._stopP2PSession = function(options = {}) {\r\n const {\r\n reason = 'success',\r\n reasonDescription = 'Turning off P2P session',\r\n requestRestart = false\r\n } = options;\r\n\r\n if (!this.p2pJingleSession) {\r\n logger.error('No P2P session to be stopped!');\r\n\r\n return;\r\n }\r\n\r\n const wasP2PEstablished = this.isP2PActive();\r\n\r\n // Swap remote tracks, but only if the P2P has been fully established\r\n if (wasP2PEstablished) {\r\n if (this.jvbJingleSession && !requestRestart) {\r\n this._resumeMediaTransferForJvbConnection();\r\n }\r\n\r\n // Remove remote P2P tracks\r\n this._removeRemoteP2PTracks();\r\n }\r\n\r\n // Stop P2P stats\r\n logger.info('Stopping remote stats for P2P connection');\r\n this.statistics.stopRemoteStats(this.p2pJingleSession.peerconnection);\r\n logger.info('Stopping CallStats for P2P connection');\r\n this.statistics.stopCallStats(this.p2pJingleSession.peerconnection);\r\n\r\n this.p2pJingleSession.terminate(\r\n () => {\r\n logger.info('P2P session terminate RESULT');\r\n },\r\n error => {\r\n // Because both initiator and responder are simultaneously\r\n // terminating their JingleSessions in case of the 'to JVB switch'\r\n // when 3rd participant joins, both will dispose their sessions and\r\n // reply with 'item-not-found' (see strophe.jingle.js). We don't\r\n // want to log this as an error since it's expected behaviour.\r\n //\r\n // We want them both to terminate, because in case of initiator's\r\n // crash the responder would stay in P2P mode until ICE fails which\r\n // could take up to 20 seconds.\r\n //\r\n // NOTE: whilst this is an error callback, 'success' as a reason is\r\n // considered as graceful session terminate\r\n // where both initiator and responder terminate their sessions\r\n // simultaneously.\r\n if (reason !== 'success') {\r\n logger.error('An error occurred while trying to terminate P2P Jingle session', error);\r\n }\r\n }, {\r\n reason,\r\n reasonDescription,\r\n sendSessionTerminate: this.room\r\n && this.getParticipantById(\r\n Strophe.getResourceFromJid(this.p2pJingleSession.remoteJid))\r\n });\r\n\r\n this.p2pJingleSession = null;\r\n\r\n // Update P2P status and other affected events/states\r\n this._setP2PStatus(false);\r\n\r\n if (wasP2PEstablished) {\r\n // Add back remote JVB tracks\r\n if (this.jvbJingleSession && !requestRestart) {\r\n this._addRemoteJVBTracks();\r\n } else {\r\n logger.info('Not adding remote JVB tracks - no session yet');\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Updates room presence if needed and send the packet in case of a modification.\r\n * @param {JingleSessionPC} jingleSession the session firing the event, contains the peer connection which\r\n * tracks we will check.\r\n * @param {Object|null} ctx a context object we can distinguish multiple calls of the same pass of updating tracks.\r\n */\r\nJitsiConference.prototype._updateRoomPresence = function(jingleSession, ctx) {\r\n if (!jingleSession) {\r\n return;\r\n }\r\n\r\n // skips sending presence twice for the same pass of updating ssrcs\r\n if (ctx) {\r\n if (ctx.skip) {\r\n return;\r\n }\r\n ctx.skip = true;\r\n }\r\n\r\n let presenceChanged = false;\r\n let muteStatusChanged, videoTypeChanged;\r\n const localTracks = jingleSession.peerconnection.getLocalTracks();\r\n const localAudioTracks = localTracks.filter(track => track.getType() === MediaType.AUDIO);\r\n const localVideoTracks = localTracks.filter(track => track.getType() === MediaType.VIDEO);\r\n\r\n // Set presence for all the available local tracks.\r\n for (const track of localTracks) {\r\n muteStatusChanged = this._setTrackMuteStatus(track.getType(), track, track.isMuted());\r\n if (track.getType() === MediaType.VIDEO) {\r\n videoTypeChanged = this._setNewVideoType(track);\r\n }\r\n presenceChanged = presenceChanged || muteStatusChanged || videoTypeChanged;\r\n }\r\n\r\n // Set the presence in the legacy format if there are no local tracks and multi stream support is not enabled.\r\n if (!FeatureFlags.isMultiStreamSupportEnabled()) {\r\n let audioMuteStatusChanged, videoMuteStatusChanged;\r\n\r\n if (!localAudioTracks?.length) {\r\n audioMuteStatusChanged = this._setTrackMuteStatus(MediaType.AUDIO, undefined, true);\r\n }\r\n if (!localVideoTracks?.length) {\r\n videoMuteStatusChanged = this._setTrackMuteStatus(MediaType.VIDEO, undefined, true);\r\n videoTypeChanged = this._setNewVideoType();\r\n }\r\n\r\n presenceChanged = presenceChanged || audioMuteStatusChanged || videoMuteStatusChanged || videoTypeChanged;\r\n }\r\n\r\n presenceChanged && this.room.sendPresence();\r\n};\r\n\r\n/**\r\n * Checks whether or not the conference is currently in the peer to peer mode.\r\n * Being in peer to peer mode means that the direct connection has been\r\n * established and the P2P connection is being used for media transmission.\r\n * @return {boolean} true if in P2P mode or false otherwise.\r\n */\r\nJitsiConference.prototype.isP2PActive = function() {\r\n return this.p2p;\r\n};\r\n\r\n/**\r\n * Returns the current ICE state of the P2P connection.\r\n * NOTE: method is used by the jitsi-meet-torture tests.\r\n * @return {string|null} an ICE state or null if there's currently\r\n * no P2P connection.\r\n */\r\nJitsiConference.prototype.getP2PConnectionState = function() {\r\n if (this.isP2PActive()) {\r\n return this.p2pJingleSession.peerconnection.getConnectionState();\r\n }\r\n\r\n return null;\r\n};\r\n\r\n/**\r\n * Configures the peerconnection so that a given framre rate can be achieved for desktop share.\r\n *\r\n * @param {number} maxFps The capture framerate to be used for desktop tracks.\r\n * @returns {boolean} true if the operation is successful, false otherwise.\r\n */\r\nJitsiConference.prototype.setDesktopSharingFrameRate = function(maxFps) {\r\n if (typeof maxFps !== 'number' || isNaN(maxFps)) {\r\n logger.error(`Invalid value ${maxFps} specified for desktop capture frame rate`);\r\n\r\n return false;\r\n }\r\n\r\n this._desktopSharingFrameRate = maxFps;\r\n\r\n // Enable or disable simulcast for plan-b screensharing based on the capture fps.\r\n this.jvbJingleSession && this.jvbJingleSession.peerconnection.setDesktopSharingFrameRate(maxFps);\r\n\r\n // Set the capture rate for desktop sharing.\r\n this.rtc.setDesktopSharingFrameRate(maxFps);\r\n\r\n return true;\r\n};\r\n\r\n/**\r\n * Manually starts new P2P session (should be used only in the tests).\r\n */\r\nJitsiConference.prototype.startP2PSession = function() {\r\n const peers = this.getParticipants();\r\n\r\n // Start peer to peer session\r\n if (peers.length === 1) {\r\n const peerJid = peers[0].getJid();\r\n\r\n this._startP2PSession(peerJid);\r\n } else {\r\n throw new Error(\r\n 'There must be exactly 1 participant to start the P2P session !');\r\n }\r\n};\r\n\r\n/**\r\n * Manually stops the current P2P session (should be used only in the tests).\r\n */\r\nJitsiConference.prototype.stopP2PSession = function(options) {\r\n this._stopP2PSession(options);\r\n};\r\n\r\n/**\r\n * Get a summary of how long current participants have been the dominant speaker\r\n * @returns {object}\r\n */\r\nJitsiConference.prototype.getSpeakerStats = function() {\r\n return this.speakerStatsCollector.getStats();\r\n};\r\n\r\n/**\r\n * Sends a face landmarks object to the xmpp server.\r\n * @param {Object} payload\r\n */\r\nJitsiConference.prototype.sendFaceLandmarks = function(payload) {\r\n if (payload.faceExpression) {\r\n this.xmpp.sendFaceExpressionEvent(this.room.roomjid, payload);\r\n }\r\n};\r\n\r\n/**\r\n * Sets the constraints for the video that is requested from the bridge.\r\n *\r\n * @param {Object} videoConstraints The constraints which are specified in the\r\n * following format. The message updates the fields that are present and leaves the\r\n * rest unchanged on the bridge. Therefore, any field that is not applicable anymore\r\n * should be cleared by passing an empty object or list (whatever is applicable).\r\n * {\r\n * 'lastN': 20,\r\n * 'selectedEndpoints': ['A', 'B', 'C'],\r\n * 'onStageEndpoints': ['A'],\r\n * 'defaultConstraints': { 'maxHeight': 180 },\r\n * 'constraints': {\r\n * 'A': { 'maxHeight': 720 }\r\n * }\r\n * }\r\n */\r\nJitsiConference.prototype.setReceiverConstraints = function(videoConstraints) {\r\n this.receiveVideoController.setReceiverConstraints(videoConstraints);\r\n};\r\n\r\n/**\r\n * Sets the maximum video size the local participant should receive from remote\r\n * participants.\r\n *\r\n * @param {number} maxFrameHeight - the maximum frame height, in pixels,\r\n * this receiver is willing to receive.\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.setReceiverVideoConstraint = function(maxFrameHeight) {\r\n this.receiveVideoController.setPreferredReceiveMaxFrameHeight(maxFrameHeight);\r\n};\r\n\r\n/**\r\n * Sets the maximum video size the local participant should send to remote\r\n * participants.\r\n * @param {number} maxFrameHeight - The user preferred max frame height.\r\n * @returns {Promise} promise that will be resolved when the operation is\r\n * successful and rejected otherwise.\r\n */\r\nJitsiConference.prototype.setSenderVideoConstraint = function(maxFrameHeight) {\r\n return this.sendVideoController.setPreferredSendMaxFrameHeight(maxFrameHeight);\r\n};\r\n\r\n/**\r\n * Creates a video SIP GW session and returns it if service is enabled. Before\r\n * creating a session one need to check whether video SIP GW service is\r\n * available in the system {@link JitsiConference.isVideoSIPGWAvailable}. Even\r\n * if there are available nodes to serve this request, after creating the\r\n * session those nodes can be taken and the request about using the\r\n * created session can fail.\r\n *\r\n * @param {string} sipAddress - The sip address to be used.\r\n * @param {string} displayName - The display name to be used for this session.\r\n * @returns {JitsiVideoSIPGWSession|Error} Returns null if conference is not\r\n * initialised and there is no room.\r\n */\r\nJitsiConference.prototype.createVideoSIPGWSession = function(sipAddress, displayName) {\r\n if (!this.room) {\r\n return new Error(VideoSIPGWConstants.ERROR_NO_CONNECTION);\r\n }\r\n\r\n return this.videoSIPGWHandler\r\n .createVideoSIPGWSession(sipAddress, displayName);\r\n};\r\n\r\n/**\r\n * Sends a conference.join analytics event.\r\n *\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype._sendConferenceJoinAnalyticsEvent = function() {\r\n const meetingId = this.getMeetingUniqueId();\r\n\r\n if (this._conferenceJoinAnalyticsEventSent || !meetingId || this.getActivePeerConnection() === null) {\r\n return;\r\n }\r\n\r\n Statistics.sendAnalytics(createConferenceEvent('joined', {\r\n meetingId,\r\n participantId: `${meetingId}.${this._statsCurrentId}`\r\n }));\r\n this._conferenceJoinAnalyticsEventSent = Date.now();\r\n};\r\n\r\n/**\r\n * Sends conference.left analytics event.\r\n * @private\r\n */\r\nJitsiConference.prototype._sendConferenceLeftAnalyticsEvent = function() {\r\n const meetingId = this.getMeetingUniqueId();\r\n\r\n if (!meetingId || !this._conferenceJoinAnalyticsEventSent) {\r\n\r\n return;\r\n }\r\n\r\n Statistics.sendAnalytics(createConferenceEvent('left', {\r\n meetingId,\r\n participantId: `${meetingId}.${this._statsCurrentId}`,\r\n stats: {\r\n duration: Math.floor((Date.now() - this._conferenceJoinAnalyticsEventSent) / 1000),\r\n perf: this.getPerformanceStats()\r\n }\r\n }));\r\n};\r\n\r\n/**\r\n * Restarts all active media sessions.\r\n *\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype._restartMediaSessions = function() {\r\n if (this.p2pJingleSession) {\r\n this._stopP2PSession({\r\n reasonDescription: 'restart',\r\n requestRestart: true\r\n });\r\n }\r\n\r\n if (this.jvbJingleSession) {\r\n this.jvbJingleSession.terminate(\r\n null /* success callback => we don't care */,\r\n error => {\r\n logger.warn('An error occurred while trying to terminate the JVB session', error);\r\n }, {\r\n reason: 'success',\r\n reasonDescription: 'restart required',\r\n requestRestart: true,\r\n sendSessionTerminate: true\r\n });\r\n }\r\n\r\n this._maybeStartOrStopP2P(false);\r\n};\r\n\r\n/**\r\n * Returns whether End-To-End encryption is enabled.\r\n *\r\n * @returns {boolean}\r\n */\r\nJitsiConference.prototype.isE2EEEnabled = function() {\r\n return Boolean(this._e2eEncryption && this._e2eEncryption.isEnabled());\r\n};\r\n\r\n/**\r\n * Returns whether End-To-End encryption is supported. Note that not all participants\r\n * in the conference may support it.\r\n *\r\n * @returns {boolean}\r\n */\r\nJitsiConference.prototype.isE2EESupported = function() {\r\n return E2EEncryption.isSupported(this.options.config);\r\n};\r\n\r\n/**\r\n * Enables / disables End-to-End encryption.\r\n *\r\n * @param {boolean} enabled whether to enable E2EE or not.\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.toggleE2EE = function(enabled) {\r\n if (!this.isE2EESupported()) {\r\n logger.warn('Cannot enable / disable E2EE: platform is not supported.');\r\n\r\n return;\r\n }\r\n\r\n this._e2eEncryption.setEnabled(enabled);\r\n};\r\n\r\n/**\r\n * Sets the key and index for End-to-End encryption.\r\n *\r\n * @param {CryptoKey} [keyInfo.encryptionKey] - encryption key.\r\n * @param {Number} [keyInfo.index] - the index of the encryption key.\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.setMediaEncryptionKey = function(keyInfo) {\r\n this._e2eEncryption.setEncryptionKey(keyInfo);\r\n};\r\n\r\n/**\r\n * Returns true if lobby support is enabled in the backend.\r\n *\r\n * @returns {boolean} whether lobby is supported in the backend.\r\n */\r\nJitsiConference.prototype.isLobbySupported = function() {\r\n return Boolean(this.room && this.room.getLobby().isSupported());\r\n};\r\n\r\n/**\r\n * Returns true if the room has members only enabled.\r\n *\r\n * @returns {boolean} whether conference room is members only.\r\n */\r\nJitsiConference.prototype.isMembersOnly = function() {\r\n return Boolean(this.room && this.room.membersOnlyEnabled);\r\n};\r\n\r\n/**\r\n * Enables lobby by moderators\r\n *\r\n * @returns {Promise} resolves when lobby room is joined or rejects with the error.\r\n */\r\nJitsiConference.prototype.enableLobby = function() {\r\n if (this.room && this.isModerator()) {\r\n return this.room.getLobby().enable();\r\n }\r\n\r\n return Promise.reject(\r\n new Error('The conference not started or user is not moderator'));\r\n};\r\n\r\n/**\r\n * Disabled lobby by moderators\r\n *\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.disableLobby = function() {\r\n if (this.room && this.isModerator()) {\r\n this.room.getLobby().disable();\r\n } else {\r\n logger.warn(`Failed to disable lobby, ${this.room ? '' : 'not in a room, '}${\r\n this.isModerator() ? '' : 'participant is not a moderator'}`);\r\n }\r\n};\r\n\r\n/**\r\n * Joins the lobby room with display name and optional email or with a shared password to skip waiting.\r\n *\r\n * @param {string} displayName Display name should be set to show it to moderators.\r\n * @param {string} email Optional email is used to present avatar to the moderator.\r\n * @returns {Promise}\r\n */\r\nJitsiConference.prototype.joinLobby = function(displayName, email) {\r\n if (this.room) {\r\n return this.room.getLobby().join(displayName, email);\r\n }\r\n\r\n return Promise.reject(new Error('The conference not started'));\r\n};\r\n\r\n/**\r\n * Gets the local id for a participant in a lobby room.\r\n * Returns undefined when current participant is not in the lobby room.\r\n * This is used for lobby room private chat messages.\r\n *\r\n * @returns {string}\r\n */\r\nJitsiConference.prototype.myLobbyUserId = function() {\r\n if (this.room) {\r\n return this.room.getLobby().getLocalId();\r\n }\r\n};\r\n\r\n/**\r\n * Sends a message to a lobby room.\r\n * When id is specified it sends a private message.\r\n * Otherwise it sends the message to all moderators.\r\n * @param {message} Object The message to send\r\n * @param {string} id The participant id.\r\n *\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.sendLobbyMessage = function(message, id) {\r\n if (this.room) {\r\n if (id) {\r\n return this.room.getLobby().sendPrivateMessage(id, message);\r\n }\r\n\r\n return this.room.getLobby().sendMessage(message);\r\n }\r\n};\r\n\r\n/**\r\n * Adds a message listener to the lobby room\r\n * @param {Function} listener The listener function,\r\n * called when a new message is received in the lobby room.\r\n *\r\n * @returns {Function} Handler returned to be able to remove it later.\r\n */\r\nJitsiConference.prototype.addLobbyMessageListener = function(listener) {\r\n if (this.room) {\r\n return this.room.getLobby().addMessageListener(listener);\r\n }\r\n};\r\n\r\n/**\r\n * Removes a message handler from the lobby room\r\n * @param {Function} handler The handler function to remove.\r\n *\r\n * @returns {void}\r\n */\r\nJitsiConference.prototype.removeLobbyMessageHandler = function(handler) {\r\n if (this.room) {\r\n return this.room.getLobby().removeMessageHandler(handler);\r\n }\r\n};\r\n\r\n/**\r\n * Denies an occupant in the lobby room access to the conference.\r\n * @param {string} id The participant id.\r\n */\r\nJitsiConference.prototype.lobbyDenyAccess = function(id) {\r\n if (this.room) {\r\n this.room.getLobby().denyAccess(id);\r\n }\r\n};\r\n\r\n/**\r\n * Approves the request to join the conference to a participant waiting in the lobby.\r\n *\r\n * @param {string} id The participant id.\r\n */\r\nJitsiConference.prototype.lobbyApproveAccess = function(id) {\r\n if (this.room) {\r\n this.room.getLobby().approveAccess(id);\r\n }\r\n};\r\n\r\n/**\r\n * Returns true if AV Moderation support is enabled in the backend.\r\n *\r\n * @returns {boolean} whether AV Moderation is supported in the backend.\r\n */\r\nJitsiConference.prototype.isAVModerationSupported = function() {\r\n return Boolean(this.room && this.room.getAVModeration().isSupported());\r\n};\r\n\r\n/**\r\n * Enables AV Moderation.\r\n * @param {MediaType} mediaType \"audio\" or \"video\"\r\n */\r\nJitsiConference.prototype.enableAVModeration = function(mediaType) {\r\n if (this.room && this.isModerator()\r\n && (mediaType === MediaType.AUDIO || mediaType === MediaType.VIDEO)) {\r\n this.room.getAVModeration().enable(true, mediaType);\r\n } else {\r\n logger.warn(`Failed to enable AV moderation, ${this.room ? '' : 'not in a room, '}${\r\n this.isModerator() ? '' : 'participant is not a moderator, '}${\r\n this.room && this.isModerator() ? 'wrong media type passed' : ''}`);\r\n }\r\n};\r\n\r\n/**\r\n * Disables AV Moderation.\r\n * @param {MediaType} mediaType \"audio\" or \"video\"\r\n */\r\nJitsiConference.prototype.disableAVModeration = function(mediaType) {\r\n if (this.room && this.isModerator()\r\n && (mediaType === MediaType.AUDIO || mediaType === MediaType.VIDEO)) {\r\n this.room.getAVModeration().enable(false, mediaType);\r\n } else {\r\n logger.warn(`Failed to disable AV moderation, ${this.room ? '' : 'not in a room, '}${\r\n this.isModerator() ? '' : 'participant is not a moderator, '}${\r\n this.room && this.isModerator() ? 'wrong media type passed' : ''}`);\r\n }\r\n};\r\n\r\n/**\r\n * Approve participant access to certain media, allows unmuting audio or video.\r\n *\r\n * @param {MediaType} mediaType \"audio\" or \"video\"\r\n * @param id the id of the participant.\r\n */\r\nJitsiConference.prototype.avModerationApprove = function(mediaType, id) {\r\n if (this.room && this.isModerator()\r\n && (mediaType === MediaType.AUDIO || mediaType === MediaType.VIDEO)) {\r\n\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n\r\n this.room.getAVModeration().approve(mediaType, participant.getJid());\r\n } else {\r\n logger.warn(`AV moderation approve skipped , ${this.room ? '' : 'not in a room, '}${\r\n this.isModerator() ? '' : 'participant is not a moderator, '}${\r\n this.room && this.isModerator() ? 'wrong media type passed' : ''}`);\r\n }\r\n};\r\n\r\n/**\r\n * Reject participant access to certain media, blocks unmuting audio or video.\r\n *\r\n * @param {MediaType} mediaType \"audio\" or \"video\"\r\n * @param id the id of the participant.\r\n */\r\nJitsiConference.prototype.avModerationReject = function(mediaType, id) {\r\n if (this.room && this.isModerator()\r\n && (mediaType === MediaType.AUDIO || mediaType === MediaType.VIDEO)) {\r\n\r\n const participant = this.getParticipantById(id);\r\n\r\n if (!participant) {\r\n return;\r\n }\r\n\r\n this.room.getAVModeration().reject(mediaType, participant.getJid());\r\n } else {\r\n logger.warn(`AV moderation reject skipped , ${this.room ? '' : 'not in a room, '}${\r\n this.isModerator() ? '' : 'participant is not a moderator, '}${\r\n this.room && this.isModerator() ? 'wrong media type passed' : ''}`);\r\n }\r\n};\r\n\r\n/**\r\n * Returns the breakout rooms manager object.\r\n *\r\n * @returns {Object} the breakout rooms manager.\r\n */\r\nJitsiConference.prototype.getBreakoutRooms = function() {\r\n return this.room?.getBreakoutRooms();\r\n};\r\n","import JitsiConference from './JitsiConference';\r\nimport * as JitsiConnectionEvents from './JitsiConnectionEvents';\r\nimport Statistics from './modules/statistics/statistics';\r\nimport XMPP from './modules/xmpp/xmpp';\r\nimport {\r\n CONNECTION_DISCONNECTED as ANALYTICS_CONNECTION_DISCONNECTED,\r\n createConnectionFailedEvent\r\n} from './service/statistics/AnalyticsEvents';\r\n\r\n/**\r\n * Creates a new connection object for the Jitsi Meet server side video\r\n * conferencing service. Provides access to the JitsiConference interface.\r\n * @param appID identification for the provider of Jitsi Meet video conferencing\r\n * services.\r\n * @param token the JWT token used to authenticate with the server(optional)\r\n * @param options Object with properties / settings related to connection with\r\n * the server.\r\n * @constructor\r\n */\r\nexport default function JitsiConnection(appID, token, options) {\r\n this.appID = appID;\r\n this.token = token;\r\n this.options = options;\r\n this.xmpp = new XMPP(options, token);\r\n\r\n /* eslint-disable max-params */\r\n this.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED,\r\n (errType, msg, credentials, details) => {\r\n Statistics.sendAnalyticsAndLog(\r\n createConnectionFailedEvent(errType, msg, details));\r\n });\r\n /* eslint-enable max-params */\r\n\r\n this.addEventListener(JitsiConnectionEvents.CONNECTION_DISCONNECTED,\r\n msg => {\r\n // we can see disconnects from normal tab closing of the browser\r\n // and then there are no msgs, but we want to log only disconnects\r\n // when there is real error\r\n // XXX Do we need the difference in handling between the log and\r\n // analytics event here?\r\n if (msg) {\r\n Statistics.sendAnalytics(\r\n ANALYTICS_CONNECTION_DISCONNECTED,\r\n { message: msg });\r\n }\r\n Statistics.sendLog(\r\n JSON.stringify(\r\n {\r\n id: ANALYTICS_CONNECTION_DISCONNECTED,\r\n msg\r\n }));\r\n });\r\n}\r\n\r\n/**\r\n * Connect the client with the server.\r\n * @param options {object} connecting options\r\n * (for example authentications parameters).\r\n */\r\nJitsiConnection.prototype.connect = function(options = {}) {\r\n this.xmpp.connect(options.id, options.password);\r\n};\r\n\r\n/**\r\n * Attach to existing connection. Can be used for optimizations. For example:\r\n * if the connection is created on the server we can attach to it and start\r\n * using it.\r\n *\r\n * @param options {object} connecting options - rid, sid and jid.\r\n */\r\nJitsiConnection.prototype.attach = function(options) {\r\n this.xmpp.attach(options);\r\n};\r\n\r\n/**\r\n * Disconnect the client from the server.\r\n * @returns {Promise} - Resolves when the disconnect process is finished or rejects with an error.\r\n */\r\nJitsiConnection.prototype.disconnect = function(...args) {\r\n // XXX Forward any arguments passed to JitsiConnection.disconnect to\r\n // XMPP.disconnect. For example, the caller of JitsiConnection.disconnect\r\n // may optionally pass the event which triggered the disconnect in order to\r\n // provide the implementation with finer-grained context.\r\n return this.xmpp.disconnect(...args);\r\n};\r\n\r\n/**\r\n * Returns the jid of the participant associated with the XMPP connection.\r\n *\r\n * @returns {string} The jid of the participant.\r\n */\r\nJitsiConnection.prototype.getJid = function() {\r\n return this.xmpp.getJid();\r\n};\r\n\r\n/**\r\n * This method allows renewal of the tokens if they are expiring.\r\n * @param token the new token.\r\n */\r\nJitsiConnection.prototype.setToken = function(token) {\r\n this.token = token;\r\n};\r\n\r\n/**\r\n * Creates and joins new conference.\r\n * @param name the name of the conference; if null - a generated name will be\r\n * provided from the api\r\n * @param options Object with properties / settings related to the conference\r\n * that will be created.\r\n * @returns {JitsiConference} returns the new conference object.\r\n */\r\nJitsiConnection.prototype.initJitsiConference = function(name, options) {\r\n return new JitsiConference({\r\n name,\r\n config: options,\r\n connection: this\r\n });\r\n};\r\n\r\n/**\r\n * Subscribes the passed listener to the event.\r\n * @param event {JitsiConnectionEvents} the connection event.\r\n * @param listener {Function} the function that will receive the event\r\n */\r\nJitsiConnection.prototype.addEventListener = function(event, listener) {\r\n this.xmpp.addListener(event, listener);\r\n};\r\n\r\n/**\r\n * Unsubscribes the passed handler.\r\n * @param event {JitsiConnectionEvents} the connection event.\r\n * @param listener {Function} the function that will receive the event\r\n */\r\nJitsiConnection.prototype.removeEventListener = function(event, listener) {\r\n this.xmpp.removeListener(event, listener);\r\n};\r\n\r\n/**\r\n * Returns measured connectionTimes.\r\n */\r\nJitsiConnection.prototype.getConnectionTimes = function() {\r\n return this.xmpp.connectionTimes;\r\n};\r\n\r\n/**\r\n * Adds new feature to the list of supported features for the local\r\n * participant.\r\n * @param {String} feature the name of the feature.\r\n * @param {boolean} submit if true - the new list of features will be\r\n * immediately submitted to the others.\r\n */\r\nJitsiConnection.prototype.addFeature = function(feature, submit = false) {\r\n this.xmpp.caps.addFeature(feature, submit, true);\r\n};\r\n\r\n/**\r\n * Removes a feature from the list of supported features for the local\r\n * participant\r\n * @param {String} feature the name of the feature.\r\n * @param {boolean} submit if true - the new list of features will be\r\n * immediately submitted to the others.\r\n */\r\nJitsiConnection.prototype.removeFeature = function(feature, submit = false) {\r\n this.xmpp.caps.removeFeature(feature, submit, true);\r\n};\r\n\r\n/**\r\n * Get object with internal logs.\r\n */\r\nJitsiConnection.prototype.getLogs = function() {\r\n const data = this.xmpp.getJingleLog();\r\n\r\n const metadata = {};\r\n\r\n metadata.time = new Date();\r\n metadata.url = window.location.href;\r\n metadata.ua = navigator.userAgent;\r\n\r\n const log = this.xmpp.getXmppLog();\r\n\r\n if (log) {\r\n metadata.xmpp = log;\r\n }\r\n\r\n data.metadata = metadata;\r\n\r\n return data;\r\n};\r\n","/**\r\n * The events for the media devices.\r\n */\r\n\r\nexport enum JitsiMediaDevicesEvents {\r\n /**\r\n * Indicates that the list of available media devices has been changed. The\r\n * event provides the following parameters to its listeners:\r\n *\r\n * @param {MediaDeviceInfo[]} devices - array of MediaDeviceInfo or\r\n * MediaDeviceInfo-like objects that are currently connected.\r\n * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo\r\n */\r\n DEVICE_LIST_CHANGED = 'mediaDevices.devicechange',\r\n\r\n /**\r\n * Event emitted when the user granted/blocked a permission for the camera / mic.\r\n * Used to keep track of the granted permissions on browsers which don't\r\n * support the Permissions API.\r\n */\r\n PERMISSIONS_CHANGED = 'rtc.permissions_changed',\r\n\r\n /**\r\n * Indicates that the environment is currently showing permission prompt to\r\n * access camera and/or microphone. The event provides the following\r\n * parameters to its listeners:\r\n *\r\n * @param {'chrome'|'opera'|'firefox'|'safari'|'nwjs'\r\n * |'react-native'|'android'} environmentType - type of browser or\r\n * other execution environment.\r\n */\r\n PERMISSION_PROMPT_IS_SHOWN = 'mediaDevices.permissionPromptIsShown',\r\n\r\n SLOW_GET_USER_MEDIA = 'mediaDevices.slowGetUserMedia'\r\n};\r\n\r\n// exported for backward compatibility\r\nexport const DEVICE_LIST_CHANGED = JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED;\r\nexport const PERMISSIONS_CHANGED = JitsiMediaDevicesEvents.PERMISSIONS_CHANGED;\r\nexport const PERMISSION_PROMPT_IS_SHOWN = JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN;\r\nexport const SLOW_GET_USER_MEDIA = JitsiMediaDevicesEvents.SLOW_GET_USER_MEDIA;\r\n","import EventEmitter from 'events';\r\n\r\nimport * as JitsiMediaDevicesEvents from './JitsiMediaDevicesEvents';\r\nimport RTC from './modules/RTC/RTC';\r\nimport browser from './modules/browser';\r\nimport Statistics from './modules/statistics/statistics';\r\nimport { MediaType } from './service/RTC/MediaType';\r\nimport RTCEvents from './service/RTC/RTCEvents';\r\n\r\nconst AUDIO_PERMISSION_NAME = 'microphone';\r\nconst PERMISSION_GRANTED_STATUS = 'granted';\r\nconst VIDEO_PERMISSION_NAME = 'camera';\r\n\r\n/**\r\n * Media devices utilities for Jitsi.\r\n */\r\nclass JitsiMediaDevices {\r\n /**\r\n * Initializes a {@code JitsiMediaDevices} object. There will be a single\r\n * instance of this class.\r\n */\r\n constructor() {\r\n this._eventEmitter = new EventEmitter();\r\n this._permissions = {};\r\n\r\n RTC.addListener(\r\n RTCEvents.DEVICE_LIST_CHANGED,\r\n devices =>\r\n this._eventEmitter.emit(\r\n JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,\r\n devices));\r\n RTC.addListener(\r\n RTCEvents.DEVICE_LIST_AVAILABLE,\r\n devices =>\r\n this._logOutputDevice(\r\n this.getAudioOutputDevice(),\r\n devices));\r\n\r\n // We would still want to update the permissions cache in case the permissions API is not supported.\r\n RTC.addListener(\r\n RTCEvents.PERMISSIONS_CHANGED,\r\n permissions => this._handlePermissionsChange(permissions));\r\n\r\n // Test if the W3C Permissions API is implemented and the 'camera' and 'microphone' permissions are\r\n // implemented. If supported add onchange listeners.\r\n this._permissionsApiSupported = new Promise(resolve => {\r\n if (!navigator.permissions) {\r\n resolve(false);\r\n\r\n return;\r\n }\r\n\r\n const self = this;\r\n\r\n const promises = [];\r\n\r\n promises.push(navigator.permissions.query({ name: VIDEO_PERMISSION_NAME })\r\n .then(status => {\r\n this._handlePermissionsChange({\r\n [MediaType.VIDEO]: this._parsePermissionState(status)\r\n });\r\n status.onchange = function() {\r\n try {\r\n self._handlePermissionsChange({\r\n [MediaType.VIDEO]: self._parsePermissionState(this)\r\n });\r\n } catch (error) {\r\n // Nothing to do.\r\n }\r\n };\r\n\r\n return true;\r\n })\r\n .catch(() => false));\r\n\r\n promises.push(navigator.permissions.query({ name: AUDIO_PERMISSION_NAME })\r\n .then(status => {\r\n this._handlePermissionsChange({\r\n [MediaType.AUDIO]: this._parsePermissionState(status)\r\n });\r\n status.onchange = function() {\r\n try {\r\n self._handlePermissionsChange({\r\n [MediaType.AUDIO]: self._parsePermissionState(this)\r\n });\r\n } catch (error) {\r\n // Nothing to do.\r\n }\r\n };\r\n\r\n return true;\r\n })\r\n .catch(() => false));\r\n\r\n Promise.all(promises).then(results => resolve(results.every(supported => supported)));\r\n\r\n });\r\n }\r\n\r\n\r\n /**\r\n * Parses a PermissionState object and returns true for granted and false otherwise.\r\n *\r\n * @param {PermissionState} permissionStatus - The PermissionState object retrieved from the Permissions API.\r\n * @returns {boolean} - True for granted and false for denied.\r\n * @throws {TypeError}\r\n */\r\n _parsePermissionState(permissionStatus = {}) {\r\n // The status attribute is deprecated, and state\r\n // should be used instead, but check both for now\r\n // for backwards compatibility.\r\n const status = permissionStatus.state || permissionStatus.status;\r\n\r\n if (typeof status !== 'string') {\r\n throw new TypeError();\r\n }\r\n\r\n return status === PERMISSION_GRANTED_STATUS;\r\n }\r\n\r\n /**\r\n * Updates the local granted/denied permissions cache. A permissions might be\r\n * granted, denied, or undefined. This is represented by having its media\r\n * type key set to {@code true} or {@code false} respectively.\r\n *\r\n * @param {Object} permissions - Object with the permissions.\r\n */\r\n _handlePermissionsChange(permissions) {\r\n const hasPermissionsChanged\r\n = [ MediaType.AUDIO, MediaType.VIDEO ]\r\n .some(type => type in permissions && permissions[type] !== this._permissions[type]);\r\n\r\n if (hasPermissionsChanged) {\r\n this._permissions = {\r\n ...this._permissions,\r\n ...permissions\r\n };\r\n this._eventEmitter.emit(JitsiMediaDevicesEvents.PERMISSIONS_CHANGED, this._permissions);\r\n\r\n if (this._permissions[MediaType.AUDIO] || this._permissions[MediaType.VIDEO]) {\r\n // Triggering device list update when the permissiions are granted in order to update\r\n // the labels the devices.\r\n // eslint-disable-next-line no-empty-function\r\n this.enumerateDevices(() => {});\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Gathers data and sends it to statistics.\r\n * @param deviceID the device id to log\r\n * @param devices list of devices\r\n */\r\n _logOutputDevice(deviceID, devices) {\r\n const device\r\n = devices.find(\r\n d => d.kind === 'audiooutput' && d.deviceId === deviceID);\r\n\r\n if (device) {\r\n Statistics.sendActiveDeviceListEvent(\r\n RTC.getEventDataForActiveDevice(device));\r\n }\r\n }\r\n\r\n /**\r\n * Executes callback with list of media devices connected.\r\n * @param {function} callback\r\n */\r\n enumerateDevices(callback) {\r\n RTC.enumerateDevices(callback);\r\n }\r\n\r\n /**\r\n * Checks if its possible to enumerate available cameras/micropones.\r\n * @returns {Promise} a Promise which will be resolved only once\r\n * the WebRTC stack is ready, either with true if the device listing is\r\n * available available or with false otherwise.\r\n */\r\n isDeviceListAvailable() {\r\n return RTC.isDeviceListAvailable();\r\n }\r\n\r\n /**\r\n * Returns true if changing the input (camera / microphone) or output\r\n * (audio) device is supported and false if not.\r\n * @param {string} [deviceType] - type of device to change. Default is\r\n * undefined or 'input', 'output' - for audio output device change.\r\n * @returns {boolean} true if available, false otherwise.\r\n */\r\n isDeviceChangeAvailable(deviceType) {\r\n return RTC.isDeviceChangeAvailable(deviceType);\r\n }\r\n\r\n /**\r\n * Checks if the permission for the given device was granted.\r\n *\r\n * @param {'audio'|'video'} [type] - type of devices to check,\r\n * undefined stands for both 'audio' and 'video' together\r\n * @returns {Promise}\r\n */\r\n isDevicePermissionGranted(type) {\r\n return new Promise(resolve => {\r\n // Shortcut: first check if we already know the permission was\r\n // granted.\r\n if (type in this._permissions) {\r\n resolve(this._permissions[type]);\r\n\r\n return;\r\n }\r\n\r\n // Check using the Permissions API.\r\n this._permissionsApiSupported.then(supported => {\r\n if (!supported) {\r\n resolve(false);\r\n\r\n return;\r\n }\r\n\r\n const promises = [];\r\n\r\n switch (type) {\r\n case MediaType.VIDEO:\r\n promises.push(\r\n navigator.permissions.query({\r\n name: VIDEO_PERMISSION_NAME\r\n }));\r\n break;\r\n case MediaType.AUDIO:\r\n promises.push(\r\n navigator.permissions.query({\r\n name: AUDIO_PERMISSION_NAME\r\n }));\r\n break;\r\n default:\r\n promises.push(\r\n navigator.permissions.query({\r\n name: VIDEO_PERMISSION_NAME\r\n }));\r\n promises.push(\r\n navigator.permissions.query({\r\n name: AUDIO_PERMISSION_NAME\r\n }));\r\n }\r\n\r\n Promise.all(promises).then(\r\n results => resolve(results.every(permissionStatus => {\r\n try {\r\n return this._parsePermissionState(permissionStatus);\r\n } catch {\r\n return false;\r\n }\r\n })),\r\n () => resolve(false)\r\n );\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Returns true if it is possible to be simultaneously capturing audio from more than one device.\r\n *\r\n * @returns {boolean}\r\n */\r\n isMultipleAudioInputSupported() {\r\n return !(\r\n (browser.isFirefox() && browser.isVersionLessThan('101'))\r\n || browser.isIosBrowser()\r\n );\r\n }\r\n\r\n /**\r\n * Returns currently used audio output device id, 'default' stands\r\n * for default device\r\n * @returns {string}\r\n */\r\n getAudioOutputDevice() {\r\n return RTC.getAudioOutputDevice();\r\n }\r\n\r\n /**\r\n * Sets current audio output device.\r\n * @param {string} deviceId - id of 'audiooutput' device from\r\n * navigator.mediaDevices.enumerateDevices(), 'default' is for\r\n * default device\r\n * @returns {Promise} - resolves when audio output is changed, is rejected\r\n * otherwise\r\n */\r\n setAudioOutputDevice(deviceId) {\r\n const availableDevices = RTC.getCurrentlyAvailableMediaDevices();\r\n\r\n if (availableDevices.length > 0) {\r\n // if we have devices info report device to stats\r\n // normally this will not happen on startup as this method is called\r\n // too early. This will happen only on user selection of new device\r\n this._logOutputDevice(\r\n deviceId, RTC.getCurrentlyAvailableMediaDevices());\r\n }\r\n\r\n return RTC.setAudioOutputDevice(deviceId);\r\n }\r\n\r\n /**\r\n * Adds an event handler.\r\n * @param {string} event - event name\r\n * @param {function} handler - event handler\r\n */\r\n addEventListener(event, handler) {\r\n this._eventEmitter.addListener(event, handler);\r\n }\r\n\r\n /**\r\n * Removes event handler.\r\n * @param {string} event - event name\r\n * @param {function} handler - event handler\r\n */\r\n removeEventListener(event, handler) {\r\n this._eventEmitter.removeListener(event, handler);\r\n }\r\n\r\n /**\r\n * Emits an event.\r\n * @param {string} event - event name\r\n */\r\n emitEvent(event, ...args) {\r\n this._eventEmitter.emit(event, ...args);\r\n }\r\n}\r\n\r\nexport default new JitsiMediaDevices();\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport RTC from '../RTC/RTC';\r\nimport Statistics from '../statistics/statistics';\r\n\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n// If after 3000 ms the detector did not find any active devices consider that there aren't any usable ones available\r\n// i.e. audioLevel > 0.008\r\nconst DETECTION_TIMEOUT = 3000;\r\n\r\n\r\n/**\r\n * Go through all audio devices on the system and return one that is active, i.e. has audio signal.\r\n *\r\n * @returns Promise - Object containing information about the found device.\r\n */\r\nexport default function getActiveAudioDevice() {\r\n\r\n return new Promise(resolve => {\r\n RTC.enumerateDevices(devices => {\r\n const audioDevices = devices.filter(device => device.kind === 'audioinput');\r\n const devicePromiseArray = [];\r\n\r\n\r\n for (const micDevice of audioDevices) {\r\n const devicePromise = RTC.obtainAudioAndVideoPermissions({ devices: [ 'audio' ],\r\n micDeviceId: micDevice.deviceId }).then(tracks => {\r\n\r\n // We expect a single device to be available when obtained from obtainAudioAndVideoPermissions\r\n // that's why only take p.value[0].\r\n const track = tracks[0];\r\n const originalStream = track.getOriginalStream();\r\n\r\n Statistics.startLocalStats(originalStream, track.setAudioLevel.bind(track));\r\n track.addEventListener(JitsiTrackEvents.LOCAL_TRACK_STOPPED, () => {\r\n Statistics.stopLocalStats(originalStream);\r\n });\r\n\r\n return track;\r\n });\r\n\r\n devicePromiseArray.push(devicePromise);\r\n }\r\n\r\n Promise.allSettled(devicePromiseArray).then(outcomeArray => {\r\n const successfulPromises = outcomeArray.filter(p => p.status === 'fulfilled');\r\n const rejectedPromises = outcomeArray.filter(p => p.status === 'rejected');\r\n\r\n\r\n const availableDevices = successfulPromises.map(p => p.value);\r\n const rejectReasons = rejectedPromises.map(p => p.value);\r\n\r\n for (const reason of rejectReasons) {\r\n logger.error('Failed to acquire audio device with error: ', reason);\r\n }\r\n\r\n // Setup event handlers for monitored devices.\r\n for (const device of availableDevices) {\r\n device.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevel => {\r\n // This is a very naive approach but works, a more accurate one would be to use rnnoise in\r\n // order to limit the number of false positives. The 0.008 constant is due to how\r\n // LocalStatsCollector from lib-jitsi-meet publishes audio-levels, in this case 0.008 denotes //\r\n // no input.\r\n if (audioLevel > 0.008) {\r\n stopActiveDevices(availableDevices);\r\n resolve({ deviceId: device.deviceId,\r\n deviceLabel: device.track.label });\r\n }\r\n });\r\n }\r\n\r\n // Cancel the detection in case no devices was found with audioLevel > 0 in the set timeout.\r\n setTimeout(() => {\r\n stopActiveDevices(availableDevices);\r\n resolve({\r\n deviceId: '',\r\n deviceLabel: '' }\r\n );\r\n }, DETECTION_TIMEOUT);\r\n\r\n });\r\n\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Stop the streams of the provided JitsiLocalTracks.\r\n *\r\n * @param {Array} deviceList - Array of JitsiLocalTracks to stop.\r\n * @returns {void}\r\n */\r\nfunction stopActiveDevices(deviceList) {\r\n for (const device of deviceList) {\r\n device.stopStream();\r\n }\r\n}\r\n","/**\r\n * The know jingle actions that can be sent and should be acted upon by\r\n * {@code ProxyConnectionService} and {@code ProxyConnectionPC}.\r\n */\r\nexport enum ACTIONS {\r\n ACCEPT = 'session-accept',\r\n CONNECTION_ERROR = 'connection-error-encountered',\r\n INITIATE = 'session-initiate',\r\n TERMINATE = 'session-terminate',\r\n TRANSPORT_INFO = 'transport-info',\r\n UNAVAILABLE = 'unavailable'\r\n};\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport { XMPPEvents } from '../../service/xmpp/XMPPEvents';\r\nimport RTC from '../RTC/RTC';\r\nimport JingleSessionPC from '../xmpp/JingleSessionPC';\r\nimport SignalingLayerImpl from '../xmpp/SignalingLayerImpl';\r\nimport { DEFAULT_STUN_SERVERS } from '../xmpp/xmpp';\r\n\r\nimport { ACTIONS } from './constants';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * An adapter around {@code JingleSessionPC} so its logic can be re-used without\r\n * an XMPP connection. It is being re-used for consistency with the rest of the\r\n * codebase and to leverage existing peer connection event handling. Also\r\n * this class provides a facade to hide most of the API for\r\n * {@code JingleSessionPC}.\r\n */\r\nexport default class ProxyConnectionPC {\r\n /**\r\n * Initializes a new {@code ProxyConnectionPC} instance.\r\n *\r\n * @param {Object} options - Values to initialize the instance with.\r\n * @param {Object} [options.pcConfig] - The {@code RTCConfiguration} to use for the WebRTC peer connection.\r\n * @param {boolean} [options.isInitiator] - If true, the local client should send offers. If false, the local\r\n * client should send answers. Defaults to false.\r\n * @param {Function} options.onRemoteStream - Callback to invoke when a remote media stream has been received\r\n * through the peer connection.\r\n * @param {string} options.peerJid - The jid of the remote client with which the peer connection is being establish\r\n * and which should receive direct messages regarding peer connection updates.\r\n * @param {boolean} [options.receiveVideo] - Whether or not the peer connection should accept incoming video\r\n * streams. Defaults to false.\r\n * @param {Function} options.onSendMessage - Callback to invoke when a message has to be sent (signaled) out.\r\n */\r\n constructor(options = {}) {\r\n this._options = {\r\n pcConfig: {},\r\n isInitiator: false,\r\n receiveAudio: false,\r\n receiveVideo: false,\r\n ...options\r\n };\r\n\r\n /**\r\n * Instances of {@code JitsiTrack} associated with this instance of\r\n * {@code ProxyConnectionPC}.\r\n *\r\n * @type {Array}\r\n */\r\n this._tracks = [];\r\n\r\n /**\r\n * The active instance of {@code JingleSessionPC}.\r\n *\r\n * @type {JingleSessionPC|null}\r\n */\r\n this._peerConnection = null;\r\n\r\n // Bind event handlers so they are only bound once for every instance.\r\n this._onError = this._onError.bind(this);\r\n this._onRemoteStream = this._onRemoteStream.bind(this);\r\n this._onSendMessage = this._onSendMessage.bind(this);\r\n }\r\n\r\n /**\r\n * Returns the jid of the remote peer with which this peer connection should\r\n * be established with.\r\n *\r\n * @returns {string}\r\n */\r\n getPeerJid() {\r\n return this._options.peerJid;\r\n }\r\n\r\n /**\r\n * Updates the peer connection based on the passed in jingle.\r\n *\r\n * @param {Object} $jingle - An XML jingle element, wrapped in query,\r\n * describing how the peer connection should be updated.\r\n * @returns {void}\r\n */\r\n processMessage($jingle) {\r\n switch ($jingle.attr('action')) {\r\n case ACTIONS.ACCEPT:\r\n this._onSessionAccept($jingle);\r\n break;\r\n\r\n case ACTIONS.INITIATE:\r\n this._onSessionInitiate($jingle);\r\n break;\r\n\r\n case ACTIONS.TERMINATE:\r\n this._onSessionTerminate($jingle);\r\n break;\r\n\r\n case ACTIONS.TRANSPORT_INFO:\r\n this._onTransportInfo($jingle);\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Instantiates a peer connection and starts the offer/answer cycle to\r\n * establish a connection with a remote peer.\r\n *\r\n * @param {Array} localTracks - Initial local tracks to add\r\n * to add to the peer connection.\r\n * @returns {void}\r\n */\r\n start(localTracks = []) {\r\n if (this._peerConnection) {\r\n return;\r\n }\r\n\r\n this._tracks = this._tracks.concat(localTracks);\r\n\r\n this._peerConnection = this._createPeerConnection();\r\n\r\n this._peerConnection.invite(localTracks);\r\n }\r\n\r\n /**\r\n * Begins the process of disconnecting from a remote peer and cleaning up\r\n * the peer connection.\r\n *\r\n * @returns {void}\r\n */\r\n stop() {\r\n if (this._peerConnection) {\r\n this._peerConnection.terminate();\r\n }\r\n\r\n this._onSessionTerminate();\r\n }\r\n\r\n /**\r\n * Instantiates a new {@code JingleSessionPC} by stubbing out the various\r\n * dependencies of {@code JingleSessionPC}.\r\n *\r\n * @private\r\n * @returns {JingleSessionPC}\r\n */\r\n _createPeerConnection() {\r\n /**\r\n * {@code JingleSessionPC} takes in the entire jitsi-meet config.js\r\n * object, which may not be accessible from the caller.\r\n *\r\n * @type {Object}\r\n */\r\n const configStub = {};\r\n\r\n /**\r\n * {@code JingleSessionPC} assumes an XMPP/Strophe connection object is\r\n * passed through, which also has the jingle plugin initialized on it.\r\n * This connection object is used to signal out peer connection updates\r\n * via iqs, and those updates need to be piped back out to the remote\r\n * peer.\r\n *\r\n * @type {Object}\r\n */\r\n const connectionStub = {\r\n // At the time this is used for Spot and it's okay to say the connection is always connected, because if\r\n // spot has no signalling it will not be in a meeting where this is used.\r\n connected: true,\r\n jingle: {\r\n terminate: () => { /** no-op */ }\r\n },\r\n sendIQ: this._onSendMessage,\r\n\r\n // Returns empty function, because it does not add any listeners for real\r\n // eslint-disable-next-line no-empty-function\r\n addEventListener: () => () => { }\r\n };\r\n\r\n /**\r\n * {@code JingleSessionPC} can take in a custom ice configuration,\r\n * depending on the peer connection type, peer-to-peer or other.\r\n * However, {@code ProxyConnectionPC} always assume a peer-to-peer\r\n * connection so the ice configuration is hard-coded with defaults.\r\n *\r\n * @type {Object}\r\n */\r\n const pcConfigStub = {\r\n iceServers: DEFAULT_STUN_SERVERS,\r\n ...this._options.pcConfig\r\n };\r\n\r\n /**\r\n * {@code JingleSessionPC} expects an instance of\r\n * {@code JitsiConference}, which has an event emitter that is used\r\n * to signal various connection updates that the local client should\r\n * act upon. The conference instance is not a dependency of a proxy\r\n * connection, but the emitted events can be relevant to the proxy\r\n * connection so the event emitter is stubbed.\r\n *\r\n * @param {string} event - The constant for the event type.\r\n * @type {Function}\r\n * @returns {void}\r\n */\r\n const emitter = event => {\r\n switch (event) {\r\n case XMPPEvents.CONNECTION_ICE_FAILED:\r\n case XMPPEvents.CONNECTION_FAILED:\r\n this._onError(ACTIONS.CONNECTION_ERROR, event);\r\n break;\r\n }\r\n };\r\n\r\n /**\r\n * {@link JingleSessionPC} expects an instance of\r\n * {@link ChatRoom} to be passed in. {@link ProxyConnectionPC}\r\n * is instantiated outside of the {@code JitsiConference}, so it must be\r\n * stubbed to prevent errors.\r\n *\r\n * @type {Object}\r\n */\r\n const roomStub = {\r\n addPresenceListener: () => { /** no-op */ },\r\n connectionTimes: [],\r\n eventEmitter: { emit: emitter },\r\n getMediaPresenceInfo: () => {\r\n // Errors occur if this function does not return an object\r\n\r\n return {};\r\n },\r\n removePresenceListener: () => { /** no-op */ },\r\n supportsRestartByTerminate: () => false\r\n };\r\n\r\n /**\r\n * A {@code JitsiConference} stub passed to the {@link RTC} module.\r\n * @type {Object}\r\n */\r\n const conferenceStub = {\r\n myUserId: () => ''\r\n };\r\n\r\n /**\r\n * Create an instance of {@code RTC} as it is required for peer\r\n * connection creation by {@code JingleSessionPC}. An existing instance\r\n * of {@code RTC} from elsewhere should not be re-used because it is\r\n * a stateful grouping of utilities.\r\n */\r\n this._rtc = new RTC(conferenceStub, {});\r\n\r\n /**\r\n * Add the remote track listener here as {@code JingleSessionPC} has\r\n * {@code TraceablePeerConnection} which uses {@code RTC}'s event\r\n * emitter.\r\n */\r\n this._rtc.addListener(\r\n RTCEvents.REMOTE_TRACK_ADDED,\r\n this._onRemoteStream\r\n );\r\n\r\n const peerConnection = new JingleSessionPC(\r\n undefined, // sid\r\n undefined, // localJid\r\n this._options.peerJid, // remoteJid\r\n connectionStub, // connection\r\n {\r\n offerToReceiveAudio: this._options.receiveAudio,\r\n offerToReceiveVideo: this._options.receiveVideo\r\n }, // mediaConstraints\r\n pcConfigStub, // pcConfig\r\n true, // isP2P\r\n this._options.isInitiator // isInitiator\r\n );\r\n\r\n const signalingLayer = new SignalingLayerImpl();\r\n\r\n signalingLayer.setChatRoom(roomStub);\r\n\r\n /**\r\n * An additional initialize call is necessary to properly set instance\r\n * variable for calling.\r\n */\r\n peerConnection.initialize(roomStub, this._rtc, signalingLayer, configStub);\r\n\r\n return peerConnection;\r\n }\r\n\r\n /**\r\n * Invoked when a connection related issue has been encountered.\r\n *\r\n * @param {string} errorType - The constant indicating the type of the error\r\n * that occured.\r\n * @param {string} details - Optional additional data about the error.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onError(errorType, details = '') {\r\n this._options.onError(this._options.peerJid, errorType, details);\r\n }\r\n\r\n /**\r\n * Callback invoked when the peer connection has received a remote media\r\n * stream.\r\n *\r\n * @param {JitsiRemoteTrack} jitsiRemoteTrack - The remote media stream\r\n * wrapped in {@code JitsiRemoteTrack}.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onRemoteStream(jitsiRemoteTrack) {\r\n this._tracks.push(jitsiRemoteTrack);\r\n\r\n this._options.onRemoteStream(jitsiRemoteTrack);\r\n }\r\n\r\n /**\r\n * Callback invoked when {@code JingleSessionPC} needs to signal a message\r\n * out to the remote peer.\r\n *\r\n * @param {XML} iq - The message to signal out.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onSendMessage(iq) {\r\n this._options.onSendMessage(this._options.peerJid, iq);\r\n }\r\n\r\n /**\r\n * Callback invoked in response to an agreement to start a proxy connection.\r\n * The passed in jingle element should contain an SDP answer to a previously\r\n * sent SDP offer.\r\n *\r\n * @param {Object} $jingle - The jingle element wrapped in jQuery.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onSessionAccept($jingle) {\r\n if (!this._peerConnection) {\r\n logger.error('Received an answer when no peer connection exists.');\r\n\r\n return;\r\n }\r\n\r\n this._peerConnection.setAnswer($jingle);\r\n }\r\n\r\n /**\r\n * Callback invoked in response to a request to start a proxy connection.\r\n * The passed in jingle element should contain an SDP offer.\r\n *\r\n * @param {Object} $jingle - The jingle element wrapped in jQuery.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onSessionInitiate($jingle) {\r\n if (this._peerConnection) {\r\n logger.error('Received an offer when an offer was already sent.');\r\n\r\n return;\r\n }\r\n\r\n this._peerConnection = this._createPeerConnection();\r\n\r\n this._peerConnection.acceptOffer(\r\n $jingle,\r\n () => { /** no-op */ },\r\n () => this._onError(\r\n this._options.peerJid,\r\n ACTIONS.CONNECTION_ERROR,\r\n 'session initiate error'\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * Callback invoked in response to a request to disconnect an active proxy\r\n * connection. Cleans up tracks and the peer connection.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _onSessionTerminate() {\r\n this._tracks.forEach(track => track.dispose());\r\n this._tracks = [];\r\n\r\n if (this._peerConnection) {\r\n this._peerConnection.onTerminated();\r\n }\r\n\r\n if (this._rtc) {\r\n this._rtc.removeListener(\r\n RTCEvents.REMOTE_TRACK_ADDED,\r\n this._onRemoteStream\r\n );\r\n\r\n this._rtc.destroy();\r\n }\r\n }\r\n\r\n /**\r\n * Callback invoked in response to ICE candidates from the remote peer.\r\n * The passed in jingle element should contain an ICE candidate.\r\n *\r\n * @param {Object} $jingle - The jingle element wrapped in jQuery.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onTransportInfo($jingle) {\r\n this._peerConnection.addIceCandidates($jingle);\r\n }\r\n}\r\n","/* globals $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $iq } from 'strophe.js';\r\n\r\nimport { MediaType } from '../../service/RTC/MediaType';\r\nimport { VideoType } from '../../service/RTC/VideoType';\r\nimport RTC from '../RTC/RTC';\r\n\r\nimport ProxyConnectionPC from './ProxyConnectionPC';\r\nimport { ACTIONS } from './constants';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * Instantiates a new ProxyConnectionPC and ensures only one exists at a given\r\n * time. Currently it assumes ProxyConnectionPC is used only for screensharing\r\n * and assumes IQs to be used for communication.\r\n */\r\nexport default class ProxyConnectionService {\r\n /**\r\n * Initializes a new {@code ProxyConnectionService} instance.\r\n *\r\n * @param {Object} options - Values to initialize the instance with.\r\n * @param {boolean} [options.convertVideoToDesktop] - Whether or not proxied video should be returned as a desktop\r\n * stream. Defaults to false.\r\n * @param {Object} [options.pcConfig] - The {@code RTCConfiguration} to use for the WebRTC peer connection.\r\n * @param {JitsiConnection} [options.jitsiConnection] - The {@code JitsiConnection} which will be used to fetch\r\n * TURN credentials for the P2P connection.\r\n * @param {Function} options.onRemoteStream - Callback to invoke when a remote video stream has been received and\r\n * converted to a {@code JitsiLocakTrack}. The {@code JitsiLocakTrack} will be passed in.\r\n * @param {Function} options.onSendMessage - Callback to invoke when a message has to be sent (signaled) out. The\r\n * arguments passed in are the jid to send the message to and the message.\r\n */\r\n constructor(options = {}) {\r\n const {\r\n jitsiConnection,\r\n ...otherOptions\r\n } = options;\r\n\r\n /**\r\n * Holds a reference to the collection of all callbacks.\r\n *\r\n * @type {Object}\r\n */\r\n this._options = {\r\n pcConfig: jitsiConnection && jitsiConnection.xmpp.connection.jingle.p2pIceConfig,\r\n ...otherOptions\r\n };\r\n\r\n /**\r\n * The active instance of {@code ProxyConnectionService}.\r\n *\r\n * @type {ProxyConnectionPC|null}\r\n */\r\n this._peerConnection = null;\r\n\r\n // Bind event handlers so they are only bound once for every instance.\r\n this._onFatalError = this._onFatalError.bind(this);\r\n this._onSendMessage = this._onSendMessage.bind(this);\r\n this._onRemoteStream = this._onRemoteStream.bind(this);\r\n }\r\n\r\n /**\r\n * Parses a message object regarding a proxy connection to create a new\r\n * proxy connection or update and existing connection.\r\n *\r\n * @param {Object} message - A message object regarding establishing or\r\n * updating a proxy connection.\r\n * @param {Object} message.data - An object containing additional message\r\n * details.\r\n * @param {string} message.data.iq - The stringified iq which explains how\r\n * and what to update regarding the proxy connection.\r\n * @param {string} message.from - The message sender's full jid. Used for\r\n * sending replies.\r\n * @returns {void}\r\n */\r\n processMessage(message) {\r\n const peerJid = message.from;\r\n\r\n if (!peerJid) {\r\n return;\r\n }\r\n\r\n // If a proxy connection has already been established and messages come\r\n // from another peer jid then those messages should be replied to with\r\n // a rejection.\r\n if (this._peerConnection\r\n && this._peerConnection.getPeerJid() !== peerJid) {\r\n this._onFatalError(\r\n peerJid,\r\n ACTIONS.CONNECTION_ERROR,\r\n 'rejected'\r\n );\r\n\r\n return;\r\n }\r\n\r\n const iq = this._convertStringToXML(message.data.iq);\r\n const $jingle = iq && iq.find('jingle');\r\n const action = $jingle && $jingle.attr('action');\r\n\r\n if (action === ACTIONS.INITIATE) {\r\n this._peerConnection = this._createPeerConnection(peerJid, {\r\n isInitiator: false,\r\n receiveVideo: true\r\n });\r\n }\r\n\r\n // Truthy check for peer connection added to protect against possibly\r\n // receiving actions before an ACTIONS.INITIATE.\r\n if (this._peerConnection) {\r\n this._peerConnection.processMessage($jingle);\r\n }\r\n\r\n // Take additional steps to ensure the peer connection is cleaned up\r\n // if it is to be closed.\r\n if (action === ACTIONS.CONNECTION_ERROR\r\n || action === ACTIONS.UNAVAILABLE\r\n || action === ACTIONS.TERMINATE) {\r\n this._selfCloseConnection();\r\n }\r\n\r\n return;\r\n }\r\n\r\n /**\r\n * Instantiates and initiates a proxy peer connection.\r\n *\r\n * @param {string} peerJid - The jid of the remote client that should\r\n * receive messages.\r\n * @param {Array} localTracks - Initial media tracks to\r\n * send through to the peer.\r\n * @returns {void}\r\n */\r\n start(peerJid, localTracks = []) {\r\n this._peerConnection = this._createPeerConnection(peerJid, {\r\n isInitiator: true,\r\n receiveVideo: false\r\n });\r\n\r\n this._peerConnection.start(localTracks);\r\n }\r\n\r\n /**\r\n * Terminates any active proxy peer connection.\r\n *\r\n * @returns {void}\r\n */\r\n stop() {\r\n if (this._peerConnection) {\r\n this._peerConnection.stop();\r\n }\r\n\r\n this._peerConnection = null;\r\n }\r\n\r\n /**\r\n * Transforms a stringified xML into a XML wrapped in jQuery.\r\n *\r\n * @param {string} xml - The XML in string form.\r\n * @private\r\n * @returns {Object|null} A jQuery version of the xml. Null will be returned\r\n * if an error is encountered during transformation.\r\n */\r\n _convertStringToXML(xml) {\r\n try {\r\n const xmlDom = new DOMParser().parseFromString(xml, 'text/xml');\r\n\r\n return $(xmlDom);\r\n } catch (e) {\r\n logger.error('Attempted to convert incorrectly formatted xml');\r\n\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Helper for creating an instance of {@code ProxyConnectionPC}.\r\n *\r\n * @param {string} peerJid - The jid of the remote peer with which the\r\n * {@code ProxyConnectionPC} will be established with.\r\n * @param {Object} options - Additional defaults to instantiate the\r\n * {@code ProxyConnectionPC} with. See the constructor of ProxyConnectionPC\r\n * for more details.\r\n * @private\r\n * @returns {ProxyConnectionPC}\r\n */\r\n _createPeerConnection(peerJid, options = {}) {\r\n if (!peerJid) {\r\n throw new Error('Cannot create ProxyConnectionPC without a peer.');\r\n }\r\n\r\n const pcOptions = {\r\n pcConfig: this._options.pcConfig,\r\n onError: this._onFatalError,\r\n onRemoteStream: this._onRemoteStream,\r\n onSendMessage: this._onSendMessage,\r\n peerJid,\r\n ...options\r\n };\r\n\r\n return new ProxyConnectionPC(pcOptions);\r\n }\r\n\r\n /**\r\n * Callback invoked when an error occurs that should cause\r\n * {@code ProxyConnectionPC} to be closed if the peer is currently\r\n * connected. Sends an error message/reply back to the peer.\r\n *\r\n * @param {string} peerJid - The peer jid with which the connection was\r\n * attempted or started, and to which an iq with error details should be\r\n * sent.\r\n * @param {string} errorType - The constant indicating the type of the error\r\n * that occured.\r\n * @param {string} details - Optional additional data about the error.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onFatalError(peerJid, errorType, details = '') {\r\n logger.error(\r\n 'Received a proxy connection error', peerJid, errorType, details);\r\n\r\n const iq = $iq({\r\n to: peerJid,\r\n type: 'set'\r\n })\r\n .c('jingle', {\r\n xmlns: 'urn:xmpp:jingle:1',\r\n action: errorType\r\n })\r\n .c('details')\r\n .t(details)\r\n .up();\r\n\r\n this._onSendMessage(peerJid, iq);\r\n\r\n if (this._peerConnection\r\n && this._peerConnection.getPeerJid() === peerJid) {\r\n this._selfCloseConnection();\r\n }\r\n }\r\n\r\n /**\r\n * Callback invoked when the remote peer of the {@code ProxyConnectionPC}\r\n * has offered a media stream. The stream is converted into a\r\n * {@code JitsiLocalTrack} for local usage if the {@code onRemoteStream}\r\n * callback is defined.\r\n *\r\n * @param {JitsiRemoteTrack} jitsiRemoteTrack - The {@code JitsiRemoteTrack}\r\n * for the peer's media stream.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onRemoteStream(jitsiRemoteTrack) {\r\n if (!this._options.onRemoteStream) {\r\n logger.error('Remote track received without callback.');\r\n jitsiRemoteTrack.dispose();\r\n\r\n return;\r\n }\r\n\r\n const isVideo = jitsiRemoteTrack.isVideoTrack();\r\n let videoType;\r\n\r\n if (isVideo) {\r\n videoType = this._options.convertVideoToDesktop\r\n ? VideoType.DESKTOP : VideoType.CAMERA;\r\n }\r\n\r\n // Grab the webrtc media stream and pipe it through the same processing\r\n // that would occur for a locally obtained media stream.\r\n const mediaStream = jitsiRemoteTrack.getOriginalStream();\r\n const jitsiLocalTracks = RTC.createLocalTracks(\r\n [\r\n {\r\n deviceId:\r\n `proxy:${this._peerConnection.getPeerJid()}`,\r\n mediaType: isVideo ? MediaType.VIDEO : MediaType.AUDIO,\r\n sourceType: 'proxy',\r\n stream: mediaStream,\r\n track: mediaStream.getVideoTracks()[0],\r\n videoType\r\n }\r\n ]);\r\n\r\n this._options.onRemoteStream(jitsiLocalTracks[0]);\r\n }\r\n\r\n /**\r\n * Formats and forwards a message an iq to be sent to a peer jid.\r\n *\r\n * @param {string} peerJid - The jid the iq should be sent to.\r\n * @param {Object} iq - The iq which would be sent to the peer jid.\r\n * @private\r\n * @returns {void}\r\n */\r\n _onSendMessage(peerJid, iq) {\r\n if (!this._options.onSendMessage) {\r\n return;\r\n }\r\n\r\n try {\r\n const stringifiedIq\r\n = new XMLSerializer().serializeToString(iq.nodeTree || iq);\r\n\r\n this._options.onSendMessage(peerJid, { iq: stringifiedIq });\r\n } catch (e) {\r\n logger.error('Attempted to send an incorrectly formatted iq.');\r\n }\r\n }\r\n\r\n /**\r\n * Invoked when preemptively closing the {@code ProxyConnectionPC}.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n _selfCloseConnection() {\r\n this.stop();\r\n\r\n this._options.onConnectionClosed\r\n && this._options.onConnectionClosed();\r\n }\r\n}\r\n","import EventEmitter from 'events';\r\n\r\nimport browser from '../browser';\r\nimport Settings from '../settings/Settings';\r\nimport ScriptUtil from '../util/ScriptUtil';\r\n\r\nimport { CALLSTATS_SCRIPT_URL } from './constants';\r\n\r\nconst PRECALL_TEST_RESULTS = 'preCallTestResults';\r\nconst emitter = new EventEmitter();\r\nlet _initialized = false;\r\nlet api = null;\r\n\r\n/**\r\n * Loads the callstats io script.\r\n *\r\n * @returns {Promise}\r\n */\r\nfunction _loadScript(options) {\r\n if (browser.isReactNative()) {\r\n return;\r\n }\r\n\r\n return new Promise(resolve => {\r\n ScriptUtil.loadScript(\r\n options.callStatsCustomScriptUrl || CALLSTATS_SCRIPT_URL,\r\n /* async */ true,\r\n /* prepend */ true,\r\n /* relativeURL */ undefined,\r\n /* loadCallback */ resolve);\r\n });\r\n}\r\n\r\n/**\r\n * Initializes the callstats lib and registers a callback to be invoked\r\n * when there are 'preCallTestResults'.\r\n *\r\n * @typedef PrecallTestOptions\r\n * @type {Object}\r\n * @property {string} callStatsID - Callstats credentials - the id.\r\n * @property {string} callStatsSecret - Callstats credentials - the secret.\r\n * @property {string} statisticsId - The user name to use when initializing callstats.\r\n * @property {string} statisticsDisplayName - The user display name.\r\n *\r\n * @param { PrecallTestOptions} options - The init options.\r\n * @returns {Promise}\r\n */\r\nfunction _initialize(options) {\r\n return new Promise((resolve, reject) => {\r\n const appId = options.callStatsID;\r\n const appSecret = options.callStatsSecret;\r\n const userId = options.statisticsId || options.statisticsDisplayName || Settings.callStatsUserName;\r\n\r\n api.initialize(appId, appSecret, userId, (status, message) => {\r\n if (status === 'success') {\r\n api.on(PRECALL_TEST_RESULTS, (...args) => {\r\n emitter.emit(PRECALL_TEST_RESULTS, ...args);\r\n });\r\n _initialized = true;\r\n resolve();\r\n } else {\r\n reject({\r\n status,\r\n message\r\n });\r\n }\r\n }, null, { disablePrecalltest: true });\r\n });\r\n}\r\n\r\n/**\r\n * Loads the callstats script and initializes the library.\r\n *\r\n * @param {Function} onResult - The callback to be invoked when results are received.\r\n * @returns {Promise}\r\n */\r\nexport async function init(options) {\r\n if (_initialized) {\r\n throw new Error('Precall Test already initialized');\r\n }\r\n\r\n const { callStatsID, callStatsSecret, disableThirdPartyRequests } = options;\r\n\r\n if (!callStatsID || !callStatsSecret || disableThirdPartyRequests) {\r\n throw new Error('Callstats is disabled');\r\n }\r\n\r\n await _loadScript(options);\r\n // eslint-disable-next-line new-cap\r\n api = new window.callstats();\r\n\r\n return _initialize(options);\r\n}\r\n\r\n/**\r\n * Executes a pre call test.\r\n *\r\n * @typedef PrecallTestResults\r\n * @type {Object}\r\n * @property {boolean} mediaConnectivity - If there is media connectivity or not.\r\n * @property {number} throughput - The average throughput.\r\n * @property {number} fractionalLoss - The packet loss.\r\n * @property {number} rtt - The round trip time.\r\n * @property {string} provider - It is usually 'callstats'.\r\n *\r\n * @returns {Promise<{PrecallTestResults}>}\r\n */\r\nexport function execute() {\r\n if (!_initialized) {\r\n return Promise.reject('uninitialized');\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n emitter.on(PRECALL_TEST_RESULTS, (status, payload) => {\r\n if (status === 'success') {\r\n resolve(payload);\r\n } else {\r\n reject({\r\n status,\r\n payload\r\n });\r\n }\r\n\r\n });\r\n\r\n api.makePrecallTest();\r\n });\r\n}\r\n\r\nexport default {\r\n init,\r\n execute\r\n};\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport { createAudioContext } from './WebAudioUtils';\r\n\r\nconst logger = getLogger(__filename);\r\n\r\n/**\r\n * The AudioMixer, as the name implies, mixes a number of MediaStreams containing audio tracks into a single\r\n * MediaStream.\r\n */\r\nexport default class AudioMixer {\r\n /**\r\n * Create AudioMixer instance.\r\n */\r\n constructor() {\r\n this._started = false;\r\n this._streamsToMix = [];\r\n this._streamMSSArray = [];\r\n }\r\n\r\n /**\r\n * Add audio MediaStream to be mixed, if the stream doesn't contain any audio tracks it will be ignored.\r\n *\r\n * @param {MediaStream} stream - MediaStream to be mixed.\r\n */\r\n addMediaStream(stream) {\r\n if (!stream.getAudioTracks()) {\r\n logger.warn('Added MediaStream doesn\\'t contain audio tracks.');\r\n }\r\n\r\n this._streamsToMix.push(stream);\r\n }\r\n\r\n /**\r\n * At this point a WebAudio ChannelMergerNode is created and and the two associated MediaStreams are connected to\r\n * it; the resulting mixed MediaStream is returned.\r\n *\r\n * @returns {MediaStream} - MediaStream containing added streams mixed together, or null if no MediaStream\r\n * is added.\r\n */\r\n start() {\r\n // If the mixer was already started just return the existing mixed stream.\r\n if (this._started) {\r\n return this._mixedMSD.stream;\r\n }\r\n\r\n this._audioContext = createAudioContext();\r\n\r\n if (!this._streamsToMix.length) {\r\n logger.warn('No MediaStream\\'s added to AudioMixer, nothing will happen.');\r\n\r\n return null;\r\n }\r\n\r\n this._started = true;\r\n\r\n this._mixedMSD = this._audioContext.createMediaStreamDestination();\r\n\r\n for (const stream of this._streamsToMix) {\r\n const streamMSS = this._audioContext.createMediaStreamSource(stream);\r\n\r\n streamMSS.connect(this._mixedMSD);\r\n\r\n // Maintain a list of MediaStreamAudioSourceNode so we can disconnect them on reset.\r\n this._streamMSSArray.push(streamMSS);\r\n }\r\n\r\n return this._mixedMSD.stream;\r\n }\r\n\r\n /**\r\n * Disconnect MediaStreamAudioSourceNode and clear references.\r\n *\r\n * @returns {void}\r\n */\r\n reset() {\r\n this._started = false;\r\n this._streamsToMix = [];\r\n\r\n // Clean up created MediaStreamAudioSourceNode.\r\n for (const streamMSS of this._streamMSSArray) {\r\n streamMSS.disconnect();\r\n }\r\n\r\n this._streamMSSArray = [];\r\n\r\n if (this._audioContext) {\r\n this._audioContext = undefined;\r\n }\r\n }\r\n}\r\n","import Logger from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceErrors from './JitsiConferenceErrors';\r\nimport * as JitsiConferenceEvents from './JitsiConferenceEvents';\r\nimport JitsiConnection from './JitsiConnection';\r\nimport * as JitsiConnectionErrors from './JitsiConnectionErrors';\r\nimport * as JitsiConnectionEvents from './JitsiConnectionEvents';\r\nimport JitsiMediaDevices from './JitsiMediaDevices';\r\nimport * as JitsiMediaDevicesEvents from './JitsiMediaDevicesEvents';\r\nimport JitsiTrackError from './JitsiTrackError';\r\nimport * as JitsiTrackErrors from './JitsiTrackErrors';\r\nimport * as JitsiTrackEvents from './JitsiTrackEvents';\r\nimport * as JitsiTranscriptionStatus from './JitsiTranscriptionStatus';\r\nimport RTC from './modules/RTC/RTC';\r\nimport browser from './modules/browser';\r\nimport NetworkInfo from './modules/connectivity/NetworkInfo';\r\nimport { ParticipantConnectionStatus }\r\n from './modules/connectivity/ParticipantConnectionStatus';\r\nimport { TrackStreamingStatus } from './modules/connectivity/TrackStreamingStatus';\r\nimport getActiveAudioDevice from './modules/detection/ActiveDeviceDetector';\r\nimport * as DetectionEvents from './modules/detection/DetectionEvents';\r\nimport TrackVADEmitter from './modules/detection/TrackVADEmitter';\r\nimport FeatureFlags from './modules/flags/FeatureFlags';\r\nimport ProxyConnectionService\r\n from './modules/proxyconnection/ProxyConnectionService';\r\nimport recordingConstants from './modules/recording/recordingConstants';\r\nimport Settings from './modules/settings/Settings';\r\nimport LocalStatsCollector from './modules/statistics/LocalStatsCollector';\r\nimport precallTest from './modules/statistics/PrecallTest';\r\nimport Statistics from './modules/statistics/statistics';\r\nimport AuthUtil from './modules/util/AuthUtil';\r\nimport GlobalOnErrorHandler from './modules/util/GlobalOnErrorHandler';\r\nimport ScriptUtil from './modules/util/ScriptUtil';\r\nimport * as VideoSIPGWConstants from './modules/videosipgw/VideoSIPGWConstants';\r\nimport AudioMixer from './modules/webaudio/AudioMixer';\r\nimport { MediaType } from './service/RTC/MediaType';\r\nimport * as ConnectionQualityEvents\r\n from './service/connectivity/ConnectionQualityEvents';\r\nimport * as E2ePingEvents from './service/e2eping/E2ePingEvents';\r\nimport { createGetUserMediaEvent } from './service/statistics/AnalyticsEvents';\r\n\r\nconst logger = Logger.getLogger(__filename);\r\n\r\n/**\r\n * The amount of time to wait until firing\r\n * {@link JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN} event.\r\n */\r\nconst USER_MEDIA_SLOW_PROMISE_TIMEOUT = 1000;\r\n\r\n/**\r\n * Extracts from an 'options' objects with a specific format (TODO what IS the\r\n * format?) the attributes which are to be logged in analytics events.\r\n *\r\n * @param options gum options (???)\r\n * @returns {*} the attributes to attach to analytics events.\r\n */\r\nfunction getAnalyticsAttributesFromOptions(options) {\r\n const attributes = {\r\n 'audio_requested':\r\n options.devices.includes('audio'),\r\n 'video_requested':\r\n options.devices.includes('video'),\r\n 'screen_sharing_requested':\r\n options.devices.includes('desktop')\r\n };\r\n\r\n if (attributes.video_requested) {\r\n attributes.resolution = options.resolution;\r\n }\r\n\r\n return attributes;\r\n}\r\n\r\n/**\r\n * Tries to deal with the following problem: {@code JitsiMeetJS} is not only\r\n * this module, it's also a global (i.e. attached to {@code window}) namespace\r\n * for all globals of the projects in the Jitsi Meet family. If lib-jitsi-meet\r\n * is loaded through an HTML {@code script} tag, {@code JitsiMeetJS} will\r\n * automatically be attached to {@code window} by webpack. Unfortunately,\r\n * webpack's source code does not check whether the global variable has already\r\n * been assigned and overwrites it. Which is OK for the module\r\n * {@code JitsiMeetJS} but is not OK for the namespace {@code JitsiMeetJS}\r\n * because it may already contain the values of other projects in the Jitsi Meet\r\n * family. The solution offered here works around webpack by merging all\r\n * existing values of the namespace {@code JitsiMeetJS} into the module\r\n * {@code JitsiMeetJS}.\r\n *\r\n * @param {Object} module - The module {@code JitsiMeetJS} (which will be\r\n * exported and may be attached to {@code window} by webpack later on).\r\n * @private\r\n * @returns {Object} - A {@code JitsiMeetJS} module which contains all existing\r\n * value of the namespace {@code JitsiMeetJS} (if any).\r\n */\r\nfunction _mergeNamespaceAndModule(module) {\r\n return (\r\n typeof window.JitsiMeetJS === 'object'\r\n ? Object.assign({}, window.JitsiMeetJS, module)\r\n : module);\r\n}\r\n\r\n/**\r\n * The public API of the Jitsi Meet library (a.k.a. {@code JitsiMeetJS}).\r\n */\r\nexport default _mergeNamespaceAndModule({\r\n\r\n version: '{#COMMIT_HASH#}',\r\n\r\n JitsiConnection,\r\n\r\n /**\r\n * {@code ProxyConnectionService} is used to connect a remote peer to a\r\n * local Jitsi participant without going through a Jitsi conference. It is\r\n * currently used for room integration development, specifically wireless\r\n * screensharing. Its API is experimental and will likely change; usage of\r\n * it is advised against.\r\n */\r\n ProxyConnectionService,\r\n\r\n constants: {\r\n participantConnectionStatus: ParticipantConnectionStatus,\r\n recording: recordingConstants,\r\n sipVideoGW: VideoSIPGWConstants,\r\n transcriptionStatus: JitsiTranscriptionStatus,\r\n trackStreamingStatus: TrackStreamingStatus\r\n },\r\n events: {\r\n conference: JitsiConferenceEvents,\r\n connection: JitsiConnectionEvents,\r\n detection: DetectionEvents,\r\n track: JitsiTrackEvents,\r\n mediaDevices: JitsiMediaDevicesEvents,\r\n connectionQuality: ConnectionQualityEvents,\r\n e2eping: E2ePingEvents\r\n },\r\n errors: {\r\n conference: JitsiConferenceErrors,\r\n connection: JitsiConnectionErrors,\r\n track: JitsiTrackErrors\r\n },\r\n errorTypes: {\r\n JitsiTrackError\r\n },\r\n logLevels: Logger.levels,\r\n mediaDevices: JitsiMediaDevices,\r\n analytics: Statistics.analytics,\r\n init(options = {}) {\r\n Settings.init(options.externalStorage);\r\n Statistics.init(options);\r\n\r\n // Multi-stream is supported only on endpoints running in Unified plan mode and the flag to disable unified\r\n // plan also needs to be taken into consideration.\r\n if (typeof options.enableUnifiedOnChrome !== 'undefined' && options.flags) {\r\n options.flags.enableUnifiedOnChrome = options.enableUnifiedOnChrome;\r\n }\r\n\r\n // Configure the feature flags.\r\n FeatureFlags.init(options.flags || { });\r\n\r\n // Initialize global window.connectionTimes\r\n // FIXME do not use 'window'\r\n if (!window.connectionTimes) {\r\n window.connectionTimes = {};\r\n }\r\n\r\n if (options.enableAnalyticsLogging !== true) {\r\n logger.warn('Analytics disabled, disposing.');\r\n this.analytics.dispose();\r\n }\r\n\r\n if (options.enableWindowOnErrorHandler) {\r\n GlobalOnErrorHandler.addHandler(\r\n this.getGlobalOnErrorHandler.bind(this));\r\n }\r\n\r\n if (this.version) {\r\n const logObject = {\r\n id: 'component_version',\r\n component: 'lib-jitsi-meet',\r\n version: this.version\r\n };\r\n\r\n Statistics.sendLog(JSON.stringify(logObject));\r\n }\r\n\r\n return RTC.init(options);\r\n },\r\n\r\n /**\r\n * Returns whether the desktop sharing is enabled or not.\r\n *\r\n * @returns {boolean}\r\n */\r\n isDesktopSharingEnabled() {\r\n return RTC.isDesktopSharingEnabled();\r\n },\r\n\r\n /**\r\n * Returns whether the current execution environment supports WebRTC (for\r\n * use within this library).\r\n *\r\n * @returns {boolean} {@code true} if WebRTC is supported in the current\r\n * execution environment (for use within this library); {@code false},\r\n * otherwise.\r\n */\r\n isWebRtcSupported() {\r\n return RTC.isWebRtcSupported();\r\n },\r\n\r\n setLogLevel(level) {\r\n Logger.setLogLevel(level);\r\n },\r\n\r\n /**\r\n * Sets the log level to the Logger instance with given id.\r\n *\r\n * @param {Logger.levels} level the logging level to be set\r\n * @param {string} id the logger id to which new logging level will be set.\r\n * Usually it's the name of the JavaScript source file including the path\r\n * ex. \"modules/xmpp/ChatRoom.js\"\r\n */\r\n setLogLevelById(level, id) {\r\n Logger.setLogLevelById(level, id);\r\n },\r\n\r\n /**\r\n * Registers new global logger transport to the library logging framework.\r\n *\r\n * @param globalTransport\r\n * @see Logger.addGlobalTransport\r\n */\r\n addGlobalLogTransport(globalTransport) {\r\n Logger.addGlobalTransport(globalTransport);\r\n },\r\n\r\n /**\r\n * Removes global logging transport from the library logging framework.\r\n *\r\n * @param globalTransport\r\n * @see Logger.removeGlobalTransport\r\n */\r\n removeGlobalLogTransport(globalTransport) {\r\n Logger.removeGlobalTransport(globalTransport);\r\n },\r\n\r\n /**\r\n * Sets global options which will be used by all loggers. Changing these\r\n * works even after other loggers are created.\r\n *\r\n * @param options\r\n * @see Logger.setGlobalOptions\r\n */\r\n setGlobalLogOptions(options) {\r\n Logger.setGlobalOptions(options);\r\n },\r\n\r\n /**\r\n * Creates the media tracks and returns them trough the callback.\r\n *\r\n * @param options Object with properties / settings specifying the tracks\r\n * which should be created. should be created or some additional\r\n * configurations about resolution for example.\r\n * @param {Array} options.effects optional effects array for the track\r\n * @param {boolean} options.firePermissionPromptIsShownEvent - if event\r\n * JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN should be fired\r\n * @param {boolean} options.fireSlowPromiseEvent - if event\r\n * JitsiMediaDevicesEvents.USER_MEDIA_SLOW_PROMISE_TIMEOUT should be fired\r\n * @param {Array} options.devices the devices that will be requested\r\n * @param {string} options.resolution resolution constraints\r\n * @param {string} options.cameraDeviceId\r\n * @param {string} options.micDeviceId\r\n * @param {intiger} interval - the interval (in ms) for\r\n * checking whether the desktop sharing extension is installed or not\r\n * @param {Function} checkAgain - returns boolean. While checkAgain()==true\r\n * createLocalTracks will wait and check on every \"interval\" ms for the\r\n * extension. If the desktop extension is not install and checkAgain()==true\r\n * createLocalTracks will finish with rejected Promise.\r\n * @param {Function} listener - The listener will be called to notify the\r\n * user of lib-jitsi-meet that createLocalTracks is starting external\r\n * extension installation process.\r\n * NOTE: If the inline installation process is not possible and external\r\n * installation is enabled the listener property will be called to notify\r\n * the start of external installation process. After that createLocalTracks\r\n * will start to check for the extension on every interval ms until the\r\n * plugin is installed or until checkAgain return false. If the extension\r\n * is found createLocalTracks will try to get the desktop sharing track and\r\n * will finish the execution. If checkAgain returns false, createLocalTracks\r\n * will finish the execution with rejected Promise.\r\n *\r\n * @deprecated old firePermissionPromptIsShownEvent\r\n * @returns {Promise.<{Array.}, JitsiConferenceError>} A promise\r\n * that returns an array of created JitsiTracks if resolved, or a\r\n * JitsiConferenceError if rejected.\r\n */\r\n createLocalTracks(options = {}, oldfirePermissionPromptIsShownEvent) {\r\n let promiseFulfilled = false;\r\n\r\n const { firePermissionPromptIsShownEvent, fireSlowPromiseEvent, ...restOptions } = options;\r\n const firePermissionPrompt = firePermissionPromptIsShownEvent || oldfirePermissionPromptIsShownEvent;\r\n\r\n if (firePermissionPrompt && !RTC.arePermissionsGrantedForAvailableDevices()) {\r\n JitsiMediaDevices.emitEvent(\r\n JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,\r\n browser.getName());\r\n } else if (fireSlowPromiseEvent) {\r\n window.setTimeout(() => {\r\n if (!promiseFulfilled) {\r\n JitsiMediaDevices.emitEvent(JitsiMediaDevicesEvents.SLOW_GET_USER_MEDIA);\r\n }\r\n }, USER_MEDIA_SLOW_PROMISE_TIMEOUT);\r\n }\r\n\r\n if (!window.connectionTimes) {\r\n window.connectionTimes = {};\r\n }\r\n window.connectionTimes['obtainPermissions.start']\r\n = window.performance.now();\r\n\r\n return RTC.obtainAudioAndVideoPermissions(restOptions)\r\n .then(tracks => {\r\n promiseFulfilled = true;\r\n\r\n window.connectionTimes['obtainPermissions.end']\r\n = window.performance.now();\r\n\r\n Statistics.sendAnalytics(\r\n createGetUserMediaEvent(\r\n 'success',\r\n getAnalyticsAttributesFromOptions(restOptions)));\r\n\r\n if (!RTC.options.disableAudioLevels) {\r\n for (let i = 0; i < tracks.length; i++) {\r\n const track = tracks[i];\r\n const mStream = track.getOriginalStream();\r\n\r\n if (track.getType() === MediaType.AUDIO) {\r\n Statistics.startLocalStats(mStream,\r\n track.setAudioLevel.bind(track));\r\n track.addEventListener(\r\n JitsiTrackEvents.LOCAL_TRACK_STOPPED,\r\n () => {\r\n Statistics.stopLocalStats(mStream);\r\n });\r\n }\r\n }\r\n }\r\n\r\n // set real device ids\r\n const currentlyAvailableMediaDevices\r\n = RTC.getCurrentlyAvailableMediaDevices();\r\n\r\n if (currentlyAvailableMediaDevices) {\r\n for (let i = 0; i < tracks.length; i++) {\r\n const track = tracks[i];\r\n\r\n track._setRealDeviceIdFromDeviceList(\r\n currentlyAvailableMediaDevices);\r\n }\r\n }\r\n\r\n // set the contentHint to \"detail\" for desktop tracks\r\n // eslint-disable-next-line prefer-const\r\n for (const track of tracks) {\r\n if (track.type === MediaType.VIDEO\r\n && track.videoType === 'desktop') {\r\n this.setVideoTrackContentHints(track.track, 'detail');\r\n }\r\n }\r\n\r\n return tracks;\r\n })\r\n .catch(error => {\r\n promiseFulfilled = true;\r\n\r\n if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {\r\n // User cancelled action is not really an error, so only\r\n // log it as an event to avoid having conference classified\r\n // as partially failed\r\n const logObject = {\r\n id: 'screensharing_user_canceled',\r\n message: error.message\r\n };\r\n\r\n Statistics.sendLog(JSON.stringify(logObject));\r\n\r\n Statistics.sendAnalytics(\r\n createGetUserMediaEvent(\r\n 'warning',\r\n {\r\n reason: 'extension install user canceled'\r\n }));\r\n } else if (error.name === JitsiTrackErrors.NOT_FOUND) {\r\n // logs not found devices with just application log to cs\r\n const logObject = {\r\n id: 'usermedia_missing_device',\r\n status: error.gum.devices\r\n };\r\n\r\n Statistics.sendLog(JSON.stringify(logObject));\r\n\r\n const attributes\r\n = getAnalyticsAttributesFromOptions(options);\r\n\r\n attributes.reason = 'device not found';\r\n attributes.devices = error.gum.devices.join('.');\r\n Statistics.sendAnalytics(\r\n createGetUserMediaEvent('error', attributes));\r\n } else {\r\n // Report gUM failed to the stats\r\n Statistics.sendGetUserMediaFailed(error);\r\n\r\n const attributes\r\n = getAnalyticsAttributesFromOptions(options);\r\n\r\n attributes.reason = error.name;\r\n Statistics.sendAnalytics(\r\n createGetUserMediaEvent('error', attributes));\r\n }\r\n\r\n window.connectionTimes['obtainPermissions.end']\r\n = window.performance.now();\r\n\r\n return Promise.reject(error);\r\n });\r\n },\r\n\r\n /**\r\n * Create a TrackVADEmitter service that connects an audio track to an VAD (voice activity detection) processor in\r\n * order to obtain VAD scores for individual PCM audio samples.\r\n * @param {string} localAudioDeviceId - The target local audio device.\r\n * @param {number} sampleRate - Sample rate at which the emitter will operate. Possible values 256, 512, 1024,\r\n * 4096, 8192, 16384. Passing other values will default to closes neighbor.\r\n * I.e. Providing a value of 4096 means that the emitter will process 4096 PCM samples at a time, higher values mean\r\n * longer calls, lowers values mean more calls but shorter.\r\n * @param {Object} vadProcessor - VAD Processors that does the actual compute on a PCM sample.The processor needs\r\n * to implement the following functions:\r\n * - getSampleLength() - Returns the sample size accepted by calculateAudioFrameVAD.\r\n * - getRequiredPCMFrequency() - Returns the PCM frequency at which the processor operates.\r\n * i.e. (16KHz, 44.1 KHz etc.)\r\n * - calculateAudioFrameVAD(pcmSample) - Process a 32 float pcm sample of getSampleLength size.\r\n * @returns {Promise}\r\n */\r\n createTrackVADEmitter(localAudioDeviceId, sampleRate, vadProcessor) {\r\n return TrackVADEmitter.create(localAudioDeviceId, sampleRate, vadProcessor);\r\n },\r\n\r\n /**\r\n * Create AudioMixer, which is essentially a wrapper over web audio ChannelMergerNode. It essentially allows the\r\n * user to mix multiple MediaStreams into a single one.\r\n *\r\n * @returns {AudioMixer}\r\n */\r\n createAudioMixer() {\r\n return new AudioMixer();\r\n },\r\n\r\n /**\r\n * Go through all audio devices on the system and return one that is active, i.e. has audio signal.\r\n *\r\n * @returns Promise - Object containing information about the found device.\r\n */\r\n getActiveAudioDevice() {\r\n return getActiveAudioDevice();\r\n },\r\n\r\n /**\r\n * Checks if its possible to enumerate available cameras/microphones.\r\n *\r\n * @returns {Promise} a Promise which will be resolved only once\r\n * the WebRTC stack is ready, either with true if the device listing is\r\n * available available or with false otherwise.\r\n * @deprecated use JitsiMeetJS.mediaDevices.isDeviceListAvailable instead\r\n */\r\n isDeviceListAvailable() {\r\n logger.warn('This method is deprecated, use '\r\n + 'JitsiMeetJS.mediaDevices.isDeviceListAvailable instead');\r\n\r\n return this.mediaDevices.isDeviceListAvailable();\r\n },\r\n\r\n /**\r\n * Returns true if changing the input (camera / microphone) or output\r\n * (audio) device is supported and false if not.\r\n *\r\n * @param {string} [deviceType] - type of device to change. Default is\r\n * {@code undefined} or 'input', 'output' - for audio output device change.\r\n * @returns {boolean} {@code true} if available; {@code false}, otherwise.\r\n * @deprecated use JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead\r\n */\r\n isDeviceChangeAvailable(deviceType) {\r\n logger.warn('This method is deprecated, use '\r\n + 'JitsiMeetJS.mediaDevices.isDeviceChangeAvailable instead');\r\n\r\n return this.mediaDevices.isDeviceChangeAvailable(deviceType);\r\n },\r\n\r\n\r\n /**\r\n * Checks if the current environment supports having multiple audio\r\n * input devices in use simultaneously.\r\n *\r\n * @returns {boolean} True if multiple audio input devices can be used.\r\n */\r\n isMultipleAudioInputSupported() {\r\n return this.mediaDevices.isMultipleAudioInputSupported();\r\n },\r\n\r\n /**\r\n * Checks if local tracks can collect stats and collection is enabled.\r\n *\r\n * @param {boolean} True if stats are being collected for local tracks.\r\n */\r\n isCollectingLocalStats() {\r\n return Statistics.audioLevelsEnabled\r\n && LocalStatsCollector.isLocalStatsSupported();\r\n },\r\n\r\n /**\r\n * Executes callback with list of media devices connected.\r\n *\r\n * @param {function} callback\r\n * @deprecated use JitsiMeetJS.mediaDevices.enumerateDevices instead\r\n */\r\n enumerateDevices(callback) {\r\n logger.warn('This method is deprecated, use '\r\n + 'JitsiMeetJS.mediaDevices.enumerateDevices instead');\r\n this.mediaDevices.enumerateDevices(callback);\r\n },\r\n\r\n /* eslint-disable max-params */\r\n\r\n /**\r\n * @returns function that can be used to be attached to window.onerror and\r\n * if options.enableWindowOnErrorHandler is enabled returns\r\n * the function used by the lib.\r\n * (function(message, source, lineno, colno, error)).\r\n */\r\n getGlobalOnErrorHandler(message, source, lineno, colno, error) {\r\n logger.error(\r\n `UnhandledError: ${message}`,\r\n `Script: ${source}`,\r\n `Line: ${lineno}`,\r\n `Column: ${colno}`,\r\n 'StackTrace: ', error);\r\n Statistics.reportGlobalError(error);\r\n },\r\n\r\n /**\r\n * Informs lib-jitsi-meet about the current network status.\r\n *\r\n * @param {boolean} isOnline - {@code true} if the internet connectivity is online or {@code false}\r\n * otherwise.\r\n */\r\n setNetworkInfo({ isOnline }) {\r\n NetworkInfo.updateNetworkInfo({ isOnline });\r\n },\r\n\r\n /**\r\n * Set the contentHint on the transmitted stream track to indicate\r\n * charaterstics in the video stream, which informs PeerConnection\r\n * on how to encode the track (to prefer motion or individual frame detail)\r\n * @param {MediaStreamTrack} track - the track that is transmitted\r\n * @param {String} hint - contentHint value that needs to be set on the track\r\n */\r\n setVideoTrackContentHints(track, hint) {\r\n if ('contentHint' in track) {\r\n track.contentHint = hint;\r\n if (track.contentHint !== hint) {\r\n logger.debug('Invalid video track contentHint');\r\n }\r\n } else {\r\n logger.debug('MediaStreamTrack contentHint attribute not supported');\r\n }\r\n },\r\n\r\n precallTest,\r\n\r\n /* eslint-enable max-params */\r\n\r\n /**\r\n * Represents a hub/namespace for utility functionality which may be of\r\n * interest to lib-jitsi-meet clients.\r\n */\r\n util: {\r\n AuthUtil,\r\n ScriptUtil,\r\n browser\r\n }\r\n});\r\n","export default {\r\n error: {\r\n BUSY: 'busy',\r\n ERROR: 'error',\r\n RESOURCE_CONSTRAINT: 'resource-constraint',\r\n UNEXPECTED_REQUEST: 'unexpected-request',\r\n SERVICE_UNAVAILABLE: 'service-unavailable'\r\n },\r\n mode: {\r\n FILE: 'file',\r\n STREAM: 'stream'\r\n },\r\n status: {\r\n OFF: 'off',\r\n ON: 'on',\r\n PENDING: 'pending'\r\n }\r\n};\r\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","__webpack_require__.amdO = {};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = (module) => {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","// startup\n// Load entry module and return exports\n// This entry module used 'module' so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(4449);\n"],"names":["root","factory","exports","module","define","amd","self","constructor","userId","displayName","isLocalStats","this","_userId","setDisplayName","_isLocalStats","setDominantSpeaker","totalDominantSpeakerTime","_dominantSpeakerStart","_hasLeft","_faceExpressions","happy","neutral","surprised","angry","fearful","disgusted","sad","getUserId","getDisplayName","newName","isDominantSpeaker","isNowDominantSpeaker","Date","now","timeElapsed","getTotalDominantSpeakerTime","total","hasLeft","markAsHasLeft","getFaceExpressions","setFaceExpressions","faceExpressions","addFaceExpression","faceExpression","duration","getTokenAuthUrl","urlPattern","roomName","roleUpgrade","replace","EventEmitterForwarder","src","dest","addListener","emit","Error","prototype","forward","args","srcEvent","Function","bind","apply","handlers","oldOnErrorHandler","window","onerror","oldOnUnhandledRejection","onunhandledrejection","forEach","handler","event","reason","GlobalOnErrorHandler","addHandler","push","callErrorHandler","error","errHandler","callUnhandledRejectionHandler","randomInt","min","max","Math","floor","random","randomElement","arr","length","RandomUtil","randomHexDigit","randomHexString","len","ret","randomAlphanumStr","result","i","currentExecutingScript","require","ScriptUtil","loadScript","async","prepend","relativeURL","loadCallback","errorCallback","d","document","tagName","script","createElement","referenceNode","getElementsByTagName","scriptEl","scriptSrc","baseScriptSrc","substring","lastIndexOf","onload","parentNode","insertBefore","appendChild","integerHash","string","char","hash","charCodeAt","pow","abs","names","generateUsername","Logger","LogCollector","logStorage","options","stringifyObjects","storeInterval","maxEntryLength","Object","keys","levels","logLevel","_log","arguments","storeLogsIntervalID","queue","totalLen","outputCache","stringify","someObject","JSON","formatLogMessage","msg","arg","ERROR","timestamp","prevMessage","prevMessageText","text","count","_flush","start","_reschedulePublishInterval","clearTimeout","setTimeout","flush","force","reschedule","isReady","cachedQueue","storeLogs","stop","consoleTransport","console","globalTransports","addGlobalTransport","transport","indexOf","removeGlobalTransport","transportIdx","splice","globalOptions","getCallerInfo","callerInfo","methodName","fileLocation","line","column","stack","split","m","match","substr","log","logger","level","Array","slice","call","disableCallerInfo","transports","concat","t","l","logPrefixes","toISOString","id","fullLogParts","methods","setGlobalOptions","setLevel","TRACE","DEBUG","INFO","LOG","WARN","idLoggers","loggers","curLevel","getLogger","setLogLevelById","setLogLevel","grammar","v","name","reg","o","format","s","u","e","p","z","r","c","b","a","encoding","rate","address","subtype","direction","config","sessionConfig","str","raddr","tcptype","generation","attribute","value","maxMessageSize","params","RegExp","dir2","clksrcExt","mediaClockValue","rateNumerator","rateDenominator","key","obj","parser","writer","write","parse","parseParams","parseFmtpConfig","parsePayloads","parseRemoteCandidates","parseImageAttributes","parseSimulcastStreamList","toIntIfInt","String","Number","parseReg","location","content","needsBlank","keyLocation","rawName","attachProperties","validLine","test","sdp","session","media","filter","type","rtp","fmtp","j","paramReducer","acc","expr","undefined","reduce","toString","map","candidates","parts","component","ip","port","item","stream","scid","paused","formatRegExp","formatStr","x","makeLine","n","defaultOuterOrder","defaultInnerOrder","opts","version","mLine","payloads","outerOrder","innerOrder","el","join","transform","transformUtils","parseSsrcs","writeSsrcs","getSsrcAttribute","ssrc","attributeName","ssrcs","ssrcInfo","Simulcast","numOfLayers","ssrcCache","processVideo","action","isArray","validateDescription","desc","clearSsrcCache","setSsrcCache","_parseSimLayers","simGroup","ssrcGroups","find","group","semantics","ssrcStr","parseInt","_buildNewToOldSsrcMap","newSsrcList","oldSsrcList","ssrcMap","newSsrc","oldSsrc","_fillInSourceDataFromCache","newSimSsrcs","newMsid","newCname","ssrcsToReplace","ssrcsToAdd","values","_generateSourceData","primarySsrc","addAssociatedStream","primarySsrcCname","primarySsrcMsid","usesUnifiedPlan","msid","simSsrcs","simSsrc","_restoreSimulcast","numSsrcs","index","array","numGroups","fidGroup","mungeRemoteDescription","enableConferenceFlag","explodeRemoteSimulcast","sources","order","simulcastSsrcs","cname","relatedGroup","relatedSsrcs","relatedSSRC","simulcastGroup","info","nuke","implodeRemoteSimulcast","invalid","some","assertGoogConference","xGoogleFlag","removeGoogConference","RTCSessionDescription","mungeLocalDescription","source","byteLength","b64","lens","getLens","validLen","placeHoldersLen","toByteArray","tmp","Arr","_byteLength","curByte","revLookup","fromByteArray","uint8","extraBytes","maxChunkLength","len2","encodeChunk","lookup","Uint8Array","code","end","num","output","defineProperty","enumerable","get","Symbol","toStringTag","__esModule","create","default","hasOwnProperty","getFirstMatch","getSecondMatch","matchAndReturnConst","getWindowsVersionName","getMacOSVersionName","getAndroidVersionName","getVersionPrecision","compareVersions","reverse","getBrowserAlias","BROWSER_ALIASES_MAP","getBrowserTypeByAlias","BROWSER_MAP","ENGINE_MAP","OS_MAP","PLATFORMS_MAP","Bada","BlackBerry","Chrome","Chromium","Epiphany","Firefox","Focus","Generic","Googlebot","Maxthon","Opera","PhantomJS","Puffin","QupZilla","QQ","QQLite","Safari","Sailfish","SeaMonkey","Sleipnir","Swing","Tizen","Vivaldi","WeChat","Roku","amazon_silk","android","bada","blackberry","chrome","chromium","epiphany","firefox","focus","generic","googlebot","google_search","ie","k_meleon","maxthon","edge","mz","naver","opera","opera_coast","phantomjs","puffin","qupzilla","qq","qqlite","safari","sailfish","samsung_internet","seamonkey","sleipnir","swing","tizen","uc","vivaldi","webos","wechat","yandex","tablet","mobile","desktop","tv","WindowsPhone","Windows","MacOS","iOS","Android","WebOS","Linux","ChromeOS","PlayStation4","EdgeHTML","Blink","Trident","Presto","Gecko","WebKit","configurable","writable","getParser","getResult","_ua","parsedResult","getUA","parseBrowser","browser","describe","getBrowser","getBrowserName","toLowerCase","getBrowserVersion","getOS","os","parseOS","getOSName","getOSVersion","getPlatform","platform","parsePlatform","getPlatformType","getEngine","engine","parseEngine","getEngineName","assign","satisfies","isOS","isPlatform","f","isBrowser","compareVersion","isEngine","is","search","versionName","vendor","model","scriptReadyRegex","fullPageUrl","href","pageUrl","scripts","supportsScriptReadyState","isNotOpera","hasNativeCurrentScriptAccessor","stackTraceLimit","Infinity","hasStackBeforeThrowing","hasStackAfterThrowing","getScriptUrlFromStack","skipStackDepth","matches","url","ignoreMessage","round","_nearestExecutingScript","eligibleScripts","readyState","err","getScriptFromUrl","hasAttribute","getSoleInlineScript","currentScript","thrownErr","near","far","origin","ReflectOwnKeys","R","Reflect","ReflectApply","target","receiver","ownKeys","getOwnPropertySymbols","getOwnPropertyNames","NumberIsNaN","isNaN","EventEmitter","init","once","emitter","Promise","resolve","reject","errorListener","removeListener","resolver","eventTargetAgnosticAddListener","flags","on","addErrorHandlerIfEventEmitter","_events","_eventsCount","_maxListeners","defaultMaxListeners","checkListener","listener","TypeError","_getMaxListeners","that","_addListener","events","existing","warning","newListener","unshift","warned","w","warn","onceWrapper","fired","wrapFn","_onceWrap","state","wrapped","_listeners","unwrap","evlistener","unwrapListeners","arrayClone","listenerCount","copy","addEventListener","wrapListener","removeEventListener","set","RangeError","getPrototypeOf","setMaxListeners","getMaxListeners","doError","er","message","context","listeners","prependListener","prependOnceListener","list","position","originalListener","shift","pop","spliceOne","off","removeAllListeners","rawListeners","eventNames","WINDOW","JS_MD5_NO_WINDOW","WEB_WORKER","NODE_JS","JS_MD5_NO_NODE_JS","process","versions","node","global","COMMON_JS","JS_MD5_NO_COMMON_JS","AMD","ARRAY_BUFFER","JS_MD5_NO_ARRAY_BUFFER","ArrayBuffer","HEX_CHARS","EXTRA","SHIFT","OUTPUT_TYPES","BASE64_ENCODE_CHAR","blocks","buffer8","buffer","Uint32Array","JS_MD5_NO_ARRAY_BUFFER_IS_VIEW","isView","createOutputMethod","outputType","Md5","update","createMethod","method","nodeWrap","crypto","eval","Buffer","nodeMethod","createHash","digest","sharedMemory","h0","h1","h2","h3","bytes","hBytes","finalized","hashed","first","notString","lastByteIndex","finalize","bc","da","hex","arrayBuffer","base64","v1","v2","v3","base64Str","md5","HASH_UNDEFINED","MAX_SAFE_INTEGER","argsTag","boolTag","dateTag","funcTag","genTag","mapTag","numberTag","objectTag","promiseTag","regexpTag","setTag","stringTag","symbolTag","weakMapTag","arrayBufferTag","dataViewTag","float32Tag","float64Tag","int8Tag","int16Tag","int32Tag","uint8Tag","uint8ClampedTag","uint16Tag","uint32Tag","reFlags","reIsHostCtor","reIsUint","cloneableTags","freeGlobal","freeSelf","freeExports","nodeType","freeModule","moduleExports","addMapEntry","pair","addSetEntry","add","arrayReduce","iteratee","accumulator","initAccum","isHostObject","mapToArray","size","overArg","func","setToArray","uid","arrayProto","funcProto","objectProto","coreJsData","maskSrcKey","exec","IE_PROTO","funcToString","objectToString","reIsNative","getPrototype","objectCreate","propertyIsEnumerable","nativeGetSymbols","nativeIsBuffer","isBuffer","nativeKeys","DataView","getNative","Map","Set","WeakMap","nativeCreate","dataViewCtorString","toSource","mapCtorString","promiseCtorString","setCtorString","weakMapCtorString","symbolProto","symbolValueOf","valueOf","Hash","entries","clear","entry","ListCache","MapCache","Stack","__data__","assignValue","object","objValue","eq","assocIndexOf","baseClone","isDeep","isFull","customizer","isObject","isArr","input","initCloneArray","copyArray","tag","getTag","isFunc","cloneBuffer","isPrototype","proto","initCloneObject","copyObject","getSymbols","copySymbols","baseAssign","cloneFunc","symbol","Ctor","cloneArrayBuffer","dataView","byteOffset","cloneDataView","typedArray","cloneTypedArray","cloneMap","regexp","lastIndex","cloneRegExp","cloneSet","initCloneByTag","stacked","props","keysFunc","symbolsFunc","offset","arrayPush","baseGetAllKeys","getAllKeys","arrayEach","subValue","newValue","getMapData","data","getValue","isFunction","baseIsNative","has","cache","pairs","LARGE_ARRAY_SIZE","isIndex","other","ctorString","isArrayLike","isLength","inherited","isObjectLike","isArrayLikeObject","isArguments","baseTimes","skipIndexes","arrayLikeKeys","baseKeys","reTrim","reIsBadHex","reIsBinary","reIsOctal","freeParseInt","nativeMax","nativeMin","toNumber","isSymbol","isBinary","wait","lastArgs","lastThis","maxWait","timerId","lastCallTime","lastInvokeTime","leading","maxing","trailing","invokeFunc","time","thisArg","leadingEdge","timerExpired","shouldInvoke","timeSinceLastCall","trailingEdge","remainingWait","debounced","isInvoking","cancel","arrayTag","errorTag","typedArrayTags","freeProcess","nodeUtil","binding","nodeIsTypedArray","isTypedArray","arraySome","predicate","nativeObjectToString","symToStringTag","SetCache","baseGetTag","isOwn","unmasked","getRawTag","baseIsArguments","baseIsEqual","bitmask","equalFunc","objIsArr","othIsArr","objTag","othTag","objIsObj","othIsObj","isSameTag","equalArrays","convert","isPartial","equalByTag","objIsWrapped","othIsWrapped","objUnwrapped","othUnwrapped","objProps","objLength","skipCtor","othValue","compared","objCtor","othCtor","equalObjects","baseIsEqualDeep","arrLength","othLength","seen","arrValue","othIndex","isMasked","resIndex","arrayFilter","baseUnary","isArg","isBuff","isType","SDPUtils","localCName","generateIdentifier","splitLines","blob","trim","splitSections","part","getDescription","sections","getMediaSections","matchPrefix","prefix","parseCandidate","candidate","foundation","protocol","priority","relatedAddress","relatedPort","tcpType","ufrag","usernameFragment","writeCandidate","toUpperCase","parseIceOptions","parseRtpMap","parsed","payloadType","clockRate","channels","numChannels","writeRtpMap","codec","pt","preferredPayloadType","parseExtmap","uri","writeExtmap","headerExtension","preferredId","parseFmtp","kv","writeFmtp","parameters","param","parseRtcpFb","parameter","writeRtcpFb","lines","rtcpFeedback","fb","parseSsrcMedia","sp","colon","parseSsrcGroup","getMid","mediaSection","mid","parseFingerprint","algorithm","getDtlsParameters","sessionpart","role","fingerprints","writeDtlsParameters","setupType","fp","parseCryptoLine","cryptoSuite","keyParams","sessionParams","writeCryptoLine","writeCryptoKeyParams","parseCryptoKeyParams","keyMethod","keySalt","lifeTime","mkiValue","mkiLength","getCryptoParameters","getIceParameters","pwd","password","writeIceParameters","iceLite","parseRtpParameters","description","codecs","headerExtensions","fecMechanisms","rtcp","mline","rtpmapline","fmtps","writeRtpDescription","kind","caps","maxptime","extension","parseRtpEncodingParameters","encodingParameters","hasRed","hasUlpfec","secondarySsrc","flows","apt","encParam","codecPayloadType","rtx","fec","mechanism","bandwidth","maxBitrate","parseRtcpParameters","rtcpParameters","remoteSsrc","rsize","reducedSize","compound","mux","writeRtcpParameters","parseMsid","spec","track","planB","msidParts","parseSctpDescription","parseMLine","maxSizeLine","sctpPort","fmt","sctpMapLines","writeSctpDescription","sctp","generateSessionId","writeSessionBoilerplate","sessId","sessVer","sessUser","sessionId","getDirection","getKind","isRejected","parseOLine","username","sessionVersion","netType","addressType","isValidSDP","charAt","global$1","_typeof","iterator","_toConsumableArray","arr2","_arrayWithoutHoles","iter","from","_iterableToArray","_nonIterableSpread","safe_add","y","lsw","str2binl","bin","md5_cmn","q","cnt","md5_ff","md5_gg","md5_hh","md5_ii","core_md5","olda","oldb","oldc","oldd","MD5","hexdigest","binarray","hex_tab","binl2hex","fromCharCode","binl2str","core_sha1","olde","rol","safe_add$1","sha1_ft","sha1_kt","core_hmac_sha1","bkey","str2binb","ipad","opad","binb2b64","triplet","binb2str","SHA1","b64_hmac_sha1","b64_sha1","str_hmac_sha1","str_sha1","utils","out","cookies","cookieName","expires","domain","path","cookieObj","isObj","cookieValue","escape","unescape","cookie","$build","attrs","Strophe","Builder","$iq","$pres","VERSION","NS","HTTPBIND","BOSH","CLIENT","AUTH","ROSTER","PROFILE","DISCO_INFO","DISCO_ITEMS","MUC","SASL","STREAM","FRAMING","BIND","SESSION","STANZAS","XHTML_IM","XHTML","tags","attributes","css","validTag","validAttribute","validCSS","style","Status","CONNECTING","CONNFAIL","AUTHENTICATING","AUTHFAIL","CONNECTED","DISCONNECTED","DISCONNECTING","ATTACHED","REDIRECT","CONNTIMEOUT","BINDREQUIRED","ErrorCondition","BAD_FORMAT","CONFLICT","MISSING_JID_NODE","NO_AUTH_MECH","UNKNOWN_REASON","LogLevel","FATAL","ElementType","NORMAL","TEXT","CDATA","FRAGMENT","TIMEOUT","SECONDARY_TIMEOUT","addNamespace","forEachChild","elem","elemName","childNodes","childNode","isTagEqual","_xmlGenerator","_makeGenerator","doc","implementation","createDocument","documentMode","_getIEXmlDom","xmlGenerator","docStrings","ActiveXObject","xmlElement","xmlTextNode","sort","attr","setAttribute","k","xmlescape","xmlunescape","createTextNode","xmlHtmlNode","html","DOMParser","parseFromString","loadXML","getText","nodeValue","copyElement","nodeName","_i","createHtml","getAttribute","cssText","cssAttrs","cssName","cssValue","_i2","createDocumentFragment","_i3","_i4","escapeNode","unescapeNode","getNodeFromJid","jid","getDomainFromJid","bare","getBareJidFromJid","getResourceFromJid","_handleError","fatal","sourceURL","fileName","lineNumber","debug","serialize","tree","getNamedItem","child","_requestId","_connectionPlugins","addConnectionPlugin","ptype","xmlns","nodeTree","up","moreattrs","removeAttribute","cnode","impNode","xmlGen","importNode","newElem","h","fragment","innerHTML","xhtml","Handler","ns","matchBare","matchBareFromJid","user","getNamespace","elNamespace","ignoreNamespaceFragment","namespaceMatch","_this","nsMatch","isMatch","elem_type","run","TimedHandler","period","lastCalled","getTime","reset","Connection","service","_this2","_proto","Websocket","Bosh","features","_sasl_data","do_session","do_bind","timedHandlers","removeTimeds","removeHandlers","addTimeds","addHandlers","protocolErrorHandlers","_idleTimeout","_disconnectTimeout","authenticated","connected","disconnecting","do_authentication","restored","_data","_uniqueId","_sasl_success_handler","_sasl_failure_handler","_sasl_challenge_handler","maxRetries","_onIdle","registerSASLMechanisms","mechanisms","F","_reset","_requests","pause","resume","getUniqueId","suffix","uuid","addProtocolErrorHandler","status_code","callback","connect","pass","hold","route","authcid","authzid","servtype","connect_callback","_changeConnectStatus","_connect","attach","sid","rid","wind","_attach","restore","_sessionCachingSupported","_restore","sessionStorage","setItem","removeItem","xmlInput","xmlOutput","rawInput","rawOutput","nextValidRid","send","_queueData","_send","sendPresence","errback","timeout","_this3","timeoutHandler","stanza","deleteTimedHandler","addTimedHandler","deleteHandler","sendIQ","_this4","iqtype","element","_sendRestart","_this5","thand","handRef","hand","SASLAnonymous","SASLExternal","SASLMD5","SASLOAuthBearer","SASLXOAuth2","SASLPlain","SASLSHA1","registerSASLMechanism","disconnect","pres","_addSysTimedHandler","_onDisconnectTimeout","_disconnect","_abortAllRequests","_doDisconnect","status","condition","plugin","statusChanged","_dataRecv","req","raw","_this6","_reqToData","strip","_emptyQueue","cond","conflict","UNKOWN_REASON","newList","_i5","_hand","_connect_cb","_callback","bodyWrap","getElementsByTagNameNS","matched","mech","authenticate","_no_auth_received","sortMechanismsByPriority","higher","swap","_attemptSASLAuth","_attemptLegacyAuth","mechanism_found","_addSysHandler","_sasl_success_cb","_sasl_failure_cb","_sasl_challenge_cb","_sasl_mechanism","onStart","request_auth_exchange","isClientFirst","response","onChallenge","btoa","challenge","atob","_onLegacyAuthIQResult","iq","_auth2_cb","_this7","serverSignature","onSuccess","streamfeature_handlers","wrapper","_onStreamFeaturesAfterSASL","explicitResourceBinding","_onResourceBindResultIQ","resource","jidNode","_establishSession","_onSessionResultIQ","onFailure","_this8","_i6","_thand","SASLMechanism","connection","_connection","auth_str","test_cnonce","cnonce","nonce","salt","Hi","U","U_old","responseText","authMessage","attribMatch","clientKey","serverKey","clientSignature","_quote","realm","host","digest_uri","cred","A1","A2","core","Strophe$1","$build$1","Request","sends","xmlData","origFunc","date","NaN","abort","dead","age","timeDead","xhr","_newXHR","getResponse","responseXML","documentElement","querySelector","textContent","XMLHttpRequest","overrideMimeType","onreadystatechange","_conn","errors","inactivity","lastResponseHeaders","_buildBody","keepalive","_cacheSession","body","_onRequestStateChange","_throttledRequestHandler","getItem","typ","_sendTerminate","_callProtocolErrorHandlers","reqStatus","_getRequestStatus","err_callback","HTTP","_hitError","time_elapsed","def","getAllResponseHeaders","valid_request","too_many_retries","_removeRequest","reqIs0","_restartRequest","_processRequest","primary_timeout","secondary_timeout","server_error","content_type","contentType","open","sync","setRequestHeader","withCredentials","e2","sendFunc","customHeaders","headers","header","backoff","Strophe$2","$build$2","new_service","pathname","_buildStream","_check_streamerror","connectstatus","errorString","_closeSocket","socket","WebSocket","onopen","_onOpen","_onError","onclose","_onClose","onmessage","_connect_cb_wrapper","_handleStreamStart","ver","streamStart","parsedMessage","see_uri","_streamWrap","_onMessage","CLOSED","close","closeString","rawStanza","firstChild","startString","$msg","_identities","_features","_items","conn","_onDiscoInfo","_onDiscoItems","addIdentity","category","lang","addFeature","var_name","removeFeature","addItem","call_back","success","to","items","_buildIQResult","query_attrs","iqresult","logging","autoSendCountOnEveryIncomingStanza","requestResponseInterval","_c","_NS","_isStreamManagementEnabled","_serverProcesssedStanzasCounter","_clientProcessedStanzasCounter","_clientSentStanzasCounter","_originalXMLOutput","_requestHandler","_incomingHandler","_requestResponseIntervalCount","_isSupported","_unacknowledgedStanzas","_acknowledgedStanzaListeners","addAcknowledgedStanzaListener","enable","_connectionStatus","getResumeToken","_resumeToken","isSupported","_resuming","_originalConnect","_connectArgs","requestAcknowledgement","getOutgoingCounter","getIncomingCounter","_interceptConnectArgs","_originalOnStreamFeaturesAfterSASL","_originalDoDisconnect","_interceptDoDisconnect","_originalDisconnect","_interceptDisconnect","_resumeState","_storedJid","property","previd","_handleServerRequestHandler","_ackHandler","_handleServerAck","_incomingStanzaHandler","_enabledHandler","_handleEnabled","_resumeFailedHandler","_handleResumeFailed","_resumedHandler","_handleResumed","_increaseSentStanzasCounter","firstElementChild","handledCount","_handleAcknowledgedStanzas","_increaseReceivedStanzasCounter","_answerProcessedStanzas","reportedHandledCount","lastKnownHandledCount","delta","_throwError","byteToHex","buf","bth","_v","_v2","_v3","_v4","_interopRequireDefault","getOutputLength","inputLength8","safeAdd","md5cmn","md5ff","md5gg","md5hh","md5ii","encodeURIComponent","length32","hexTab","md5ToHexEncodedArray","wordsToMd5","length8","bytesToWords","getRandomValues","rnds8","msCrypto","ROTL","K","H","N","ceil","M","W","T","_rng","_bytesToUuid","_nodeId","_clockseq","_lastMSecs","_lastNSecs","clockseq","seedBytes","rng","msecs","nsecs","dt","tl","tmh","_md","_default","hashfunc","generateUUID","namespace","stringToBytes","uuidToBytes","idx","DNS","URL","rnds","_sha","logDisabled_","deprecationWarnings_","extractVersion","uastring","pos","wrapPeerConnectionEvent","eventNameToWrap","RTCPeerConnection","nativeAddEventListener","nativeEventName","cb","wrappedCallback","modifiedEvent","handleEvent","_eventMap","nativeRemoveEventListener","unwrappedCb","delete","disableLog","bool","disableWarnings","deprecated","oldMethod","newMethod","detectBrowser","navigator","mozGetUserMedia","userAgent","webkitGetUserMedia","isSecureContext","webkitRTCPeerConnection","RTCIceGatherer","supportsUnifiedPlan","RTCRtpTransceiver","val","compactObject","isEmptyObject","walkStats","stats","base","resultSet","endsWith","filterStats","outbound","streamStatsType","filteredResult","trackStats","trackIdentifier","trackStat","trackId","shimGetUserMedia","browserDetails","mediaDevices","constraintsToChrome_","mandatory","optional","cc","ideal","exact","oldname_","oc","mix","advanced","shimConstraints_","constraints","audio","remap","video","face","facingMode","getSupportedFacingModeLies","getSupportedConstraints","enumerateDevices","then","devices","dev","label","includes","deviceId","shimError_","PermissionDeniedError","PermissionDismissedError","InvalidStateError","DevicesNotFoundError","ConstraintNotSatisfiedError","TrackStartError","MediaDeviceFailedDueToShutdown","MediaDeviceKillSwitchOn","TabCaptureError","ScreenCaptureError","DeviceCaptureError","constraint","constraintName","getUserMedia","onError","origGetUserMedia","cs","getAudioTracks","getVideoTracks","getTracks","DOMException","shimGetDisplayMedia","getSourceId","getDisplayMedia","sourceId","widthSpecified","width","heightSpecified","height","frameRateSpecified","frameRate","chromeMediaSource","chromeMediaSourceId","maxFrameRate","maxWidth","maxHeight","shimMediaStream","MediaStream","webkitMediaStream","shimOnTrack","_ontrack","origSetRemoteDescription","setRemoteDescription","_ontrackpoly","te","getReceivers","Event","transceiver","streams","dispatchEvent","shimGetSendersWithDtmf","shimSenderWithDtmf","pc","dtmf","_dtmf","createDTMFSender","_pc","getSenders","_senders","origAddTrack","addTrack","sender","origRemoveTrack","removeTrack","origAddStream","addStream","origRemoveStream","removeStream","RTCRtpSender","origGetSenders","senders","shimGetStats","origGetStats","getStats","selector","onSucc","onErr","fixChromeStats_","standardReport","report","standardStats","localcandidate","remotecandidate","stat","makeMapStats","successCallbackWrapper_","shimSenderReceiverGetStats","RTCRtpReceiver","origGetReceivers","receivers","srcElement","MediaStreamTrack","shimAddTrackRemoveTrackWithNative","getLocalStreams","_shimmedLocalStreams","streamId","existingSenders","newSenders","newSender","shimAddTrackRemoveTrack","origGetLocalStreams","nativeStreams","_reverseStreams","_streams","newStream","replaceInternalStreamId","internalId","externalStream","internalStream","replaceExternalStreamId","signalingState","alreadyExists","oldStream","nativeMethod","methodObj","origSetLocalDescription","setLocalDescription","origLocalDescription","getOwnPropertyDescriptor","streamid","shimPeerConnection","RTCIceCandidate","fixNegotiationNeeded","getConfiguration","sdpSemantics","nativeGetUserMedia","getSettings","nativeGetSettings","applyConstraints","nativeApplyConstraints","preferredMediaSource","mediaSource","RTCTrackEvent","mozRTCPeerConnection","modernStatsTypes","inboundrtp","outboundrtp","candidatepair","nativeGetStats","shimSenderGetStats","shimReceiverGetStats","shimRemoveStream","shimRTCDataChannel","DataChannel","RTCDataChannel","shimAddTransceiver","origAddTransceiver","addTransceiver","setParametersPromises","initParameters","shouldPerformCheck","sendEncodings","encodingParam","parseFloat","scaleResolutionDownBy","maxFramerate","getParameters","encodings","setParameters","catch","shimGetParameters","origGetParameters","shimCreateOffer","origCreateOffer","createOffer","all","finally","shimCreateAnswer","origCreateAnswer","createAnswer","shimLocalStreamsAPI","_localStreams","_addTrack","tracks","shimRemoteStreamsAPI","getRemoteStreams","_remoteStreams","_onaddstream","_onaddstreampoly","shimCallbacksAPI","addIceCandidate","successCallback","failureCallback","promise","withCallback","_getUserMedia","shimConstraints","errcb","shimRTCIceServerUrls","OrigPeerConnection","pcConfig","pcConstraints","iceServers","newIceServers","server","urls","generateCertificate","shimTrackEventTransceiver","shimCreateOfferLegacy","offerOptions","offerToReceiveAudio","audioTransceiver","getTransceivers","setDirection","offerToReceiveVideo","videoTransceiver","shimAudioContext","AudioContext","webkitAudioContext","shimRTCIceCandidate","NativeRTCIceCandidate","nativeCandidate","parsedCandidate","augmentedCandidate","toJSON","sdpMid","sdpMLineIndex","shimMaxMessageSize","_sctp","sctpInDescription","getRemoteFirefoxVersion","getCanSendMaxMessageSize","remoteIsFirefox","canSendMaxMessageSize","getMaxMessageSize","isFirefox","canSendMMS","remoteMMS","POSITIVE_INFINITY","shimSendThrowTypeError","wrapDcSend","dc","origDataChannelSend","origCreateDataChannel","createDataChannel","dataChannel","channel","shimConnectionState","completed","checking","iceConnectionState","_onconnectionstatechange","origMethod","_connectionstatechangepoly","_lastConnectionState","connectionState","newEvent","removeExtmapAllowMixed","nativeSRD","shimAddIceCandidateNullOrEmpty","nativeAddIceCandidate","adapter","shimChrome","shimFirefox","shimSafari","commonShim","chromeShim","browserShim","firefoxShim","safariShim","adapterFactory","CAMERA","DESKTOP","DESKTOP_HIGH_FPS","NONE","ENVIRONMENT","USER","AV1","H264","OPUS","ULPFEC","VP8","VP9","RTCEvents","CREATE_ANSWER_FAILED","CREATE_OFFER_FAILED","DATA_CHANNEL_OPEN","ENDPOINT_CONN_STATUS_CHANGED","DOMINANT_SPEAKER_CHANGED","LASTN_ENDPOINT_CHANGED","FORWARDED_SOURCES_CHANGED","PERMISSIONS_CHANGED","SENDER_VIDEO_CONSTRAINTS_CHANGED","LASTN_VALUE_CHANGED","LOCAL_TRACK_SSRC_UPDATED","LOCAL_TRACK_MAX_ENABLED_RESOLUTION_CHANGED","TRACK_ATTACHED","REMOTE_TRACK_ADDED","REMOTE_TRACK_MUTE","REMOTE_TRACK_REMOVED","REMOTE_TRACK_UNMUTE","SET_LOCAL_DESCRIPTION_FAILED","SET_REMOTE_DESCRIPTION_FAILED","AUDIO_OUTPUT_DEVICE_CHANGED","DEVICE_LIST_CHANGED","DEVICE_LIST_WILL_CHANGE","DEVICE_LIST_AVAILABLE","ENDPOINT_MESSAGE_RECEIVED","ENDPOINT_STATS_RECEIVED","LOCAL_UFRAG_CHANGED","REMOTE_UFRAG_CHANGED","VideoType","IDENTITY_UPDATED","LOCAL_JID","XMPPEvents","JitsiConferenceErrors","AUTHENTICATION_REQUIRED","CHAT_ERROR","SETTINGS_ERROR","CONFERENCE_DESTROYED","CONFERENCE_MAX_USERS","CONNECTION_ERROR","CONFERENCE_RESTARTED","NOT_ALLOWED_ERROR","MEMBERS_ONLY_ERROR","CONFERENCE_ACCESS_DENIED","FOCUS_DISCONNECTED","FOCUS_LEFT","GRACEFUL_SHUTDOWN","ICE_FAILED","INCOMPATIBLE_SERVER_VERSIONS","OFFER_ANSWER_FAILED","PASSWORD_NOT_SUPPORTED","PASSWORD_REQUIRED","RESERVATION_ERROR","VIDEOBRIDGE_NOT_AVAILABLE","JitsiConferenceEvents","AUDIO_INPUT_STATE_CHANGE","AUDIO_UNMUTE_PERMISSIONS_CHANGED","AUTH_STATUS_CHANGED","BEFORE_STATISTICS_DISPOSED","CONFERENCE_ERROR","CONFERENCE_FAILED","CONFERENCE_JOIN_IN_PROGRESS","CONFERENCE_JOINED","CONFERENCE_LEFT","CONFERENCE_UNIQUE_ID_SET","CONNECTION_ESTABLISHED","CONNECTION_INTERRUPTED","CONNECTION_RESTORED","DATA_CHANNEL_OPENED","DISPLAY_NAME_CHANGED","CONFERENCE_CREATED_TIMESTAMP","DTMF_SUPPORT_CHANGED","JVB121_STATUS","KICKED","PARTICIPANT_KICKED","LAST_N_ENDPOINTS_CHANGED","LOCK_STATE_CHANGED","SERVER_REGION_CHANGED","_MEDIA_SESSION_STARTED","_MEDIA_SESSION_ACTIVE_CHANGED","MEMBERS_ONLY_CHANGED","MESSAGE_RECEIVED","NO_AUDIO_INPUT","NOISY_MIC","NON_PARTICIPANT_MESSAGE_RECEIVED","PRIVATE_MESSAGE_RECEIVED","PARTICIPANT_CONN_STATUS_CHANGED","PARTCIPANT_FEATURES_CHANGED","PARTICIPANT_PROPERTY_CHANGED","P2P_STATUS","PHONE_NUMBER_CHANGED","PROPERTIES_CHANGED","RECORDER_STATE_CHANGED","VIDEO_SIP_GW_AVAILABILITY_CHANGED","VIDEO_SIP_GW_SESSION_STATE_CHANGED","START_MUTED_POLICY_CHANGED","STARTED_MUTED","SUBJECT_CHANGED","SUSPEND_DETECTED","TALK_WHILE_MUTED","TRACK_ADDED","TRACK_AUDIO_LEVEL_CHANGED","TRACK_MUTE_CHANGED","TRACK_REMOVED","TRACK_UNMUTE_REJECTED","TRANSCRIPTION_STATUS_CHANGED","USER_JOINED","USER_LEFT","USER_ROLE_CHANGED","USER_STATUS_CHANGED","VIDEO_UNMUTE_PERMISSIONS_CHANGED","BOT_TYPE_CHANGED","LOBBY_USER_JOINED","LOBBY_USER_UPDATED","LOBBY_USER_LEFT","AV_MODERATION_APPROVED","AV_MODERATION_REJECTED","AV_MODERATION_CHANGED","AV_MODERATION_PARTICIPANT_APPROVED","AV_MODERATION_PARTICIPANT_REJECTED","FACE_LANDMARK_ADDED","BREAKOUT_ROOMS_MOVE_TO_ROOM","BREAKOUT_ROOMS_UPDATED","CALLSTATS_SCRIPT_URL","JitsiTrackErrors","CONSTRAINT_FAILED","ELECTRON_DESKTOP_PICKER_ERROR","ELECTRON_DESKTOP_PICKER_NOT_FOUND","GENERAL","NOT_FOUND","PERMISSION_DENIED","SCREENSHARING_GENERIC_ERROR","SCREENSHARING_USER_CANCELED","TRACK_IS_DISPOSED","TRACK_NO_STREAM_FOUND","UNSUPPORTED_RESOLUTION","TRACK_ERROR_TO_MESSAGE_MAP","JitsiTrackError","gum","failedConstraintName","minWidth","minHeight","getResolutionFromFailedConstraint","AnalyticsEvents","TYPE_OPERATIONAL","TYPE_PAGE","TYPE_TRACK","TYPE_UI","ACTION_JINGLE_RESTART","ACTION_JINGLE_SA_TIMEOUT","ACTION_JINGLE_SI_RECEIVED","ACTION_JINGLE_SI_TIMEOUT","ACTION_JINGLE_TERMINATE","ACTION_JINGLE_TR_RECEIVED","ACTION_JINGLE_TR_SUCCESS","ACTION_P2P_DECLINED","ACTION_P2P_ESTABLISHED","ACTION_P2P_FAILED","ACTION_P2P_SWITCH_TO_JVB","AVAILABLE_DEVICE","CONNECTION_DISCONNECTED","FEEDBACK","ICE_DURATION","ICE_ESTABLISHMENT_DURATION_DIFF","ICE_STATE_CHANGED","NO_BYTES_SENT","TRACK_UNMUTED","createConferenceEvent","createConnectionStageReachedEvent","stage","actionSubject","createGetUserMediaEvent","createJingleEvent","createP2PEvent","createRemotelyMutedEvent","mediaType","createRtpStatsEvent","Events","AUDIO_LEVEL","BEFORE_DISPOSED","BYTE_SENT_STATS","CONNECTION_STATS","LONG_TASKS_STATS","CHROME","OPERA","FIREFOX","INTERNET_EXPLORER","SAFARI","NWJS","ELECTRON","REACT_NATIVE","UNKNOWN","bowserNameToJitsiName","_detectElectron","JitsiMeetElectron","_detectNWJS","_detectReactNative","product","DummyLocalStorage","_storage","keyName","keyValue","jitsiLocalStorage","super","localStorage","_localStorageDisabled","ignore","isLocalStorageDisabled","dontEmitChangedEvent","localStorageContent","browserInfo","_bowser","Bowser","detectedBrowserInfo","bowser","detectors","_detectChromiumBased","_detect","_name","_version","getName","isChrome","isOpera","isIExplorer","isSafari","isNWJS","isElectron","isReactNative","getVersion","_checkCondition","checkTree","isVersionGreaterThan","isVersionLessThan","isVersionEqualTo","doesVideoMuteByStreamRemove","isChromiumBased","isWebKitBased","isIosBrowser","maxTouchPoints","Boolean","isTwa","matchMedia","_getSafariVersion","_getChromiumBasedVersion","isSupportedAndroidBrowser","isSupportedIOSBrowser","_getIOSVersion","isUserInteractionRequiredForUnmute","supportsVideoMuteOnConnInterrupted","supportsBandwidthStatistics","supportsCodecPreferences","getCapabilities","supportsDeviceChangeEvent","ondevicechange","supportsLocalCandidateRttStatistics","supportsPerformanceObserver","PerformanceObserver","supportedEntryTypes","supportsReceiverStats","supportsRTTStatistics","supportsVP9","usesSdpMungingForSimulcast","usesAdapter","usesRidsForSimulcast","supportsGetDisplayMedia","supportsEncodedTransform","RTCRtpScriptTransform","supportsInsertableStreams","createEncodedStreams","ReadableStream","postMessage","supportsAudioRed","mimeType","supportsVADDetection","supportsRTX","ua","disposed","analyticsHandlers","permanentProperties","conferenceName","addPermanentProperties","dispose","setAnalyticsHandlers","_setUserProperties","_sendEvent","setUserProperties","properties","setConferenceName","sendEvent","eventName","_verifyRequiredFields","objectType","containerType","containerId","objectId","_maybeCacheEvent","wrtcFuncNames","reportType","_fabrics","CallStats","backend","backendInitialized","atLeastOneFabric","defaultInstance","callStatsInstance","fabrics","hasFabric","_addNewFabric","_emptyReportQueue","csInstance","defaultConfID","confID","defaultPC","peerconnection","reportsQueue","errorData","_reportError","eventData","sendFabricEvent","associateMstWithUserID","callStatsId","usageLabel","_error","reportError","theBackend","tryCatchMethods","originalMethod","theArguments","debugMethods","originalReportError","exception","CallStatsBackend","callstats","_traceAndCatchBackendCalls","userID","aliasName","userName","callStatsID","callStatsSecret","configParams","applicationName","applicationVersion","siteID","initialize","_initCallback","getWiFiStatsMethod","attachWifiStatsHandler","addresses","devicesData","_reportEvent","conferenceID","overall","comment","sendUserFeedback","mute","tpc","remoteUserID","fabricAttributes","remoteEndpointType","isP2P","endpointType","peer","addNewFabric","fabricUsage","multiplex","_addNewFabricCallback","associateStreamWithVideoTag","isLocal","streamEndpointId","sendDominantSpeakerEvent","sendTerminateEvent","fabricEvent","fabricTerminated","sendIceConnectionFailedEvent","sendCreateOfferFailed","sendCreateAnswerFailed","sendResumeOrHoldEvent","isResume","sendScreenSharingEvent","sendSetLocalDescFailed","sendSetRemoteDescFailed","sendAddIceCandidateFailed","LocalStatsCollector","interval","intervalId","intervalMilis","audioLevel","safeCounterIncrement","number","nextValue","calculateAverage","valueArray","filterPositiveValues","suspend","isLocalStatsSupported","analyser","createAnalyser","smoothingTimeConstant","fftSize","createMediaStreamSource","setInterval","frequencyBinCount","getByteTimeDomainData","samples","maxVolume","toFixed","timeDomainDataToAudioLevel","newLevel","lastLevel","diff","animateLevel","clearInterval","RunningAverage","average","addNext","getAverage","PerformanceObserverStats","statsInterval","eventEmitter","longTasks","maxDuration","performanceStatsInterval","getLongTasksStats","avgRatePerMinute","maxDurationMs","startObserver","longTaskEventHandler","getEntries","task","observer","observe","buffered","startTime","longTasksIntervalId","_lastTimeStamp","StatisticsEvents","stopObserver","MediaType","_runInLiteMode","runInLiteMode","_sourceNameSignaling","sourceNameSignaling","_sendMultipleVideoStreams","sendMultipleVideoStreams","_ssrcRewriting","ssrcRewritingOnBridgeSupported","_usesUnifiedPlan","enableUnifiedOnChrome","isMultiStreamSupportEnabled","isRunInLiteModeEnabled","isSourceNameSignalingEnabled","isSsrcRewritingSupported","calculatePacketLoss","lostPackets","totalPackets","SsrcStats","loss","bitrate","download","upload","resolution","framerate","ConferenceStats","packetLoss","StatsCollector","audioLevelsInterval","baselineAudioLevelsReport","currentAudioLevelsReport","currentStatsReport","previousStatsReport","audioLevelReportHistory","audioLevelsIntervalId","conferenceStats","audioLevelsIntervalMilis","speakerList","statsIntervalId","statsIntervalMilis","ssrc2stats","setLoss","setResolution","addBitrate","resetBitrate","setFramerate","setCodec","setSpeakerList","startAudioLevelStats","audioLevels","getAudioLevels","processAudioLevelReport","processStats","processStatsReport","_processAndEmitReport","bitrateDownload","bitrateUpload","resolutions","framerates","audioCodec","videoCodec","audioBitrateDownload","audioBitrateUpload","videoBitrateDownload","videoBitrateUpload","ssrcStats","isDownloadStream","packetsTotal","packetsLost","getTrackBySSRC","isAudioTrack","FeatureFlags","sourceName","getSourceName","codecDesc","participantId","getParticipantId","userResolutions","userFramerates","userCodecs","avgAudioLevels","localAvgAudioLevels","avgAudioLevel","sum","currentValue","getNonNegativeValue","_calculateBitrate","before","fieldName","bytesNow","bytesBefore","bytesProcessed","timeMs","bitrateKbps","byteSentStats","nominated","availableIncomingBitrate","availableOutgoingBitrate","remoteUsedCandidate","remoteCandidateId","localUsedCandidate","localCandidateId","localip","conferenceStatsTransport","p2p","localCandidateType","candidateType","remoteCandidateType","networkType","rtt","currentRoundTripTime","packetsNow","packetsBefore","packetsDiff","packetsLostNow","packetsLostBefore","packetsLostDiff","frameHeight","frameWidth","framesPerSecond","bytesSent","codecId","codecShortType","VIDEO","remoteSource","localVideoTracks","getLocalTracks","getSsrcByTrackId","framesSent","numberOfActiveStreams","getActiveSimulcastStreams","getLocalSSRC","AUDIO","_instances","isCallstatsLoaded","_initCallStatsBackend","isBackendInitialized","initBackend","Statistics","xmpp","rtpStatsMap","callStatsIntegrationEnabled","enableCallStats","disableThirdPartyRequests","callStatsApplicationLogsDisabled","customScriptUrl","loadCallStatsAPI","callsStatsInstances","instances","audioLevelsEnabled","disableAudioLevels","pcStatsInterval","longTasksStatsInterval","analytics","startRemoteStats","stopRemoteStats","rtpStats","RTPStats","localStats","startLocalStats","LocalStats","addAudioLevelListener","removeAudioLevelListener","addBeforeDisposedListener","removeBeforeDisposedListener","addConnectionStatsListener","removeConnectionStatsListener","addByteSentStatsListener","removeByteSentStatsListener","addLongTasksStatsListener","attachLongTasksStats","conference","performanceObserverStats","removeLongTasksStatsListener","callStats","stopCallStats","tpcId","_stopRemoteStats","stopLocalStats","startCallStats","newInstance","_getAllCallStatsInstances","csInstances","statistics","isCallstatsEnabled","sendConnectionResumeOrHoldEvent","instance","sendMuteEvent","muted","roomJid","sendActiveDeviceListEvent","globalSet","sendGetUserMediaFailed","formatJitsiTrackErrorForCallStats","sendLog","globalSubSet","next","csPerStats","sendApplicationLog","sendFeedback","rating","reportGlobalError","sendAnalyticsAndLog","eventToLog","sendAnalytics","JitsiConferenceEventManager","xmppListeners","p2pJingleSession","jvbJingleSession","isMuted","getType","JitsiTrackEvents","setupChatRoomListeners","chatRoom","room","chatRoomForwarder","jingleSession","rtc","closeBridgeChannel","participant","getParticipantById","setFeatures","offerIq","_setBridgeChannel","actor","mutedByFocusActor","setAudioMute","isMutedByFocus","mutedVideoByFocusActor","setVideoMute","isVideoMutedByFocus","_onMucJoined","isJvbConnectionInterrupted","connectionTimes","leave","_onConferenceRestarted","_onIceConnectionFailed","getStatus","setTerminator","setInitiator","setParticipantPropertyListener","setProperty","onMemberKicked","onSuspendDetected","onMemberJoined","_onMemberBotTypeChanged","onMemberLeft","onDisplayNameChanged","onLocalRoleChanged","isModerator","recorderSession","logObject","getError","onUserRoleChanged","AuthenticationEvents","authEnabled","authIdentity","txt","myJid","ts","_status","payload","addPresenceListener","myUserId","startAudioMuted","startVideoMuted","updated","startMutedPolicy","setupRTCListeners","onRemoteTrackAdded","onRemoteTrackRemoved","dominant","previous","lastDominantSpeaker","roomjid","performance","isVideoTrack","videoType","removeXMPPListeners","setupXMPPListeners","_addConferenceXMPPListener","onIncomingCall","onCallAccepted","onTransportInfo","onCallEnded","audioMuted","videoMuted","ignoreStartMuted","createdTimestamp","actorJid","actorParticipant","getParticipants","getJid","enabled","setupStatisticsListeners","setAudioLevel","startSilent","onByteSentStatsReceived","LOCAL_TRACK_STOPPED","TRACK_AUDIO_OUTPUT_CHANGED","TRACK_VIDEOTYPE_CHANGED","NO_DATA_FROM_SOURCE","TRACK_STREAMING_STATUS_CHANGED","ParticipantConnectionStatus","ACTIVE","INACTIVE","INTERRUPTED","RESTORING","ParticipantConnectionStatusHandler","isConnectionActiveByJvb","isInLastN","isRestoringTimedout","isVideoMuted","isVideoTrackFrozen","trackTimers","connStatusFromJvb","outOfLastNTimeout","p2pRtcMuteTimeout","rtcMuteTimeout","rtcMutedTimestamp","enteredLastNTimestamp","restoringTimers","connectionStatusMap","_getVideoFrozenTimeout","isP2PActive","_onEndpointConnStatusChanged","onEndpointConnStatusChanged","_onP2PStatus","refreshConnectionStatusForAll","_onUserLeft","onUserLeft","_onTrackRtcMuted","onTrackRtcMuted","_onTrackRtcUnmuted","onTrackRtcUnmuted","_onRemoteTrackAdded","_onRemoteTrackRemoved","_onSignallingMuteChanged","onSignallingMuteChanged","_onTrackVideoTypeChanged","onTrackVideoTypeChanged","_onLastNChanged","_onLastNValueChanged","participantIds","clearRtcMutedTimestamp","endpointId","isActive","figureOutConnectionStatus","_changeConnectionStatus","newStatus","getConnectionStatus","getId","_setConnectionStatus","remoteTrack","hasAnyVideoRTCMuted","hasAnyVideoTrackWebRTCMuted","participants","inP2PMode","isRestoringTimedOut","_isRestoringTimedout","audioOnlyMode","getLastN","isConnActiveByJvb","newState","_getNewStateForP2PMode","_getNewStateForJvbMode","_clearRestoringTimer","oldConnectionStatus","connectionStatus","nowMs","maybeSendParticipantConnectionStatusEvent","startedMs","videoTracks","getTracksByMediaType","participantConnectionStatus","createParticipantConnectionStatusEvent","leavingLastN","enteringLastN","rTimer","JitsiParticipant","hidden","statsID","identity","isReplacing","isReplaced","_jid","_id","_conference","_displayName","_supportsDTMF","_tracks","_role","_hidden","_statsID","_properties","_identity","_isReplacing","_isReplaced","getConference","getProperty","jitsiTrack","isWebRTCTrackMuted","oldValue","getStatsID","isHidden","isHiddenFromRecorder","isAudioMuted","_isMediaTypeMuted","getRole","setRole","newRole","setIsReplacing","newIsReplacing","setIsReplaced","newIsReplaced","supportsDTMF","getFeatures","hasFeature","feature","newFeatures","getBotType","_botType","setBotType","newBotType","getConnectionJid","_connectionJid","setConnectionJid","newJid","JitsiConnectionEvents","CONNECTION_FAILED","WRONG_STATE","DISPLAY_NAME_REQUIRED","JitsiConnectionErrors","CONNECTION_DROPPED_ERROR","OTHER_ERROR","SERVER_ERROR","Deferred","clearRejectTimeout","_timeout","setRejectTimeout","ms","Listenable","kJitsiE2EE","E2EEcontext","sharedKey","baseUrl","ljm","workerUrl","workerBlob","Blob","createObjectURL","_worker","Worker","operation","cleanup","cleanupAll","handleReceiver","receiverStreams","readableStream","readable","writableStream","handleSender","senderStreams","setKey","keyIndex","KeyHandler","e2eeCtx","E2EEContext","_enabling","_onMediaSessionStarted","_onLocalTrackAdded","_setupReceiverE2EEForTrack","_trackMuteChanged","isEnabled","_setEnabled","setLocalParticipantProperty","_restartMediaSessions","setEncryptionKey","getMediaSessions","_setupSenderE2EEForTrack","localTracks","findReceiverForTrack","findSenderForTrack","ExternallyManagedKeyHandler","keyInfo","encryptionKey","v4","OLM_MESSAGE_TYPE","OLM_MESSAGE_TYPES","kOlmData","OlmAdapterEvents","OLM_ID_KEY_READY","PARTICIPANT_E2EE_CHANNEL_READY","PARTICIPANT_KEY_UPDATED","OlmAdapter","_conf","_init","_key","_keyIndex","_reqs","_sessionInitialization","_bootstrapOlm","_onEndpointMessageReceived","_onConferenceLeft","_onParticipantLeft","_onParticipantPropertyChanged","promises","localParticipantId","FEATURE_E2EE","_sendSessionInit","allSettled","Olm","pId","olmData","_getParticipantOlmData","uuidv4","JITSI_MEET_MUC_TYPE","olm","ciphertext","_encryptKeyInfo","_sendMessage","updateCurrentKey","clearParticipantSession","free","clearAllParticipantsSessions","_olmAccount","Account","idKeys","identity_keys","_idKey","curve25519","get_library_version","_onIdKeyReady","idKey","_onParticipantE2EEChannelReady","base64js","encrypt","_sendError","Session","create_outbound","otKey","ack","pendingSessionUuid","create_inbound","remove_one_time_keys","decrypt","json","safeJsonParse","lastKey","isEqual","isE2EEEnabled","sendMessage","generate_one_time_keys","otKeys","one_time_keys","mark_keys_as_published","ManagedKeyHandler","_conferenceJoined","_olmAdapter","_rotateKey","debounce","_rotateKeyImpl","_ratchetKey","_ratchetKeyImpl","_onParticipantKeyUpdated","_onParticipantJoined","initSessions","_generateKey","updateKey","material","keyBytes","subtle","importKey","newKey","textEncoder","TextEncoder","deriveBits","encode","ratchet","E2EEncryption","e2ee","_externallyManaged","externallyManagedKey","_keyHandler","testing","disableE2EE","enableEncodedTransformSupport","setEnabled","IDENTITY_PROPERTIES","IDENTITY_PROPERTIES_FOR_COMPARE","compareIdentities","res","parseDiscoInfo","identities","$","each","_","Caps","disco","rooms","externalFeatures","emuc","_addChatRoom","_removeChatRoom","submit","external","_generateVersion","_updateRoomWithExternalFeatures","removeFromPresence","children","addOrReplaceInPresence","getFeaturesAndIdentities","_getDiscoInfo","_fixChatRoomPresenceMap","_notifyVersionChanged","sortedIdentities","accumulatedValue","sortedFeatures","generateSha","NETWORK_INFO_EVENT","_current","isOnline","updateNetworkInfo","ResumeTask","stropheConnection","_stropheConn","_resumeRetryN","_retryDelay","retryDelay","schedule","_cancelResume","_networkOnlineListener","NetworkInfo","_scheduleResume","_resumeTimeout","retry","minDelay","getJitterDelay","_resumeConnection","streamManagement","resumeToken","pattern","oldToken","LastRequestTracker","_lastSuccess","_lastFailedMessage","startTracking","xmppConnection","originalRawInput","rawMessage","getLastFailedMessage","getTimeSinceLastSuccess","getConnectionPluginDefinition","ConnectionPluginListenable","PingConnectionPlugin","ConnectionPlugin","getTimeSinceLastServerResponse","onPingThresholdExceeded","pingOptions","failedPings","_onPingThresholdExceeded","_getTimeSinceLastServerResponse","pingInterval","pingTimeout","pingThreshold","threshold","pingTimestampsToKeep","pingExecIntervals","ping","_addPingExecutionTimestamp","sendIQ2","startInterval","remoteJid","_lastServerCheck","errmsg","stopInterval","getPingSuspendTime","pingIntervals","maxInterval","previousTS","currentInterval","XmppConnection","CONN_STATUS_CHANGED","CONN_SHARD_CHANGED","enableWebsocketResume","websocketKeepAlive","websocketKeepAliveUrl","serviceUrl","shard","xmppPing","_options","_usesWebsocket","startsWith","_rawInputTracker","LastSuccessTracker","_resumeTask","_deferredIQs","_onPingErrorThresholdExceeded","_oneSuccessfulConnect","websocket","isUsingWebSocket","OPEN","pingDomain","_maybeStartWSKeepAlive","_stropheConnectionCb","targetCallback","blockCallback","_maybeEnableStreamResume","_keepAliveAndCheckShard","_processDeferredIQs","_tryResumingConnection","_wsKeepAlive","_clearDeferredIQs","deferred","closeWebsocket","intervalWithJitter","fetch","responseShard","timeLeft","sendUnavailableBeacon","sendBeacon","JitsiTranscriptionStatus","ON","OFF","AVModeration","_xmpp","_mainRoom","_moderationEnabledByType","_whitelistAudio","_whitelistVideo","avModerationComponentAddress","approve","jidToWhitelist","jidToBlacklist","removed","approved","whitelists","newWhitelists","oldList","FEATURE_KEY","BREAKOUT_ROOM_ACTIONS","ADD","REMOVE","MOVE_TO_ROOM","BREAKOUT_ROOM_EVENTS","BreakoutRooms","_handleMessages","_rooms","createBreakoutRoom","subject","removeBreakoutRoom","breakoutRoomJid","sendParticipantToRoom","participantJid","getComponentAddress","breakoutRoomsComponentAddress","_setIsBreakoutRoom","isBreakoutRoom","_isBreakoutRoom","myroomjid","_setMainRoomJid","_mainRoomJid","getMainRoomJid","EMAIL_COMMAND","Lobby","mainRoom","maybeJoinLobbyRoom","_maybeJoinLobbyRoom","lobbyRoomJid","lobbySupported","setMembersOnly","disable","lobbyRoom","membersOnlyEnabled","sendPrivateMessage","getLocalId","addMessageListener","removeMessageHandler","setLobbyRoomJid","joined","email","customDomain","createRoom","disableDiscoInfo","disableFocus","enableLobby","nick","isHiddenDomain","botType","members","getBreakoutRooms","avatar","clean","isSelfPresence","invitePassword","denyAccess","kick","approveAccess","mainRoomJid","memberRoomJid","msgToSend","_callStatsUserName","_machineId","externalStorage","callStatsUserName","UsernameGenerator","generateCallStatsUserName","machineId","amDid","jitsiMeetId","_p8","generateJitsiMeetId","createExpBackoffTimer","step","Moderator","Settings","xmppService","getNextTimeout","getNextErrorTimeout","externalAuthEnabled","sipGatewayEnabled","attachEvent","isExternalAuthEnabled","isSipGatewayEnabled","onMucMemberLeft","setFocusUserJid","focusJid","focusUserJid","getFocusUserJid","getFocusComponent","focusComponent","hosts","createConferenceIq","machineUID","disableRtx","audioPacketDelay","startBitrate","minBitrate","rtcstatsEnabled","callstatsDisabled","parseSessionId","resultIq","parseConfigOptions","authenticationEnabled","allocateConferenceFocus","_allocateConferenceFocusSuccess","_allocateConferenceFocusError","invalidSession","reservationErr","errorCode","errorTextNode","errorMsg","anonymousdomain","waitMs","retrySec","errorIq","prop","getLoginUrl","urlCallback","_getLoginUrl","popup","urlCb","failureCb","decodeURIComponent","getPopupLoginUrl","logout","logoutUrl","packet2JSON","nodes","json2packet","packet","filterNodeFromPresenceJSON","MEMBERS_AFFILIATIONS","ChatRoom","XMPP","replaceParticipant","presMap","presHandlers","_removeConnListeners","inProgressEmitted","focusMucJid","noBridgeAvailable","moderator","lobby","avModeration","breakoutRooms","initPresenceMap","lastPresences","phoneNumber","phonePin","participantPropertyListener","locked","transcriptionStatus","xns","statsId","presenceUpdateTime","onConnStatusChanged","fromJoin","billingId","presenceSyncTime","doLeave","discoRoomInfo","getInfo","meetingIdValEl","setMeetingId","membersOnly","lobbyRoomField","isBreakoutField","breakoutMainRoomField","meetingId","createNonAnonymousRoom","getForm","form","formSubmit","onPresence","member","statusEl","hasStatusUpdate","hasVersionUpdate","xElement","mucUserItem","isReplaceParticipant","affiliation","isFocus","hiddenDomain","fromHiddenDomain","presence","xEl","remove","extractIdentityInformation","userInfo","hiddenFromRecorderFeatureEnabled","groupInfo","_extractFeatures","_initFocus","memberOfThis","displayJids","restartByTerminateSupported","supportsRestartByTerminate","att","phone","pin","processNode","var","focusFeatures","tagHandlers","elementName","setSubject","onParticipantLeft","skipEvents","onPresenceUnavailable","destroySelect","reasonSelect","isKick","membersKeys","actorSelect","actorNick","onMessage","settingsErrorMsg","subjectText","stamp","dateParts","invite","passwordSelect","jsonMessage","parsedJson","tryParseJSONAndVerify","onPresenceError","lobbyRoomNode","lobbyRoomOldNode","setAffiliation","grantIQ","kickIQ","lockRoom","onNotSupported","formsubmit","formToSubmit","addToPresence","matchingNodes","getFromPresence","removePresenceListener","handlerIdx","mucJid","getMemberRole","peerJid","addAudioInfoToPresence","audioMutedTagName","addVideoInfoToPresence","videoMutedTagName","getMediaPresenceInfo","mutedNode","codecTypeNode","videoTypeNode","codecType","getLastPresence","mucNick","isSIPCallingSupported","dial","rayo","hangup","getLobby","getAVModeration","getPhoneNumber","getPhonePin","getMeetingId","muteParticipant","iqToFocus","onMute","onMuteVideo","onMucLeft","doReject","MucConnectionPlugin","isRoomCreated","_createSourceExtension","owner","sourceCompactJson","_createSsrcGroupExtension","ssrcGroupCompactJson","_getOrCreateRtpDescription","jingle","expandSourcesFromJson","jsonMessageXml","audioRtpDescription","videoRtpDescription","ownerSources","videoSources","videoSsrcGroups","audioSources","audioSsrcGroups","MediaDirection","ScreenObtainer","obtainStream","_createObtainStreamMethod","JitsiMeetNW","obtainDesktopStream","jitsiError","obtainScreenOnElectron","obtainScreenFromGetDisplayMediaRN","obtainScreenFromGetDisplayMedia","_getAudioConstraints","audioQuality","stereo","autoGainControl","channelCount","echoCancellation","noiseSuppression","JitsiMeetScreenObtainer","openDesktopPicker","desktopSharingFrameRate","desktopSharingSources","streamType","screenShareAudio","audioConstraints","optionalConstraints","minFrameRate","screen","sourceType","setScreenSharingResolutionConstraints","cursor","errorDetails","errorName","errorStack","setDesktopSharingFrameRate","maxFps","SDPUtil","filterSpecialChars","iceparams","mediadesc","sessiondesc","findLine","parseICEUfrag","parseICEPwd","buildICEUfrag","frag","buildICEPwd","parseMID","parseMSIDAttribute","ssrcLines","msidLine","buildMLine","parseRTPMap","clockrate","parseSCTPMap","parseSCTPPort","buildRTPMap","parseCrypto","fingerprint","parseICECandidate","elems","network","buildICECandidate","cand","hasOwnAttribute","parseSSRC","parseSourceNameLine","sourceNameLine","ssrcSdpLine","parseRTCPFB","haystack","needle","findLines","needles","candidateToJingle","candidateFromJingle","parsePrimaryVideoSsrc","videoMLine","generateSsrc","ssrcLine","parseGroupSsrcs","ssrcGroup","getMedia","getUfrag","ufragLines","preferCodec","codecName","matchingPayloadTypes","payloadTypes","payloadIndex","stripCodec","highProfile","h264Pts","removePts","stripH264HighCodec","CodecMimeType","rtxApts","rtxPts","keepPts","rtcpFb","SDP","mediaI","arrayEquals","array1","array2","equals","SDPDiffer","mySDP","otherSDP","failICE","removeTcpCandidates","removeUdpCandidates","addMlineForNewLocalSource","clonedeep","RECVONLY","groups","mids","getMediaSsrcMap","mediaSSRCs","mediaindex","linessrc","containsSSRC","medias","toJingle","thecreator","assrcline","creator","amidline","rtpmap","afmtpline","fmtpParameters","rtcpFbToJingle","availableSsrc","ssrcParameters","ridLines","rids","ridLine","ridInfo","extmapLines","extmap","SENDONLY","SENDRECV","transportToJingle","sctpport","sctpmap","sctpAttrs","setupLine","setup","iceParameters","payloadtype","feedback","rtcpFbFromJingle","feedbackElementTrrInt","fromJingle","contents","jingle2media","__","hdrExt","userSources","nonUserSources","isUserSource","sourceStr","getNewMedia","myMedias","othersMedias","newMedia","othersMediaIdx","myMedia","othersMedia","otherSsrcGroup","mySsrcGroup","modify","sdpMediaSsrcs","modified","ssrcNum","mediaSsrc","hasQueueMicrotask","queueMicrotask","hasSetImmediate","setImmediate","hasNextTick","nextTick","fallback","fn","wrap","defer","setImmediate$1","handlePromise","invokeCallback","isAsync","wrapAsync","asyncFn","awaitify","arity","cbArgs","applyEach","eachfn","fns","callArgs","_asyncMap","results","counter","_iteratee","iterCb","breakLoop","callFn","onlyOnce","asyncEachOfLimit","generator","limit","done","canceled","awaiting","running","replenish","iterDone","iterateeCallback","handleError","eachOfLimit","asyncIterator","isAsyncIterable","nextElem","coll","createArrayIterator","okeys","getIterator","createES2015Iterator","createIterator","looping","eachOfLimit$2","eachOfArrayLike","iteratorCallback","eachOfGeneric","eachOf$1","map$1","eachOfSeries$1","DLL","head","tail","removeLink","prev","empty","insertAfter","newNode","setInitial","toArray","cur","testFn","curr","dll","worker","concurrency","numRunning","workersList","drain","saturated","unsaturated","ev","trigger","processingScheduled","_insert","insertAtFront","rejectOnError","rej","promiseCallback","started","_tasks","_createCB","tasks","idle","_maybeDrain","eventMethod","handleAndRemove","isProcessing","datum","pushAsync","kill","unshiftAsync","defineProperties","memo","mapLimit$1","concatLimit$1","mapResults","_createTester","check","testResult","testPassed","consoleFunc","resultArgs","_withoutIndex","_fn","_test","truth","eachLimit$2","eachSeries$1","filterArray","truthValues","filterGeneric","_filter","innerArgs","ensureAsync","newObj","taskCb","criteria","comparator","left","right","rest","taskIndex","nextTask","AsyncQueue","_queue","_processQueueTasks","_stopped","finishedCallback","shutdown","JingleSessionState","PENDING","ENDED","JingleSession","localJid","mediaConstraints","isInitiator","usedrip","dripContainer","_signalingLayer","initiatorJid","responderJid","signalingLayer","doInitialize","addIceCandidates","getState","addSources","removeSources","terminate","failure","acceptOffer","_getInitiatorJid","MediaSessionEvents","IQ_TIMEOUT","JingleSessionPC","jingleContents","videoContents","maxFrameHeightSel","receiverConstraints","sourceFrameHeightSel","_bridgeSessionId","_cachedOldLocalSdp","_cachedNewLocalSdp","_iceCheckingStartedTimestamp","_gatheringStartedTimestamp","localRecvMaxFrameHeight","_sourceReceiverConstraints","_localVideoActive","_remoteVideoActive","_gatheringReported","lasticecandidate","closed","remoteRecvMaxFrameHeight","remoteSourceMaxFrameHeights","modificationQueue","wasConnected","establishmentDuration","_xmppListeners","onXmppStatusChanged","_removeSenderVideoConstraintsChangeListener","_assertNotEnded","isReconnect","wasstable","webrtcIceUdpDisable","webrtcIceTcpDisable","pcOptions","gatherStats","maxstats","capScreenshareBitrate","enableInsertableStreams","videoQuality","forceTurnRelay","disableSimulcast","abtestSuspendVideo","_abtestSuspendVideoEnabled","preferH264","disableH264","preferredCodec","createPeerConnection","onicecandidate","phase","initiator","sendIceCandidate","onsignalingstatechange","oniceconnectionstatechange","isStable","reconnect","usesTerminateForRestart","enableIceRestart","iceStarted","onconnectionstatechange","icestate","onnegotiationneeded","remoteDescription","workFunction","oldSdp","localDescription","_renegotiate","configureSenderVideoEncodings","newSdp","notifyMySSRCUpdate","getRemoteRecvMaxFrameHeight","getRemoteSourcesRecvMaxFrameHeight","localSDP","ice","jcand","errorMesssage","sendIceCandidates","cands","fingerprintLine","required","newJingleErrorHandler","sendIceFailedNotification","sessionInfo","iceCandidates","rtcCandidate","iceCandidate","outerHTML","readSsrcInfo","ssrcElement","setTrackSourceName","setSSRCOwner","i3","ssrcInfoElement","jidOrEndpointId","generateRecvonlySsrc","getConfiguredVideoCodec","jingleOffer","setOfferAnswerCycle","sendSessionAccept","addTracks","offerSdp","sendSessionInitiate","setAnswer","jingleAnswer","remoteSdp","_responderRenegotiate","jingleOfferAnswerIq","audioTracks","newRemoteSdp","_processNewJingleOfferIq","oldLocalSdp","bridgeSessionId","sendContentModify","newLocalSdp","setVideoCodecs","preferred","disabled","current","replaceTransport","jingleOfferElem","enableForcedReload","sendTransportAccept","originalOffer","clone","newFingerprint","accept","responder","maxFrameHeight","sessionModify","setReceiverVideoConstraint","sourceReceiverConstraints","transportAccept","medialines","sendTransportReject","transportReject","setSenderVideoConstraint","jitsiLocalTrack","getLocalVideoTracks","getLocalVideoTrack","setSenderVideoConstraints","sendSessionTerminate","sessionTerminate","reasonDescription","restart","requestRestart","onTerminated","reasonCondition","reasonText","_parseSsrcInfoFromSourceAdd","sourceAddElem","currentRemoteSdp","addSsrcInfo","i1","midFound","i2","addRemoteStream","_addOrRemoveRemoteStream","removeRemoteStream","removeRemoteStreamsOnLeave","finishCallback","removeSsrcInfo","getRemoteSourceInfoByParticipant","_processRemoteRemoveSource","newLocalSDP","isAdd","logPrefix","errMsg","addOrRemoveSsrcInfo","_parseSsrcInfoFromSourceRemove","_processRemoteAddSource","findIndex","desiredDirection","getDesiredMediaDirection","optionalRemoteSdp","_initiatorRenegotiate","answer","offer","replaceTracks","oldLocalSDP","replaceTrack","oldTrack","newTrack","clearRecvonlySsrc","shouldRenegotiate","setSourceName","sourceRemoveElem","_verifyNoSSRCChanged","operationName","oldSDP","currentLocalSDP","sdpDiff","addedMedia","removedMedia","addTrackAsUnmute","_addRemoveTrackAsMuteUnmute","removeTrackAsMute","isMute","removeTrackMute","addTrackUnmute","setMediaTransferActive","audioActive","videoActive","logAudioStr","logVideoStr","isSessionActive","audioActiveChanged","setAudioTransferActive","pcVideoActiveChanged","setVideoTransferActive","modifyContents","newVideoSenders","parseVideoSenders","newMaxFrameHeight","parseMaxFrameHeight","sourceMaxFrameHeights","parseSourceMaxFrameHeight","_modifyRemoteVideoActive","remoteVideoSenders","isRemoteVideoActive","newSDP","getSignaledSourceInfo","sdpDiffer","mediaIndex","signaledSsrcs","ctx","removedSsrcInfo","addedSsrcInfo","request","errResponse","errorElSel","errorReasonSel","errorMsgSel","getIceConnectionState","getConnectionState","abTesting","enableSuspendVideoTest","_parseIceCandidates","parseCandidates","candidateAttrs","JingleConnectionPlugin","iceConfig","sessions","jvbIceConfig","jvb","p2pIceConfig","onJingle","fromJid","sess","jsonMessages","audioVideoSsrcs","logMessage","endpoint","startMuted","iceUfrag","icePwd","dtlsFingerprint","successTime","newP2PJingleSession","me","getStunAndTurnCredentials","v2Res","onReceiveStunAndTurnCredentials","v1Res","iceservers","dict","credential","temp","useTurnUdp","getLog","updateLog","StropheLogger","logIncoming","logOutgoing","RAYO_XMLNS","RayoConnectionPlugin","onRayo","roomPass","callResource","lastErrorStatus","resetLastErrorStatusRegExpr","lastErrorStatusRegExpr","FAILURE_REGEX","DEFAULT_STUN_SERVERS","FEATURE_JIGASI","token","disconnectInProgress","authenticatedUser","deploymentInfo","trace","errStatusCapture","createConnection","bosh","details","shard_changed","suspend_time","time_since_last_success","_initStrophePlugins","initFeaturesList","disableBeforeUnloadHandlers","enableOpusRed","enableRemb","enableTcc","enableLipSync","getConnection","connectionHandler","credentials","statusStr","_maybeSendDeploymentInfoStat","_sysMessageHandler","sendDiscoInfo","_resetState","sendDeploymentInfo","_processDiscoInfoIdentities","anonymousConnectionFailed","connectionFailed","lastErrorMsg","wasIntentionalDisconnect","_getConnectionFailedReasonDetails","lastFailedRawMessage","_parseConnectionFailedMessage","speakerStatsComponentAddress","conferenceDurationComponentAddress","processLobbyFeatures","fr","region","backendRelease","_onPrivateMessage","_onSystemMessage","foundIceServers","attaching","configDomain","hostname","onCreateResource","getRoomJid","mucNickname","muc","getJingleLog","getXmppLog","getSessions","disconnectListener","_cleanupXmppConnection","evType","p2pStunServers","stunServers","iceTransportPolicy","headersArr","sendFaceExpressionEvent","expression","jsonString","users","created_timestamp","acceptedStatuses","aprops","authenticateAndUpgradeRole","rejectPromise","onLoginSuccessful","roomPassword","authenticationError","connectionError","CodecSelection","disabledCodec","_getCodecMimeType","jvbCodec","p2pCodec","jvbPreferredCodec","_isCodecSupported","p2pPreferredCodec","_selectPreferredCodec","mediaSession","currentCodec","selectedCodec","enforcePreferredCodec","remoteParticipants","remote","peerMediaInfo","getPeerMediaInfo","peerCodec","getPreferredCodec","BridgeChannel","wsUrl","_channel","_eventEmitter","_mode","_areRetriesEnabled","_closedFromClient","datachannel","_handleChannel","_wsUrl","_initWebSocket","ws","_startConnectionRetries","timeoutS","reload","isOpen","_retryTimeout","_stopConnectionRetries","_retryWebSocketConnection","closeEvent","createBridgeChannelClosedEvent","mode","sendEndpointStatsMessage","colibriClass","msgPayload","sendSetLastNMessage","lastN","sendSelectedEndpointsMessage","endpointIds","selectedEndpoints","sendReceiverVideoConstraintMessage","maxFrameHeightPixels","sendNewReceiverVideoConstraintsMessage","sendVideoTypeMessage","sendSourceVideoTypeMessage","dominantSpeakerEndpoint","previousSpeakers","active","lastNEndpoints","forwardedSources","videoConstraints","jsonObject","DEFAULT_CONSTRAINTS","audioOutputDeviceId","audioOutputChanged","disableAP","disableAEC","disableNS","disableAGC","featureDetectionAudioEl","isAudioOutputDeviceChangeAvailable","setSinkId","availableDevicesPollTimer","availableDevices","emptyFuncton","updateGrantedPermissions","um","audioTracksReceived","videoTracksReceived","grantedPermissions","sendDeviceListToAnalytics","deviceList","audioInputDeviceCount","audioOutputDeviceCount","videoInputDeviceCount","videoOutputDeviceCount","device","groupId","updateKnownDevices","pds","newDevices","mediaDeviceInfoToJSON","facing","compareAvailableMediaDevices","rtcUtils","origAttachMediaStream","RTCPeerConnectionType","attachMediaStream","getStreamID","getTrackID","srcObject","isDeviceChangeAvailable","getAudioOutputDevice","ex","screenObtainer","isDeviceListAvailable","ds","umDevices","gumTimeout","timeoutExpired","_getDesktopMedia","_getMissingTracks","requestedDevices","missingDevices","audioDeviceRequested","videoDeviceRequested","obtainAudioAndVideoPermissions","otherOptions","mediaStreamsMetaData","maybeRequestDesktopDevice","desktopSharingSourceDevice","matchingDevice","maybeRequestCaptureDevices","requestedCaptureDevices","Resolutions","cameraDeviceId","CameraFacingMode","micDeviceId","getConstraints","desktopStream","desktopAudioTracks","desktopAudioStream","desktopVideoTracks","desktopVideoStream","avStream","audioStream","effects","videoStream","stopMediaStream","deviceType","mediaStream","release","isDesktopSharingEnabled","setAudioOutputDevice","getCurrentlyAvailableMediaDevices","arePermissionsGrantedForAvailableDevices","getEventDataForActiveDevice","deviceData","trackHandler2Prop","JitsiTrack","streamInactiveHandler","trackMediaType","containers","_streamInactiveHandler","_setStream","_addMediaStreamInactiveHandler","onended","oninactive","_setHandler","_unregisterHandlers","videoTrack","getVideoType","isLocalAudioTrack","getOriginalStream","getStreamId","getTrack","getTrackLabel","getTrackId","getUsageLabel","_maybeFireTrackAttached","container","_onTrackAttach","RTCUtils","_attachTTFMTracker","detach","_onTrackDetach","isScreenSharing","newAudioLevel","getMSID","setAudioOutput","JitsiLocalTrack","rtcId","_setEffectInProgress","effect","_startStreamEffect","displaySurface","metadata","maxEnabledResolution","_constraints","_prevSetMuted","_facingMode","_trackEnded","_hasSentData","_testDataSent","_realDeviceId","_sourceName","_trackMutedTS","_onDeviceListWillChange","oldRealDeviceId","_setRealDeviceIdFromDeviceList","_onAudioOutputDeviceChanged","_initNoDataFromSourceHandlers","_addStreamToConferenceAsUnmute","_addLocalTrackAsUnmute","_fireNoDataFromSourceEvent","isReceivingData","createNoDataFromSourceEvent","_isNoDataFromSourceEventsEnabled","_queueSetMuted","setMuted","_setMuted","_removeStreamFromConferenceAsMute","_removeLocalTrackAsMute","_sendMuteStatus","_setTrackMuteStatus","logMuteInfo","_streamEffect","_stopStreamEffect","stopStream","streamOptions","getDeviceId","getCameraFacingMode","streamsInfo","PRESENTER","streamInfo","cont","_sendBridgeVideoTypeMessage","storedMSID","_originalStream","startEffect","stopEffect","_switchCamera","_switchStreamEffect","setEffect","removeTrackPromise","trackSettings","getDuration","isEnded","_stopStreamInProgress","_effectEnabled","setConference","unmute","found","PLAN_B_MIDS","findSimGroup","grp","findFidGroup","addSimGroupSources","sourceGroups","sourceList","findSourcebyId","relatedFidGroup","relatedSsrc","addSourcesToMline","otherSsrc","simGroup2","checkIfMlineForSsrcExists","mlines","Interop","toPlanB","every","sessionMedia","bLine","bundle","msidSemantic","semantic","resStr","toUnifiedPlan","currentDesc","iceRestart","newDesc","oldDesc","newMLine","oldMLine","checkForIceRestart","newIceUfrag","newIcePwd","newMline","mLineForData","ssrc2group","createSourceGroupMap","bundleOnly","resultSdp","SignalingEvents","PEER_MUTED_CHANGED","PEER_VIDEO_TYPE_CHANGED","SOURCE_MUTED_CHANGED","SOURCE_VIDEO_TYPE_CHANGED","getSourceNameForJitsiTrack","trackIdx","getMediaTypeFromSourceName","firstLetterOfMediaTypeIdx","firstLetterOfMediaType","SignalingLayer","getSSRCOwner","getPeerSourceInfo","getTrackSourceName","parsePrimarySSRC","parseSecondarySSRC","_getSSRCCount","MLineWrap","getSSRCAttrValue","ssrcNumber","attrName","ssrcObj","removeSSRC","addSSRCAttribute","findGroup","findGroups","findGroupByPrimarySSRC","primarySSRC","findSSRCByMSID","getSSRCCount","containsAnySSRCGroups","getPrimaryVideoSsrc","fecGroup","getRtxSSRC","getSSRCs","getPrimaryVideoSSRCs","videoSSRCs","ssrcGroupInfo","dumpSSRCGroups","removeGroupsWithSSRC","removeGroupsBySemantics","replaceSSRC","oldSSRC","newSSRC","addSSRCGroup","SdpTransformWrap","rawSDP","parsedSDP","selectMedia","selectedMLines","toRawSDP","LocalSdpMunger","localEndpointId","audioSourcesToMsidMap","videoSourcesToMsidMap","_addMutedLocalVideoTracksToSDP","transformer","localVideos","isCamera","isInPeerConnection","isMediaStreamInPc","requiredSSRCs","isSimulcastOn","simulcast","sdpConsistency","cachedPrimarySsrc","primaryCname","rtxModifier","modifyRtxSsrcs2","_generateMsidAttribute","pcId","_transformMediaIdentifiers","streamAndTrackIDs","mediaDirection","generatedMsid","maybeAddMutedLocalVideoTracksToSDP","transformStreamIdentifiers","sessionDesc","audioMLine","_injectSourceNames","videoMlines","nameExists","trackIndex","updateAssociatedRtxStream","primarySsrcInfo","rtxSsrc","previousRtxSSRC","RtxModifier","correspondingRtxSsrcs","ssrcMapping","modifyRtxSsrcs","sdpStr","sdpTransformer","videoMLines","primaryVideoSsrcs","correspondingRtxSsrc","stripRtx","fidGroups","SdpConsistency","clearVideoSsrcCache","injectRecvOnly","setPrimarySsrc","hasPrimarySsrcCached","makeVideoPrimarySsrcsConsistent","newPrimarySsrc","SdpSimulcast","_ssrcCache","_fillSsrcsFromCache","cachedSsrcs","newSsrcs","_getSsrcAttribute","_generateNewSsrcsForSimulcast","addAssociatedAttributes","_generateSsrc","TrackStreamingStatus","TrackStreamingStatusImpl","isInForwardedSources","restoringTimer","streamingStatusMap","trackTimer","outOfForwardedSourcesTimeout","figureOutStreamingStatus","_onForwardedSourcesChanged","onForwardedSourcesChanged","maybeSendTrackStreamingStatusEvent","_changeStreamingStatus","getTrackStreamingStatus","_setTrackStreamingStatus","isVideoRTCMuted","oldStreamingStatus","streamingStatus","trackStreamingStatus","createTrackStreamingStatusEvent","leavingForwardedSources","enteringForwardedSources","_clearEnteredForwardedSourcesTimestamp","_setEnteredForwardedSourcesTimestamp","enteredForwardedSourcesTimestamp","_getEnteredForwardedSourcesTimestamp","_isCurrentTrack","ttfmTrackerAudioAttached","ttfmTrackerVideoAttached","containerEvents","JitsiRemoteTrack","ownerEndpointId","_trackStreamingStatus","_trackStreamingStatusImpl","_enteredForwardedSourcesTimestamp","_addEventListener","_removeEventListener","hasBeenMuted","_bindTrackHandlers","_containerHandlers","_containerEventHandler","_onTrackMute","_onTrackUnmute","_initTrackStreamingStatus","_disposeTrackStreamingStatus","setMute","getSSRC","_setVideoType","_playCallback","getConnectionTimes","gumStart","gumEnd","gumDuration","ttfm","_getStatus","_p2pConnStatusRtcMuteTimeout","_peerConnStatusRtcMuteTimeout","_peerConnStatusOutOfLastNTimeout","HD_BITRATE","SIM_LAYER_RIDS","TPCUtils","bitrateSettings","maxBitratesVideo","standardBitrates","low","standard","high","videoBitrates","encodingBitrates","localStreamEncodingsConfig","_getStreamEncodings","localTrack","ensureCorrectOrderOfSsrcs","parsedSdp","reorderedSsrcs","findTransceiver","insertUnifiedPlanSimulcastReceive","simulcast_03","simulcastLine","transceiverInit","calculateEncodingsActiveState","localVideoTrack","newHeight","isSharingLowFpsScreen","calculateEncodingsBitrates","desktopShareBitrate","desktopBitrate","presenterEnabled","isNewLocalSource","currentDirection","setEncodings","transceivers","updateEncodingsResolution","TraceablePeerConnection","audioTransferActive","_dtmfSender","_dtmfTonesQueue","videoTransferActive","remoteTracks","_addedStreams","localSSRCs","localUfrag","remoteUfrag","_dtlsTransport","_peerVideoTypeChanged","_peerMutedChanged","_sourceMutedChanged","_sourceVideoTypeChanged","safeConstraints","rtcStatsSFUP2P","tpcUtils","statsinterval","_capScreenshareBitrate","_usesTransceiverCodecPreferences","interop","localSdpMunger","getLocalEndpointId","_senderVideoMaxHeight","_senderMaxHeights","what","onTrack","evt","_remoteTrackAdded","_remoteTrackRemoved","onaddstream","_remoteStreamAdded","onremovestream","_remoteStreamRemoved","ondatachannel","_processStat","statValue","endTime","times","dumpSDP","isAddOperation","hasLocalSource","hasAnyTracksOfType","mediaTransferActive","_getReceiversByEndpointIds","endpoints","getRemoteTracks","remoteTrackIds","audioReceivers","getSynchronizationSources","endpointTracksByMediaType","primarySsrcs","fidLines","getTargetVideoBitrates","findTrackById","RTC","isUserStreamById","onaddtrack","onremovetrack","streamAudioTracks","audioTrack","streamVideoTracks","remoteSDP","mediaLines","mls","trackSsrc","_createRemoteTrack","remoteTracksMap","userTracksByMediaType","existingTrack","isUserStream","toBeRemoved","_removeRemoteTrack","removeRemoteTracks","removedTracks","remoteTracksByMedia","_extractSSRCMap","groupsMap","groupSSRCs","_getSSRC","_injectSsrcGroupForUnifiedSimulcast","getters","audioMedia","changed","videoMedia","enforceSendRecv","_adjustRemoteMediaDirection","_isSharingScreen","_mungeCodecOrder","codecPreference","bitrates","hdBitrate","webrtcStream","_addStream","generateNewStreamSSRCInfo","rtxSsrcMapping","promiseChain","_assertTrackBelongs","webRtcStream","_removeStream","doesBelong","defaultCodec","lowFps","findSenderByKind","negotiationNeeded","oldTrackSSRC","mediaActive","_ensureSimulcastGroupIsLast","localSdp","videoStartIndex","simStartIndex","otherStartIndex","simEndIndex","simStr","otherEndIndex","sdpHead","simStrTrimmed","sdpTail","_adjustLocalMediaDirection","modifiedDirection","desiredAudioDirection","desiredVideoDirection","hasRemoteSource","_mungeOpus","opusMaxAverageBitrate","mLines","fmtpOpus","fmtpConfig","sdpChanged","maxaveragebitrate","mungedConfig","_mungeInactive","_initializeDtlsTransport","onstatechange","currentDescription","firstSsrcs","newSsrcLines","filteredLines","ssrcId","cnameLine","replaceDefaultUnifiedPlanMsid","normalizePlanB","videoSender","preference","degradationPreference","encodingsEnabledState","maxBitrates","scaleFactor","layer","sendTones","tones","interToneGap","rtpSender","localAudioTrack","ontonechange","_onToneChange","toneBuffer","insertDTMF","tone","peerTracks","_removePeerConnection","_createOfferOrAnswer","isOffer","logName","handleSuccess","resolveFn","rejectFn","_processLocalSSRCsMap","handleFailure","eventType","capabilities","setCodecPreferences","oaPromise","_extractPrimarySSRC","sourceIndex","sourceIdentifier","newSSRCNum","oldSSRCNum","activeStreams","currNumSsrcs","ConnectionQualityEvents","peerConnectionIdCounter","rtcTrackIdCounter","_createLocalTracks","mediaStreamMetaData","metaData","peerConnections","_lastN","_lastNEndpoints","_forwardedSources","_maxFrameHeight","_selectedEndpoints","_lastNChangeListener","_forwardedSourcesChangeListener","_onDeviceListChanged","_updateAudioOutputForAudioTracks","_videoType","BridgeVideoType","destroy","_channelOpenListener","tracksInfo","initializeBridgeChannel","logError","msgType","_receiverVideoConstraints","oldLastNEndpoints","leavingLastNEndpoints","enteringLastNEndpoints","oldForwardedSources","setNewReceiverVideoConstraints","setVideoType","sendSourceVideoType","selectEndpoints","ids","signaling","encodedInsertableStreams","bundlePolicy","newConnection","traceablePeerConnection","addLocalTrack","getForwardedSources","localVideo","getLocalAudioTrack","localAudio","pcRemoteTracks","mutePromises","removeLocalTrack","elSelector","sendChannelMessage","setLastN","remoteAudioTracks","LOCAL_STATS_UPDATED","REMOTE_STATS_UPDATED","kSimulcastFormats","layers","targetRN","ConnectionQuality","_localStats","connectionQuality","jvbRTT","_lastConnectionQualityUpdate","_remoteStats","_timeIceConnected","_timeVideoUnmuted","ConferenceEvents","_updateLocalConnectionQuality","_broadcastLocalStats","ICE_CONNECTION_STATE_CHANGED","_updateRemoteStats","_updateLocalStats","_maybeUpdateUnmuteTime","serverRegion","bridgeCount","_calculateConnectionQuality","resolutionName","quality","activeTPC","getActivePeerConnection","videoQualitySettings","millisSinceStart","simulcastFormat","targetHeight","rampUp","getTarget","maxIncreasePerSecond","prevConnectionQuality","diffSeconds","updateLocalConnectionQuality","isConnectionInterrupted","IceFailedHandling","_actOnIceFailed","explicitlyDisabled","useTerminateForRestart","jvbConnection","jvbConnIceState","_canceled","_iceFailedTimeout","DetectionEvents","DETECTOR_STATE_CHANGE","VAD_NOISY_DEVICE","VAD_REPORT_PUBLISHED","VAD_SCORE_PUBLISHED","VAD_TALK_WHILE_MUTED","NoAudioSignalDetection","_timeoutTrigger","_hasAudioInput","_audioLevel","_trackAdded","_clearTriggerTimeout","_handleAudioInputStateChange","_handleNoAudioInputDetection","_eventFired","_audioTrack","P2PDominantSpeakerDetection","myUserID","createAudioContext","AudioContextImpl","TrackVADEmitter","procNodeSampleRate","vadProcessor","_procNodeSampleRate","_vadProcessor","_localTrack","_bufferResidue","Float32Array","_audioContext","sampleRate","getRequiredPCMFrequency","_vadSampleSize","getSampleLength","_onAudioProcess","_initializeAudioContext","_audioSource","_audioProcessingNode","createScriptProcessor","audioEvent","inData","inputBuffer","getChannelData","completeInData","sampleTimestamp","pcmSample","vadScore","calculateAudioFrameVAD","score","pcmData","_connectAudioGraph","onaudioprocess","destination","_disconnectAudioGraph","_cleanupResources","getDeviceLabel","_destroyed","VADAudioAnalyser","createVADProcessor","_createVADProcessor","_vadEmitter","_isVADEmitterRunning","_detectionServices","_vadInitTracker","_processVADScore","_trackRemoved","addVADDetectionService","vadService","detector","_stopVADEmitter","_startVADEmitter","processVADScore","_changeDetectorsMuteState","changeMuteState","vadEmitter","VADNoiseDetection","_processing","_scoreArray","_audioLvlArray","_active","_calculateNoisyScore","scoreAvg","audioLevelAvg","_setActiveState","_recordValues","avgAudioLvl","_processTimeout","posAudioLevels","VADTalkMutedDetection","_calculateVADScore","E2ePingEvents","E2E_RTT_CHANGED","E2E_PING_REQUEST","E2E_PING_RESPONSE","ParticipantWrapper","e2eping","requests","lastRequestId","sendRequest","handleResponse","maybeLogRttAndStop","scheduleNext","getDelay","removeParticipant","conferenceSize","totalSeconds","numRequests","maxMessagesPerSecond","requestId","requestMessage","timeSent","numRequestsWithResponses","totalNumRequests","JitsiE2EPingEvents","E2ePing","maxConferenceSize","participantJoined","participantLeft","messageReceived","conferenceJoined","handleRequest","participantWrapper","Jvb121EventGenerator","_jvb121","evaluateStatus","oldStatus","getParticipantCount","ReceiverVideoConstraints","_defaultConstraints","defaultConstraints","onStageEndpoints","updateLastN","updateReceiveResolution","updateReceiverVideoConstraints","updateSelectedEndpoints","ReceiveVideoController","_rtc","startLastN","channelLastN","useNewBandwidthAllocationStrategy","_getDefaultSourceReceiverConstraints","remoteVideoTracks","remoteEndpointIds","oldConstraints","newConstraints","setPreferredReceiveMaxFrameHeight","setReceiverConstraints","isEndpointsFormat","isSourcesFormat","p2pSession","mappedConstraints","SendVideoController","_preferredSendMaxFrameHeight","_sourceSenderConstraints","_configureConstraintsForLocalSources","_onSenderConstraintsReceived","_propagateSendMaxFrameHeight","sourceConstraints","getActiveMediaSession","_senderVideoConstraints","idealHeight","sendMaxFrameHeight","_selectSendMaxFrameHeight","activeMediaSession","setPreferredSendMaxFrameHeight","getFocusRecordingUpdate","jibriStatus","recordingMode","sessionID","getHiddenDomainUpdate","liveStreamViewURLContainer","liveStreamViewURL","modeContainer","sessionIDContainer","getSessionIdFromIq","jibri","getSessionId","sessionIdContainer","isFromFocus","JibriSession","_setSessionID","setStatus","getID","_sessionID","getInitiator","_initiator","getLiveStreamViewURL","_liveStreamViewURL","getTerminator","_terminator","getMode","setError","setLiveStreamViewURL","appData","broadcastId","_createIQ","recordingXMLUtils","_setErrorFromIq","AudioOutputProblemDetector","_localAudioLevelCache","_reportedParticipants","_audioProblemCandidates","_numberOfRemoteAudioLevelsReceived","_onLocalAudioLevelsReport","_onRemoteAudioLevelReceived","_clearUserData","numberOfReports","localAudioLevels","remoteAudioLevels","localAudioLevelsString","createAudioOutputProblemEvent","AverageStatReport","calculate","appendReport","ConnectionAvgStats","avgRtpStatsReporter","_n","_sampleIdx","_avgRTT","_avgRemoteRTTMap","_avgRtpStatsReporter","_avgEnd2EndRTT","_onConnectionStats","_calculateAvgStats","_onRemoteStatsUpdated","_processRemoteStats","batchReport","jvbEnd2EndRTT","jvbStatsMonitor","avgRemoteRTT","_calculateAvgRemoteRTT","avgLocalRTT","_resetAvgStats","remoteAvg","avg","validData","rttAvg","AvgRTPStatsReporter","_avgAudioBitrateUp","_avgAudioBitrateDown","_avgVideoBitrateUp","_avgVideoBitrateDown","_avgBandwidthUp","_avgBandwidthDown","_avgPacketLossTotal","_avgPacketLossUp","_avgPacketLossDown","_avgRemoteFPS","_avgRemoteScreenFPS","_avgLocalFPS","_avgLocalScreenFPS","_avgRemoteCameraPixels","_avgRemoteScreenPixels","_avgLocalCameraPixels","_avgLocalScreenPixels","_avgCQ","_cachedTransportStats","_onLocalStatsUpdated","_maybeSendTransportAnalyticsEvent","_onP2PStatusChanged","p2pStatsMonitor","_onJvb121StatusChanged","_resetAvgJvbStats","confSize","_calculateAvgVideoFps","_calculateAvgVideoPixels","peerResolutions","peerPixelsSum","peerCount","myID","peerID","videosResolution","peerAvgPixels","_calculatePeerAvgVideoPixels","videos","peerSsrcCount","peerSsrcPixels","peerFpsSum","videosFps","peerAvgFPS","_calculatePeerAvgVideoFps","peerSsrcFps","transportStats","SpeakerStatsCollector","dominantSpeakerId","SpeakerStats","_onDominantSpeaker","_onUserJoin","_onUserLeave","_onDisplayNameChange","_onFaceLandmarkAdd","_updateStats","oldDominantSpeaker","newDominantSpeaker","savedUser","newStats","speakerStatsToUpdate","newParticipant","RecordingResult","wordArray","TrackRecorder","recorder","AUDIO_WEBM","AUDIO_OGG","startRecorder","trackRecorder","stopRecorder","determineCorrectFileType","MediaRecorder","isTypeSupported","AudioRecorder","jitsiConference","recorders","fileType","isRecording","instantiateTrackRecorder","updateNames","originalStream","ondataavailable","dataEvent","recorderToRemove","click","revokeObjectURL","getRecordingResults","getFileType","Word","word","begin","getWord","getBeginTime","getEndTime","SphinxService","recordingResult","verify","formatResponse","audioBlob","sphinxURL","toReturn","getURL","audioFileBlob","DONE","audioRecorder","objects","filler","BEFORE_STATE","RECORDING_STATE","TRANSCRIBING_STATE","FINISHED_STATE","Transcriber","transcriptionService","transcription","lineLength","blobCallBack","transcriber","getUTCMilliseconds","wordObject","maybeMerge","hasPopulatedArrays","twoDimensionalArray","callBack","merge","arrays","potentialWords","pushWordToSortedArray","lowestWordArray","wordToAdd","updateTranscription","foundSmaller","wordToCompare","getTranscription","ComponentsVersions","addCommandListener","processVersions","VideoSIPGWStatusConstants","VideoSIPGWStateConstants","VideoSIPGWErrorConstants","mucResource","getComponentVersion","componentName","STATUS_AVAILABLE","STATUS_UNDEFINED","STATUS_BUSY","STATE_ON","STATE_OFF","STATE_PENDING","STATE_RETRYING","STATE_FAILED","ERROR_NO_CONNECTION","ERROR_SESSION_EXISTS","STATE_CHANGED","JitsiVideoSIPGWSession","sipAddress","VideoSIPGWConstants","_sendJibriIQ","setState","failureReason","oldState","addStateListener","removeStateListener","sipaddress","displayname","VideoSIPGW","sessionStateChangeListener","sessionStateChanged","handleJibriSIPState","Constants","failure_reason","createVideoSIPGWSession","SOURCE_INFO_PRESENCE_ELEMENT","SignalingLayerImpl","ssrcOwners","_localSourceState","_remoteSourceState","_sourceNames","_addLocalSourceInfoToPresence","_doesEndpointSendNewSourceInfo","setChatRoom","oldChatRoom","_audioMuteHandler","_videoMuteHandler","_videoTypeHandler","_sourceInfoHandler","_memberLeftHandler","_bindChatRoomEventHandlers","emitAudioMutedEvent","emitVideoMutedEvent","emitVideoTypeEvent","sourceInfoJSON","emitEventsFromHere","endpointSourceState","newMutedState","oldSourceState","newVideoType","newSourceNames","_findEndpointSourceInfoForMediaType","remoteSourceState","sourceInfo","legacyGetPeerMediaInfo","lastPresence","mediaInfo","endpointMediaSource","existingOwner","setTrackMuteStatus","setTrackVideoType","existingName","JitsiConference","eventManager","componentsVersions","dtmfManager","somebodySupportsDTMF","wasStopped","avgRtpStatsN","_audioOutputProblemDetector","speakerStatsCollector","deferredStartP2PTask","delay","backToP2PDelay","isP2PConnectionInterrupted","videoSIPGWHandler","recordingManager","_sessions","_chatRoom","getSession","_handleFocusPresence","_handleJibriPresence","startRecording","_addSession","_emitSessionUpdate","stopRecording","_createSession","_conferenceJoinAnalyticsEventSent","isE2EESupported","_e2eEncryption","_audioSenderLimitReached","_videoSenderLimitReached","JitsiConnection","appID","errType","errorType","errorMessage","createConnectionFailedEvent","ANALYTICS_CONNECTION_DISCONNECTED","JitsiMediaDevicesEvents","resourceCreator","isAuthenticatedUser","re","codecSettings","codecSelection","_statsCurrentId","statisticsId","_onIceConnectionInterrupted","_onIceConnectionRestored","_onIceConnectionEstablished","_updateProperties","_sendConferenceJoinAnalyticsEvent","_removeLocalSourceOnReject","_updateRoomPresence","_registerRtcListeners","receiveVideoController","sendVideoController","callStatsThreshold","statisticsDisplayName","callStatsCustomScriptUrl","callStatsConfigParams","enableTalkWhileMuted","_audioAnalyser","vadTalkMutedDetection","enableNoisyMicDetection","vadNoiseDetection","enableNoAudioDetection","_noAudioSignalDetection","hasAudioSignal","jvb121Status","p2pDominantSpeakerDetection","userRegion","transcriptionLanguage","_maybeSetSITimeout","isJoined","isP2PEnabled","isP2PTestModeEnabled","p2pTestMode","onLocalTrackRemoved","_sendConferenceLeftAnalyticsEvent","_delayedIceFailed","_maybeClearSITimeout","leaveError","localtrack","_desktopSharingFrameRate","isAuthEnabled","isLoggedIn","getAuthLogin","getExternalAuthUrl","urlForPopup","getPerformanceStats","longTasksStats","eventId","command","removeCommandListener","sendTextMessage","sendPrivateTextMessage","sendCommand","sendCommandOnce","removeCommand","nickKey","getTranscriber","localAudioTracks","getTranscriptionStatus","addTrackPromises","_setupNewTrack","_fireMuteChangeEvent","_fireAudioLevelChangeEvent","activeTpc","actorId","_getInitialLocalTracks","trackType","isStartAudioMuted","isStartVideoMuted","muteHandler","audioLevelHandler","oldVideoType","oldTrackBelongsToConference","_doReplaceTrack","replaceTrackPromises","_setNewVideoType","videoTypeChanged","videoTypeTagName","trackVideoType","legacyTypeChanged","presenceChanged","audioMuteChanged","videoMuteChanged","addAsUnmutePromises","removeAsMutePromises","lock","unlock","selectParticipant","selectParticipants","isInteger","isVideoActive","countHidden","grantOwner","revokeOwner","isMyself","isMembersOnly","kickParticipant","_sessionInitiateTimeout","muteMediaType","fullJid","_updateFeatures","_maybeStartOrStopP2P","updateDTMFSupport","botParticipant","mediaSessions","tracksToBeRemoved","kickedParticipantId","kickedParticipant","transportInfo","removedTrack","_onIncomingCallP2P","rejectReason","contentName","_shouldBeInP2PMode","_rejectIncomingCall","_acceptP2PIncomingCall","_acceptJvbIncomingCall","_suspendMediaTransferForJvbConnection","webSocket","stopOptions","forceJvb121","p2pFailed","_stopP2PSession","isDTMFSupported","peerConnection","startTranscriber","stopTranscriber","getMeetingUniqueId","setStartMutedPolicy","policy","getStartMutedPolicy","removeLocalParticipantProperty","getLocalParticipantProperty","overallFeedback","detailedFeedback","getSsrcByTrack","remoteUserId","_fireIncompatibleVersionsEvent","sendEndpointMessage","broadcastEndpointMessage","sendThroughVideobridge","messageType","messageToSend","remoteID","_addRemoteJVBTracks","_addRemoteTracks","_addRemoteP2PTracks","p2pEstablishmentDuration","jvbEstablishmentDuration","forceJVB121Ratio","establishmentDurationDiff","_setP2PStatus","_removeRemoteJVBTracks","audioLimitReached","videoLimitReached","_maybeClearDeferredStartP2P","_removeRemoteTracks","_removeRemoteP2PTracks","sessionNickname","_resumeMediaTransferForJvbConnection","_startP2PSession","userLeftEvent","peers","shouldBeInP2P","myId","peersId","hasBotPeer","wasP2PEstablished","skip","muteStatusChanged","audioMuteStatusChanged","videoMuteStatusChanged","getP2PConnectionState","startP2PSession","stopP2PSession","getSpeakerStats","sendFaceLandmarks","perf","toggleE2EE","setMediaEncryptionKey","isLobbySupported","disableLobby","joinLobby","myLobbyUserId","sendLobbyMessage","addLobbyMessageListener","removeLobbyMessageHandler","lobbyDenyAccess","lobbyApproveAccess","isAVModerationSupported","enableAVModeration","disableAVModeration","avModerationApprove","avModerationReject","setToken","initJitsiConference","getLogs","PERMISSION_PROMPT_IS_SHOWN","SLOW_GET_USER_MEDIA","AUDIO_PERMISSION_NAME","VIDEO_PERMISSION_NAME","_permissions","_logOutputDevice","permissions","_handlePermissionsChange","_permissionsApiSupported","query","_parsePermissionState","onchange","supported","permissionStatus","deviceID","isDevicePermissionGranted","isMultipleAudioInputSupported","emitEvent","stopActiveDevices","ACTIONS","ProxyConnectionPC","receiveAudio","receiveVideo","_peerConnection","_onRemoteStream","_onSendMessage","getPeerJid","processMessage","$jingle","ACCEPT","_onSessionAccept","INITIATE","_onSessionInitiate","TERMINATE","_onSessionTerminate","TRANSPORT_INFO","_onTransportInfo","_createPeerConnection","connectionStub","pcConfigStub","roomStub","jitsiRemoteTrack","onRemoteStream","onSendMessage","PRECALL_TEST_RESULTS","_initialized","api","_loadScript","appId","appSecret","disablePrecalltest","_initialize","execute","makePrecallTest","AudioMixer","_started","_streamsToMix","_streamMSSArray","addMediaStream","_mixedMSD","createMediaStreamDestination","streamMSS","getAnalyticsAttributesFromOptions","video_requested","JitsiMeetJS","ProxyConnectionService","jitsiConnection","_onFatalError","_convertStringToXML","UNAVAILABLE","_selfCloseConnection","xml","xmlDom","isVideo","convertVideoToDesktop","jitsiLocalTracks","createLocalTracks","stringifiedIq","XMLSerializer","serializeToString","onConnectionClosed","constants","recording","BUSY","RESOURCE_CONSTRAINT","UNEXPECTED_REQUEST","SERVICE_UNAVAILABLE","FILE","sipVideoGW","detection","errorTypes","logLevels","JitsiMediaDevices","enableAnalyticsLogging","enableWindowOnErrorHandler","getGlobalOnErrorHandler","isWebRtcSupported","addGlobalLogTransport","globalTransport","removeGlobalLogTransport","setGlobalLogOptions","oldfirePermissionPromptIsShownEvent","promiseFulfilled","firePermissionPromptIsShownEvent","fireSlowPromiseEvent","restOptions","mStream","currentlyAvailableMediaDevices","setVideoTrackContentHints","createTrackVADEmitter","localAudioDeviceId","createAudioMixer","getActiveAudioDevice","audioDevices","devicePromiseArray","micDevice","devicePromise","outcomeArray","successfulPromises","rejectedPromises","rejectReasons","deviceLabel","isCollectingLocalStats","lineno","colno","setNetworkInfo","hint","contentHint","precallTest","util","AuthUtil","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","loaded","__webpack_modules__","amdO","getter","definition","g","globalThis","nmd","paths","__webpack_exports__"],"sourceRoot":""}