{"version":3,"file":"lib-jitsi-meet.min.js","mappings":"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","sources":["webpack://JitsiMeetJS/webpack/universalModuleDefinition","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/async/lib/async.js","webpack://JitsiMeetJS/./index.js","webpack://JitsiMeetJS/./modules/statistics/SpeakerStats.js","webpack://JitsiMeetJS/./modules/transcription/audioRecorder.js","webpack://JitsiMeetJS/./modules/transcription/recordingResult.js","webpack://JitsiMeetJS/./modules/transcription/transcriber.js","webpack://JitsiMeetJS/./modules/transcription/transcriptionServices/AbstractTranscriptionService.js","webpack://JitsiMeetJS/./modules/transcription/transcriptionServices/SphinxTranscriptionService.js","webpack://JitsiMeetJS/./modules/transcription/word.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/./service/RTC/BridgeVideoType.js","webpack://JitsiMeetJS/./service/RTC/CameraFacingMode.js","webpack://JitsiMeetJS/./service/RTC/CodecMimeType.js","webpack://JitsiMeetJS/./service/RTC/MediaDirection.js","webpack://JitsiMeetJS/./service/RTC/RTCEvents.js","webpack://JitsiMeetJS/./service/RTC/Resolutions.js","webpack://JitsiMeetJS/./service/RTC/VideoType.js","webpack://JitsiMeetJS/./service/authentication/AuthenticationEvents.js","webpack://JitsiMeetJS/./service/statistics/constants.js","webpack://JitsiMeetJS/./service/xmpp/XMPPEvents.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/./JitsiConferenceErrors.js","webpack://JitsiMeetJS/./JitsiConferenceEvents.js","webpack://JitsiMeetJS/./modules/statistics/constants.js","webpack://JitsiMeetJS/./JitsiTrackErrors.js","webpack://JitsiMeetJS/./JitsiTrackError.js","webpack://JitsiMeetJS/./service/statistics/AnalyticsEvents.js","webpack://JitsiMeetJS/./service/statistics/Events.js","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.js","webpack://JitsiMeetJS/./modules/statistics/RTPStatsCollector.js","webpack://JitsiMeetJS/./modules/statistics/statistics.js","webpack://JitsiMeetJS/./JitsiConferenceEventManager.js","webpack://JitsiMeetJS/./JitsiTrackEvents.js","webpack://JitsiMeetJS/./modules/connectivity/ParticipantConnectionStatus.js","webpack://JitsiMeetJS/./JitsiParticipant.js","webpack://JitsiMeetJS/./JitsiConnectionEvents.js","webpack://JitsiMeetJS/./JitsiConnectionErrors.js","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.js","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/flags/FeatureFlags.js","webpack://JitsiMeetJS/./modules/xmpp/JingleHelperFunctions.js","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/./modules/util/AsyncQueue.js","webpack://JitsiMeetJS/./modules/xmpp/JingleSessionState.js","webpack://JitsiMeetJS/./modules/xmpp/JingleSession.js","webpack://JitsiMeetJS/./modules/xmpp/MediaSessionEvents.js","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.js","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/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.js","webpack://JitsiMeetJS/./modules/connectivity/ConnectionQuality.js","webpack://JitsiMeetJS/./modules/connectivity/IceFailedHandling.js","webpack://JitsiMeetJS/./modules/detection/DetectionEvents.js","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.js","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/version/ComponentsVersions.js","webpack://JitsiMeetJS/./modules/videosipgw/VideoSIPGWConstants.js","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.js","webpack://JitsiMeetJS/./JitsiMediaDevices.js","webpack://JitsiMeetJS/./modules/detection/ActiveDeviceDetector.js","webpack://JitsiMeetJS/./modules/proxyconnection/constants.js","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/./node_modules/strophe.js/dist/strophe.umd.js","webpack://JitsiMeetJS/./node_modules/strophe.js/node_modules/ws/browser.js","webpack://JitsiMeetJS/./node_modules/strophejs-plugin-disco/lib/strophe.disco.js","webpack://JitsiMeetJS/./node_modules/strophejs-plugin-stream-management/lib/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/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 ","/* 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","/*!\n * async\n * https://github.com/caolan/async\n *\n * Copyright 2010-2014 Caolan McMahon\n * Released under the MIT license\n */\n/*jshint onevar: false, indent:4 */\n/*global setImmediate: false, setTimeout: false, console: false */\n(function () {\n\n var async = {};\n\n // global on the server, window in the browser\n var root, previous_async;\n\n root = this;\n if (root != null) {\n previous_async = root.async;\n }\n\n async.noConflict = function () {\n root.async = previous_async;\n return async;\n };\n\n function only_once(fn) {\n var called = false;\n return function() {\n if (called) throw new Error(\"Callback was already called.\");\n called = true;\n fn.apply(root, arguments);\n }\n }\n\n //// cross-browser compatiblity functions ////\n\n var _toString = Object.prototype.toString;\n\n var _isArray = Array.isArray || function (obj) {\n return _toString.call(obj) === '[object Array]';\n };\n\n var _each = function (arr, iterator) {\n if (arr.forEach) {\n return arr.forEach(iterator);\n }\n for (var i = 0; i < arr.length; i += 1) {\n iterator(arr[i], i, arr);\n }\n };\n\n var _map = function (arr, iterator) {\n if (arr.map) {\n return arr.map(iterator);\n }\n var results = [];\n _each(arr, function (x, i, a) {\n results.push(iterator(x, i, a));\n });\n return results;\n };\n\n var _reduce = function (arr, iterator, memo) {\n if (arr.reduce) {\n return arr.reduce(iterator, memo);\n }\n _each(arr, function (x, i, a) {\n memo = iterator(memo, x, i, a);\n });\n return memo;\n };\n\n var _keys = function (obj) {\n if (Object.keys) {\n return Object.keys(obj);\n }\n var keys = [];\n for (var k in obj) {\n if (obj.hasOwnProperty(k)) {\n keys.push(k);\n }\n }\n return keys;\n };\n\n //// exported async module functions ////\n\n //// nextTick implementation with browser-compatible fallback ////\n if (typeof process === 'undefined' || !(process.nextTick)) {\n if (typeof setImmediate === 'function') {\n async.nextTick = function (fn) {\n // not a direct alias for IE10 compatibility\n setImmediate(fn);\n };\n async.setImmediate = async.nextTick;\n }\n else {\n async.nextTick = function (fn) {\n setTimeout(fn, 0);\n };\n async.setImmediate = async.nextTick;\n }\n }\n else {\n async.nextTick = process.nextTick;\n if (typeof setImmediate !== 'undefined') {\n async.setImmediate = function (fn) {\n // not a direct alias for IE10 compatibility\n setImmediate(fn);\n };\n }\n else {\n async.setImmediate = async.nextTick;\n }\n }\n\n async.each = function (arr, iterator, callback) {\n callback = callback || function () {};\n if (!arr.length) {\n return callback();\n }\n var completed = 0;\n _each(arr, function (x) {\n iterator(x, only_once(done) );\n });\n function done(err) {\n if (err) {\n callback(err);\n callback = function () {};\n }\n else {\n completed += 1;\n if (completed >= arr.length) {\n callback();\n }\n }\n }\n };\n async.forEach = async.each;\n\n async.eachSeries = function (arr, iterator, callback) {\n callback = callback || function () {};\n if (!arr.length) {\n return callback();\n }\n var completed = 0;\n var iterate = function () {\n iterator(arr[completed], function (err) {\n if (err) {\n callback(err);\n callback = function () {};\n }\n else {\n completed += 1;\n if (completed >= arr.length) {\n callback();\n }\n else {\n iterate();\n }\n }\n });\n };\n iterate();\n };\n async.forEachSeries = async.eachSeries;\n\n async.eachLimit = function (arr, limit, iterator, callback) {\n var fn = _eachLimit(limit);\n fn.apply(null, [arr, iterator, callback]);\n };\n async.forEachLimit = async.eachLimit;\n\n var _eachLimit = function (limit) {\n\n return function (arr, iterator, callback) {\n callback = callback || function () {};\n if (!arr.length || limit <= 0) {\n return callback();\n }\n var completed = 0;\n var started = 0;\n var running = 0;\n\n (function replenish () {\n if (completed >= arr.length) {\n return callback();\n }\n\n while (running < limit && started < arr.length) {\n started += 1;\n running += 1;\n iterator(arr[started - 1], function (err) {\n if (err) {\n callback(err);\n callback = function () {};\n }\n else {\n completed += 1;\n running -= 1;\n if (completed >= arr.length) {\n callback();\n }\n else {\n replenish();\n }\n }\n });\n }\n })();\n };\n };\n\n\n var doParallel = function (fn) {\n return function () {\n var args = Array.prototype.slice.call(arguments);\n return fn.apply(null, [async.each].concat(args));\n };\n };\n var doParallelLimit = function(limit, fn) {\n return function () {\n var args = Array.prototype.slice.call(arguments);\n return fn.apply(null, [_eachLimit(limit)].concat(args));\n };\n };\n var doSeries = function (fn) {\n return function () {\n var args = Array.prototype.slice.call(arguments);\n return fn.apply(null, [async.eachSeries].concat(args));\n };\n };\n\n\n var _asyncMap = function (eachfn, arr, iterator, callback) {\n arr = _map(arr, function (x, i) {\n return {index: i, value: x};\n });\n if (!callback) {\n eachfn(arr, function (x, callback) {\n iterator(x.value, function (err) {\n callback(err);\n });\n });\n } else {\n var results = [];\n eachfn(arr, function (x, callback) {\n iterator(x.value, function (err, v) {\n results[x.index] = v;\n callback(err);\n });\n }, function (err) {\n callback(err, results);\n });\n }\n };\n async.map = doParallel(_asyncMap);\n async.mapSeries = doSeries(_asyncMap);\n async.mapLimit = function (arr, limit, iterator, callback) {\n return _mapLimit(limit)(arr, iterator, callback);\n };\n\n var _mapLimit = function(limit) {\n return doParallelLimit(limit, _asyncMap);\n };\n\n // reduce only has a series version, as doing reduce in parallel won't\n // work in many situations.\n async.reduce = function (arr, memo, iterator, callback) {\n async.eachSeries(arr, function (x, callback) {\n iterator(memo, x, function (err, v) {\n memo = v;\n callback(err);\n });\n }, function (err) {\n callback(err, memo);\n });\n };\n // inject alias\n async.inject = async.reduce;\n // foldl alias\n async.foldl = async.reduce;\n\n async.reduceRight = function (arr, memo, iterator, callback) {\n var reversed = _map(arr, function (x) {\n return x;\n }).reverse();\n async.reduce(reversed, memo, iterator, callback);\n };\n // foldr alias\n async.foldr = async.reduceRight;\n\n var _filter = function (eachfn, arr, iterator, callback) {\n var results = [];\n arr = _map(arr, function (x, i) {\n return {index: i, value: x};\n });\n eachfn(arr, function (x, callback) {\n iterator(x.value, function (v) {\n if (v) {\n results.push(x);\n }\n callback();\n });\n }, function (err) {\n callback(_map(results.sort(function (a, b) {\n return a.index - b.index;\n }), function (x) {\n return x.value;\n }));\n });\n };\n async.filter = doParallel(_filter);\n async.filterSeries = doSeries(_filter);\n // select alias\n async.select = async.filter;\n async.selectSeries = async.filterSeries;\n\n var _reject = function (eachfn, arr, iterator, callback) {\n var results = [];\n arr = _map(arr, function (x, i) {\n return {index: i, value: x};\n });\n eachfn(arr, function (x, callback) {\n iterator(x.value, function (v) {\n if (!v) {\n results.push(x);\n }\n callback();\n });\n }, function (err) {\n callback(_map(results.sort(function (a, b) {\n return a.index - b.index;\n }), function (x) {\n return x.value;\n }));\n });\n };\n async.reject = doParallel(_reject);\n async.rejectSeries = doSeries(_reject);\n\n var _detect = function (eachfn, arr, iterator, main_callback) {\n eachfn(arr, function (x, callback) {\n iterator(x, function (result) {\n if (result) {\n main_callback(x);\n main_callback = function () {};\n }\n else {\n callback();\n }\n });\n }, function (err) {\n main_callback();\n });\n };\n async.detect = doParallel(_detect);\n async.detectSeries = doSeries(_detect);\n\n async.some = function (arr, iterator, main_callback) {\n async.each(arr, function (x, callback) {\n iterator(x, function (v) {\n if (v) {\n main_callback(true);\n main_callback = function () {};\n }\n callback();\n });\n }, function (err) {\n main_callback(false);\n });\n };\n // any alias\n async.any = async.some;\n\n async.every = function (arr, iterator, main_callback) {\n async.each(arr, function (x, callback) {\n iterator(x, function (v) {\n if (!v) {\n main_callback(false);\n main_callback = function () {};\n }\n callback();\n });\n }, function (err) {\n main_callback(true);\n });\n };\n // all alias\n async.all = async.every;\n\n async.sortBy = function (arr, iterator, callback) {\n async.map(arr, function (x, callback) {\n iterator(x, function (err, criteria) {\n if (err) {\n callback(err);\n }\n else {\n callback(null, {value: x, criteria: criteria});\n }\n });\n }, function (err, results) {\n if (err) {\n return callback(err);\n }\n else {\n var fn = function (left, right) {\n var a = left.criteria, b = right.criteria;\n return a < b ? -1 : a > b ? 1 : 0;\n };\n callback(null, _map(results.sort(fn), function (x) {\n return x.value;\n }));\n }\n });\n };\n\n async.auto = function (tasks, callback) {\n callback = callback || function () {};\n var keys = _keys(tasks);\n var remainingTasks = keys.length\n if (!remainingTasks) {\n return callback();\n }\n\n var results = {};\n\n var listeners = [];\n var addListener = function (fn) {\n listeners.unshift(fn);\n };\n var removeListener = function (fn) {\n for (var i = 0; i < listeners.length; i += 1) {\n if (listeners[i] === fn) {\n listeners.splice(i, 1);\n return;\n }\n }\n };\n var taskComplete = function () {\n remainingTasks--\n _each(listeners.slice(0), function (fn) {\n fn();\n });\n };\n\n addListener(function () {\n if (!remainingTasks) {\n var theCallback = callback;\n // prevent final callback from calling itself if it errors\n callback = function () {};\n\n theCallback(null, results);\n }\n });\n\n _each(keys, function (k) {\n var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]];\n var taskCallback = function (err) {\n var args = Array.prototype.slice.call(arguments, 1);\n if (args.length <= 1) {\n args = args[0];\n }\n if (err) {\n var safeResults = {};\n _each(_keys(results), function(rkey) {\n safeResults[rkey] = results[rkey];\n });\n safeResults[k] = args;\n callback(err, safeResults);\n // stop subsequent errors hitting callback multiple times\n callback = function () {};\n }\n else {\n results[k] = args;\n async.setImmediate(taskComplete);\n }\n };\n var requires = task.slice(0, Math.abs(task.length - 1)) || [];\n var ready = function () {\n return _reduce(requires, function (a, x) {\n return (a && results.hasOwnProperty(x));\n }, true) && !results.hasOwnProperty(k);\n };\n if (ready()) {\n task[task.length - 1](taskCallback, results);\n }\n else {\n var listener = function () {\n if (ready()) {\n removeListener(listener);\n task[task.length - 1](taskCallback, results);\n }\n };\n addListener(listener);\n }\n });\n };\n\n async.retry = function(times, task, callback) {\n var DEFAULT_TIMES = 5;\n var attempts = [];\n // Use defaults if times not passed\n if (typeof times === 'function') {\n callback = task;\n task = times;\n times = DEFAULT_TIMES;\n }\n // Make sure times is a number\n times = parseInt(times, 10) || DEFAULT_TIMES;\n var wrappedTask = function(wrappedCallback, wrappedResults) {\n var retryAttempt = function(task, finalAttempt) {\n return function(seriesCallback) {\n task(function(err, result){\n seriesCallback(!err || finalAttempt, {err: err, result: result});\n }, wrappedResults);\n };\n };\n while (times) {\n attempts.push(retryAttempt(task, !(times-=1)));\n }\n async.series(attempts, function(done, data){\n data = data[data.length - 1];\n (wrappedCallback || callback)(data.err, data.result);\n });\n }\n // If a callback is passed, run this as a controll flow\n return callback ? wrappedTask() : wrappedTask\n };\n\n async.waterfall = function (tasks, callback) {\n callback = callback || function () {};\n if (!_isArray(tasks)) {\n var err = new Error('First argument to waterfall must be an array of functions');\n return callback(err);\n }\n if (!tasks.length) {\n return callback();\n }\n var wrapIterator = function (iterator) {\n return function (err) {\n if (err) {\n callback.apply(null, arguments);\n callback = function () {};\n }\n else {\n var args = Array.prototype.slice.call(arguments, 1);\n var next = iterator.next();\n if (next) {\n args.push(wrapIterator(next));\n }\n else {\n args.push(callback);\n }\n async.setImmediate(function () {\n iterator.apply(null, args);\n });\n }\n };\n };\n wrapIterator(async.iterator(tasks))();\n };\n\n var _parallel = function(eachfn, tasks, callback) {\n callback = callback || function () {};\n if (_isArray(tasks)) {\n eachfn.map(tasks, function (fn, callback) {\n if (fn) {\n fn(function (err) {\n var args = Array.prototype.slice.call(arguments, 1);\n if (args.length <= 1) {\n args = args[0];\n }\n callback.call(null, err, args);\n });\n }\n }, callback);\n }\n else {\n var results = {};\n eachfn.each(_keys(tasks), function (k, callback) {\n tasks[k](function (err) {\n var args = Array.prototype.slice.call(arguments, 1);\n if (args.length <= 1) {\n args = args[0];\n }\n results[k] = args;\n callback(err);\n });\n }, function (err) {\n callback(err, results);\n });\n }\n };\n\n async.parallel = function (tasks, callback) {\n _parallel({ map: async.map, each: async.each }, tasks, callback);\n };\n\n async.parallelLimit = function(tasks, limit, callback) {\n _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);\n };\n\n async.series = function (tasks, callback) {\n callback = callback || function () {};\n if (_isArray(tasks)) {\n async.mapSeries(tasks, function (fn, callback) {\n if (fn) {\n fn(function (err) {\n var args = Array.prototype.slice.call(arguments, 1);\n if (args.length <= 1) {\n args = args[0];\n }\n callback.call(null, err, args);\n });\n }\n }, callback);\n }\n else {\n var results = {};\n async.eachSeries(_keys(tasks), function (k, callback) {\n tasks[k](function (err) {\n var args = Array.prototype.slice.call(arguments, 1);\n if (args.length <= 1) {\n args = args[0];\n }\n results[k] = args;\n callback(err);\n });\n }, function (err) {\n callback(err, results);\n });\n }\n };\n\n async.iterator = function (tasks) {\n var makeCallback = function (index) {\n var fn = function () {\n if (tasks.length) {\n tasks[index].apply(null, arguments);\n }\n return fn.next();\n };\n fn.next = function () {\n return (index < tasks.length - 1) ? makeCallback(index + 1): null;\n };\n return fn;\n };\n return makeCallback(0);\n };\n\n async.apply = function (fn) {\n var args = Array.prototype.slice.call(arguments, 1);\n return function () {\n return fn.apply(\n null, args.concat(Array.prototype.slice.call(arguments))\n );\n };\n };\n\n var _concat = function (eachfn, arr, fn, callback) {\n var r = [];\n eachfn(arr, function (x, cb) {\n fn(x, function (err, y) {\n r = r.concat(y || []);\n cb(err);\n });\n }, function (err) {\n callback(err, r);\n });\n };\n async.concat = doParallel(_concat);\n async.concatSeries = doSeries(_concat);\n\n async.whilst = function (test, iterator, callback) {\n if (test()) {\n iterator(function (err) {\n if (err) {\n return callback(err);\n }\n async.whilst(test, iterator, callback);\n });\n }\n else {\n callback();\n }\n };\n\n async.doWhilst = function (iterator, test, callback) {\n iterator(function (err) {\n if (err) {\n return callback(err);\n }\n var args = Array.prototype.slice.call(arguments, 1);\n if (test.apply(null, args)) {\n async.doWhilst(iterator, test, callback);\n }\n else {\n callback();\n }\n });\n };\n\n async.until = function (test, iterator, callback) {\n if (!test()) {\n iterator(function (err) {\n if (err) {\n return callback(err);\n }\n async.until(test, iterator, callback);\n });\n }\n else {\n callback();\n }\n };\n\n async.doUntil = function (iterator, test, callback) {\n iterator(function (err) {\n if (err) {\n return callback(err);\n }\n var args = Array.prototype.slice.call(arguments, 1);\n if (!test.apply(null, args)) {\n async.doUntil(iterator, test, callback);\n }\n else {\n callback();\n }\n });\n };\n\n async.queue = function (worker, concurrency) {\n if (concurrency === undefined) {\n concurrency = 1;\n }\n function _insert(q, data, pos, callback) {\n if (!q.started){\n q.started = true;\n }\n if (!_isArray(data)) {\n data = [data];\n }\n if(data.length == 0) {\n // call drain immediately if there are no tasks\n return async.setImmediate(function() {\n if (q.drain) {\n q.drain();\n }\n });\n }\n _each(data, function(task) {\n var item = {\n data: task,\n callback: typeof callback === 'function' ? callback : null\n };\n\n if (pos) {\n q.tasks.unshift(item);\n } else {\n q.tasks.push(item);\n }\n\n if (q.saturated && q.tasks.length === q.concurrency) {\n q.saturated();\n }\n async.setImmediate(q.process);\n });\n }\n\n var workers = 0;\n var q = {\n tasks: [],\n concurrency: concurrency,\n saturated: null,\n empty: null,\n drain: null,\n started: false,\n paused: false,\n push: function (data, callback) {\n _insert(q, data, false, callback);\n },\n kill: function () {\n q.drain = null;\n q.tasks = [];\n },\n unshift: function (data, callback) {\n _insert(q, data, true, callback);\n },\n process: function () {\n if (!q.paused && workers < q.concurrency && q.tasks.length) {\n var task = q.tasks.shift();\n if (q.empty && q.tasks.length === 0) {\n q.empty();\n }\n workers += 1;\n var next = function () {\n workers -= 1;\n if (task.callback) {\n task.callback.apply(task, arguments);\n }\n if (q.drain && q.tasks.length + workers === 0) {\n q.drain();\n }\n q.process();\n };\n var cb = only_once(next);\n worker(task.data, cb);\n }\n },\n length: function () {\n return q.tasks.length;\n },\n running: function () {\n return workers;\n },\n idle: function() {\n return q.tasks.length + workers === 0;\n },\n pause: function () {\n if (q.paused === true) { return; }\n q.paused = true;\n q.process();\n },\n resume: function () {\n if (q.paused === false) { return; }\n q.paused = false;\n q.process();\n }\n };\n return q;\n };\n \n async.priorityQueue = function (worker, concurrency) {\n \n function _compareTasks(a, b){\n return a.priority - b.priority;\n };\n \n function _binarySearch(sequence, item, compare) {\n var beg = -1,\n end = sequence.length - 1;\n while (beg < end) {\n var mid = beg + ((end - beg + 1) >>> 1);\n if (compare(item, sequence[mid]) >= 0) {\n beg = mid;\n } else {\n end = mid - 1;\n }\n }\n return beg;\n }\n \n function _insert(q, data, priority, callback) {\n if (!q.started){\n q.started = true;\n }\n if (!_isArray(data)) {\n data = [data];\n }\n if(data.length == 0) {\n // call drain immediately if there are no tasks\n return async.setImmediate(function() {\n if (q.drain) {\n q.drain();\n }\n });\n }\n _each(data, function(task) {\n var item = {\n data: task,\n priority: priority,\n callback: typeof callback === 'function' ? callback : null\n };\n \n q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item);\n\n if (q.saturated && q.tasks.length === q.concurrency) {\n q.saturated();\n }\n async.setImmediate(q.process);\n });\n }\n \n // Start with a normal queue\n var q = async.queue(worker, concurrency);\n \n // Override push to accept second parameter representing priority\n q.push = function (data, priority, callback) {\n _insert(q, data, priority, callback);\n };\n \n // Remove unshift function\n delete q.unshift;\n\n return q;\n };\n\n async.cargo = function (worker, payload) {\n var working = false,\n tasks = [];\n\n var cargo = {\n tasks: tasks,\n payload: payload,\n saturated: null,\n empty: null,\n drain: null,\n drained: true,\n push: function (data, callback) {\n if (!_isArray(data)) {\n data = [data];\n }\n _each(data, function(task) {\n tasks.push({\n data: task,\n callback: typeof callback === 'function' ? callback : null\n });\n cargo.drained = false;\n if (cargo.saturated && tasks.length === payload) {\n cargo.saturated();\n }\n });\n async.setImmediate(cargo.process);\n },\n process: function process() {\n if (working) return;\n if (tasks.length === 0) {\n if(cargo.drain && !cargo.drained) cargo.drain();\n cargo.drained = true;\n return;\n }\n\n var ts = typeof payload === 'number'\n ? tasks.splice(0, payload)\n : tasks.splice(0, tasks.length);\n\n var ds = _map(ts, function (task) {\n return task.data;\n });\n\n if(cargo.empty) cargo.empty();\n working = true;\n worker(ds, function () {\n working = false;\n\n var args = arguments;\n _each(ts, function (data) {\n if (data.callback) {\n data.callback.apply(null, args);\n }\n });\n\n process();\n });\n },\n length: function () {\n return tasks.length;\n },\n running: function () {\n return working;\n }\n };\n return cargo;\n };\n\n var _console_fn = function (name) {\n return function (fn) {\n var args = Array.prototype.slice.call(arguments, 1);\n fn.apply(null, args.concat([function (err) {\n var args = Array.prototype.slice.call(arguments, 1);\n if (typeof console !== 'undefined') {\n if (err) {\n if (console.error) {\n console.error(err);\n }\n }\n else if (console[name]) {\n _each(args, function (x) {\n console[name](x);\n });\n }\n }\n }]));\n };\n };\n async.log = _console_fn('log');\n async.dir = _console_fn('dir');\n /*async.info = _console_fn('info');\n async.warn = _console_fn('warn');\n async.error = _console_fn('error');*/\n\n async.memoize = function (fn, hasher) {\n var memo = {};\n var queues = {};\n hasher = hasher || function (x) {\n return x;\n };\n var memoized = function () {\n var args = Array.prototype.slice.call(arguments);\n var callback = args.pop();\n var key = hasher.apply(null, args);\n if (key in memo) {\n async.nextTick(function () {\n callback.apply(null, memo[key]);\n });\n }\n else if (key in queues) {\n queues[key].push(callback);\n }\n else {\n queues[key] = [callback];\n fn.apply(null, args.concat([function () {\n memo[key] = arguments;\n var q = queues[key];\n delete queues[key];\n for (var i = 0, l = q.length; i < l; i++) {\n q[i].apply(null, arguments);\n }\n }]));\n }\n };\n memoized.memo = memo;\n memoized.unmemoized = fn;\n return memoized;\n };\n\n async.unmemoize = function (fn) {\n return function () {\n return (fn.unmemoized || fn).apply(null, arguments);\n };\n };\n\n async.times = function (count, iterator, callback) {\n var counter = [];\n for (var i = 0; i < count; i++) {\n counter.push(i);\n }\n return async.map(counter, iterator, callback);\n };\n\n async.timesSeries = function (count, iterator, callback) {\n var counter = [];\n for (var i = 0; i < count; i++) {\n counter.push(i);\n }\n return async.mapSeries(counter, iterator, callback);\n };\n\n async.seq = function (/* functions... */) {\n var fns = arguments;\n return function () {\n var that = this;\n var args = Array.prototype.slice.call(arguments);\n var callback = args.pop();\n async.reduce(fns, args, function (newargs, fn, cb) {\n fn.apply(that, newargs.concat([function () {\n var err = arguments[0];\n var nextargs = Array.prototype.slice.call(arguments, 1);\n cb(err, nextargs);\n }]))\n },\n function (err, results) {\n callback.apply(that, [err].concat(results));\n });\n };\n };\n\n async.compose = function (/* functions... */) {\n return async.seq.apply(null, Array.prototype.reverse.call(arguments));\n };\n\n var _applyEach = function (eachfn, fns /*args...*/) {\n var go = function () {\n var that = this;\n var args = Array.prototype.slice.call(arguments);\n var callback = args.pop();\n return eachfn(fns, function (fn, cb) {\n fn.apply(that, args.concat([cb]));\n },\n callback);\n };\n if (arguments.length > 2) {\n var args = Array.prototype.slice.call(arguments, 2);\n return go.apply(this, args);\n }\n else {\n return go;\n }\n };\n async.applyEach = doParallel(_applyEach);\n async.applyEachSeries = doSeries(_applyEach);\n\n async.forever = function (fn, callback) {\n function next(err) {\n if (err) {\n if (callback) {\n return callback(err);\n }\n throw err;\n }\n fn(next);\n }\n next();\n };\n\n // Node.js\n if (typeof module !== 'undefined' && module.exports) {\n module.exports = async;\n }\n // AMD / RequireJS\n else if (typeof define !== 'undefined' && define.amd) {\n define([], function () {\n return async;\n });\n }\n // included directly via \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\"\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]],\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.\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// a=ice-options:foo bar\nSDPUtils.parseIceOptions = function(line) {\n return line.substr(14).split(' ');\n};\n\n// Parses an 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// Generate an 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 an 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 a=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 an ftmp 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 an a=ftmp 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]) {\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 an 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// 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 an 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\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\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],\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.\n // Note2: 'algorithm' is not case sensitive except 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","/**\r\n * The errors for the conference.\r\n */\r\n\r\n/**\r\n * Indicates that client must be authenticated to create the conference.\r\n */\r\nexport const AUTHENTICATION_REQUIRED = 'conference.authenticationRequired';\r\n\r\n/**\r\n * Indicates that chat error occurred.\r\n */\r\nexport const CHAT_ERROR = 'conference.chatError';\r\n\r\n/**\r\n * Indicates that a settings error occurred.\r\n */\r\nexport const SETTINGS_ERROR = 'conference.settingsError';\r\n\r\n/**\r\n * Indicates that conference has been destroyed.\r\n */\r\nexport const CONFERENCE_DESTROYED = 'conference.destroyed';\r\n\r\n/**\r\n * Indicates that max users limit has been reached.\r\n */\r\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const CONFERENCE_ACCESS_DENIED = 'conference.connectionError.accessDenied';\r\n\r\n/**\r\n * Indicates that focus error happened.\r\n */\r\nexport const FOCUS_DISCONNECTED = 'conference.focusDisconnected';\r\n\r\n/**\r\n * Indicates that focus left the conference.\r\n */\r\nexport const FOCUS_LEFT = 'conference.focusLeft';\r\n\r\n/**\r\n * Indicates that graceful shutdown happened.\r\n */\r\nexport const GRACEFUL_SHUTDOWN = 'conference.gracefulShutdown';\r\n\r\n/**\r\n * Indicates that the media connection has failed.\r\n */\r\nexport const 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\nexport const INCOMPATIBLE_SERVER_VERSIONS\r\n = 'conference.incompatible_server_versions';\r\n\r\n/**\r\n * Indicates that offer/answer had failed.\r\n */\r\nexport const OFFER_ANSWER_FAILED = 'conference.offerAnswerFailed';\r\n\r\n/**\r\n * Indicates that password cannot be set for this conference.\r\n */\r\nexport const 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\nexport const PASSWORD_REQUIRED = 'conference.passwordRequired';\r\n\r\n/**\r\n * Indicates that reservation system returned error.\r\n */\r\nexport const RESERVATION_ERROR = 'conference.reservationError';\r\n\r\n/**\r\n * Indicates that there is no available videobridge.\r\n */\r\nexport const VIDEOBRIDGE_NOT_AVAILABLE = 'conference.videobridgeNotAvailable';\r\n","/**\r\n * The events for the conference.\r\n */\r\n\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\nexport const 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\nexport const AUDIO_UNMUTE_PERMISSIONS_CHANGED = 'conference.audio_unmute_permissions_changed';\r\n\r\n/**\r\n * Indicates that authentication status changed.\r\n */\r\nexport const 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\nexport const BEFORE_STATISTICS_DISPOSED = 'conference.beforeStatisticsDisposed';\r\n\r\n/**\r\n * Indicates that an error occured.\r\n */\r\nexport const CONFERENCE_ERROR = 'conference.error';\r\n\r\n/**\r\n * Indicates that conference failed.\r\n */\r\nexport const CONFERENCE_FAILED = 'conference.failed';\r\n\r\n/**\r\n * Indicates that conference is in progress of joining.\r\n */\r\nexport const 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\nexport const CONFERENCE_JOINED = 'conference.joined';\r\n\r\n/**\r\n * Indicates that conference has been left.\r\n */\r\nexport const CONFERENCE_LEFT = 'conference.left';\r\n\r\n/**\r\n * Indicates that the conference unique identifier has been set.\r\n */\r\nexport const 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 whenVthe *ICE* connection enters 'connected'\r\n * state for the first time.\r\n */\r\nexport const 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\nexport const 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\nexport const 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\nexport const DATA_CHANNEL_OPENED = 'conference.dataChannelOpened';\r\n\r\n/**\r\n * A user has changed it display name\r\n */\r\nexport const DISPLAY_NAME_CHANGED = 'conference.displayNameChanged';\r\n\r\n/**\r\n * The dominant speaker was changed.\r\n */\r\nexport const DOMINANT_SPEAKER_CHANGED = 'conference.dominantSpeaker';\r\n\r\n/**\r\n * UTC conference timestamp when first participant joined.\r\n */\r\nexport const CONFERENCE_CREATED_TIMESTAMP = 'conference.createdTimestamp';\r\n\r\n/**\r\n * Indicates that DTMF support changed.\r\n */\r\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const LAST_N_ENDPOINTS_CHANGED = 'conference.lastNEndpointsChanged';\r\n\r\n/**\r\n * Indicates that the room has been locked or unlocked.\r\n */\r\nexport const 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\nexport const 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\nexport const _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\nexport const _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\nexport const MEMBERS_ONLY_CHANGED = 'conference.membersOnlyChanged';\r\n\r\n/**\r\n * New text message was received.\r\n */\r\nexport const MESSAGE_RECEIVED = 'conference.messageReceived';\r\n\r\n/**\r\n * Event indicates that the current selected input device has no signal\r\n */\r\nexport const 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\nexport const 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\nexport const NON_PARTICIPANT_MESSAGE_RECEIVED = 'conference.non_participant_message_received';\r\n\r\n/**\r\n * New private text message was received.\r\n */\r\nexport const 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\nexport const PARTICIPANT_CONN_STATUS_CHANGED\r\n = 'conference.participant_conn_status_changed';\r\n\r\n/**\r\n * Indicates that the features of the participant has been changed.\r\n */\r\nexport const PARTCIPANT_FEATURES_CHANGED\r\n = '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\nexport const PARTICIPANT_PROPERTY_CHANGED\r\n = '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\nexport const P2P_STATUS = 'conference.p2pStatus';\r\n\r\n/**\r\n * Indicates that phone number changed.\r\n */\r\nexport const PHONE_NUMBER_CHANGED = 'conference.phoneNumberChanged';\r\n\r\n/**\r\n * The conference properties changed.\r\n * @type {string}\r\n */\r\nexport const PROPERTIES_CHANGED = 'conference.propertiesChanged';\r\n\r\n/**\r\n * Indicates that recording state changed.\r\n */\r\nexport const 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\nexport const VIDEO_SIP_GW_AVAILABILITY_CHANGED\r\n = '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\nexport const VIDEO_SIP_GW_SESSION_STATE_CHANGED\r\n = 'conference.videoSIPGWSessionStateChanged';\r\n\r\n/**\r\n * Indicates that start muted settings changed.\r\n */\r\nexport const START_MUTED_POLICY_CHANGED\r\n = 'conference.start_muted_policy_changed';\r\n\r\n/**\r\n * Indicates that the local user has started muted.\r\n */\r\nexport const STARTED_MUTED = 'conference.started_muted';\r\n\r\n/**\r\n * Indicates that subject of the conference has changed.\r\n */\r\nexport const SUBJECT_CHANGED = 'conference.subjectChanged';\r\n\r\n/**\r\n * Indicates that DTMF support changed.\r\n */\r\nexport const SUSPEND_DETECTED = 'conference.suspendDetected';\r\n\r\n/**\r\n * Event indicates that local user is talking while he muted himself\r\n */\r\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const TRANSCRIPTION_STATUS_CHANGED\r\n = 'conference.transcriptionStatusChanged';\r\n\r\n\r\n/**\r\n * A new user joined the conference.\r\n */\r\nexport const USER_JOINED = 'conference.userJoined';\r\n\r\n/**\r\n * A user has left the conference.\r\n */\r\nexport const USER_LEFT = 'conference.userLeft';\r\n\r\n/**\r\n * User role changed.\r\n */\r\nexport const USER_ROLE_CHANGED = 'conference.roleChanged';\r\n\r\n/**\r\n * User status changed.\r\n */\r\nexport const 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\nexport const 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\nexport const BOT_TYPE_CHANGED = 'conference.bot_type_changed';\r\n\r\n/**\r\n * A new user joined the lobby room.\r\n */\r\nexport const LOBBY_USER_JOINED = 'conference.lobby.userJoined';\r\n\r\n/**\r\n * A user from the lobby room has been update.\r\n */\r\nexport const LOBBY_USER_UPDATED = 'conference.lobby.userUpdated';\r\n\r\n/**\r\n * A user left the lobby room.\r\n */\r\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const AV_MODERATION_PARTICIPANT_REJECTED = 'conference.av_moderation.participant.rejected';\r\n\r\n/**\r\n * A new facial expression is added with its duration for a participant\r\n */\r\nexport const FACIAL_EXPRESSION_ADDED = 'conference.facial_expression.added';\r\n\r\n/**\r\n * Event fired when a participant is requested to join a given (breakout) room.\r\n */\r\nexport const 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\nexport const BREAKOUT_ROOMS_UPDATED = 'conference.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\n/**\r\n * An error which indicates that some of requested constraints in\r\n * getUserMedia call were not satisfied.\r\n */\r\nexport const 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\nexport const ELECTRON_DESKTOP_PICKER_ERROR\r\n = '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\nexport const ELECTRON_DESKTOP_PICKER_NOT_FOUND\r\n = 'gum.electron_desktop_picker_not_found';\r\n\r\n/**\r\n * Generic getUserMedia error.\r\n */\r\nexport const GENERAL = 'gum.general';\r\n\r\n/**\r\n * An error which indicates that requested device was not found.\r\n */\r\nexport const 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\nexport const PERMISSION_DENIED = 'gum.permission_denied';\r\n\r\n/**\r\n * Generic error for screensharing failure.\r\n */\r\nexport const SCREENSHARING_GENERIC_ERROR\r\n = '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\nexport const SCREENSHARING_USER_CANCELED\r\n = 'gum.screensharing_user_canceled';\r\n\r\n\r\n/**\r\n * Indicates that the timeout passed to the obtainAudioAndVideoPermissions has expired without GUM resolving.\r\n */\r\nexport const 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\nexport const 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\nexport const 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\nexport const UNSUPPORTED_RESOLUTION = 'gum.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\n/**\r\n * The constant which identifies an event of type \"operational\".\r\n * @type {string}\r\n */\r\nexport const TYPE_OPERATIONAL = 'operational';\r\n\r\n/**\r\n * The constant which identifies an event of type \"page\".\r\n * @type {string}\r\n */\r\nexport const TYPE_PAGE = 'page';\r\n\r\n/**\r\n * The constant which identifies an event of type \"track\".\r\n * @type {string}\r\n */\r\nexport const TYPE_TRACK = 'track';\r\n\r\n/**\r\n * The constant which identifies an event of type \"ui\".\r\n * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const ACTION_JINGLE_TR_RECEIVED\r\n = '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 * @type {string}\r\n */\r\nexport const ACTION_JINGLE_TR_SUCCESS\r\n = '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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const ACTION_P2P_ESTABLISHED = 'established';\r\n\r\n/**\r\n * The \"action\" value for P2P events which indicates that something failed.\r\n * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const ICE_ESTABLISHMENT_DURATION_DIFF\r\n = '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\nexport const 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\nexport const 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\nexport const TRACK_UNMUTED = '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 = function() {\r\n const bridgeDown = 'bridge.down';\r\n\r\n return {\r\n action: bridgeDown,\r\n actionSubject: bridgeDown,\r\n type: TYPE_OPERATIONAL\r\n };\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\r\n = function(errorType, errorMessage, details) {\r\n return {\r\n type: 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/**\r\n * Creates a conference event.\r\n *\r\n * @param {string} action - The action of the event.\r\n * @param {Object} attributes - The attributes to be added to the event.\r\n * @returns {{type: string, source: string, action: string, attributes: object}}\r\n */\r\nexport function createConferenceEvent(action, attributes) {\r\n return {\r\n action,\r\n attributes,\r\n source: 'conference',\r\n type: TYPE_OPERATIONAL\r\n };\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 = function(stage, attributes) {\r\n const action = 'connection.stage.reached';\r\n\r\n return {\r\n action,\r\n actionSubject: stage,\r\n attributes,\r\n source: action,\r\n type: TYPE_OPERATIONAL\r\n };\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 = function(participantId, region, rtt) {\r\n const attributes = {\r\n 'participant_id': participantId,\r\n region,\r\n rtt\r\n };\r\n\r\n return {\r\n attributes,\r\n name: 'e2e_rtt',\r\n type: TYPE_OPERATIONAL\r\n };\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 = function() {\r\n const action = 'focus.left';\r\n\r\n return {\r\n action,\r\n actionSubject: action,\r\n type: TYPE_OPERATIONAL\r\n };\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 * @returns {{type: string, source: string, name: string}}\r\n */\r\nexport const createGetUserMediaEvent = function(action, attributes = {}) {\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n source: 'get.user.media',\r\n action,\r\n attributes\r\n };\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 * @returns {{type: string, source: string, name: string}}\r\n */\r\nexport const createParticipantConnectionStatusEvent = function(attributes = {}) {\r\n const action = 'duration';\r\n\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n source: 'peer.conn.status',\r\n action,\r\n attributes\r\n };\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 = function(action, attributes = {}) {\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n action,\r\n source: 'jingle',\r\n attributes\r\n };\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 {String} the media type of the local track ('audio' or\r\n * 'video').\r\n */\r\nexport const createNoDataFromSourceEvent = function(mediaType, value) {\r\n return {\r\n attributes: {\r\n 'media_type': mediaType,\r\n value\r\n },\r\n action: 'track.no.data.from.source',\r\n type: TYPE_OPERATIONAL\r\n };\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 = function(action, attributes = {}) {\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n action,\r\n source: 'p2p',\r\n attributes\r\n };\r\n};\r\n\r\n/**\r\n * Indicates that we received a remote command to mute.\r\n */\r\nexport const createRemotelyMutedEvent = function(mediaType) {\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n action: 'remotely.muted',\r\n mediaType\r\n };\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 = function(attributes) {\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n action: 'rtp.stats',\r\n attributes\r\n };\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 * @returns {{type: string, action: string, attributes: *}}\r\n */\r\nexport const createRttByRegionEvent = function(attributes) {\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n action: 'rtt.by.region',\r\n attributes\r\n };\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 * @returns {{type: string, action: string, attributes: *}}\r\n */\r\nexport const createTransportStatsEvent = function(attributes) {\r\n return {\r\n type: TYPE_OPERATIONAL,\r\n action: 'transport.stats',\r\n attributes\r\n };\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 {string} 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 function createAudioOutputProblemEvent(userID, localAudioLevels, remoteAudioLevels) {\r\n return {\r\n type: 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/**\r\n * Creates an event which contains an information related to the bridge channel close event.\r\n *\r\n * @param {string} code - A code from {@link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent}\r\n * @param {string} reason - A string which describes the reason for closing the bridge channel.\r\n * @returns {{type: string, action: string, attributes: { code: string, reason: string }}}\r\n */\r\nexport const createBridgeChannelClosedEvent = function(code, reason) {\r\n return {\r\n type: 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/**\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 = function(attributes) {\r\n return createConnectionStageReachedEvent('ttfm', attributes);\r\n};\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\nexport const 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\nexport const BEFORE_DISPOSED = 'statistics.before_disposed';\r\n\r\n/**\r\n * An event carrying all statistics by ssrc.\r\n */\r\nexport const 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\nexport const CONNECTION_STATS = 'statistics.connectionstats';\r\n\r\n/**\r\n * An event carrying performance stats.\r\n */\r\nexport const LONG_TASKS_STATS = 'statistics.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 return !this.isReactNative();\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","/**\r\n * The audio type.\r\n */\r\nexport const AUDIO = 'audio';\r\n\r\n/**\r\n * The presenter type.\r\n */\r\nexport const PRESENTER = 'presenter';\r\n\r\n/**\r\n * The video type.\r\n */\r\nexport const VIDEO = 'video';\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as MediaType from '../../service/RTC/MediaType';\r\nimport * as StatisticsEvents from '../../service/statistics/Events';\r\nimport browser from '../browser';\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 const participantId = track.getParticipantId();\r\n\r\n if (participantId) {\r\n const resolution = ssrcStats.resolution;\r\n\r\n if (resolution.width\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) {\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) {\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 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.getLocalSSRC(localVideoTracks[0]);\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 * as 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\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\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\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","/**\r\n * The media track was removed to the conference.\r\n */\r\nexport const 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\nexport const TRACK_AUDIO_LEVEL_CHANGED = 'track.audioLevelsChanged';\r\n\r\n/**\r\n * The audio output of the track was changed.\r\n */\r\nexport const TRACK_AUDIO_OUTPUT_CHANGED = 'track.audioOutputChanged';\r\n\r\n/**\r\n * A media track mute status was changed.\r\n */\r\nexport const 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\nexport const 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\nexport const 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\nexport const NO_AUDIO_INPUT = 'track.no_audio_input';\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport * as 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 * as 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} Wheter this participants 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 * The events for the connection.\r\n */\r\n\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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const DISPLAY_NAME_REQUIRED = 'connection.display_name_required';\r\n","/**\r\n * The errors for the connection.\r\n */\r\n\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\nexport const CONNECTION_DROPPED_ERROR = 'connection.droppedError';\r\n\r\n/**\r\n * Not specified errors.\r\n */\r\nexport const 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\nexport const 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\nexport const SERVER_ERROR = 'connection.serverError';\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","/**\r\n * The transciption is on.\r\n *\r\n * @type {String}\r\n */\r\nexport const ON = 'on';\r\n\r\n/**\r\n * The transciption is off.\r\n *\r\n * @type {String}\r\n */\r\nexport const OFF = 'off';\r\n","import { getLogger } from '@jitsi/logger';\r\nimport { $msg } from 'strophe.js';\r\n\r\nimport * as 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 } 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 return this._isBreakoutRoom;\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 * 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 * as MediaType from '../../service/RTC/MediaType';\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 */\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 for (const tag of [ 'id', 'name', 'avatar' ]) {\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 nick: Strophe.getResourceFromJid(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': 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: undefined // no video type 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","import { getLogger } from '@jitsi/logger';\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.sourceNameSignaling - Enables source names in the signaling.\r\n */\r\n init(flags) {\r\n this._sourceNameSignaling = Boolean(flags.sourceNameSignaling);\r\n\r\n logger.info(`Source name signaling: ${this._sourceNameSignaling}`);\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\nexport default new FeatureFlags();\r\n","/* global $ */\r\n\r\nimport { getLogger } from '@jitsi/logger';\r\nimport { $build } from 'strophe.js';\r\n\r\nimport * as 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\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 video = typeof desktopSharingFrameRate === 'object' ? { frameRate: desktopSharingFrameRate } : true;\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 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 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 * 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","import { getLogger } from '@jitsi/logger';\r\nimport async 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 = async.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","/**\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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const ENDED = '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","export default {\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","/* 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 {\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 /* 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 * 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 * 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 * 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\r\n = $(contents).find(\r\n '>description>'\r\n + '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 (this.isP2P) {\r\n // In P2P all SSRCs are owner by the remote peer\r\n this._signalingLayer.setSSRCOwner(\r\n ssrc, Strophe.getResourceFromJid(this.remoteJid));\r\n } else {\r\n if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n // Only set sourceName for non-P2P case\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 $(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 && 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(\r\n ssrc,\r\n 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\r\n this.room.eventEmitter.emit(XMPPEvents.SESSION_ACCEPT, this);\r\n },\r\n error => {\r\n failure(error);\r\n\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 localTrack of localTracks) {\r\n addTracks.push(this.peerconnection.addTrack(localTrack, 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\r\n for (const track of localTracks) {\r\n addTracks.push(this.peerconnection.addTrack(track, this.isInitiator));\r\n }\r\n\r\n const newRemoteSdp\r\n = this._processNewJingleOfferIq(jingleOfferAnswerIq);\r\n const oldLocalSdp\r\n = 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\r\n // the initial O/A cycle. We want to adjust the video\r\n // media direction only in the local SDP and the Jingle\r\n // contents direction included in the initial\r\n // offer/answer is mapped to the remote SDP. Jingle\r\n // 'content-modify' IQ is processed in a way that it\r\n // will only modify local SDP when remote peer is no\r\n // longer interested in receiving video content.\r\n // Changing media direction in the remote SDP will mess\r\n // up our SDP translation chain (simulcast, video mute,\r\n // RTX etc.)\r\n //\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 || this.localRecvMaxFrameHeight)) {\r\n this.sendContentModify();\r\n }\r\n }\r\n\r\n // Old local SDP will be available when we're setting answer\r\n // for the first time, but not when offer and it's fine\r\n // since we're generating an answer now it will contain all\r\n // our SSRCs\r\n if (oldLocalSdp) {\r\n const newLocalSdp\r\n = new SDP(this.peerconnection.localDescription.sdp);\r\n\r\n this.notifyMySSRCUpdate(\r\n 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: '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 }\r\n\r\n logger.info(`${this} sending content-modify, video senders: ${senders}, max frame height: ${maxFrameHeight}`);\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 */\r\n setReceiverVideoConstraint(maxFrameHeight) {\r\n logger.info(`${this} setReceiverVideoConstraint - max frame height: ${maxFrameHeight}`);\r\n\r\n this.localRecvMaxFrameHeight = maxFrameHeight;\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 } else {\r\n this.rtc.setReceiverVideoConstraint(maxFrameHeight);\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 * @returns {Promise} promise that will be resolved when the operation is\r\n * successful and rejected otherwise.\r\n */\r\n setSenderVideoConstraint(maxFrameHeight) {\r\n if (this._assertNotEnded()) {\r\n logger.info(`${this} setSenderVideoConstraint: ${maxFrameHeight}`);\r\n\r\n // RN doesn't support RTCRtpSenders yet, aggresive layer suspension on RN is implemented\r\n // by changing the media direction in the SDP. This is applicable to jvb sessions only.\r\n if (!this.isP2P && browser.isReactNative() && typeof maxFrameHeight !== 'undefined') {\r\n const videoActive = maxFrameHeight > 0;\r\n\r\n return this.setMediaTransferActive(true, videoActive);\r\n }\r\n\r\n const promise = typeof maxFrameHeight === 'undefined'\r\n ? this.peerconnection.configureSenderVideoEncodings()\r\n : this.peerconnection.setSenderVideoConstraints(maxFrameHeight);\r\n\r\n return promise;\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 /* 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 });\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 the remote tracks and SSRCs associated with a remote endpoint.\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 this.peerconnection.removeRemoteTracks(id);\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 lines.forEach(line => {\r\n const mid = remoteSdp.media.findIndex(mLine => mLine.includes(line));\r\n\r\n if (mid > -1) {\r\n const mediaType = SDPUtil.parseMLine(remoteSdp.media[mid].split('\\r\\n')[0])?.media;\r\n\r\n if (this.isP2P) {\r\n // Do not remove ssrcs from m-line in p2p mode. If the ssrc is removed and added back to\r\n // the same m-line (on source-add), Chrome/Safari do not render the media even if it is\r\n // being received and decoded from the remote peer. The webrtc spec is not clear about\r\n // m-line re-use and the browser vendors have implemented this differently. Currently work\r\n // around this by changing the media direction, that should be enough for the browser to\r\n // fire the \"removetrack\" event on the associated MediaStream. Also, the current direction\r\n // of the transceiver for p2p will depend on whether a local sources is added or not. It\r\n // will be 'sendrecv' if the local source is present, 'sendonly' otherwise.\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 // Change the port to 0 to reject the m-line associated with the source. The rejected\r\n // m-lines are recycled when new ssrcs need to be added to the remote description.\r\n const port = SDPUtil.parseMLine(remoteSdp.media[mid].split('\\r\\n')[0])?.port;\r\n\r\n remoteSdp.media[mid] = remoteSdp.media[mid].replace(`${line}\\r\\n`, '');\r\n remoteSdp.media[mid] = remoteSdp.media[mid].replace(\r\n `m=${mediaType} ${port}`,\r\n `m=${mediaType} 0`);\r\n }\r\n }\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 const remoteSdp = new SDP(this.peerconnection.remoteDescription.sdp);\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 * 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 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();\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();\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\r\n = JingleSessionPC.parseVideoSenders(jingleContents);\r\n const newMaxFrameHeight\r\n = JingleSessionPC.parseMaxFrameHeight(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(\r\n MediaSessionEvents.REMOTE_VIDEO_CONSTRAINTS_CHANGED, this);\r\n }\r\n\r\n if (newVideoSenders === null) {\r\n logger.error(\r\n `${this} - failed to parse video \"senders\" attribute in`\r\n + '\"content-modify\" action');\r\n\r\n return;\r\n }\r\n\r\n const workFunction = finishedCallback => {\r\n if (this._assertNotEnded('content-modify')\r\n && this._modifyRemoteVideoActive(newVideoSenders)) {\r\n // Will do the sRD/sLD cycle to update SDPs and adjust\r\n // 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(\r\n 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 const removedAnySSRCs = 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\r\n if (removedAnySSRCs) {\r\n const sourceInfo = getSignaledSourceInfo(sdpDiffer);\r\n\r\n // Log only the SSRCs instead of the full IQ.\r\n logger.info(`${this} Sending source-remove for ${sourceInfo.mediaType} ssrcs=${sourceInfo.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 const containsNewSSRCs = sdpDiffer.toJingle(add);\r\n\r\n if (containsNewSSRCs) {\r\n const sourceInfo = getSignaledSourceInfo(sdpDiffer);\r\n\r\n // Log only the SSRCs instead of the full IQ.\r\n logger.info(`${this} Sending source-add for ${sourceInfo.mediaType} ssrcs=${sourceInfo.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, sourceInfo.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 * as 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 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 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\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 === '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.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 = onCreateResource\r\n ? onCreateResource(this.connection.jid, this.authenticatedUser)\r\n : RandomUtil.randomHexString(8).toLowerCase();\r\n\r\n logger.info(`JID ${this.connection.jid} using MUC nickname ${mucNickname}`);\r\n roomjid += mucNickname;\r\n\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 facial expression 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 sendFacialExpressionEvent(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('facialExpression', {\r\n xmlns: 'http://jitsi.org/jitmeet',\r\n room: roomJid,\r\n expression: payload.facialExpression,\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 logObject.id = 'deployment_info';\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 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 * as 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 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 // The new/latest list of last-n endpoint IDs (i.e. endpoints for which the bridge is sending 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 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 '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 = browser.isChromiumBased() || browser.isReactNative()\r\n ? { optional: [\r\n { googScreencastMinBitrate: 100 },\r\n { googCpuOveruseDetection: true }\r\n ] }\r\n : {};\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(error);\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 // Calling getUserMedia again (for preview) kills the track returned by the first getUserMedia call because of\r\n // https://bugs.webkit.org/show_bug.cgi?id=179363. Therefore, do not show microphone/camera options on mobile\r\n // Safari.\r\n if ((deviceType === 'audioinput' || deviceType === 'input') && browser.isIosBrowser()) {\r\n return false;\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\n * Configures the given PeerConnection constraints to either enable or\r\n * disable (according to the value of the 'enable' parameter) the\r\n * 'googSuspendBelowMinBitrate' option.\r\n * @param constraints the constraints on which to operate.\r\n * @param enable {boolean} whether to enable or disable the suspend video\r\n * option.\r\n */\r\n setSuspendVideo(constraints, enable) {\r\n if (!constraints.optional) {\r\n constraints.optional = [];\r\n }\r\n\r\n // Get rid of all \"googSuspendBelowMinBitrate\" constraints (we assume\r\n // that the elements of constraints.optional contain a single property).\r\n constraints.optional\r\n = constraints.optional.filter(\r\n c => !c.hasOwnProperty('googSuspendBelowMinBitrate'));\r\n\r\n if (enable) {\r\n constraints.optional.push({ googSuspendBelowMinBitrate: 'true' });\r\n }\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 * as 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 * as 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 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 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 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 if (this.isAudioTrack()\r\n || this.videoType === VideoType.DESKTOP\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 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(\r\n 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 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 let device = devices.find(d => d.kind === kind && d.label === track.label);\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 if (this.conference) {\r\n promise = promise.then(() => 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.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 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 * 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 * 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 * Returns a rejected m-line (with port 0) if its exists in the provided description.\n * @param {Object} desc - the current remote description.\n * @param {String} mediaType - media type.\n * @returns {Object}\n */\nfunction checkForRejectedMlines(desc, mediaType) {\n if (!desc || !mediaType) {\n return null;\n }\n\n return desc.media.find(m => m.port === 0 && m.type === mediaType);\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 * 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 if (mLine.sources && Array.isArray(mLine.sources)) {\n media[type].sources = (media[type].sources || []).concat(mLine.sources);\n }\n if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) {\n media[type].ssrcGroups = media[type].ssrcGroups.concat(mLine.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 if the m-line for the ssrcs already exists.\n if (checkIfMlineForSsrcExists(ssrc, ssrc2group, currentDesc.media)) {\n return;\n }\n\n // Check if a rejected m-line with port 0 exists and recycle it if so.\n const existingMline = checkForRejectedMlines(currentDesc, type);\n\n if (existingMline) {\n existingMline.port = mLine.port;\n existingMline.direction = 'sendonly';\n addSourcesToMline(existingMline, ssrc, ssrc2group, mLine.sources);\n\n return;\n }\n\n // Append a new m-line.\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","/**\r\n * Event triggered when participant's muted status changes.\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\nexport const PEER_MUTED_CHANGED = 'signaling.peerMuted';\r\n\r\n/**\r\n * Event triggered when participant's video type changes.\r\n * @param {string} endpointId the video owner's ID (MUC nickname)\r\n * @param {VideoType} videoType the new value\r\n */\r\nexport const PEER_VIDEO_TYPE_CHANGED = 'signaling.peerVideoType';\r\n","\r\nimport Listenable from '../../modules/util/Listenable';\r\nimport * as 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 the first media SDP of given name.\r\n * @param {string} mediaType the name of the media e.g. 'audio', 'video',\r\n * 'data'.\r\n * @return {MLineWrap|null} return {@link MLineWrap} instance for the media\r\n * line or null if not found. The object returned references\r\n * the underlying SDP state held by this SdpTransformWrap instance\r\n * (it's not a copy).\r\n */\r\n selectMedia(mediaType) {\r\n const selectedMLine\r\n = this.parsedSDP.media.find(mLine => mLine.type === mediaType);\r\n\r\n return selectedMLine ? new MLineWrap(selectedMLine) : 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 * as 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 }\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('video');\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.warn(`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\r\n * the id of {@link LocalSdpMunger#tpc} at the end, preceding by a dash\r\n * sign.\r\n *\r\n * @param {MLineWrap} mediaSection - The media part (audio or video) of the\r\n * session description which will be modified in place.\r\n * @returns {void}\r\n * @private\r\n */\r\n _transformMediaIdentifiers(mediaSection) {\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 if (streamAndTrackIDs.length === 2) {\r\n ssrcLine.value\r\n = this._generateMsidAttribute(\r\n mediaSection.mLine?.type,\r\n streamAndTrackIDs[1],\r\n streamAndTrackIDs[0]);\r\n } else {\r\n logger.warn(`Unable to munge local MSID - weird format detected: ${ssrcLine.value}`);\r\n }\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) {\r\n const generatedMsid = this._generateMsidAttribute(mediaSection.mLine?.type, 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('audio');\r\n\r\n if (audioMLine) {\r\n this._transformMediaIdentifiers(audioMLine);\r\n this._injectSourceNames(audioMLine);\r\n }\r\n\r\n const videoMLine = transformer.selectMedia('video');\r\n\r\n if (videoMLine) {\r\n this._transformMediaIdentifiers(videoMLine);\r\n this._injectSourceNames(videoMLine);\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\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, 0)\r\n });\r\n }\r\n }\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\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\r\n * already have them. If the video ssrc has been\r\n * seen before, and already had an RTX ssrc generated,\r\n * the same RTX ssrc will be used again.\r\n * @param {string} sdpStr sdp in raw string format\r\n */\r\n modifyRtxSsrcs(sdpStr) {\r\n const sdpTransformer = new SdpTransformWrap(sdpStr);\r\n const videoMLine = sdpTransformer.selectMedia('video');\r\n\r\n if (!videoMLine) {\r\n logger.debug(`No 'video' media found in the sdp: ${sdpStr}`);\r\n\r\n return sdpStr;\r\n }\r\n\r\n return this.modifyRtxSsrcs2(videoMLine)\r\n ? sdpTransformer.toRawSDP() : sdpStr;\r\n }\r\n\r\n /**\r\n * Does the same thing as {@link modifyRtxSsrcs}, but takes the\r\n * {@link MLineWrap} instance wrapping video media as an argument.\r\n * @param {MLineWrap} videoMLine\r\n * @return {boolean} true if the SDP wrapped by\r\n * {@link SdpTransformWrap} has been modified or false otherwise.\r\n */\r\n modifyRtxSsrcs2(videoMLine) {\r\n if (videoMLine.direction === 'recvonly') {\r\n\r\n return false;\r\n }\r\n if (videoMLine.getSSRCCount() < 1) {\r\n\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 * @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 videoMLine = sdpTransformer.selectMedia('video');\r\n\r\n if (!videoMLine) {\r\n logger.debug(`No 'video' media found in the sdp: ${sdpStr}`);\r\n\r\n return sdpStr;\r\n }\r\n if (videoMLine.direction === 'recvonly') {\r\n logger.debug('RtxModifier doing nothing, video m line is recvonly');\r\n\r\n return sdpStr;\r\n }\r\n if (videoMLine.getSSRCCount() < 1) {\r\n logger.debug('RtxModifier doing nothing, no video ssrcs present');\r\n\r\n return sdpStr;\r\n }\r\n if (!videoMLine.containsAnySSRCGroups()) {\r\n logger.debug('RtxModifier doing nothing, '\r\n + 'no video ssrcGroups present');\r\n\r\n return sdpStr;\r\n }\r\n const fidGroups = videoMLine.findGroups('FID');\r\n\r\n // Remove the fid groups from the mline\r\n\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 return sdpTransformer.toRawSDP();\r\n }\r\n}\r\n","import { getLogger } from '@jitsi/logger';\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('video');\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 * as JitsiTrackEvents from '../../JitsiTrackEvents';\r\nimport { createTtfmEvent } from '../../service/statistics/AnalyticsEvents';\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\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 * 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 * 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 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 * 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 * as MediaType from '../../service/RTC/MediaType';\r\nimport VideoType from '../../service/RTC/VideoType';\r\nimport browser from '../browser';\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 // 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: [ localTrack.getOriginalStream() ],\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 && 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 * @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 when done.\r\n */\r\n replaceTrack(oldTrack, newTrack) {\r\n const mediaType = newTrack?.getType() ?? oldTrack?.getType();\r\n const transceiver = this.findTransceiver(mediaType, oldTrack);\r\n const track = newTrack?.getTrack() ?? null;\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 }\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 if (idx === 0 && 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 * as 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 * as 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 { 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 // FIXME: We should support multiple streams per jid.\r\n /**\r\n * The map holds remote tracks associated with this peer connection.\r\n * It maps user's JID to media type and remote track\r\n * (one track per media type per user's JID).\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(\r\n SignalingEvents.PEER_VIDEO_TYPE_CHANGED,\r\n this._peerVideoTypeChanged);\r\n\r\n this._peerMutedChanged = this._peerMutedChanged.bind(this);\r\n this.signalingLayer.on(\r\n SignalingEvents.PEER_MUTED_CHANGED,\r\n this._peerMutedChanged);\r\n this.options = options;\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 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: this._usesUnifiedPlan\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 // 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(\r\n endpointId,\r\n 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(\r\n endpointId,\r\n mediaType,\r\n 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 * 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 track.\r\n *\r\n * @returns {JitsiLocalTrack|undefined} - local video track.\r\n */\r\nTraceablePeerConnection.prototype.getLocalVideoTrack = function() {\r\n return this.getLocalTracks(MediaType.VIDEO)[0];\r\n};\r\n\r\n/**\r\n * Checks whether or not this {@link TraceablePeerConnection} instance contains\r\n * any local tracks for given mediaType.\r\n * @param {MediaType} mediaType\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 * @param {string} [endpointId] the track owner's identifier (MUC nickname)\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\nTraceablePeerConnection.prototype.getRemoteTracks = function(\r\n endpointId,\r\n mediaType) {\r\n const remoteTracks = [];\r\n const endpoints\r\n = endpointId ? [ endpointId ] : this.remoteTracks.keys();\r\n\r\n for (const endpoint of endpoints) {\r\n const endpointTrackMap = this.remoteTracks.get(endpoint);\r\n\r\n if (!endpointTrackMap) {\r\n\r\n // Otherwise an empty Map() would have to be allocated above\r\n // eslint-disable-next-line no-continue\r\n continue;\r\n }\r\n\r\n for (const trackMediaType of endpointTrackMap.keys()) {\r\n // per media type filtering\r\n if (!mediaType || mediaType === trackMediaType) {\r\n const mediaTrack = endpointTrackMap.get(trackMediaType);\r\n\r\n if (mediaTrack) {\r\n remoteTracks.push(mediaTrack);\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\r\n = this.signalingLayer.getPeerMediaInfo(ownerEndpointId, mediaType);\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 this.remoteTracks.set(ownerEndpointId, remoteTracksMap);\r\n }\r\n\r\n const existingTrack = remoteTracksMap.get(mediaType);\r\n\r\n if (existingTrack && existingTrack.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 (existingTrack) {\r\n logger.error(`${this} received a second remote track for track[endpoint=${ownerEndpointId},type=${mediaType}]`\r\n + 'deleting the existing track');\r\n\r\n // The exisiting track needs to be removed here. We can get here when Jicofo reverses the order of source-add\r\n // and source-remove messages. Ideally, when a remote endpoint changes source, like switching devices, it sends\r\n // a source-remove (for old ssrc) followed by a source-add (for new ssrc) and Jicofo then should forward these\r\n // two messages to all the other endpoints in the conference in the same order. However, sometimes, these\r\n // messages arrive at the client in the reverse order resulting in two remote tracks (of same media type) being\r\n // created and in case of video, a black strip (that of the first track which has ended) appears over the live\r\n // track obscuring it. Removing the existing track when that happens will fix this issue.\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 remoteTracksMap.set(mediaType, remoteTrack);\r\n\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 * @param {MediaStream} stream WebRTC MediaStream instance which is the parent\r\n * of the track.\r\n * @param {MediaStreamTrack} track the WebRTC MediaStreamTrack which has been\r\n * removed from the PeerConnection.\r\n */\r\nTraceablePeerConnection.prototype._remoteTrackRemoved = function(\r\n stream,\r\n 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 logger.info(`${this} remote track removed stream[id=${streamId},trackId=${trackId}]`);\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 if (!this._removeRemoteTrackById(streamId, trackId)) {\r\n // NOTE this warning is always printed when user leaves the room,\r\n // because we remove remote tracks manually on MUC member left event,\r\n // before the SSRCs are removed by Jicofo. In most cases it is fine to\r\n // ignore this warning, but still it's better to keep it printed for\r\n // debugging purposes.\r\n //\r\n // We could change the behaviour to emit track removed only from here,\r\n // but the order of the events will change and consuming apps could\r\n // behave unexpectedly (the \"user left\" event would come before \"track\r\n // removed\" events).\r\n logger.warn(`${this} Removed track not found for stream[id=${streamId},trackId=${trackId}]`);\r\n }\r\n};\r\n\r\n/**\r\n * Finds remote track by it's stream and track ids.\r\n * @param {string} streamId the media stream id as defined by the WebRTC\r\n * @param {string} trackId the media track id as defined by the WebRTC\r\n * @return {JitsiRemoteTrack|undefined} the track's instance or\r\n * undefined if not found.\r\n * @private\r\n */\r\nTraceablePeerConnection.prototype._getRemoteTrackById = function(\r\n streamId,\r\n trackId) {\r\n // .find will break the loop once the first match is found\r\n for (const endpointTrackMap of this.remoteTracks.values()) {\r\n for (const mediaTrack of endpointTrackMap.values()) {\r\n // FIXME verify and try to use ===\r\n /* eslint-disable eqeqeq */\r\n if (mediaTrack.getStreamId() == streamId\r\n && mediaTrack.getTrackId() == trackId) {\r\n return mediaTrack;\r\n }\r\n\r\n /* eslint-enable eqeqeq */\r\n }\r\n }\r\n\r\n return undefined;\r\n};\r\n\r\n/**\r\n * Removes all JitsiRemoteTracks associated with given MUC nickname\r\n * (resource part of the JID). Returns array of removed tracks.\r\n *\r\n * @param {string} owner - The resource part of the MUC JID.\r\n * @returns {JitsiRemoteTrack[]}\r\n */\r\nTraceablePeerConnection.prototype.removeRemoteTracks = function(owner) {\r\n const removedTracks = [];\r\n const remoteTracksMap = this.remoteTracks.get(owner);\r\n\r\n if (remoteTracksMap) {\r\n const removedAudioTrack = remoteTracksMap.get(MediaType.AUDIO);\r\n const removedVideoTrack = remoteTracksMap.get(MediaType.VIDEO);\r\n\r\n removedAudioTrack && removedTracks.push(removedAudioTrack);\r\n removedVideoTrack && removedTracks.push(removedVideoTrack);\r\n\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\r\n * {@link RTCEvents.REMOTE_TRACK_REMOVED}.\r\n * @param {JitsiRemoteTrack} toBeRemoved\r\n */\r\nTraceablePeerConnection.prototype._removeRemoteTrack = function(toBeRemoved) {\r\n toBeRemoved.dispose();\r\n const participantId = toBeRemoved.getParticipantId();\r\n const remoteTracksMap = this.remoteTracks.get(participantId);\r\n\r\n if (!remoteTracksMap) {\r\n logger.error(`${this} removeRemoteTrack: no remote tracks map for endpoint=${participantId}`);\r\n } else if (!remoteTracksMap.delete(toBeRemoved.getType())) {\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 * Removes and disposes JitsiRemoteTrack identified by given stream and\r\n * track ids.\r\n *\r\n * @param {string} streamId the media stream id as defined by the WebRTC\r\n * @param {string} trackId the media track id as defined by the WebRTC\r\n * @returns {JitsiRemoteTrack|undefined} the track which has been removed or\r\n * undefined if no track matching given stream and track ids was\r\n * found.\r\n */\r\nTraceablePeerConnection.prototype._removeRemoteTrackById = function(\r\n streamId,\r\n trackId) {\r\n const toBeRemoved = this._getRemoteTrackById(streamId, trackId);\r\n\r\n if (toBeRemoved) {\r\n this._removeRemoteTrack(toBeRemoved);\r\n }\r\n\r\n return 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 (unfied-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 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 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'\r\n && 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 ? mLine.type : 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\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);\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);\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 with the\r\n * same msid but no ssrc-group, since unified plan signals the simulcast\r\n * group via the a=simulcast line. Unfortunately, Jicofo will complain\r\n * if it sees ssrcs with matching msids but no ssrc-group, so we'll inject\r\n * an ssrc-group line to make Jicofo happy.\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\r\n * contain an inject ssrc-group for simulcast\r\n */\r\nTraceablePeerConnection.prototype._injectSsrcGroupForUnifiedSimulcast\r\n = 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 video.ssrcGroups.push({\r\n semantics: 'SIM',\r\n ssrcs: ssrcs.join(' ')\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\r\n * peerconnection, false otherwise.\r\n */\r\nTraceablePeerConnection.prototype._isSharingScreen = function() {\r\n const track = this.getLocalVideoTrack();\r\n\r\n return 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 if (webrtcStream) {\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 }\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(() => this.isP2P);\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 return this.peerconnection.getSenders().find(s => s.track && s.track.kind === mediaType);\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 return this.peerconnection.getSenders().find(s => s.track === track);\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 const negotiationNeeded = 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 const transceiver = this.tpcUtils.findTransceiver(mediaType, oldTrack);\r\n\r\n return promise\r\n .then(() => {\r\n oldTrack && this.localTracks.delete(oldTrack.rtcId);\r\n newTrack && this.localTracks.set(newTrack.rtcId, newTrack);\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 }\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 // Renegotiate only in the case of P2P. We rely on 'negotiationeeded' to be fired for JVB.\r\n return configureEncodingsPromise.then(() => this.isP2P || 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(\r\n 'removeStreamMute',\r\n localTrack.rtcId, webRtcStream ? webRtcStream.id : null);\r\n\r\n if (!this._assertTrackBelongs('removeStreamMute', 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);\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} removeStreamMute - 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);\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);\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);\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 * 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 * @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.\r\n */\r\nTraceablePeerConnection.prototype.configureSenderVideoEncodings = function() {\r\n return this.setSenderVideoConstraints(this._senderVideoMaxHeight);\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 (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(remoteDescription);\r\n this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(remoteDescription));\r\n\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 * @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.\r\n */\r\nTraceablePeerConnection.prototype.setSenderVideoConstraints = function(frameHeight) {\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 this._senderVideoMaxHeight = frameHeight;\r\n const localVideoTrack = this.getLocalVideoTrack();\r\n\r\n if (!localVideoTrack || localVideoTrack.isMuted()) {\r\n return Promise.resolve();\r\n }\r\n const videoSender = this.findSenderByKind(MediaType.VIDEO);\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 remoteTrack of peerTracks.values()) {\r\n this._removeRemoteTrack(remoteTrack);\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.getLocalVideoTrack();\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(\r\n `create${logName}`\r\n + 'OnSuccess::postTransform (simulcast)',\r\n 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 const sourceIdentifier = this._usesUnifiedPlan ? track.getType() : 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 * as 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 Statistics from '../statistics/statistics';\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 * 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 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(\r\n RTCEvents.DATA_CHANNEL_OPEN,\r\n this._channelOpenListener\r\n );\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.removeListener(RTCEvents.DATA_CHANNEL_OPEN, this._channelOpenListener);\r\n this._channelOpenListener = null;\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\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 * 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 (typeof options.abtestSuspendVideo !== 'undefined') {\r\n RTCUtils.setSuspendVideo(pcConstraints, options.abtestSuspendVideo);\r\n\r\n Statistics.analytics.addPermanentProperties(\r\n { abtestSuspendVideo: options.abtestSuspendVideo });\r\n }\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 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 * 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 * 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","/**\r\n * Indicates that the local connection statistics were updated.\r\n */\r\nexport const 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\nexport const REMOTE_STATS_UPDATED = 'cq.remote_stats_updated';\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 facial expression.\r\n */\r\nconst FACIAL_EXPRESSION_MESSAGE_TYPE = 'facial_expression';\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 === FACIAL_EXPRESSION_MESSAGE_TYPE) {\r\n this.eventEmitter.emit(\r\n ConferenceEvents.FACIAL_EXPRESSION_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 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","/**\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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const 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\nexport const VAD_TALK_WHILE_MUTED = 'detection.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","/**\r\n * Indicates that the end-to-end round-trip-time for a participant has changed.\r\n */\r\nexport const E2E_RTT_CHANGED = 'e2eping.e2e_rtt_changed';\r\n","import { getLogger } from '@jitsi/logger';\r\n\r\nimport * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport * as E2ePingEvents\r\n from '../../service/e2eping/E2ePingEvents';\r\nimport { createE2eRttEvent } from '../../service/statistics/AnalyticsEvents';\r\nimport Statistics from '../statistics/statistics';\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 * 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.clearIntervals = this.clearIntervals.bind(this);\r\n this.sendRequest = this.sendRequest.bind(this);\r\n this.handleResponse = this.handleResponse.bind(this);\r\n this.maybeSendAnalytics = this.maybeSendAnalytics.bind(this);\r\n this.sendAnalytics = this.sendAnalytics.bind(this);\r\n\r\n // If the data channel was already open (this is likely a participant\r\n // joining an existing conference) send a request immediately.\r\n if (e2eping.isDataChannelOpen) {\r\n this.sendRequest();\r\n }\r\n\r\n this.pingInterval = window.setInterval(\r\n this.sendRequest, e2eping.pingIntervalMs);\r\n this.analyticsInterval = window.setTimeout(\r\n this.maybeSendAnalytics, this.e2eping.analyticsIntervalMs);\r\n }\r\n\r\n /**\r\n * Clears the interval which sends pings.\r\n * @type {*}\r\n */\r\n clearIntervals() {\r\n if (this.pingInterval) {\r\n window.clearInterval(this.pingInterval);\r\n }\r\n if (this.analyticsInterval) {\r\n window.clearInterval(this.analyticsInterval);\r\n }\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 this.e2eping.eventEmitter.emit(\r\n E2ePingEvents.E2E_RTT_CHANGED,\r\n this.participant,\r\n request.rtt);\r\n }\r\n\r\n this.maybeSendAnalytics();\r\n }\r\n\r\n /**\r\n * Goes over the requests, clearing ones which we don't need anymore, and\r\n * if it finds at least one request with a valid RTT in the last\r\n * 'analyticsIntervalMs' then sends an analytics event.\r\n * @type {*}\r\n */\r\n maybeSendAnalytics() {\r\n const now = window.performance.now();\r\n\r\n // The RTT we'll report is the minimum RTT measured in the last\r\n // analyticsInterval\r\n let rtt = Infinity;\r\n let request, requestId;\r\n\r\n // It's time to send analytics. Clean up all requests and find the\r\n for (requestId in this.requests) {\r\n if (this.requests.hasOwnProperty(requestId)) {\r\n request = this.requests[requestId];\r\n\r\n if (request.timeSent < now - this.e2eping.analyticsIntervalMs) {\r\n // An old request. We don't care about it anymore.\r\n delete this.requests[requestId];\r\n } else if (request.rtt) {\r\n rtt = Math.min(rtt, request.rtt);\r\n }\r\n }\r\n }\r\n\r\n if (rtt < Infinity) {\r\n this.sendAnalytics(rtt);\r\n }\r\n }\r\n\r\n /**\r\n * Sends an analytics event for this participant with the given RTT.\r\n * @type {*}\r\n */\r\n sendAnalytics(rtt) {\r\n Statistics.sendAnalytics(createE2eRttEvent(\r\n this.id,\r\n this.participant.getProperty('region'),\r\n rtt));\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 // The interval at which pings will be sent (<= 0 disables sending).\r\n this.pingIntervalMs = 10000;\r\n\r\n // The interval at which analytics events will be sent.\r\n this.analyticsIntervalMs = 60000;\r\n\r\n // Maps a participant ID to its ParticipantWrapper\r\n this.participants = {};\r\n\r\n // Whether the WebRTC channel has been opened or not.\r\n this.isDataChannelOpen = false;\r\n\r\n if (options && options.e2eping) {\r\n if (typeof options.e2eping.pingInterval === 'number') {\r\n this.pingIntervalMs = options.e2eping.pingInterval;\r\n }\r\n if (typeof options.e2eping.analyticsInterval === 'number') {\r\n this.analyticsIntervalMs = options.e2eping.analyticsInterval;\r\n }\r\n\r\n // We want to report at most once a ping interval.\r\n if (this.analyticsIntervalMs > 0 && this.analyticsIntervalMs\r\n < this.pingIntervalMs) {\r\n this.analyticsIntervalMs = this.pingIntervalMs;\r\n }\r\n }\r\n logger.info(\r\n `Initializing e2e ping; pingInterval=${\r\n this.pingIntervalMs}, analyticsInterval=${\r\n this.analyticsIntervalMs}.`);\r\n\r\n this.participantJoined = this.participantJoined.bind(this);\r\n conference.on(\r\n JitsiConferenceEvents.USER_JOINED,\r\n this.participantJoined);\r\n\r\n this.participantLeft = this.participantLeft.bind(this);\r\n conference.on(\r\n JitsiConferenceEvents.USER_LEFT,\r\n this.participantLeft);\r\n\r\n this.messageReceived = this.messageReceived.bind(this);\r\n conference.on(\r\n JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,\r\n this.messageReceived);\r\n\r\n this.dataChannelOpened = this.dataChannelOpened.bind(this);\r\n conference.on(\r\n JitsiConferenceEvents.DATA_CHANNEL_OPENED,\r\n this.dataChannelOpened);\r\n }\r\n\r\n /**\r\n * Notifies this instance that the communications channel has been opened\r\n * and it can now send messages via sendMessage.\r\n */\r\n dataChannelOpened() {\r\n this.isDataChannelOpen = true;\r\n\r\n // We don't want to wait the whole interval before sending the first\r\n // request, but we can't send it immediately after the participant joins\r\n // either, because our data channel might not have initialized.\r\n // So once the data channel initializes, send requests to everyone.\r\n // Wait an additional 200ms to give a chance to the remote side (if it\r\n // also just connected as is the case for the first 2 participants in a\r\n // conference) to open its data channel.\r\n for (const id in this.participants) {\r\n if (this.participants.hasOwnProperty(id)) {\r\n const participantWrapper = this.participants[id];\r\n\r\n window.setTimeout(participantWrapper.sendRequest, 200);\r\n }\r\n }\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.pingIntervalMs <= 0) {\r\n return;\r\n }\r\n\r\n if (this.participants[id]) {\r\n logger.info(\r\n `Participant wrapper already exists for ${id}. Clearing.`);\r\n this.participants[id].clearIntervals();\r\n delete this.participants[id];\r\n }\r\n\r\n this.participants[id] = new ParticipantWrapper(participant, this);\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.pingIntervalMs <= 0) {\r\n return;\r\n }\r\n\r\n if (this.participants[id]) {\r\n this.participants[id].clearIntervals();\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(\r\n `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(\r\n JitsiConferenceEvents.USER_JOINED,\r\n this.participantJoined);\r\n this.conference.off(\r\n JitsiConferenceEvents.USER_LEFT,\r\n this.participantLeft);\r\n this.conference.off(\r\n JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,\r\n this.messageReceived);\r\n this.conference.off(\r\n JitsiConferenceEvents.DATA_CHANNEL_OPENED,\r\n this.dataChannelOpened);\r\n\r\n for (const id in this.participants) {\r\n if (this.participants.hasOwnProperty(id)) {\r\n this.participants[id].clearIntervals();\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 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\nexport class 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 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 // 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 * 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);\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 maxFrameHeight && session.setReceiverVideoConstraint(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 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 * as JitsiConferenceEvents from '../../JitsiConferenceEvents';\r\nimport RTCEvents from '../../service/RTC/RTCEvents';\r\nimport MediaSessionEvents from '../xmpp/MediaSessionEvents';\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 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.layerSuspensionEnabled = conference.options?.config?.enableLayerSuspension ?? true;\r\n this.rtc = rtc;\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._propagateSendMaxFrameHeight());\r\n this.rtc.on(\r\n RTCEvents.SENDER_VIDEO_CONSTRAINTS_CHANGED,\r\n videoConstraints => {\r\n // Propagate the sender constraint only if it has changed.\r\n 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 /**\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 mediaSession.addListener(\r\n MediaSessionEvents.REMOTE_VIDEO_CONSTRAINTS_CHANGED,\r\n session => {\r\n if (session === this.conference._getActiveMediaSession()) {\r\n this._propagateSendMaxFrameHeight();\r\n }\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 * @returns {Promise}\r\n * @private\r\n */\r\n _propagateSendMaxFrameHeight() {\r\n const sendMaxFrameHeight = this.selectSendMaxFrameHeight();\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));\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 * @returns {number|undefined}\r\n */\r\n selectSendMaxFrameHeight() {\r\n const activeMediaSession = this.conference._getActiveMediaSession();\r\n const remoteRecvMaxFrameHeight = activeMediaSession\r\n ? activeMediaSession.isP2P\r\n ? activeMediaSession.getRemoteRecvMaxFrameHeight()\r\n : this.layerSuspensionEnabled ? this._senderVideoConstraints?.idealHeight : undefined\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 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 * as 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 * as MediaType from '../../service/RTC/MediaType';\r\nimport * as 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.FACIAL_EXPRESSION_ADDED,\r\n this._onFacialExpressionAdd.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 * Adds a new facial expression with its duration 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 facial expression with its duration.\r\n * @returns {void}\r\n * @private\r\n */\r\n _onFacialExpressionAdd(userId, data) {\r\n const savedUser = this.stats.users[userId];\r\n\r\n if (savedUser) {\r\n savedUser.addFacialExpression(data.facialExpression, 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.setFacialExpressions(newStats[userId].facialExpressions);\r\n }\r\n }\r\n}\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","/**\r\n * Status that video SIP GW service is available.\r\n * @type {string}\r\n */\r\nexport const STATUS_AVAILABLE = 'available';\r\n\r\n/**\r\n * Status that video SIP GW service is not available.\r\n * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const STATUS_BUSY = 'busy';\r\n\r\n/**\r\n * Video SIP GW session state, currently running.\r\n * @type {string}\r\n */\r\nexport const STATE_ON = 'on';\r\n\r\n/**\r\n * Video SIP GW session state, currently stopped and not running.\r\n * @type {string}\r\n */\r\nexport const STATE_OFF = 'off';\r\n\r\n/**\r\n * Video SIP GW session state, currently is starting.\r\n * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const STATE_RETRYING = 'retrying';\r\n\r\n/**\r\n * Video SIP GW session state, tried to start but it failed.\r\n * @type {string}\r\n */\r\nexport const STATE_FAILED = 'failed';\r\n\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 * @type {string}\r\n */\r\nexport const 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 * @type {string}\r\n */\r\nexport const ERROR_SESSION_EXISTS = 'error_session_already_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 * as 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 this.chatRoom.addOrReplaceInPresence(\r\n SOURCE_INFO_PRESENCE_ELEMENT,\r\n { value: JSON.stringify(this._localSourceState) });\r\n }\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 room.addPresenceListener('videoType', this._videoTypeHandler);\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 && mediaType === MediaType.AUDIO) {\r\n emitAudioMutedEvent(endpointId, newMutedState);\r\n } else {\r\n emitVideoMutedEvent(endpointId, newMutedState);\r\n }\r\n }\r\n\r\n const newVideoType = sourceInfoJSON[sourceName].videoType;\r\n\r\n if (oldSourceState.videoType !== newVideoType) {\r\n oldSourceState.videoType = newVideoType;\r\n emitEventsFromHere && emitVideoTypeEvent(endpointId, newVideoType);\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) {\r\n const legacyGetPeerMediaInfo = () => {\r\n if (this.chatRoom) {\r\n return this.chatRoom.getMediaPresenceInfo(owner, mediaType);\r\n }\r\n logger.error('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 throw new Error(`getPeerMediaInfo - no presence stored for: ${owner}`);\r\n }\r\n\r\n if (!this._doesEndpointSendNewSourceInfo(owner)) {\r\n return legacyGetPeerMediaInfo();\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 return this._remoteSourceState[owner] ? this._remoteSourceState[owner][sourceName] : 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 this._addLocalSourceInfoToPresence();\r\n }\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 */\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 this._addLocalSourceInfoToPresence();\r\n }\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 * as 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 * as 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 coference 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 this.e2eping = new E2ePing(\r\n this,\r\n config,\r\n (message, to) => {\r\n try {\r\n this.sendMessage(\r\n 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 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 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 // 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 * @private\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 * @private\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()) {\r\n this.rtc.sendSourceVideoType(\r\n getSourceNameForJitsiTrack(this.myUserId(), MediaType.VIDEO, 0),\r\n videoType\r\n );\r\n } else {\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 * 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 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};\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 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 // updates presence when we replace the video tracks desktop with screen and screen with desktop\r\n if (oldTrackBelongsToConference && oldTrack?.isVideoTrack()\r\n\r\n // we do not want to send presence update during setEffect switching, which does remove and then add\r\n && !(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 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 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 if (FeatureFlags.isSourceNameSignalingEnabled() && track) {\r\n // FIXME once legacy signaling using 'sendCommand' is removed, signalingLayer.setTrackVideoType must be adjusted\r\n // to send the presence (not just modify it).\r\n this._signalingLayer.setTrackVideoType(\r\n getSourceNameForJitsiTrack(\r\n this.myUserId(),\r\n track.getType(),\r\n 0\r\n ),\r\n track.videoType);\r\n\r\n // TODO: Optimize to detect whether presence was changed, for now always report changed to send presence\r\n return true;\r\n }\r\n\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 set\r\n // and the mute status\r\n return this.room.addOrReplaceInPresence(videoTypeTagName, { value: trackVideoType });\r\n }\r\n\r\n return false;\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 if (FeatureFlags.isSourceNameSignalingEnabled()) {\r\n // TODO When legacy signaling part is removed, remember to adjust signalingLayer.setTrackMuteStatus, so that\r\n // it triggers sending the presence (it only updates it for now, because the legacy code below sends).\r\n this._signalingLayer.setTrackMuteStatus(\r\n getSourceNameForJitsiTrack(this.myUserId(), mediaType, 0),\r\n isMuted\r\n );\r\n }\r\n\r\n if (!this.room) {\r\n return false;\r\n }\r\n\r\n if (mediaType === MediaType.AUDIO) {\r\n return this.room.addAudioInfoToPresence(isMuted);\r\n }\r\n\r\n return this.room.addVideoInfoToPresence(isMuted);\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 .then(() => {\r\n // Signal the video type to the bridge.\r\n track.isVideoTrack() && this._sendBridgeVideoTypeMessage(track);\r\n });\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 .then(() => {\r\n // Signal the video type to the bridge.\r\n track.isVideoTrack() && this._sendBridgeVideoTypeMessage();\r\n });\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 * 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.getJid(), '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.room.myroomjid, role);\r\n } else if (participant) {\r\n this.room.setAffiliation(participant.getJid(), role);\r\n }\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.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 into\r\n * the conference.\r\n *\r\n * @param {JitsiRemoteTrack} track the JitsiRemoteTrack which was added to this\r\n * 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.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(\r\n JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED,\r\n id,\r\n 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(\r\n 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\r\n let rejectReason;\r\n\r\n if ((!this.isP2PEnabled() && !this.isP2PTestModeEnabled()) || browser.isFirefox() || browser.isWebKitBased()) {\r\n rejectReason = {\r\n reason: 'decline',\r\n reasonDescription: 'P2P disabled',\r\n errorMsg: 'P2P mode disabled in the configuration'\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 && options.errorMsg) {\r\n GlobalOnErrorHandler.callErrorHandler(new Error(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(\r\n '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(\r\n '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(\r\n JitsiConferenceEvents._MEDIA_SESSION_ACTIVE_CHANGED,\r\n 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 const localAudioTracks = jingleSession.peerconnection.getLocalTracks(MediaType.AUDIO);\r\n const localVideoTracks = jingleSession.peerconnection.getLocalTracks(MediaType.VIDEO);\r\n let presenceChanged = false;\r\n\r\n if (localAudioTracks && localAudioTracks.length) {\r\n presenceChanged = this._setTrackMuteStatus(MediaType.AUDIO, localAudioTracks[0], localAudioTracks[0].isMuted());\r\n } else if (this._setTrackMuteStatus(MediaType.AUDIO, undefined, true)) {\r\n presenceChanged = true;\r\n }\r\n\r\n if (localVideoTracks && localVideoTracks.length) {\r\n const muteStatusChanged = this._setTrackMuteStatus(\r\n MediaType.VIDEO, localVideoTracks[0], localVideoTracks[0].isMuted());\r\n const videoTypeChanged = this._setNewVideoType(localVideoTracks[0]);\r\n\r\n presenceChanged = presenceChanged || muteStatusChanged || videoTypeChanged;\r\n } else {\r\n const muteStatusChanged = this._setTrackMuteStatus(MediaType.VIDEO, undefined, true);\r\n const videoTypeChanged = this._setNewVideoType(); // set back to default video type\r\n\r\n presenceChanged = presenceChanged || muteStatusChanged || 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 facial expression with its duration to the xmpp server.\r\n * @param {Object} payload\r\n */\r\nJitsiConference.prototype.sendFacialExpression = function(payload) {\r\n this.xmpp.sendFacialExpressionEvent(this.room.roomjid, payload);\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 * 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\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\nexport const 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\nexport const 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\nexport const PERMISSION_PROMPT_IS_SHOWN\r\n = 'mediaDevices.permissionPromptIsShown';\r\n\r\nexport const SLOW_GET_USER_MEDIA = 'mediaDevices.slowGetUserMedia';\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 * as 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 !(browser.isFirefox() || browser.isIosBrowser());\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 const 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, 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 * as 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 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 * as 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 },\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 // 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","(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n typeof define === 'function' && define.amd ? define(['exports'], factory) :\n (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.strophe = {}));\n})(this, (function (exports) { 'use strict';\n\n var global$1 = typeof global !== \"undefined\" ? global : typeof self !== \"undefined\" ? self : typeof window !== \"undefined\" ? window : {};\n\n /*\n * This module provides uniform\n * Shims APIs and globals that are not present in all JS environments,\n * the most common example for Strophe being browser APIs like WebSocket\n * and DOM that don't exist under nodejs.\n *\n * Usually these will be supplied in nodejs by conditionally requiring a\n * NPM module that provides a compatible implementation.\n */\n\n /* global global */\n\n /**\n * WHATWG WebSockets API\n * https://www.w3.org/TR/websockets/\n *\n * Interface to use the web socket protocol\n *\n * Used implementations:\n * - supported browsers: built-in in WebSocket global\n * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Browser_compatibility\n * - nodejs: use standard-compliant 'ws' module\n * https://www.npmjs.com/package/ws\n */\n function getWebSocketImplementation() {\n let WebSocketImplementation = global$1.WebSocket;\n\n if (typeof WebSocketImplementation === 'undefined') {\n try {\n WebSocketImplementation = require('ws');\n } catch (err) {\n throw new Error('You must install the \"ws\" package to use Strophe in nodejs.');\n }\n }\n\n return WebSocketImplementation;\n }\n\n const WebSocket = getWebSocketImplementation();\n /**\n * DOMParser\n * https://w3c.github.io/DOM-Parsing/#the-domparser-interface\n *\n * Interface to parse XML strings into Document objects\n *\n * Used implementations:\n * - supported browsers: built-in in DOMParser global\n * https://developer.mozilla.org/en-US/docs/Web/API/DOMParser#Browser_compatibility\n * - nodejs: use 'xmldom' module\n * https://www.npmjs.com/package/xmldom\n */\n\n function getDOMParserImplementation() {\n let DOMParserImplementation = global$1.DOMParser;\n\n if (typeof DOMParserImplementation === 'undefined') {\n try {\n DOMParserImplementation = require('xmldom').DOMParser;\n } catch (err) {\n throw new Error('You must install the \"xmldom\" package to use Strophe in nodejs.');\n }\n }\n\n return DOMParserImplementation;\n }\n\n const DOMParser = getDOMParserImplementation();\n /**\n * Gets IE xml doc object. Used by getDummyXMLDocument shim.\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\n function _getIEXmlDom() {\n const 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 (let d = 0; d < docStrings.length; d++) {\n try {\n // eslint-disable-next-line no-undef\n const doc = new ActiveXObject(docStrings[d]);\n return doc;\n } catch (e) {// Try next one\n }\n }\n }\n /**\n * Creates a dummy XML DOM document to serve as an element and text node generator.\n *\n * Used implementations:\n * - IE < 10: avoid using createDocument() due to a memory leak, use ie-specific\n * workaround\n * - other supported browsers: use document's createDocument\n * - nodejs: use 'xmldom'\n */\n\n\n function getDummyXMLDOMDocument() {\n // nodejs\n if (typeof document === 'undefined') {\n try {\n const DOMImplementation = require('xmldom').DOMImplementation;\n\n return new DOMImplementation().createDocument('jabber:client', 'strophe', null);\n } catch (err) {\n throw new Error('You must install the \"xmldom\" package to use Strophe in nodejs.');\n }\n } // IE < 10\n\n\n if (document.implementation.createDocument === undefined || document.implementation.createDocument && document.documentMode && document.documentMode < 10) {\n const doc = _getIEXmlDom();\n\n doc.appendChild(doc.createElement('strophe'));\n return doc;\n } // All other supported browsers\n\n\n return document.implementation.createDocument('jabber:client', 'strophe', null);\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 const safe_add$1 = function (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\n const bit_rol = function (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 const str2binl = function (str) {\n if (typeof str !== \"string\") {\n throw new Error(\"str2binl was passed a non-string\");\n }\n\n const bin = [];\n\n for (let 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 const binl2str = function (bin) {\n let str = \"\";\n\n for (let 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 const binl2hex = function (binarray) {\n const hex_tab = \"0123456789abcdef\";\n let str = \"\";\n\n for (let 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 const md5_cmn = function (q, a, b, x, s, t) {\n return safe_add$1(bit_rol(safe_add$1(safe_add$1(a, q), safe_add$1(x, t)), s), b);\n };\n\n const md5_ff = function (a, b, c, d, x, s, t) {\n return md5_cmn(b & c | ~b & d, a, b, x, s, t);\n };\n\n const md5_gg = function (a, b, c, d, x, s, t) {\n return md5_cmn(b & d | c & ~d, a, b, x, s, t);\n };\n\n const md5_hh = function (a, b, c, d, x, s, t) {\n return md5_cmn(b ^ c ^ d, a, b, x, s, t);\n };\n\n const md5_ii = function (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 const core_md5 = function (x, len) {\n /* append padding */\n x[len >> 5] |= 0x80 << len % 32;\n x[(len + 64 >>> 9 << 4) + 14] = len;\n let a = 1732584193;\n let b = -271733879;\n let c = -1732584194;\n let d = 271733878;\n let olda, oldb, oldc, oldd;\n\n for (let 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$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 }\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 const MD5 = {\n hexdigest: function (s) {\n return binl2hex(core_md5(str2binl(s), s.length * 8));\n },\n hash: function (s) {\n return binl2str(core_md5(str2binl(s), s.length * 8));\n }\n };\n\n /** Class: Strophe.SASLMechanism\n *\n * Encapsulates an SASL authentication mechanism.\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 * SCRAM-SHA-1 - 60\n * PLAIN - 50\n * OAUTHBEARER - 40\n * X-OAUTH2 - 30\n * ANONYMOUS - 20\n * EXTERNAL - 10\n *\n * See: Strophe.Connection.addSupportedSASLMechanisms\n */\n class SASLMechanism {\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 constructor(name, isClientFirst, priority) {\n /** PrivateVariable: mechname\n * Mechanism name.\n */\n this.mechname = 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 * Example: (This will cause Strophe to choose the mechanism that the server sent first)\n *\n * > Strophe.SASLPlain.priority = Strophe.SASLSHA1.priority;\n *\n * See for a list of available mechanisms.\n *\n */\n\n this.priority = priority;\n }\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\n\n test() {\n // eslint-disable-line class-methods-use-this\n return true;\n }\n /** PrivateFunction: onStart\n * Called before starting mechanism on some connection.\n *\n * Parameters:\n * (Strophe.Connection) connection - Target Connection.\n */\n\n\n onStart(connection) {\n this._connection = connection;\n }\n /** PrivateFunction: onChallenge\n * Called by protocol implementation on incoming challenge.\n *\n * By deafult, if the client is expected to send data first (isClientFirst === true),\n * this method is called with `challenge` as null on the first call,\n * unless `clientChallenge` is overridden in the relevant subclass.\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\n\n onChallenge(connection, challenge) {\n // eslint-disable-line\n throw new Error(\"You should implement challenge handling!\");\n }\n /** PrivateFunction: clientChallenge\n * Called by the protocol implementation if the client is expected to send\n * data first in the authentication exchange (i.e. isClientFirst === true).\n *\n * Parameters:\n * (Strophe.Connection) connection - Target Connection.\n *\n * Returns:\n * (String) Mechanism response.\n */\n\n\n clientChallenge(connection) {\n if (!this.isClientFirst) {\n throw new Error(\"clientChallenge should not be called if isClientFirst is false!\");\n }\n\n return this.onChallenge(connection);\n }\n /** PrivateFunction: onFailure\n * Protocol informs mechanism implementation about SASL failure.\n */\n\n\n onFailure() {\n this._connection = null;\n }\n /** PrivateFunction: onSuccess\n * Protocol informs mechanism implementation about SASL success.\n */\n\n\n onSuccess() {\n this._connection = null;\n }\n\n }\n\n class SASLAnonymous extends SASLMechanism {\n /** PrivateConstructor: SASLAnonymous\n * SASL ANONYMOUS authentication.\n */\n constructor(mechname = 'ANONYMOUS', isClientFirst = false, priority = 20) {\n super(mechname, isClientFirst, priority);\n }\n\n test(connection) {\n // eslint-disable-line class-methods-use-this\n return connection.authcid === null;\n }\n\n }\n\n class SASLExternal extends SASLMechanism {\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 constructor(mechname = 'EXTERNAL', isClientFirst = true, priority = 10) {\n super(mechname, isClientFirst, priority);\n }\n\n onChallenge(connection) {\n // eslint-disable-line class-methods-use-this\n\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\n }\n\n const utils = {\n utf16to8: function (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 (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 (const cookieName in cookies) {\n if (Object.prototype.hasOwnProperty.call(cookies, cookieName)) {\n let expires = '';\n let domain = '';\n let path = '';\n const cookieObj = cookies[cookieName];\n const isObj = typeof cookieObj === \"object\";\n const 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 class SASLOAuthBearer extends SASLMechanism {\n /** PrivateConstructor: SASLOAuthBearer\n * SASL OAuth Bearer authentication.\n */\n constructor(mechname = 'OAUTHBEARER', isClientFirst = true, priority = 40) {\n super(mechname, isClientFirst, priority);\n }\n\n test(connection) {\n // eslint-disable-line class-methods-use-this\n return connection.pass !== null;\n }\n\n onChallenge(connection) {\n // eslint-disable-line class-methods-use-this\n let 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 + \"\\u0001\";\n auth_str = auth_str + 'auth=Bearer ';\n auth_str = auth_str + connection.pass;\n auth_str = auth_str + \"\\u0001\";\n auth_str = auth_str + \"\\u0001\";\n return utils.utf16to8(auth_str);\n }\n\n }\n\n class SASLPlain extends SASLMechanism {\n /** PrivateConstructor: SASLPlain\n * SASL PLAIN authentication.\n */\n constructor(mechname = 'PLAIN', isClientFirst = true, priority = 50) {\n super(mechname, isClientFirst, priority);\n }\n\n test(connection) {\n // eslint-disable-line class-methods-use-this\n return connection.authcid !== null;\n }\n\n onChallenge(connection) {\n // eslint-disable-line class-methods-use-this\n const {\n authcid,\n authzid,\n domain,\n pass\n } = connection;\n\n if (!domain) {\n throw new Error(\"SASLPlain onChallenge: domain is not defined!\");\n } // Only include authzid if it differs from authcid.\n // See: https://tools.ietf.org/html/rfc6120#section-6.3.8\n\n\n let auth_str = authzid !== `${authcid}@${domain}` ? authzid : '';\n auth_str = auth_str + \"\\u0000\";\n auth_str = auth_str + authcid;\n auth_str = auth_str + \"\\u0000\";\n auth_str = auth_str + pass;\n return utils.utf16to8(auth_str);\n }\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(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(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(a, olda);\n b = safe_add(b, oldb);\n c = safe_add(c, oldc);\n d = safe_add(d, oldd);\n e = safe_add(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(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 const SHA1 = {\n b64_hmac_sha1: function (key, data) {\n return binb2b64(core_hmac_sha1(key, data));\n },\n b64_sha1: function (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 (key, data) {\n return binb2str(core_hmac_sha1(key, data));\n },\n str_sha1: function (s) {\n return binb2str(core_sha1(str2binb(s), s.length * 8));\n }\n };\n\n class SASLSHA1 extends SASLMechanism {\n /** PrivateConstructor: SASLSHA1\n * SASL SCRAM SHA 1 authentication.\n */\n constructor(mechname = 'SCRAM-SHA-1', isClientFirst = true, priority = 60) {\n super(mechname, isClientFirst, priority);\n }\n\n test(connection) {\n // eslint-disable-line class-methods-use-this\n return connection.authcid !== null;\n }\n\n onChallenge(connection, challenge) {\n // eslint-disable-line class-methods-use-this\n let nonce, salt, iter, Hi, U, U_old, i, k;\n let responseText = \"c=biws,\";\n let authMessage = `${connection._sasl_data[\"client-first-message-bare\"]},${challenge},`;\n const cnonce = connection._sasl_data.cnonce;\n const attribMatch = /([a-z]+)=([^,]+)(,|$)/;\n\n while (challenge.match(attribMatch)) {\n const 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 const 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 const clientKey = SHA1.core_hmac_sha1(Hi, \"Client Key\");\n const serverKey = SHA1.str_hmac_sha1(Hi, \"Server Key\");\n const 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 clientChallenge(connection, test_cnonce) {\n // eslint-disable-line class-methods-use-this\n const cnonce = test_cnonce || MD5.hexdigest(\"\" + Math.random() * 1234567890);\n let 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 return auth_str;\n }\n\n }\n\n class SASLXOAuth2 extends SASLMechanism {\n /** PrivateConstructor: SASLXOAuth2\n * SASL X-OAuth2 authentication.\n */\n constructor(mechname = 'X-OAUTH2', isClientFirst = true, priority = 30) {\n super(mechname, isClientFirst, priority);\n }\n\n test(connection) {\n // eslint-disable-line class-methods-use-this\n return connection.pass !== null;\n }\n\n onChallenge(connection) {\n // eslint-disable-line class-methods-use-this\n let auth_str = '\\u0000';\n\n if (connection.authcid !== null) {\n auth_str = auth_str + connection.authzid;\n }\n\n auth_str = auth_str + \"\\u0000\";\n auth_str = auth_str + connection.pass;\n return utils.utf16to8(auth_str);\n }\n\n }\n\n /**\n * Implementation of atob() according to the HTML and Infra specs, except that\n * instead of throwing INVALID_CHARACTER_ERR we return null.\n */\n\n function atob$1(data) {\n // Web IDL requires DOMStrings to just be converted using ECMAScript\n // ToString, which in our case amounts to using a template literal.\n data = `${data}`; // \"Remove all ASCII whitespace from data.\"\n\n data = data.replace(/[ \\t\\n\\f\\r]/g, \"\"); // \"If data's length divides by 4 leaving no remainder, then: if data ends\n // with one or two U+003D (=) code points, then remove them from data.\"\n\n if (data.length % 4 === 0) {\n data = data.replace(/==?$/, \"\");\n } // \"If data's length divides by 4 leaving a remainder of 1, then return\n // failure.\"\n //\n // \"If data contains a code point that is not one of\n //\n // U+002B (+)\n // U+002F (/)\n // ASCII alphanumeric\n //\n // then return failure.\"\n\n\n if (data.length % 4 === 1 || /[^+/0-9A-Za-z]/.test(data)) {\n return null;\n } // \"Let output be an empty byte sequence.\"\n\n\n let output = \"\"; // \"Let buffer be an empty buffer that can have bits appended to it.\"\n //\n // We append bits via left-shift and or. accumulatedBits is used to track\n // when we've gotten to 24 bits.\n\n let buffer = 0;\n let accumulatedBits = 0; // \"Let position be a position variable for data, initially pointing at the\n // start of data.\"\n //\n // \"While position does not point past the end of data:\"\n\n for (let i = 0; i < data.length; i++) {\n // \"Find the code point pointed to by position in the second column of\n // Table 1: The Base 64 Alphabet of RFC 4648. Let n be the number given in\n // the first cell of the same row.\n //\n // \"Append to buffer the six bits corresponding to n, most significant bit\n // first.\"\n //\n // atobLookup() implements the table from RFC 4648.\n buffer <<= 6;\n buffer |= atobLookup(data[i]);\n accumulatedBits += 6; // \"If buffer has accumulated 24 bits, interpret them as three 8-bit\n // big-endian numbers. Append three bytes with values equal to those\n // numbers to output, in the same order, and then empty buffer.\"\n\n if (accumulatedBits === 24) {\n output += String.fromCharCode((buffer & 0xff0000) >> 16);\n output += String.fromCharCode((buffer & 0xff00) >> 8);\n output += String.fromCharCode(buffer & 0xff);\n buffer = accumulatedBits = 0;\n } // \"Advance position by 1.\"\n\n } // \"If buffer is not empty, it contains either 12 or 18 bits. If it contains\n // 12 bits, then discard the last four and interpret the remaining eight as\n // an 8-bit big-endian number. If it contains 18 bits, then discard the last\n // two and interpret the remaining 16 as two 8-bit big-endian numbers. Append\n // the one or two bytes with values equal to those one or two numbers to\n // output, in the same order.\"\n\n\n if (accumulatedBits === 12) {\n buffer >>= 4;\n output += String.fromCharCode(buffer);\n } else if (accumulatedBits === 18) {\n buffer >>= 2;\n output += String.fromCharCode((buffer & 0xff00) >> 8);\n output += String.fromCharCode(buffer & 0xff);\n } // \"Return output.\"\n\n\n return output;\n }\n /**\n * A lookup table for atob(), which converts an ASCII character to the\n * corresponding six-bit number.\n */\n\n\n function atobLookup(chr) {\n if (/[A-Z]/.test(chr)) {\n return chr.charCodeAt(0) - \"A\".charCodeAt(0);\n }\n\n if (/[a-z]/.test(chr)) {\n return chr.charCodeAt(0) - \"a\".charCodeAt(0) + 26;\n }\n\n if (/[0-9]/.test(chr)) {\n return chr.charCodeAt(0) - \"0\".charCodeAt(0) + 52;\n }\n\n if (chr === \"+\") {\n return 62;\n }\n\n if (chr === \"/\") {\n return 63;\n } // Throw exception; should not be hit in tests\n\n\n return undefined;\n }\n\n var atob_1 = atob$1;\n\n /**\n * btoa() as defined by the HTML and Infra specs, which mostly just references\n * RFC 4648.\n */\n\n function btoa$1(s) {\n let i; // String conversion as required by Web IDL.\n\n s = `${s}`; // \"The btoa() method must throw an \"InvalidCharacterError\" DOMException if\n // data contains any character whose code point is greater than U+00FF.\"\n\n for (i = 0; i < s.length; i++) {\n if (s.charCodeAt(i) > 255) {\n return null;\n }\n }\n\n let out = \"\";\n\n for (i = 0; i < s.length; i += 3) {\n const groupsOfSix = [undefined, undefined, undefined, undefined];\n groupsOfSix[0] = s.charCodeAt(i) >> 2;\n groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;\n\n if (s.length > i + 1) {\n groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;\n groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;\n }\n\n if (s.length > i + 2) {\n groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;\n groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;\n }\n\n for (let j = 0; j < groupsOfSix.length; j++) {\n if (typeof groupsOfSix[j] === \"undefined\") {\n out += \"=\";\n } else {\n out += btoaLookup(groupsOfSix[j]);\n }\n }\n }\n\n return out;\n }\n /**\n * Lookup table for btoa(), which converts a six-bit number into the\n * corresponding ASCII character.\n */\n\n\n function btoaLookup(idx) {\n if (idx < 26) {\n return String.fromCharCode(idx + \"A\".charCodeAt(0));\n }\n\n if (idx < 52) {\n return String.fromCharCode(idx - 26 + \"a\".charCodeAt(0));\n }\n\n if (idx < 62) {\n return String.fromCharCode(idx - 52 + \"0\".charCodeAt(0));\n }\n\n if (idx === 62) {\n return \"+\";\n }\n\n if (idx === 63) {\n return \"/\";\n } // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.\n\n\n return undefined;\n }\n\n var btoa_1 = btoa$1;\n\n var abab = {\n atob: atob_1,\n btoa: btoa_1\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-2018, OGG, LLC\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 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 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 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 const Strophe = {\n /** Constant: VERSION */\n VERSION: \"1.4.3\",\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(tag) {\n for (let 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(tag, attribute) {\n if (typeof Strophe.XHTML.attributes[tag] !== 'undefined' && Strophe.XHTML.attributes[tag].length > 0) {\n for (let 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\n validCSS(style) {\n for (let 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\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 ATTACHFAIL: 12\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(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(elem, elemName, func) {\n for (let i = 0; i < elem.childNodes.length; i++) {\n const 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(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 /** Function: xmlGenerator\n * Get the DOM document to generate elements.\n *\n * Returns:\n * The currently used DOM document.\n */\n xmlGenerator() {\n if (!Strophe._xmlGenerator) {\n Strophe._xmlGenerator = getDummyXMLDOMDocument();\n }\n\n return Strophe._xmlGenerator;\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(name) {\n if (!name) {\n return null;\n }\n\n const 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 (let a = 1; a < arguments.length; a++) {\n const 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 (let i = 0; i < arg.length; i++) {\n const 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 (const 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(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(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(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(html) {\n let node; //ensure text is escaped\n\n if (DOMParser) {\n const 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(elem) {\n if (!elem) {\n return null;\n }\n\n let str = \"\";\n\n if (elem.childNodes.length === 0 && elem.nodeType === Strophe.ElementType.TEXT) {\n str += elem.nodeValue;\n }\n\n for (let 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(elem) {\n let el;\n\n if (elem.nodeType === Strophe.ElementType.NORMAL) {\n el = Strophe.xmlElement(elem.tagName);\n\n for (let i = 0; i < elem.attributes.length; i++) {\n el.setAttribute(elem.attributes[i].nodeName, elem.attributes[i].value);\n }\n\n for (let 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(elem) {\n let el;\n\n if (elem.nodeType === Strophe.ElementType.NORMAL) {\n const 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 (let i = 0; i < Strophe.XHTML.attributes[tag].length; i++) {\n const attribute = Strophe.XHTML.attributes[tag][i];\n let 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 const css = [];\n const cssAttrs = value.split(';');\n\n for (let j = 0; j < cssAttrs.length; j++) {\n const attr = cssAttrs[j].split(':');\n const cssName = attr[0].replace(/^\\s*/, \"\").replace(/\\s*$/, \"\").toLowerCase();\n\n if (Strophe.XHTML.validCSS(cssName)) {\n const 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 (let i = 0; i < elem.childNodes.length; i++) {\n el.appendChild(Strophe.createHtml(elem.childNodes[i]));\n }\n } catch (e) {\n // invalid elements\n el = Strophe.xmlTextNode('');\n }\n } else {\n el = Strophe.xmlGenerator().createDocumentFragment();\n\n for (let i = 0; i < elem.childNodes.length; i++) {\n el.appendChild(Strophe.createHtml(elem.childNodes[i]));\n }\n }\n } else if (elem.nodeType === Strophe.ElementType.FRAGMENT) {\n el = Strophe.xmlGenerator().createDocumentFragment();\n\n for (let i = 0; i < elem.childNodes.length; i++) {\n el.appendChild(Strophe.createHtml(elem.childNodes[i]));\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(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(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(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(jid) {\n const bare = Strophe.getBareJidFromJid(jid);\n\n if (bare.indexOf(\"@\") < 0) {\n return bare;\n } else {\n const 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(jid) {\n if (!jid) {\n return null;\n }\n\n const 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(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(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(level, msg) {\n if (level === this.LogLevel.FATAL) {\n var _console;\n\n (_console = console) === null || _console === void 0 ? void 0 : _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(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(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(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(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(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(elem) {\n if (!elem) {\n return null;\n }\n\n if (typeof elem.tree === \"function\") {\n elem = elem.tree();\n }\n\n const names = [...Array(elem.attributes.length).keys()].map(i => elem.attributes[i].nodeName);\n names.sort();\n let result = names.reduce((a, n) => `${a} ${n}=\"${Strophe.xmlescape(elem.attributes.getNamedItem(n).value)}\"`, `<${elem.nodeName}`);\n\n if (elem.childNodes.length > 0) {\n result += \">\";\n\n for (let i = 0; i < elem.childNodes.length; i++) {\n const 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(name, ptype) {\n Strophe._connectionPlugins[name] = ptype;\n }\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 = class Builder {\n constructor(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 /** 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\n\n tree() {\n return this.nodeTree;\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\n\n toString() {\n return Strophe.serialize(this.nodeTree);\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\n\n up() {\n this.node = this.node.parentNode;\n return this;\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\n\n root() {\n this.node = this.nodeTree;\n return this;\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\n\n attrs(moreattrs) {\n for (const 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 /** 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\n\n c(name, attrs, text) {\n const 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 /** 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\n\n cnode(elem) {\n let impNode;\n const xmlGen = Strophe.xmlGenerator();\n\n try {\n impNode = xmlGen.importNode !== undefined;\n } catch (e) {\n impNode = false;\n }\n\n const newElem = impNode ? xmlGen.importNode(elem, true) : Strophe.copyElement(elem);\n this.node.appendChild(newElem);\n this.node = newElem;\n return this;\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\n\n t(text) {\n const child = Strophe.xmlTextNode(text);\n this.node.appendChild(child);\n return this;\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\n\n h(html) {\n const fragment = Strophe.xmlGenerator().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 const 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 };\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(elem) {\n let 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(elem) {\n let nsMatch = false;\n\n if (!this.ns) {\n return true;\n } else {\n Strophe.forEachChild(elem, null, 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(elem) {\n let from = elem.getAttribute('from');\n\n if (this.options.matchBareFromJid) {\n from = Strophe.getBareJidFromJid(from);\n }\n\n const 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(elem) {\n let 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() {\n return \"{Handler: \" + this.handler + \"(\" + this.name + \",\" + this.id + \",\" + this.ns + \")}\";\n }\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 Strophe.TimedHandler = class TimedHandler {\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 constructor(period, handler) {\n this.period = period;\n this.handler = handler;\n this.lastCalled = new Date().getTime();\n this.user = true;\n }\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\n\n run() {\n this.lastCalled = new Date().getTime();\n return this.handler();\n }\n /** PrivateFunction: reset\n * Reset the last called time for the Strophe.TimedHandler.\n */\n\n\n reset() {\n this.lastCalled = new Date().getTime();\n }\n /** PrivateFunction: toString\n * Get a string representation of the Strophe.TimedHandler object.\n *\n * Returns:\n * The string representation.\n */\n\n\n toString() {\n return \"{TimedHandler: \" + this.handler + \"(\" + this.period + \")}\";\n }\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 SCRAM-SHA-1\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-SHA-1 - 60\n * PLAIN - 50\n * OAUTHBEARER - 40\n * X-OAUTH2 - 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 * protocol:\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 * worker:\n *\n * Set this option to URL from where the shared worker script should be loaded.\n *\n * To run the websocket connection inside a shared worker.\n * This allows you to share a single websocket-based connection between\n * multiple Strophe.Connection instances, for example one per browser tab.\n *\n * The script to use is the one in `src/shared-connection-worker.js`.\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 = class Connection {\n constructor(service, options) {\n // The service URL\n this.service = service; // Configuration options\n\n this.options = options || {};\n this.setProtocol();\n /* The connected JID. */\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_bind = false;\n this.do_session = false;\n this.mechanisms = {}; // 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(() => this._onIdle(), 100);\n utils.addCookies(this.options.cookies);\n this.registerSASLMechanisms(this.options.mechanisms); // initialize plugins\n\n for (const k in Strophe._connectionPlugins) {\n if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) {\n const F = function () {};\n\n F.prototype = Strophe._connectionPlugins[k];\n this[k] = new F();\n this[k].init(this);\n }\n }\n }\n /** Function: setProtocol\n * Select protocal based on this.options or this.service\n */\n\n\n setProtocol() {\n const proto = this.options.protocol || \"\";\n\n if (this.options.worker) {\n this._proto = new Strophe.WorkerWebsocket(this);\n } else if (this.service.indexOf(\"ws:\") === 0 || this.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 }\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\n\n 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 /** 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\n\n pause() {\n this.paused = true;\n }\n /** Function: resume\n * Resume the request manager.\n *\n * This resumes after pause() has been called.\n */\n\n\n resume() {\n this.paused = false;\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\n\n getUniqueId(suffix) {\n // eslint-disable-line class-methods-use-this\n const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n const 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 /** 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\n\n addProtocolErrorHandler(protocol, status_code, callback) {\n this.protocolErrorHandlers[protocol][status_code] = callback;\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 * (Integer) disconnection_timeout - The optional disconnection timeout\n * in milliseconds before _doDisconnect will be called.\n */\n\n\n connect(jid, pass, callback, wait, hold, route, authcid, disconnection_timeout = 3000) {\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 this.connect_callback = callback;\n this.disconnecting = false;\n this.connected = false;\n this.authenticated = false;\n this.restored = false;\n this.disconnection_timeout = disconnection_timeout; // 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 /** 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\n\n attach(jid, sid, rid, callback, wait, hold, wind) {\n if (this._proto._attach) {\n return this._proto._attach(jid, sid, rid, callback, wait, hold, wind);\n } else {\n const error = new Error('The \"attach\" method is not available for your connection protocol');\n error.name = 'StropheSessionError';\n throw error;\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\n\n restore(jid, callback, wait, hold, wind) {\n if (this._sessionCachingSupported()) {\n this._proto._restore(jid, callback, wait, hold, wind);\n } else {\n const error = new Error('The \"restore\" method can only be used with a BOSH connection.');\n error.name = 'StropheSessionError';\n throw error;\n }\n }\n /** PrivateFunction: _sessionCachingSupported\n * Checks whether sessionStorage and JSON are supported and whether we're\n * using BOSH.\n */\n\n\n _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 /** 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\n\n xmlInput(elem) {\n // eslint-disable-line\n return;\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\n\n xmlOutput(elem) {\n // eslint-disable-line\n return;\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\n\n rawInput(data) {\n // eslint-disable-line\n return;\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\n\n rawOutput(data) {\n // eslint-disable-line\n return;\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\n\n nextValidRid(rid) {\n // eslint-disable-line\n return;\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\n\n send(elem) {\n if (elem === null) {\n return;\n }\n\n if (typeof elem.sort === \"function\") {\n for (let 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 /** 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\n\n flush() {\n // cancel the pending idle period and run the idle function\n // immediately\n clearTimeout(this._idleTimeout);\n\n this._onIdle();\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\n\n sendPresence(elem, callback, errback, timeout) {\n let timeoutHandler = null;\n\n if (typeof elem.tree === \"function\") {\n elem = elem.tree();\n }\n\n let 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 const handler = this.addHandler(stanza => {\n // remove timeout handler if there is one\n if (timeoutHandler) {\n this.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, () => {\n // get rid of normal handler\n this.deleteHandler(handler); // call errback on timeout with null stanza\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 /** 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\n\n sendIQ(elem, callback, errback, timeout) {\n let timeoutHandler = null;\n\n if (typeof elem.tree === \"function\") {\n elem = elem.tree();\n }\n\n let 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 const handler = this.addHandler(stanza => {\n // remove timeout handler if there is one\n if (timeoutHandler) {\n this.deleteTimedHandler(timeoutHandler);\n }\n\n const 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 const error = new Error(`Got bad IQ type of ${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, () => {\n // get rid of normal handler\n this.deleteHandler(handler); // call errback on timeout with null stanza\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 /** PrivateFunction: _queueData\n * Queue outgoing data for later sending. Also ensures that the data\n * is a DOMElement.\n */\n\n\n _queueData(element) {\n if (element === null || !element.tagName || !element.childNodes) {\n const error = new Error(\"Cannot queue non-DOMElement.\");\n error.name = \"StropheError\";\n throw error;\n }\n\n this._data.push(element);\n }\n /** PrivateFunction: _sendRestart\n * Send an xmpp:restart stanza.\n */\n\n\n _sendRestart() {\n this._data.push(\"restart\");\n\n this._proto._sendRestart();\n\n this._idleTimeout = setTimeout(() => this._onIdle(), 100);\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\n\n addTimedHandler(period, handler) {\n const thand = new Strophe.TimedHandler(period, handler);\n this.addTimeds.push(thand);\n return thand;\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\n\n 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 /** 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\n\n addHandler(handler, ns, name, type, id, from, options) {\n const hand = new Strophe.Handler(handler, ns, name, type, id, from, options);\n this.addHandlers.push(hand);\n return hand;\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\n\n 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 const i = this.addHandlers.indexOf(handRef);\n\n if (i >= 0) {\n this.addHandlers.splice(i, 1);\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\n\n registerSASLMechanisms(mechanisms) {\n this.mechanisms = {};\n mechanisms = mechanisms || [Strophe.SASLAnonymous, Strophe.SASLExternal, Strophe.SASLOAuthBearer, Strophe.SASLXOAuth2, Strophe.SASLPlain, Strophe.SASLSHA1];\n mechanisms.forEach(m => this.registerSASLMechanism(m));\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\n\n registerSASLMechanism(Mechanism) {\n const mechanism = new Mechanism();\n this.mechanisms[mechanism.mechname] = mechanism;\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\n\n disconnect(reason) {\n this._changeConnectStatus(Strophe.Status.DISCONNECTING, reason);\n\n if (reason) {\n Strophe.warn(\"Disconnect was called because: \" + reason);\n } else {\n Strophe.info(\"Disconnect was called\");\n }\n\n if (this.connected) {\n let 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(this.disconnection_timeout, 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 /** 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\n\n _changeConnectStatus(status, condition, elem) {\n // notify all plugins listening for status changes\n for (const k in Strophe._connectionPlugins) {\n if (Object.prototype.hasOwnProperty.call(Strophe._connectionPlugins, k)) {\n const plugin = this[k];\n\n if (plugin.statusChanged) {\n try {\n plugin.statusChanged(status, condition);\n } catch (err) {\n Strophe.error(`${k} plugin caused an exception changing status: ${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: ${e}`);\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\n\n _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 /** 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\n\n _dataRecv(req, raw) {\n const 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 const hand = this.removeHandlers.pop();\n const 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 const 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 let cond = elem.getAttribute(\"condition\");\n const 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, child => {\n // process handlers\n const newList = this.handlers;\n this.handlers = [];\n\n for (let i = 0; i < newList.length; i++) {\n const hand = newList[i]; // 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) && (this.authenticated || !hand.user)) {\n if (hand.run(child)) {\n this.handlers.push(hand);\n }\n } else {\n this.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 /** 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\n\n _connect_cb(req, _callback, raw) {\n Strophe.debug(\"_connect_cb was called\");\n this.connected = true;\n let 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 const 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 let 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 const matched = Array.from(bodyWrap.getElementsByTagName(\"mechanism\")).map(m => this.mechanisms[m.textContent]).filter(m => m);\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 /** 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\n\n sortMechanismsByPriority(mechanisms) {\n // eslint-disable-line class-methods-use-this\n // Sorting mechanisms according to priority.\n for (let i = 0; i < mechanisms.length - 1; ++i) {\n let higher = i;\n\n for (let j = i + 1; j < mechanisms.length; ++j) {\n if (mechanisms[j].priority > mechanisms[higher].priority) {\n higher = j;\n }\n }\n\n if (higher !== i) {\n const swap = mechanisms[i];\n mechanisms[i] = mechanisms[higher];\n mechanisms[higher] = swap;\n }\n }\n\n return mechanisms;\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\n\n authenticate(matched) {\n if (!this._attemptSASLAuth(matched)) {\n this._attemptLegacyAuth();\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\n\n _attemptSASLAuth(mechanisms) {\n mechanisms = this.sortMechanismsByPriority(mechanisms || []);\n let mechanism_found = false;\n\n for (let i = 0; i < mechanisms.length; ++i) {\n if (!mechanisms[i].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 = mechanisms[i];\n\n this._sasl_mechanism.onStart(this);\n\n const request_auth_exchange = $build(\"auth\", {\n 'xmlns': Strophe.NS.SASL,\n 'mechanism': this._sasl_mechanism.mechname\n });\n\n if (this._sasl_mechanism.isClientFirst) {\n const response = this._sasl_mechanism.clientChallenge(this);\n\n request_auth_exchange.t(abab.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 /** PrivateFunction: _sasl_challenge_cb\n * _Private_ handler for the SASL challenge\n *\n */\n\n\n _sasl_challenge_cb(elem) {\n const challenge = abab.atob(Strophe.getText(elem));\n\n const response = this._sasl_mechanism.onChallenge(this, challenge);\n\n const stanza = $build('response', {\n 'xmlns': Strophe.NS.SASL\n });\n\n if (response !== \"\") {\n stanza.t(abab.btoa(response));\n }\n\n this.send(stanza.tree());\n return true;\n }\n /** PrivateFunction: _attemptLegacyAuth\n *\n * Attempt legacy (i.e. non-SASL) authentication.\n */\n\n\n _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 /** 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\n\n _onLegacyAuthIQResult(elem) {\n // eslint-disable-line no-unused-vars\n // build plaintext auth iq\n const 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 /** 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\n\n _sasl_success_cb(elem) {\n if (this._sasl_data[\"server-signature\"]) {\n let serverSignature;\n const success = abab.atob(Strophe.getText(elem));\n const attribMatch = /([a-z]+)=([^,]+)(,|$)/;\n const 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 const streamfeature_handlers = [];\n\n const wrapper = (handlers, elem) => {\n while (handlers.length) {\n this.deleteHandler(handlers.pop());\n }\n\n this._onStreamFeaturesAfterSASL(elem);\n\n return false;\n };\n\n streamfeature_handlers.push(this._addSysHandler(elem => wrapper(streamfeature_handlers, elem), null, \"stream:features\", null, null));\n streamfeature_handlers.push(this._addSysHandler(elem => wrapper(streamfeature_handlers, elem), Strophe.NS.STREAM, \"features\", null, null)); // we must send an xmpp:restart now\n\n this._sendRestart();\n\n return false;\n }\n /** PrivateFunction: _onStreamFeaturesAfterSASL\n * Parameters:\n * (XMLElement) elem - The matching stanza.\n *\n * Returns:\n * false to remove the handler.\n */\n\n\n _onStreamFeaturesAfterSASL(elem) {\n // save stream:features for future usage\n this.features = elem;\n\n for (let i = 0; i < elem.childNodes.length; i++) {\n const 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 /** 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\n\n 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 const 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 /** 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\n\n _onResourceBindResultIQ(elem) {\n if (elem.getAttribute(\"type\") === \"error\") {\n Strophe.warn(\"Resource binding failed.\");\n const conflict = elem.getElementsByTagName(\"conflict\");\n let 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 const bind = elem.getElementsByTagName(\"bind\");\n\n if (bind.length > 0) {\n const jidNode = bind[0].getElementsByTagName(\"jid\");\n\n if (jidNode.length > 0) {\n this.authenticated = true;\n this.jid = Strophe.getText(jidNode[0]);\n\n if (this.do_session) {\n this._establishSession();\n } else {\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 /** 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\n\n _establishSession() {\n if (!this.do_session) {\n throw new Error(`Strophe.Connection.prototype._establishSession ` + `called but apparently ${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 /** 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\n\n _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 this.authenticated = false;\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 /** 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\n\n _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 /** 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\n\n _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 /** 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\n\n _addSysTimedHandler(period, handler) {\n const thand = new Strophe.TimedHandler(period, handler);\n thand.user = false;\n this.addTimeds.push(thand);\n return thand;\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\n\n _addSysHandler(handler, ns, name, type, id) {\n const hand = new Strophe.Handler(handler, ns, name, type, id);\n hand.user = false;\n this.addHandlers.push(hand);\n return hand;\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\n\n _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 /** 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\n\n _onIdle() {\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 const thand = this.removeTimeds.pop();\n const 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 const now = new Date().getTime();\n const newList = [];\n\n for (let i = 0; i < this.timedHandlers.length; i++) {\n const thand = this.timedHandlers[i];\n\n if (this.authenticated || !thand.user) {\n const 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(() => this._onIdle(), 100);\n }\n }\n\n };\n Strophe.SASLMechanism = SASLMechanism;\n /** Constants: SASL mechanisms\n * Available authentication mechanisms\n *\n * Strophe.SASLAnonymous - SASL ANONYMOUS authentication.\n * Strophe.SASLPlain - SASL PLAIN authentication.\n * Strophe.SASLSHA1 - SASL SCRAM-SHA-1 authentication\n * Strophe.SASLOAuthBearer - SASL OAuth Bearer authentication\n * Strophe.SASLExternal - SASL EXTERNAL authentication\n * Strophe.SASLXOAuth2 - SASL X-OAuth2 authentication\n */\n\n Strophe.SASLAnonymous = SASLAnonymous;\n Strophe.SASLPlain = SASLPlain;\n Strophe.SASLSHA1 = SASLSHA1;\n Strophe.SASLOAuthBearer = SASLOAuthBearer;\n Strophe.SASLExternal = SASLExternal;\n Strophe.SASLXOAuth2 = SASLXOAuth2;\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 /** 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 Strophe.Request = class Request {\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 constructor(elem, func, rid, sends) {\n this.id = ++Strophe._requestId;\n this.xmlData = elem;\n this.data = Strophe.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 const 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 const now = new Date();\n return (now - this.dead) / 1000;\n };\n\n this.xhr = this._newXHR();\n }\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\n\n getResponse() {\n let 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.error(\"invalid response received\");\n Strophe.error(\"responseText: \" + this.xhr.responseText);\n Strophe.error(\"responseXML: \" + Strophe.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.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.error(\"invalid response received: \" + node.querySelector('parsererror').textContent);\n Strophe.error(\"responseText: \" + this.xhr.responseText);\n const error = new Error();\n error.name = Strophe.ErrorCondition.BAD_FORMAT;\n throw error;\n }\n }\n\n return node;\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\n\n _newXHR() {\n let 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 };\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.Bosh = class Bosh {\n constructor(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 /** PrivateFunction: _buildBody\n * _Private_ helper function to generate the wrapper for BOSH.\n *\n * Returns:\n * A Strophe.Builder with a element.\n */\n\n\n _buildBody() {\n const bodyWrap = $build('body', {\n 'rid': this.rid++,\n 'xmlns': Strophe.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 /** PrivateFunction: _reset\n * Reset the connection.\n *\n * This function is called by the reset function of the Strophe Connection\n */\n\n\n _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 /** PrivateFunction: _connect\n * _Private_ function that initializes the BOSH connection.\n *\n * Creates and sends the Request that initializes the BOSH connection.\n */\n\n\n _connect(wait, hold, route) {\n this.wait = wait || this.wait;\n this.hold = hold || this.hold;\n this.errors = 0;\n\n const 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.NS.BOSH\n });\n\n if (route) {\n body.attrs({\n 'route': route\n });\n }\n\n const _connect_cb = this._conn._connect_cb;\n\n this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, _connect_cb.bind(this._conn)), body.tree().getAttribute(\"rid\")));\n\n this._throttledRequestHandler();\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\n\n _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.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.Status.ATTACHED, null);\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\n\n _restore(jid, callback, wait, hold, wind) {\n const 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.getBareJidFromJid(session.jid) === Strophe.getBareJidFromJid(jid) || // If authcid is null, then it's an anonymous login, so\n // we compare only the domains:\n Strophe.getNodeFromJid(jid) === null && Strophe.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 const error = new Error(\"_restore: no restoreable session.\");\n error.name = \"StropheSessionError\";\n throw error;\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\n\n _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 /** 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\n\n _connect_cb(bodyWrap) {\n const typ = bodyWrap.getAttribute(\"type\");\n\n if (typ !== null && typ === \"terminate\") {\n // an error occurred\n let cond = bodyWrap.getAttribute(\"condition\");\n Strophe.error(\"BOSH-Connection failed: \" + cond);\n const 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.Status.CONNFAIL, cond);\n } else {\n this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, \"unknown\");\n }\n\n this._conn._doDisconnect(cond);\n\n return Strophe.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 const wind = bodyWrap.getAttribute('requests');\n\n if (wind) {\n this.window = parseInt(wind, 10);\n }\n\n const hold = bodyWrap.getAttribute('hold');\n\n if (hold) {\n this.hold = parseInt(hold, 10);\n }\n\n const wait = bodyWrap.getAttribute('wait');\n\n if (wait) {\n this.wait = parseInt(wait, 10);\n }\n\n const inactivity = bodyWrap.getAttribute('inactivity');\n\n if (inactivity) {\n this.inactivity = parseInt(inactivity, 10);\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\n\n _disconnect(pres) {\n this._sendTerminate(pres);\n }\n /** PrivateFunction: _doDisconnect\n * _Private_ function to disconnect.\n *\n * Resets the SID and RID.\n */\n\n\n _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 /** 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\n\n _emptyQueue() {\n return this._requests.length === 0;\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\n\n _callProtocolErrorHandlers(req) {\n const reqStatus = Bosh._getRequestStatus(req);\n\n const err_callback = this._conn.protocolErrorHandlers.HTTP[reqStatus];\n\n if (err_callback) {\n err_callback.call(this, reqStatus);\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\n\n _hitError(reqStatus) {\n this.errors++;\n Strophe.warn(\"request errored, status: \" + reqStatus + \", number of errors: \" + this.errors);\n\n if (this.errors > 4) {\n this._conn._onDisconnectTimeout();\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\n\n _no_auth_received(callback) {\n Strophe.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 const body = this._buildBody();\n\n this._requests.push(new Strophe.Request(body.tree(), this._onRequestStateChange.bind(this, callback), body.tree().getAttribute(\"rid\")));\n\n this._throttledRequestHandler();\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\n\n _onDisconnectTimeout() {\n this._abortAllRequests();\n }\n /** PrivateFunction: _abortAllRequests\n * _Private_ helper function that makes sure all pending requests are aborted.\n */\n\n\n _abortAllRequests() {\n while (this._requests.length > 0) {\n const req = this._requests.pop();\n\n req.abort = true;\n req.xhr.abort();\n\n req.xhr.onreadystatechange = function () {};\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\n\n _onIdle() {\n const 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.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 const body = this._buildBody();\n\n for (let 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.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.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 const time_elapsed = this._requests[0].age();\n\n if (this._requests[0].dead !== null) {\n if (this._requests[0].timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait)) {\n this._throttledRequestHandler();\n }\n }\n\n if (time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait)) {\n Strophe.warn(\"Request \" + this._requests[0].id + \" timed out, over \" + Math.floor(Strophe.TIMEOUT * this.wait) + \" seconds since last activity\");\n\n this._throttledRequestHandler();\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\n\n static _getRequestStatus(req, def) {\n let 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.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 /** 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\n\n _onRequestStateChange(func, req) {\n Strophe.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 const reqStatus = Bosh._getRequestStatus(req);\n\n this.lastResponseHeaders = req.xhr.getAllResponseHeaders();\n\n if (this._conn.disconnecting && reqStatus >= 400) {\n this._hitError(reqStatus);\n\n this._callProtocolErrorHandlers(req);\n\n return;\n }\n\n const valid_request = reqStatus > 0 && reqStatus < 500;\n const 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.debug(\"request id \" + req.id + \" should now be removed\");\n }\n\n if (reqStatus === 200) {\n // request succeeded\n const reqIs0 = this._requests[0] === req;\n const 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.SECONDARY_TIMEOUT * this.wait)) {\n this._restartRequest(0);\n }\n\n this._conn.nextValidRid(Number(req.rid) + 1);\n\n Strophe.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.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.Status.DISCONNECTING, null);\n\n this._conn._doDisconnect();\n }\n } else {\n Strophe.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.Status.CONNFAIL, \"giving-up\");\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\n\n _processRequest(i) {\n let req = this._requests[i];\n\n const reqStatus = Bosh._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 const time_elapsed = req.age();\n const primary_timeout = !isNaN(time_elapsed) && time_elapsed > Math.floor(Strophe.TIMEOUT * this.wait);\n const secondary_timeout = req.dead !== null && req.timeDead() > Math.floor(Strophe.SECONDARY_TIMEOUT * this.wait);\n const 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.error(`Request ${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.Request(req.xmlData, req.origFunc, req.rid, req.sends);\n req = this._requests[i];\n }\n\n if (req.xhr.readyState === 0) {\n Strophe.debug(\"request id \" + req.id + \".\" + req.sends + \" posting\");\n\n try {\n const 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.error(\"XHR open failed: \" + e2.toString());\n\n if (!this._conn.connected) {\n this._conn._changeConnectStatus(Strophe.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 const sendFunc = () => {\n req.date = new Date();\n\n if (this._conn.options.customHeaders) {\n const headers = this._conn.options.customHeaders;\n\n for (const 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 const backoff = Math.min(Math.floor(Strophe.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.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.Connection.prototype.rawOutput) {\n this._conn.rawOutput(req.data);\n }\n } else {\n Strophe.debug(\"_processRequest: \" + (i === 0 ? \"first\" : \"second\") + \" request has readyState of \" + req.xhr.readyState);\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\n\n _removeRequest(req) {\n Strophe.debug(\"removing request\");\n\n for (let 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 /** 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\n\n _restartRequest(i) {\n const req = this._requests[i];\n\n if (req.dead === null) {\n req.dead = new Date();\n }\n\n this._processRequest(i);\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\n\n _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 /** 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\n\n _sendTerminate(pres) {\n Strophe.debug(\"_sendTerminate was called\");\n\n const body = this._buildBody().attrs({\n type: \"terminate\"\n });\n\n if (pres) {\n body.cnode(pres.tree());\n }\n\n const req = new Strophe.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 /** 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\n\n _send() {\n clearTimeout(this._conn._idleTimeout);\n\n this._throttledRequestHandler();\n\n this._conn._idleTimeout = setTimeout(() => this._conn._onIdle(), 100);\n }\n /** PrivateFunction: _sendRestart\n *\n * Send an xmpp:restart stanza.\n */\n\n\n _sendRestart() {\n this._throttledRequestHandler();\n\n clearTimeout(this._conn._idleTimeout);\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\n\n _throttledRequestHandler() {\n if (!this._requests) {\n Strophe.debug(\"_throttledRequestHandler called with \" + \"undefined requests\");\n } else {\n Strophe.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 /** 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\n Strophe.Bosh.prototype.strip = null;\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 /** 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 Strophe.Websocket = class Websocket {\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 constructor(connection) {\n this._conn = connection;\n this.strip = \"wrapper\";\n const 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 let 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 /** 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\n\n _buildStream() {\n return $build(\"open\", {\n \"xmlns\": Strophe.NS.FRAMING,\n \"to\": this._conn.domain,\n \"version\": '1.0'\n });\n }\n /** PrivateFunction: _checkStreamError\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\n\n _checkStreamError(bodyWrap, connectstatus) {\n let errors;\n\n if (bodyWrap.getElementsByTagNameNS) {\n errors = bodyWrap.getElementsByTagNameNS(Strophe.NS.STREAM, \"error\");\n } else {\n errors = bodyWrap.getElementsByTagName(\"stream:error\");\n }\n\n if (errors.length === 0) {\n return false;\n }\n\n const error = errors[0];\n let condition = \"\";\n let text = \"\";\n const ns = \"urn:ietf:params:xml:ns:xmpp-streams\";\n\n for (let i = 0; i < error.childNodes.length; i++) {\n const 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 let 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.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 /** 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\n\n _reset() {\n // eslint-disable-line class-methods-use-this\n return;\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\n\n _connect() {\n // Ensure that there is no open WebSocket from a previous Connection.\n this._closeSocket();\n\n this.socket = new WebSocket(this._conn.service, \"xmpp\");\n\n this.socket.onopen = () => this._onOpen();\n\n this.socket.onerror = e => this._onError(e);\n\n this.socket.onclose = e => this._onClose(e); // Gets replaced with this._onMessage once _onInitialMessage is called\n\n\n this.socket.onmessage = message => this._onInitialMessage(message);\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\n\n _connect_cb(bodyWrap) {\n const error = this._checkStreamError(bodyWrap, Strophe.Status.CONNFAIL);\n\n if (error) {\n return Strophe.Status.CONNFAIL;\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\n\n _handleStreamStart(message) {\n let error = false; // Check for errors in the tag\n\n const ns = message.getAttribute(\"xmlns\");\n\n if (typeof ns !== \"string\") {\n error = \"Missing xmlns in \";\n } else if (ns !== Strophe.NS.FRAMING) {\n error = \"Wrong xmlns in : \" + ns;\n }\n\n const 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.Status.CONNFAIL, error);\n\n this._conn._doDisconnect();\n\n return false;\n }\n\n return true;\n }\n /** PrivateFunction: _onInitialMessage\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\n\n _onInitialMessage(message) {\n if (message.data.indexOf(\"\\s*)*/, \"\");\n if (data === '') return;\n const 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 const 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 const see_uri = parsedMessage.getAttribute(\"see-other-uri\");\n\n if (see_uri) {\n const service = this._conn.service; // Valid scenarios: WSS->WSS, WS->ANY\n\n const isSecureRedirect = service.indexOf(\"wss:\") >= 0 && see_uri.indexOf(\"wss:\") >= 0 || service.indexOf(\"ws:\") >= 0;\n\n if (isSecureRedirect) {\n this._conn._changeConnectStatus(Strophe.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.Status.CONNFAIL, \"Received closing stream\");\n\n this._conn._doDisconnect();\n }\n } else {\n this._replaceMessageHandler();\n\n const string = this._streamWrap(message.data);\n\n const elem = new DOMParser().parseFromString(string, \"text/xml\").documentElement;\n\n this._conn._connect_cb(elem, null, message.data);\n }\n }\n /** PrivateFunction: _replaceMessageHandler\n *\n * Called by _onInitialMessage in order to replace itself with the general message handler.\n * This method is overridden by Strophe.WorkerWebsocket, which manages a\n * websocket connection via a service worker and doesn't have direct access\n * to the socket.\n */\n\n\n _replaceMessageHandler() {\n this.socket.onmessage = m => this._onMessage(m);\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\n\n _disconnect(pres) {\n if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {\n if (pres) {\n this._conn.send(pres);\n }\n\n const close = $build(\"close\", {\n \"xmlns\": Strophe.NS.FRAMING\n });\n\n this._conn.xmlOutput(close.tree());\n\n const closeString = Strophe.serialize(close);\n\n this._conn.rawOutput(closeString);\n\n try {\n this.socket.send(closeString);\n } catch (e) {\n Strophe.warn(\"Couldn't send tag.\");\n }\n }\n\n setTimeout(() => this._conn._doDisconnect, 0);\n }\n /** PrivateFunction: _doDisconnect\n * _Private_ function to disconnect.\n *\n * Just closes the Socket for WebSockets\n */\n\n\n _doDisconnect() {\n Strophe.debug(\"WebSockets _doDisconnect was called\");\n\n this._closeSocket();\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\n\n _streamWrap(stanza) {\n // eslint-disable-line class-methods-use-this\n return \"\" + stanza + '';\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\n\n _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.debug(e.message);\n }\n }\n\n this.socket = null;\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\n\n _emptyQueue() {\n // eslint-disable-line class-methods-use-this\n return true;\n }\n /** PrivateFunction: _onClose\n * _Private_ function to handle websockets closing.\n */\n\n\n _onClose(e) {\n if (this._conn.connected && !this._conn.disconnecting) {\n Strophe.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.error(\"Websocket closed unexcectedly\");\n\n this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, \"The WebSocket connection could not be established or was disconnected.\");\n\n this._conn._doDisconnect();\n } else {\n Strophe.debug(\"Websocket closed\");\n }\n }\n /** PrivateFunction: _no_auth_received\n *\n * Called on stream start/restart when no stream:features\n * has been received.\n */\n\n\n _no_auth_received(callback) {\n Strophe.error(\"Server did not offer a supported authentication mechanism\");\n\n this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, Strophe.ErrorCondition.NO_AUTH_MECH);\n\n if (callback) {\n callback.call(this._conn);\n }\n\n this._conn._doDisconnect();\n }\n /** PrivateFunction: _onDisconnectTimeout\n * _Private_ timeout handler for handling non-graceful disconnection.\n *\n * This does nothing for WebSockets\n */\n\n\n _onDisconnectTimeout() {} // eslint-disable-line class-methods-use-this\n\n /** PrivateFunction: _abortAllRequests\n * _Private_ helper function that makes sure all pending requests are aborted.\n */\n\n\n _abortAllRequests() {} // eslint-disable-line class-methods-use-this\n\n /** PrivateFunction: _onError\n * _Private_ function to handle websockets errors.\n *\n * Parameters:\n * (Object) error - The websocket error.\n */\n\n\n _onError(error) {\n Strophe.error(\"Websocket error \" + JSON.stringify(error));\n\n this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, \"The WebSocket connection could not be established or was disconnected.\");\n\n this._disconnect();\n }\n /** PrivateFunction: _onIdle\n * _Private_ function called by Strophe.Connection._onIdle\n *\n * sends all queued stanzas\n */\n\n\n _onIdle() {\n const data = this._conn._data;\n\n if (data.length > 0 && !this._conn.paused) {\n for (let i = 0; i < data.length; i++) {\n if (data[i] !== null) {\n let stanza;\n\n if (data[i] === \"restart\") {\n stanza = this._buildStream().tree();\n } else {\n stanza = data[i];\n }\n\n const rawStanza = Strophe.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 /** 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\n\n _onMessage(message) {\n let elem; // check for closing stream\n\n const 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 /** PrivateFunction: _onOpen\n * _Private_ function to handle websockets connection setup.\n *\n * The opening stream tag is sent here.\n */\n\n\n _onOpen() {\n Strophe.debug(\"Websocket open\");\n\n const start = this._buildStream();\n\n this._conn.xmlOutput(start.tree());\n\n const startString = Strophe.serialize(start);\n\n this._conn.rawOutput(startString);\n\n this.socket.send(startString);\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\n\n _reqToData(stanza) {\n // eslint-disable-line class-methods-use-this\n return stanza;\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\n\n _send() {\n this._conn.flush();\n }\n /** PrivateFunction: _sendRestart\n *\n * Send an xmpp:restart stanza.\n */\n\n\n _sendRestart() {\n clearTimeout(this._conn._idleTimeout);\n\n this._conn._onIdle.bind(this._conn)();\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 2020, JC Brand\n */\n const lmap = {};\n lmap['debug'] = Strophe.LogLevel.DEBUG;\n lmap['info'] = Strophe.LogLevel.INFO;\n lmap['warn'] = Strophe.LogLevel.WARN;\n lmap['error'] = Strophe.LogLevel.ERROR;\n lmap['fatal'] = Strophe.LogLevel.FATAL;\n /** Class: Strophe.WorkerWebsocket\n * _Private_ helper class that handles a websocket connection inside a shared worker.\n */\n\n Strophe.WorkerWebsocket = class WorkerWebsocket extends Strophe.Websocket {\n /** PrivateConstructor: Strophe.WorkerWebsocket\n * Create and initialize a Strophe.WorkerWebsocket object.\n *\n * Parameters:\n * (Strophe.Connection) connection - The Strophe.Connection\n *\n * Returns:\n * A new Strophe.WorkerWebsocket object.\n */\n constructor(connection) {\n super(connection);\n this._conn = connection;\n this.worker = new SharedWorker(this._conn.options.worker, 'Strophe XMPP Connection');\n\n this.worker.onerror = e => {\n var _console;\n\n (_console = console) === null || _console === void 0 ? void 0 : _console.error(e);\n Strophe.log(Strophe.LogLevel.ERROR, `Shared Worker Error: ${e}`);\n };\n }\n\n get socket() {\n return {\n 'send': str => this.worker.port.postMessage(['send', str])\n };\n }\n\n _connect() {\n this._messageHandler = m => this._onInitialMessage(m);\n\n this.worker.port.start();\n\n this.worker.port.onmessage = ev => this._onWorkerMessage(ev);\n\n this.worker.port.postMessage(['_connect', this._conn.service, this._conn.jid]);\n }\n\n _attach(callback) {\n this._messageHandler = m => this._onMessage(m);\n\n this._conn.connect_callback = callback;\n this.worker.port.start();\n\n this.worker.port.onmessage = ev => this._onWorkerMessage(ev);\n\n this.worker.port.postMessage(['_attach', this._conn.service]);\n }\n\n _attachCallback(status, jid) {\n if (status === Strophe.Status.ATTACHED) {\n this._conn.jid = jid;\n this._conn.authenticated = true;\n this._conn.connected = true;\n this._conn.restored = true;\n\n this._conn._changeConnectStatus(Strophe.Status.ATTACHED);\n } else if (status === Strophe.Status.ATTACHFAIL) {\n this._conn.authenticated = false;\n this._conn.connected = false;\n this._conn.restored = false;\n\n this._conn._changeConnectStatus(Strophe.Status.ATTACHFAIL);\n }\n }\n\n _disconnect(readyState, pres) {\n pres && this._conn.send(pres);\n const close = $build(\"close\", {\n \"xmlns\": Strophe.NS.FRAMING\n });\n\n this._conn.xmlOutput(close.tree());\n\n const closeString = Strophe.serialize(close);\n\n this._conn.rawOutput(closeString);\n\n this.worker.port.postMessage(['send', closeString]);\n\n this._conn._doDisconnect();\n }\n\n _onClose(e) {\n if (this._conn.connected && !this._conn.disconnecting) {\n Strophe.error(\"Websocket closed unexpectedly\");\n\n this._conn._doDisconnect();\n } else if (e && e.code === 1006 && !this._conn.connected) {\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.error(\"Websocket closed unexcectedly\");\n\n this._conn._changeConnectStatus(Strophe.Status.CONNFAIL, \"The WebSocket connection could not be established or was disconnected.\");\n\n this._conn._doDisconnect();\n } else {\n Strophe.debug(\"Websocket closed\");\n }\n }\n\n _closeSocket() {\n this.worker.port.postMessage(['_closeSocket']);\n }\n /** PrivateFunction: _replaceMessageHandler\n *\n * Called by _onInitialMessage in order to replace itself with the general message handler.\n * This method is overridden by Strophe.WorkerWebsocket, which manages a\n * websocket connection via a service worker and doesn't have direct access\n * to the socket.\n */\n\n\n _replaceMessageHandler() {\n this._messageHandler = m => this._onMessage(m);\n }\n /** PrivateFunction: _onWorkerMessage\n * _Private_ function that handles messages received from the service worker\n */\n\n\n _onWorkerMessage(ev) {\n const {\n data\n } = ev;\n const method_name = data[0];\n\n if (method_name === '_onMessage') {\n this._messageHandler(data[1]);\n } else if (method_name in this) {\n try {\n this[method_name].apply(this, ev.data.slice(1));\n } catch (e) {\n Strophe.log(Strophe.LogLevel.ERROR, e);\n }\n } else if (method_name === 'log') {\n const level = data[1];\n const msg = data[2];\n Strophe.log(lmap[level], msg);\n } else {\n Strophe.log(Strophe.LogLevel.ERROR, `Found unhandled service worker message: ${data}`);\n }\n }\n\n };\n\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 global$1.Strophe = core.Strophe;\n const {\n b64_sha1\n } = SHA1;\n\n exports.$build = $build;\n exports.$iq = $iq;\n exports.$msg = $msg;\n exports.$pres = $pres;\n exports.Strophe = Strophe;\n exports.b64_sha1 = b64_sha1;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n}));\n","'use strict';\n\nmodule.exports = function () {\n throw new Error(\n 'ws does not work in the browser. Browser clients must use the native ' +\n 'WebSocket object'\n );\n};\n","(function (global, factory) {\n\ttypeof exports === 'object' && typeof module !== 'undefined' ? factory(require('strophe.js')) :\n\ttypeof define === 'function' && define.amd ? define(['strophe.js'], factory) :\n\t(factory(global.window));\n}(this, (function (strophe_js) { 'use strict';\n\nstrophe_js.Strophe.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_js.Strophe.NS.DISCO_INFO, 'iq', 'get', null, null);\n // disco items\n conn.addHandler(this._onDiscoItems.bind(this), strophe_js.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\t\treturn this._originalOnStreamFeaturesAfterSASL.apply(this._c, arguments);\n\t\t},\n\n\t\tstatusChanged: function (status) {\n\t\t\tthis._connectionStatus = status;\n\t\t\tif (!this.getResumeToken()\n\t\t\t\t&& (status === strophe_js.Strophe.Status.CONNECTED || status === strophe_js.Strophe.Status.DISCONNECTED)) {\n\t\t\t\tthis.logging && strophe_js.Strophe.debug('SM reset state');\n\n\t\t\t\tthis._serverProcesssedStanzasCounter = 0;\n\t\t\t\tthis._clientProcessedStanzasCounter = 0;\n\n\t\t\t\tthis._clientSentStanzasCounter = 0;\n\n\t\t\t\tthis._isStreamManagementEnabled = false;\n\t\t\t\tthis._requestResponseIntervalCount = 0;\n\n\t\t\t\t// FIXME not described in JSDocs\n\t\t\t\tthis._resuming = false;\n\n\t\t\t\tif (status === strophe_js.Strophe.Status.DISCONNECTED) {\n\t\t\t\t\tthis._isSupported = false;\n\t\t\t\t}\n\n\t\t\t\tthis._unacknowledgedStanzas = [];\n\n\t\t\t\tif (this._requestHandler) {\n\t\t\t\t\tthis._c.deleteHandler(this._requestHandler);\n\t\t\t\t}\n\n\t\t\t\tif (this._incomingHandler) {\n\t\t\t\t\tthis._c.deleteHandler(this._incomingHandler);\n\t\t\t\t}\n\n\t\t\t\tthis._requestHandler = this._c.addHandler(this._handleServerRequestHandler.bind(this), this._NS, 'r');\n\t\t\t\tthis._ackHandler = this._c.addHandler(this._handleServerAck.bind(this), this._NS, 'a');\n\t\t\t\tthis._incomingHandler = this._c.addHandler(this._incomingStanzaHandler.bind(this));\n\n\t\t\t\t// FIXME handler instances stored, but never used\n\t\t\t\tthis._enabledHandler = this._c._addSysHandler(this._handleEnabled.bind(this), this._NS, 'enabled');\n\t\t\t\tthis._resumeFailedHandler = this._c._addSysHandler(this._handleResumeFailed.bind(this), this._NS, 'failed');\n\t\t\t\tthis._resumedHandler = this._c._addSysHandler(this._handleResumed.bind(this), this._NS,'resumed');\n\n\t\t\t} else if (status === strophe_js.Strophe.Status.BINDREQUIRED) {\n\t\t\t\tthis._c.jid = this._storedJid;\n\n\t\t\t\t// Restore Strophe handlers\n\t\t\t\tfor (const property in this._resumeState) {\n\t\t\t\t\tthis._c[property] = this._resumeState[property];\n\t\t\t\t}\n\n\t\t\t\t// FIXME check conditions if there's session ID and if enabled\n\t\t\t\tthis._c.send(strophe_js.$build('resume', {\n\t\t\t\t\txmlns: this._NS,\n\t\t\t\t\th: this._clientProcessedStanzasCounter,\n\t\t\t\t\tprevid: this._resumeToken\n\t\t\t\t}));\n\t\t\t\tthis._c.flush();\n\t\t\t} else if (status === strophe_js.Strophe.Status.ERROR) {\n\t\t\t\tthis.logging && strophe_js.Strophe.debug('SM cleared resume token on error');\n\t\t\t\tthis._resumeToken = undefined;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t* This method overrides the send method implemented by Strophe.Connection\n\t\t* to count outgoing stanzas\n\t\t*\n\t\t* @method Send\n\t\t* @public\n\t\t*/\n\t\txmlOutput: function(elem) {\n\t\t\tif (strophe_js.Strophe.isTagEqual(elem, 'iq') ||\n\t\t\t\tstrophe_js.Strophe.isTagEqual(elem, 'presence') ||\n\t\t\t\tstrophe_js.Strophe.isTagEqual(elem, 'message')) {\n\t\t\t\tthis._increaseSentStanzasCounter(elem);\n\t\t\t}\n\n\t\t\treturn this._originalXMLOutput.call(this._c, elem);\n\t\t},\n\n\t\t_handleEnabled: function(elem) {\n\t\t\tthis._isStreamManagementEnabled = true;\n\t\t\t// FIXME fail if requested, but not enabled\n\t\t\tthis._resumeToken = elem.getAttribute('resume') === 'true' && elem.getAttribute('id');\n\n\t\t\tthis._c.resume();\n\n\t\t\treturn true;\n\t\t},\n\n\t\t_handleResumeFailed: function(elem) {\n\t\t\tconst error = elem && elem.firstElementChild && elem.firstElementChild.tagName;\n\n\t\t\tthis._c._changeConnectStatus(strophe_js.Strophe.Status.ERROR, error, elem);\n\t\t\tthis._c._doDisconnect();\n\n\t\t\treturn true;\n\t\t},\n\n\t\t_handleResumed: function(elem) {\n\t\t\t// FIXME check if in the correct state\n\t\t\tvar handledCount = parseInt(elem.getAttribute('h'));\n\t\t\tthis._handleAcknowledgedStanzas(handledCount, this._serverProcesssedStanzasCounter);\n\n\t\t\tthis._resuming = false;\n\t\t\tthis._c.do_bind = false; // No need to bind our resource anymore\n\t\t\tthis._c.authenticated = true;\n\t\t\tthis._c.restored = true;\n\n\t\t\tif (this._unacknowledgedStanzas.length > 0) {\n\t\t\t\tthis.logging && strophe_js.Strophe.debug('SM Sending unacknowledged stanzas', this._unacknowledgedStanzas);\n\t\t\t\tfor(const stanza of this._unacknowledgedStanzas) {\n\t\t\t\t\tthis._c.send(stanza);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.logging && strophe_js.Strophe.debug('SM No unacknowledged stanzas', this._unacknowledgedStanzas);\n\t\t\t}\n\n\t\t\tthis._c._changeConnectStatus(strophe_js.Strophe.Status.CONNECTED, null);\n\n\t\t\treturn true;\n\t\t},\n\n\t\t_incomingStanzaHandler: function(elem) {\n\t\t\tif (strophe_js.Strophe.isTagEqual(elem, 'iq') || strophe_js.Strophe.isTagEqual(elem, 'presence') || strophe_js.Strophe.isTagEqual(elem, 'message')) {\n\t\t\t\tthis._increaseReceivedStanzasCounter();\n\n\t\t\t\tif (this.autoSendCountOnEveryIncomingStanza) {\n\t\t\t\t\tthis._answerProcessedStanzas();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\n\t\t_handleAcknowledgedStanzas: function(reportedHandledCount, lastKnownHandledCount) {\n\t\t\tvar delta = reportedHandledCount - lastKnownHandledCount;\n\n\t\t\tif (delta < 0) {\n\t\t\t\tthis._throwError('New reported stanza count lower than previous. New: ' + reportedHandledCount + ' - Previous: ' + lastKnownHandledCount);\n\t\t\t}\n\n\t\t\tif (delta > this._unacknowledgedStanzas.length) {\n\t\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\t}\n\n\t\t\tfor(var i = 0; i < delta; i++) {\n\t\t\t\tvar stanza = this._unacknowledgedStanzas.shift();\n\t\t\t\tfor (var j = 0; j < this._acknowledgedStanzaListeners.length; j++) {\n\t\t\t\t\tthis._acknowledgedStanzaListeners[j](stanza);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.logging && this._unacknowledgedStanzas.length > 0) {\n\t\t\t\tstrophe_js.Strophe.warn('SM Unacknowledged stanzas', this._unacknowledgedStanzas);\n\t\t\t}\n\n\t\t\tthis._serverProcesssedStanzasCounter = reportedHandledCount;\n\n\t\t\tif (this.requestResponseInterval > 0) {\n\t\t\t\tthis._requestResponseIntervalCount = 0;\n\t\t\t}\n\t\t},\n\n\t\t_handleServerRequestHandler: function() {\n\t\t\tthis._answerProcessedStanzas();\n\n\t\t\treturn true;\n\t\t},\n\n\t\t_handleServerAck: function(elem){\n\t\t\tvar handledCount = parseInt(elem.getAttribute('h'));\n\t\t\tthis._handleAcknowledgedStanzas(handledCount, this._serverProcesssedStanzasCounter);\n\n\t\t\treturn true;\n\t\t},\n\n\t\t_answerProcessedStanzas: function() {\n\t\t\tif (this._isStreamManagementEnabled) {\n\t\t\t\tthis._c.send(strophe_js.$build('a', { xmlns: this._NS, h: this._clientProcessedStanzasCounter }));\n\t\t\t}\n\t\t},\n\n\t\t_increaseSentStanzasCounter: function(elem) {\n\t\t\tif (this._isStreamManagementEnabled) {\n\t\t\t\tif (this._unacknowledgedStanzas.indexOf(elem) !== -1) {\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis._unacknowledgedStanzas.push(elem);\n\t\t\t\tthis._clientSentStanzasCounter++;\n\n\t\t\t\tif (this.requestResponseInterval > 0) {\n\t\t\t\t\tthis._requestResponseIntervalCount++;\n\n\t\t\t\t\tif (this._requestResponseIntervalCount === this.requestResponseInterval) {\n\t\t\t\t\t\t// FIXME Can not call send from onIdle.\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tif (this._connectionStatus === strophe_js.Strophe.Status.CONNECTED) {\n\t\t\t\t\t\t\t\tthis.requestAcknowledgement();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t_increaseReceivedStanzasCounter: function() {\n\t\t\tif (this._isStreamManagementEnabled) {\n\t\t\t\tthis._clientProcessedStanzasCounter++;\n\t\t\t}\n\t\t},\n\n\t\t_throwError: function(msg) {\n\t\t\tstrophe_js.Strophe.error(msg);\n\t\t\tthrow new Error(msg);\n\t\t}\n\n\t});\n\n})));\n//# sourceMappingURL=strophe.stream-management.js.map\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","// 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","Logger","LogCollector","logStorage","options","this","stringifyObjects","storeInterval","maxEntryLength","Object","keys","levels","forEach","logLevel","_log","apply","arguments","bind","storeLogsIntervalID","queue","totalLen","outputCache","prototype","stringify","someObject","JSON","error","formatLogMessage","msg","i","len","length","arg","ERROR","timestamp","prevMessage","prevMessageText","text","count","push","_flush","start","_reschedulePublishInterval","window","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","Error","stack","split","m","match","substr","log","logger","level","args","Array","slice","call","disableCallerInfo","transports","concat","t","l","logPrefixes","Date","toISOString","id","fullLogParts","methods","setGlobalOptions","setLevel","TRACE","DEBUG","INFO","LOG","WARN","idLoggers","loggers","curLevel","getLogger","setLogLevelById","setLogLevel","grammar","v","name","reg","o","names","format","s","u","e","p","z","r","c","b","a","rate","address","subtype","direction","config","sessionConfig","str","raddr","tcptype","generation","attribute","value","maxMessageSize","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","substring","stream","scid","paused","formatRegExp","formatStr","replace","x","makeLine","Function","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","Math","floor","random","_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","previous_async","async","only_once","fn","called","noConflict","_toString","_isArray","_each","arr","iterator","_map","results","_keys","k","hasOwnProperty","process","nextTick","setImmediate","each","callback","completed","done","err","eachSeries","iterate","forEachSeries","eachLimit","limit","_eachLimit","forEachLimit","started","running","replenish","doParallel","doSeries","_asyncMap","eachfn","mapSeries","mapLimit","_mapLimit","doParallelLimit","memo","inject","foldl","reduceRight","reversed","reverse","foldr","_filter","sort","filterSeries","select","selectSeries","_reject","reject","rejectSeries","_detect","main_callback","result","detect","detectSeries","any","every","all","sortBy","criteria","left","right","auto","tasks","remainingTasks","listeners","addListener","unshift","taskComplete","theCallback","task","taskCallback","safeResults","rkey","requires","abs","ready","listener","removeListener","retry","times","attempts","wrappedTask","wrappedCallback","wrappedResults","retryAttempt","finalAttempt","seriesCallback","series","data","waterfall","wrapIterator","next","_parallel","parallel","parallelLimit","makeCallback","_concat","cb","y","concatSeries","whilst","doWhilst","until","doUntil","worker","concurrency","_insert","q","pos","drain","saturated","workers","empty","kill","shift","idle","pause","resume","priorityQueue","_compareTasks","priority","sequence","compare","beg","end","mid","_binarySearch","cargo","payload","working","drained","ts","ds","_console_fn","dir","memoize","hasher","queues","memoized","pop","unmemoized","unmemoize","counter","timesSeries","seq","fns","that","newargs","nextargs","compose","_applyEach","go","applyEach","applyEachSeries","forever","constructor","userId","displayName","isLocalStats","_userId","setDisplayName","_isLocalStats","setDominantSpeaker","totalDominantSpeakerTime","_dominantSpeakerStart","_hasLeft","_facialExpressions","happy","neutral","surprised","angry","fearful","disgusted","sad","getUserId","getDisplayName","newName","isDominantSpeaker","isNowDominantSpeaker","now","timeElapsed","getTotalDominantSpeakerTime","total","hasLeft","markAsHasLeft","getFacialExpressions","setFacialExpressions","facialExpressions","addFacialExpression","facialExpression","duration","RecordingResult","require","AUDIO_WEBM","AUDIO_OGG","TrackRecorder","track","recorder","startTime","startRecorder","trackRecorder","stopRecorder","determineCorrectFileType","MediaRecorder","isTypeSupported","AudioRecorder","jitsiConference","recorders","fileType","isRecording","addTrack","isAudioTrack","instantiateTrackRecorder","updateNames","originalStream","getOriginalStream","MediaStream","getAudioTracks","mimeType","ondataavailable","dataEvent","size","removeTrack","isVideoTrack","getParticipantId","recorderToRemove","conference","isLocal","getParticipantById","download","blob","Blob","url","URL","createObjectURL","document","createElement","body","appendChild","style","href","click","revokeObjectURL","getRecordingResults","getFileType","wordArray","SphinxService","BEFORE_STATE","RECORDING_STATE","TRANSCRIBING_STATE","FINISHED_STATE","Transcriber","audioRecorder","transcriptionService","transcription","state","lineLength","blobCallBack","transcriber","answer","offset","getUTCMilliseconds","wordObject","begin","word","maybeMerge","hasPopulatedArrays","twoDimensionalArray","callBack","recordingResult","send","merge","arrays","potentialWords","pushWordToSortedArray","lowestWordArray","wordToAdd","updateTranscription","foundSmaller","wordToCompare","getTranscription","getState","reset","TranscriptionService","sendRequest","response","verify","formatResponse","audioBlob","Word","message","sphinxURL","toReturn","includes","getURL","create","audioFileBlob","request","XMLHttpRequest","onreadystatechange","readyState","DONE","status","responseText","open","setRequestHeader","objects","filler","json","getWord","getBeginTime","getEndTime","getTokenAuthUrl","urlPattern","roomName","roleUpgrade","EventEmitterForwarder","src","dest","emit","forward","srcEvent","handlers","oldOnErrorHandler","onerror","oldOnUnhandledRejection","onunhandledrejection","handler","event","reason","GlobalOnErrorHandler","addHandler","callErrorHandler","errHandler","callUnhandledRejectionHandler","randomInt","min","max","randomElement","RandomUtil","randomHexDigit","randomHexString","ret","randomAlphanumStr","currentExecutingScript","ScriptUtil","loadScript","prepend","relativeURL","loadCallback","errorCallback","d","tagName","script","referenceNode","getElementsByTagName","scriptEl","scriptSrc","baseScriptSrc","lastIndexOf","onload","parentNode","insertBefore","integerHash","string","char","hash","charCodeAt","pow","generateUsername","CAMERA","DESKTOP","DESKTOP_HIGH_FPS","NONE","ENVIRONMENT","USER","H264","OPUS","ULPFEC","VP8","VP9","INACTIVE","RECVONLY","SENDONLY","SENDRECV","CREATE_ANSWER_FAILED","CREATE_OFFER_FAILED","DATA_CHANNEL_OPEN","ENDPOINT_CONN_STATUS_CHANGED","DOMINANT_SPEAKER_CHANGED","LASTN_ENDPOINT_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","width","height","IDENTITY_UPDATED","LOCAL_JID","ADD_ICE_CANDIDATE_FAILED","AUDIO_MUTED_BY_FOCUS","VIDEO_MUTED_BY_FOCUS","AUTHENTICATION_REQUIRED","BRIDGE_DOWN","CALL_ACCEPTED","CALL_INCOMING","CALL_ENDED","CHAT_ERROR_RECEIVED","SETTINGS_ERROR_RECEIVED","CONFERENCE_PROPERTIES_CHANGED","CONNECTION_ESTABLISHED","CONNECTION_FAILED","CONNECTION_INTERRUPTED","CONNECTION_RESTORED","CONNECTION_ICE_FAILED","CONNECTION_RESTARTED","CONNECTION_STATUS_CHANGED","DISPLAY_NAME_CHANGED","EMUC_ROOM_ADDED","EMUC_ROOM_REMOVED","ETHERPAD","FOCUS_DISCONNECTED","FOCUS_LEFT","GRACEFUL_SHUTDOWN","ICE_RESTARTING","ICE_RESTART_SUCCESS","KICKED","LOCAL_ROLE_CHANGED","MEETING_ID_SET","MESSAGE_RECEIVED","INVITE_MESSAGE_RECEIVED","PRIVATE_MESSAGE_RECEIVED","MUC_MEMBER_BOT_TYPE_CHANGED","MUC_DESTROYED","MUC_JOIN_IN_PROGRESS","MUC_JOINED","MUC_MEMBER_JOINED","MUC_MEMBER_LEFT","MUC_LOBBY_MEMBER_JOINED","MUC_LOBBY_MEMBER_UPDATED","MUC_LOBBY_MEMBER_LEFT","MUC_DENIED_ACCESS","MUC_LEFT","MUC_ROLE_CHANGED","MUC_LOCK_CHANGED","MUC_MEMBERS_ONLY_CHANGED","PARTICIPANT_AUDIO_MUTED","PARTICIPANT_VIDEO_MUTED","PARTICIPANT_VIDEO_TYPE_CHANGED","PARTICIPANT_FEATURES_CHANGED","PASSWORD_REQUIRED","PHONE_NUMBER_CHANGED","PRESENCE_RECEIVED","PRESENCE_STATUS","PROMPT_FOR_LOGIN","READY_TO_JOIN","RECORDER_STATE_CHANGED","REMOTE_STATS","RENEGOTIATION_FAILED","RESERVATION_ERROR","ROOM_CONNECT_ERROR","ROOM_CONNECT_NOT_ALLOWED_ERROR","ROOM_JOIN_ERROR","ROOM_CONNECT_MEMBERS_ONLY_ERROR","ROOM_MAX_USERS_ERROR","SENDING_CHAT_MESSAGE","SENDING_PRIVATE_CHAT_MESSAGE","SESSION_ACCEPT","SESSION_ACCEPT_ERROR","SESSION_ACCEPT_TIMEOUT","SOURCE_ADD","SOURCE_ADD_ERROR","SOURCE_REMOVE","SOURCE_REMOVE_ERROR","SPEAKER_STATS_RECEIVED","CONFERENCE_TIMESTAMP_RECEIVED","AV_MODERATION_APPROVED","AV_MODERATION_REJECTED","AV_MODERATION_RECEIVED","AV_MODERATION_CHANGED","AV_MODERATION_PARTICIPANT_APPROVED","AV_MODERATION_PARTICIPANT_REJECTED","BREAKOUT_ROOMS_MOVE_TO_ROOM","BREAKOUT_ROOMS_EVENT","BREAKOUT_ROOMS_UPDATED","START_MUTED_FROM_FOCUS","SUBJECT_CHANGED","SUSPEND_DETECTED","TRANSCRIPTION_STATUS_CHANGED","TRANSPORT_INFO","VIDEO_SIP_GW_AVAILABILITY_CHANGED","VIDEO_SIP_GW_SESSION_STATE_CHANGED","ICE_CONNECTION_STATE_CHANGED","JSON_MESSAGE_RECEIVED","byteLength","b64","lens","getLens","validLen","placeHoldersLen","toByteArray","tmp","Arr","_byteLength","curByte","revLookup","fromByteArray","uint8","extraBytes","maxChunkLength","len2","encodeChunk","lookup","Uint8Array","code","num","output","defineProperty","enumerable","get","Symbol","toStringTag","__esModule","default","getFirstMatch","getSecondMatch","matchAndReturnConst","getWindowsVersionName","getMacOSVersionName","getAndroidVersionName","getVersionPrecision","compareVersions","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","pageUrl","scripts","supportsScriptReadyState","isNotOpera","hasNativeCurrentScriptAccessor","stackTraceLimit","Infinity","hasStackBeforeThrowing","hasStackAfterThrowing","getScriptUrlFromStack","skipStackDepth","matches","ignoreMessage","round","_nearestExecutingScript","eligibleScripts","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","errorListener","resolver","eventTargetAgnosticAddListener","flags","on","addErrorHandlerIfEventEmitter","_events","_eventsCount","_maxListeners","defaultMaxListeners","checkListener","TypeError","_getMaxListeners","_addListener","events","existing","warning","newListener","warned","w","warn","onceWrapper","fired","wrapFn","_onceWrap","wrapped","_listeners","unwrap","evlistener","unwrapListeners","arrayClone","listenerCount","copy","addEventListener","wrapListener","removeEventListener","set","RangeError","getPrototypeOf","setMaxListeners","getMaxListeners","doError","er","context","prependListener","prependOnceListener","list","position","originalListener","spliceOne","off","removeAllListeners","rawListeners","eventNames","WINDOW","JS_MD5_NO_WINDOW","WEB_WORKER","NODE_JS","JS_MD5_NO_NODE_JS","versions","node","g","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","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","arrayPush","baseGetAllKeys","getAllKeys","arrayEach","subValue","newValue","getMapData","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","trim","splitSections","part","getDescription","sections","getMediaSections","matchPrefix","prefix","parseCandidate","candidate","foundation","protocol","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","params","param","parseRtcpFb","parameter","writeRtcpFb","lines","rtcpFeedback","fb","parseSsrcMedia","sp","colon","parseSsrcGroup","getMid","mediaSection","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","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","CHAT_ERROR","SETTINGS_ERROR","CONFERENCE_DESTROYED","CONFERENCE_MAX_USERS","CONNECTION_ERROR","CONFERENCE_RESTARTED","NOT_ALLOWED_ERROR","MEMBERS_ONLY_ERROR","CONFERENCE_ACCESS_DENIED","ICE_FAILED","INCOMPATIBLE_SERVER_VERSIONS","OFFER_ANSWER_FAILED","PASSWORD_NOT_SUPPORTED","VIDEOBRIDGE_NOT_AVAILABLE","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","DATA_CHANNEL_OPENED","CONFERENCE_CREATED_TIMESTAMP","DTMF_SUPPORT_CHANGED","JVB121_STATUS","PARTICIPANT_KICKED","LAST_N_ENDPOINTS_CHANGED","LOCK_STATE_CHANGED","SERVER_REGION_CHANGED","_MEDIA_SESSION_STARTED","_MEDIA_SESSION_ACTIVE_CHANGED","MEMBERS_ONLY_CHANGED","NO_AUDIO_INPUT","NOISY_MIC","NON_PARTICIPANT_MESSAGE_RECEIVED","PARTICIPANT_CONN_STATUS_CHANGED","PARTCIPANT_FEATURES_CHANGED","PARTICIPANT_PROPERTY_CHANGED","P2P_STATUS","PROPERTIES_CHANGED","START_MUTED_POLICY_CHANGED","STARTED_MUTED","TALK_WHILE_MUTED","TRACK_ADDED","TRACK_AUDIO_LEVEL_CHANGED","TRACK_MUTE_CHANGED","TRACK_REMOVED","TRACK_UNMUTE_REJECTED","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","FACIAL_EXPRESSION_ADDED","CALLSTATS_SCRIPT_URL","CONSTRAINT_FAILED","ELECTRON_DESKTOP_PICKER_ERROR","ELECTRON_DESKTOP_PICKER_NOT_FOUND","GENERAL","NOT_FOUND","PERMISSION_DENIED","SCREENSHARING_GENERIC_ERROR","SCREENSHARING_USER_CANCELED","TIMEOUT","TRACK_IS_DISPOSED","TRACK_NO_STREAM_FOUND","UNSUPPORTED_RESOLUTION","TRACK_ERROR_TO_MESSAGE_MAP","JitsiTrackError","devices","gum","constraints","JitsiTrackErrors","constraintName","constraint","video","failedConstraintName","mandatory","minWidth","minHeight","getResolutionFromFailedConstraint","TYPE_OPERATIONAL","TYPE_PAGE","TYPE_TRACK","CONNECTION_DISCONNECTED","ICE_DURATION","createConferenceEvent","attributes","createConnectionStageReachedEvent","stage","actionSubject","createGetUserMediaEvent","createJingleEvent","createP2PEvent","createRemotelyMutedEvent","mediaType","createRtpStatsEvent","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","userAgent","navigator","JitsiMeetElectron","_detectNWJS","_detectReactNative","product","DummyLocalStorage","_storage","getItem","keyName","setItem","keyValue","removeItem","serialize","jitsiLocalStorage","super","localStorage","_localStorageDisabled","ignore","isLocalStorageDisabled","dontEmitChangedEvent","localStorageContent","browserInfo","_bowser","Bowser","detectedBrowserInfo","bowser","detectors","_detectChromiumBased","_name","_version","getName","isChrome","isOpera","isFirefox","isIExplorer","isSafari","isNWJS","isElectron","isReactNative","getVersion","_checkCondition","checkTree","isVersionGreaterThan","isVersionLessThan","isVersionEqualTo","doesVideoMuteByStreamRemove","isChromiumBased","isWebKitBased","isIosBrowser","maxTouchPoints","Boolean","mediaDevices","getUserMedia","RTCRtpTransceiver","isTwa","matchMedia","isSupported","_getSafariVersion","_getChromiumBasedVersion","isSupportedAndroidBrowser","isSupportedIOSBrowser","_getIOSVersion","isUserInteractionRequiredForUnmute","supportsVideoMuteOnConnInterrupted","supportsBandwidthStatistics","supportsCodecPreferences","RTCRtpReceiver","getCapabilities","supportsDeviceChangeEvent","ondevicechange","supportsLocalCandidateRttStatistics","supportsPerformanceObserver","PerformanceObserver","supportedEntryTypes","supportsReceiverStats","supportsRTTStatistics","supportsVP9","usesSdpMungingForSimulcast","usesAdapter","usesRidsForSimulcast","supportsGetDisplayMedia","getDisplayMedia","supportsEncodedTransform","RTCRtpScriptTransform","supportsInsertableStreams","RTCRtpSender","createEncodedStreams","ReadableStream","postMessage","supportsAudioRed","supportsUnifiedPlan","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","debug","_addNewFabric","_emptyReportQueue","csInstance","defaultConfID","confID","defaultPC","peerconnection","report","reportsQueue","errorData","_reportError","pc","eventData","sendFabricEvent","associateMstWithUserID","callStatsId","usageLabel","cs","_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","then","addresses","catch","devicesData","_reportEvent","conferenceID","overall","comment","sendUserFeedback","mute","tpc","remoteUserID","fabricAttributes","remoteEndpointType","isP2P","endpointType","peer","server","addNewFabric","fabricUsage","multiplex","_addNewFabricCallback","success","associateStreamWithVideoTag","streamEndpointId","sendDominantSpeakerEvent","sendTerminateEvent","fabricEvent","fabricTerminated","delete","sendIceConnectionFailedEvent","sendCreateOfferFailed","sendCreateAnswerFailed","sendResumeOrHoldEvent","isResume","sendScreenSharingEvent","sendSetLocalDescFailed","sendSetRemoteDescFailed","sendAddIceCandidateFailed","AudioContext","webkitAudioContext","LocalStatsCollector","interval","intervalId","intervalMilis","audioLevel","safeCounterIncrement","number","nextValue","calculateAverage","valueArray","filterPositiveValues","suspend","isLocalStatsSupported","analyser","createAnalyser","smoothingTimeConstant","fftSize","createMediaStreamSource","connect","setInterval","frequencyBinCount","getByteTimeDomainData","samples","maxVolume","parseFloat","toFixed","timeDomainDataToAudioLevel","newLevel","lastLevel","diff","animateLevel","clearInterval","RunningAverage","average","addNext","getAverage","PerformanceObserverStats","statsInterval","eventEmitter","longTasks","maxDuration","performanceStatsInterval","stats","getLongTasksStats","avgRatePerMinute","maxDurationMs","startObserver","longTaskEventHandler","getEntries","observer","observe","buffered","longTasksIntervalId","_lastTimeStamp","StatisticsEvents","stopObserver","disconnect","AUDIO","PRESENTER","VIDEO","calculatePacketLoss","lostPackets","totalPackets","SsrcStats","loss","bitrate","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","getStats","processAudioLevelReport","processStats","processStatsReport","_processAndEmitReport","bitrateDownload","bitrateUpload","resolutions","framerates","audioCodec","videoCodec","audioBitrateDownload","audioBitrateUpload","videoBitrateDownload","videoBitrateUpload","ssrcStats","isDownloadStream","packetsTotal","packetsLost","getTrackBySSRC","participantId","userResolutions","userFramerates","codecDesc","userCodecs","audio","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","frameRate","framesPerSecond","bytesSent","codecId","codecShortType","MediaType","remoteSource","localVideoTracks","getLocalTracks","getLocalSSRC","framesSent","numberOfActiveStreams","getActiveSimulcastStreams","trackIdentifier","getSsrcByTrackId","_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","performanceObserverStats","JitsiConferenceEvents","removeLongTasksStatsListener","from","callStats","stopCallStats","tpcId","_stopRemoteStats","stopLocalStats","startCallStats","newInstance","_getAllCallStatsInstances","csInstances","statistics","isCallstatsEnabled","sendConnectionResumeOrHoldEvent","instance","sendMuteEvent","muted","roomJid","sendActiveDeviceListEvent","globalSet","sendGetUserMediaFailed","formatJitsiTrackErrorForCallStats","sendLog","globalSubSet","csPerStats","sendApplicationLog","sendFeedback","rating","reportGlobalError","sendAnalyticsAndLog","eventToLog","sendAnalytics","JitsiConferenceEventManager","xmppListeners","p2pJingleSession","jvbJingleSession","isMuted","getType","setupChatRoomListeners","chatRoom","room","chatRoomForwarder","XMPPEvents","jingleSession","rtc","closeBridgeChannel","features","participant","Strophe","setFeatures","offerIq","_setBridgeChannel","actor","mutedByFocusActor","setAudioMute","isMutedByFocus","mutedVideoByFocusActor","setVideoMute","isVideoMutedByFocus","_onMucJoined","isJvbConnectionInterrupted","connectionTimes","JitsiConferenceErrors","bridgeDown","createBridgeDownEvent","_onConferenceRestarted","_onIceConnectionFailed","createFocusLeftEvent","jid","resource","getStatus","setTerminator","setInitiator","setParticipantPropertyListener","setProperty","onMemberKicked","onSuspendDetected","onMemberJoined","_onMemberBotTypeChanged","onMemberLeft","onDisplayNameChanged","onLocalRoleChanged","isModerator","recorderSession","logObject","getError","onUserRoleChanged","AuthenticationEvents","authEnabled","authIdentity","txt","myJid","_status","addPresenceListener","myUserId","startAudioMuted","startVideoMuted","updated","startMutedPolicy","setupRTCListeners","RTCEvents","onRemoteTrackAdded","onRemoteTrackRemoved","dominant","previous","lastDominantSpeaker","roomjid","performance","videoType","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","ParticipantConnectionStatus","ACTIVE","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","JitsiTrackEvents","hasAnyVideoRTCMuted","hasAnyVideoTrackWebRTCMuted","timeout","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","_connectionStatus","_properties","_identity","_isReplacing","_isReplaced","_features","getConference","getProperty","getTracks","jitsiTrack","isWebRTCTrackMuted","oldValue","getStatsID","isHidden","isAudioMuted","_isMediaTypeMuted","getRole","setRole","newRole","setIsReplacing","newIsReplacing","setIsReplaced","newIsReplaced","supportsDTMF","getFeatures","hasFeature","feature","newFeatures","getBotType","_botType","setBotType","newBotType","WRONG_STATE","DISPLAY_NAME_REQUIRED","CONNECTION_DROPPED_ERROR","OTHER_ERROR","SERVER_ERROR","Deferred","promise","clearRejectTimeout","_timeout","setRejectTimeout","ms","Listenable","kJitsiE2EE","E2EEcontext","sharedKey","baseUrl","ljm","querySelector","idx","workerUrl","workerBlob","_worker","Worker","operation","cleanup","cleanupAll","handleReceiver","receiverStreams","readableStream","readable","writableStream","handleSender","sender","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","uuid","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","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","salt","encode","ratchet","getRandomValues","E2EEncryption","e2ee","_externallyManaged","externallyManagedKey","_keyHandler","testing","disableE2EE","enableEncodedTransformSupport","setEnabled","IDENTITY_PROPERTIES","IDENTITY_PROPERTIES_FOR_COMPARE","compareIdentities","res","parseDiscoInfo","identities","$","_","getAttribute","category","Caps","connection","disco","rooms","externalFeatures","emuc","_addChatRoom","_removeChatRoom","addFeature","submit","external","_generateVersion","_updateRoomWithExternalFeatures","removeFeature","sendPresence","removeFromPresence","children","addOrReplaceInPresence","getFeaturesAndIdentities","_getDiscoInfo","_fixChatRoomPresenceMap","xmlns","ver","_notifyVersionChanged","sortedIdentities","accumulatedValue","sortedFeatures","b64_sha1","generateSha","_identities","NETWORK_INFO_EVENT","_current","isOnline","updateNetworkInfo","ResumeTask","stropheConnection","_stropheConn","_resumeRetryN","_retryDelay","retryDelay","schedule","_cancelResume","_networkOnlineListener","NetworkInfo","_scheduleResume","_resumeTimeout","minDelay","base","getJitterDelay","_resumeConnection","streamManagement","resumeToken","getResumeToken","service","pattern","oldToken","LastRequestTracker","_lastSuccess","_lastFailedMessage","startTracking","xmppConnection","originalRawInput","rawInput","rawMessage","connected","getLastFailedMessage","getTimeSinceLastSuccess","getConnectionPluginDefinition","ConnectionPluginListenable","PingConnectionPlugin","ConnectionPlugin","getTimeSinceLastServerResponse","onPingThresholdExceeded","pingOptions","failedPings","_onPingThresholdExceeded","_getTimeSinceLastServerResponse","pingInterval","pingTimeout","pingThreshold","threshold","pingTimestampsToKeep","pingExecIntervals","ping","_addPingExecutionTimestamp","iq","$iq","to","sendIQ2","startInterval","remoteJid","_lastServerCheck","errmsg","stopInterval","getTime","getPingSuspendTime","pingIntervals","maxInterval","previousTS","currentInterval","XmppConnection","Events","CONN_STATUS_CHANGED","CONN_SHARD_CHANGED","Status","enableWebsocketResume","websocketKeepAlive","websocketKeepAliveUrl","serviceUrl","shard","xmppPing","_options","_usesWebsocket","startsWith","maxRetries","_rawInputTracker","LastSuccessTracker","_resumeTask","_deferredIQs","addConnectionPlugin","_onPingErrorThresholdExceeded","_oneSuccessfulConnect","websocket","_proto","socket","isUsingWebSocket","WebSocket","OPEN","disconnecting","domain","lastResponseHeaders","pingDomain","_maybeStartWSKeepAlive","plugin","attach","sid","rid","_stropheConnectionCb","pass","targetCallback","blockCallback","_maybeEnableStreamResume","_keepAliveAndCheckShard","_processDeferredIQs","_tryResumingConnection","_wsKeepAlive","_clearDeferredIQs","deferred","closeWebsocket","_closeSocket","_onClose","enable","intervalWithJitter","fetch","responseShard","headers","timeLeft","sendIQ","stanza","elem","errback","sendUnavailableBeacon","sendBeacon","_changeConnectStatus","_buildBody","attrs","pres","$pres","cnode","tree","_abortAllRequests","_doDisconnect","ON","OFF","AVModeration","_xmpp","_mainRoom","_moderationEnabledByType","_whitelistAudio","_whitelistVideo","_onMessage","avModerationComponentAddress","$msg","up","approve","jidToWhitelist","jidToBlacklist","removed","approved","whitelists","newWhitelists","oldList","newList","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","_setMainRoomJid","_mainRoomJid","getMainRoomJid","EMAIL_COMMAND","Lobby","mainRoom","maybeJoinLobbyRoom","_maybeJoinLobbyRoom","lobbyRoomJid","lobbySupported","setMembersOnly","disable","lobbyRoom","membersOnlyEnabled","leave","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","attr","parseConfigOptions","authenticationEnabled","allocateConferenceFocus","_allocateConferenceFocusSuccess","_allocateConferenceFocusError","invalidSession","reservationErr","errorCode","errorTextNode","errorMsg","anonymousdomain","waitMs","retrySec","authenticate","errorIq","prop","getLoginUrl","urlCallback","failureCallback","_getLoginUrl","popup","urlCb","failureCb","decodeURIComponent","getPopupLoginUrl","logout","logoutUrl","packet2JSON","xmlElement","nodes","child","json2packet","packet","filterNodeFromPresenceJSON","nodeName","MEMBERS_AFFILIATIONS","ChatRoom","XMPP","myroomjid","replaceParticipant","presMap","presHandlers","_removeConnListeners","inProgressEmitted","focusMucJid","noBridgeAvailable","moderator","lobby","avModeration","breakoutRooms","initPresenceMap","lastPresences","phoneNumber","phonePin","participantPropertyListener","locked","transcriptionStatus","JitsiTranscriptionStatus","xns","statsId","presenceUpdateTime","onConnStatusChanged","fromJoin","billingId","presenceSyncTime","doLeave","discoRoomInfo","getInfo","meetingIdValEl","setMeetingId","membersOnly","lobbyRoomField","isBreakoutField","breakoutMainRoomField","meetingId","createNonAnonymousRoom","getForm","form","formSubmit","CONNECTED","onPresence","member","statusEl","textContent","hasStatusUpdate","hasVersionUpdate","xElement","getElementsByTagNameNS","mucUserItem","isReplaceParticipant","affiliation","isFocus","hiddenDomain","fromHiddenDomain","presence","xEl","remove","extractIdentityInformation","userInfo","user","groupInfo","_extractFeatures","_initFocus","memberOfThis","displayJids","restartByTerminateSupported","supportsRestartByTerminate","att","phone","pin","processNode","var","focusFeatures","tagHandlers","elementName","sendPrivateMessage","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","onSuccess","onError","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","_sourceNameSignaling","sourceNameSignaling","isSourceNameSignalingEnabled","_createSourceExtension","owner","sourceCompactJson","$build","FeatureFlags","_createSsrcGroupExtension","ssrcGroupCompactJson","_getOrCreateRtpDescription","jingle","expandSourcesFromJson","jsonMessageXml","audioRtpDescription","videoRtpDescription","ownerSources","videoSources","videoSsrcGroups","audioSources","audioSsrcGroups","ScreenObtainer","obtainStream","_createObtainStreamMethod","onFailure","JitsiMeetNW","obtainDesktopStream","jitsiError","obtainScreenOnElectron","obtainScreenFromGetDisplayMediaRN","obtainScreenFromGetDisplayMedia","_getAudioConstraints","audioQuality","stereo","autoGainControl","channelCount","echoCancellation","noiseSuppression","JitsiMeetScreenObtainer","openDesktopPicker","desktopSharingFrameRate","desktopSharingSources","streamId","streamType","screenShareAudio","audioConstraints","optionalConstraints","optional","chromeMediaSource","chromeMediaSourceId","minFrameRate","maxFrameRate","maxWidth","screen","maxHeight","sourceId","sourceType","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","MediaDirection","rtcpFb","SDP","mediaI","raw","arrayEquals","array1","array2","equals","SDPDiffer","mySDP","otherSDP","failICE","removeTcpCandidates","removeUdpCandidates","getMediaSsrcMap","mediaSSRCs","mediaindex","linessrc","containsSSRC","medias","toJingle","thecreator","assrcline","creator","amidline","rtpmap","afmtpline","fmtpParameters","rtcpFbToJingle","availableSsrc","ssrcParameters","sourceName","ridLines","rids","ridLine","ridInfo","extmapLines","extmap","senders","transportToJingle","sctpport","sctpmap","sctpAttrs","streams","setupLine","setup","iceParameters","payloadtype","feedback","rtcpFbFromJingle","feedbackElementTrrInt","fromJingle","groups","contents","jingle2media","setAttribute","__","hdrExt","userSources","nonUserSources","isUserSource","sourceStr","getNewMedia","myMedias","othersMedias","newMedia","othersMediaIdx","myMedia","othersMedia","otherSsrcGroup","matched","mySsrcGroup","modify","sdpMediaSsrcs","modified","ssrcNum","mediaSsrc","AsyncQueue","_queue","_processQueueTasks","_stopped","finishedCallback","shutdown","PENDING","ENDED","JingleSession","localJid","mediaConstraints","pcConfig","isInitiator","usedrip","dripContainer","_signalingLayer","initiatorJid","responderJid","signalingLayer","JingleSessionState","doInitialize","addIceCandidates","addSources","removeSources","terminate","failure","acceptOffer","_getInitiatorJid","IQ_TIMEOUT","JingleSessionPC","jingleContents","videoContents","maxFrameHeightSel","_bridgeSessionId","_cachedOldLocalSdp","_cachedNewLocalSdp","_iceCheckingStartedTimestamp","_gatheringStartedTimestamp","localRecvMaxFrameHeight","_localVideoActive","_remoteVideoActive","_gatheringReported","lasticecandidate","closed","remoteRecvMaxFrameHeight","modificationQueue","wasConnected","establishmentDuration","_xmppListeners","onXmppStatusChanged","_removeSenderVideoConstraintsChangeListener","_assertNotEnded","isReconnect","wasstable","webrtcIceUdpDisable","webrtcIceTcpDisable","pcOptions","gatherStats","maxstats","capScreenshareBitrate","enableInsertableStreams","videoQuality","forceTurnRelay","enableUnifiedOnChrome","disableSimulcast","abtestSuspendVideo","_abtestSuspendVideoEnabled","preferH264","disableH264","preferredCodec","createPeerConnection","onicecandidate","ev","phase","initiator","sendIceCandidate","onsignalingstatechange","signalingState","connectionState","oniceconnectionstatechange","isStable","iceConnectionState","reconnect","usesTerminateForRestart","enableIceRestart","iceStarted","onconnectionstatechange","icestate","onnegotiationneeded","remoteDescription","workFunction","oldSdp","localDescription","_renegotiate","configureSenderVideoEncodings","newSdp","notifyMySSRCUpdate","getRemoteRecvMaxFrameHeight","localSDP","ice","sdpMLineIndex","jcand","errorMesssage","sendIceCandidates","cands","sdpMid","fingerprintLine","required","newJingleErrorHandler","sendIceFailedNotification","sessionInfo","iceCandidates","rtcCandidate","RTCIceCandidate","iceCandidate","addIceCandidate","outerHTML","readSsrcInfo","ssrcElement","setSSRCOwner","setTrackSourceName","i3","ssrcInfoElement","jidOrEndpointId","generateRecvonlySsrc","getConfiguredVideoCodec","jingleOffer","setOfferAnswerCycle","sendSessionAccept","addTracks","localTrack","createOffer","offerSdp","setLocalDescription","sendSessionInitiate","setAnswer","jingleAnswer","remoteSdp","_responderRenegotiate","jingleOfferAnswerIq","newRemoteSdp","_processNewJingleOfferIq","oldLocalSdp","bridgeSessionId","sendContentModify","newLocalSdp","setVideoCodecs","preferred","disabled","current","replaceTransport","jingleOfferElem","enableForcedReload","sendTransportAccept","originalOffer","clone","newFingerprint","accept","responder","maxFrameHeight","sessionModify","setReceiverVideoConstraint","transportAccept","medialines","sendTransportReject","transportReject","setSenderVideoConstraint","videoActive","setMediaTransferActive","setSenderVideoConstraints","sendSessionTerminate","sessionTerminate","reasonDescription","restart","requestRestart","onTerminated","reasonCondition","reasonText","close","_parseSsrcInfoFromSourceAdd","sourceAddElem","currentRemoteSdp","addSsrcInfo","i1","i2","addRemoteStream","_addOrRemoveRemoteStream","removeRemoteStream","removeRemoteStreamsOnLeave","finishCallback","removeSsrcInfo","getRemoteSourceInfoByParticipant","removeRemoteTracks","_processRemoteRemoveSource","newLocalSDP","isAdd","logPrefix","errMsg","addOrRemoveSsrcInfo","_parseSsrcInfoFromSourceRemove","_processRemoteAddSource","findIndex","desiredDirection","getDesiredMediaDirection","optionalRemoteSdp","_initiatorRenegotiate","setRemoteDescription","createAnswer","offer","replaceTrack","oldTrack","newTrack","clearRecvonlySsrc","shouldRenegotiate","sourceRemoveElem","_verifyNoSSRCChanged","operationName","oldSDP","currentLocalSDP","sdpDiff","addedMedia","removedMedia","addTrackAsUnmute","_addRemoveTrackAsMuteUnmute","removeTrackAsMute","isMute","oldLocalSDP","removeTrackMute","addTrackUnmute","audioActive","logAudioStr","logVideoStr","isSessionActive","audioActiveChanged","setAudioTransferActive","pcVideoActiveChanged","setVideoTransferActive","modifyContents","newVideoSenders","parseVideoSenders","newMaxFrameHeight","parseMaxFrameHeight","MediaSessionEvents","_modifyRemoteVideoActive","remoteVideoSenders","isRemoteVideoActive","newSDP","getSignaledSourceInfo","sdpDiffer","mediaIndex","signaledSsrcs","removedAnySSRCs","ctx","sourceInfo","errResponse","errorElSel","errorReasonSel","errorMsgSel","getIceConnectionState","getConnectionState","abTesting","enableSuspendVideoTest","_parseIceCandidates","parseCandidates","candidateAttrs","JingleConnectionPlugin","iceConfig","sessions","jvbIceConfig","jvb","p2pIceConfig","offerToReceiveAudio","offerToReceiveVideo","onJingle","fromJid","sess","jsonMessages","audioVideoSsrcs","logMessage","endpoint","startMuted","iceUfrag","icePwd","dtlsFingerprint","successTime","newP2PJingleSession","me","getStunAndTurnCredentials","v2Res","onReceiveStunAndTurnCredentials","v1Res","iceservers","dict","urls","credential","temp","useTurnUdp","iceServers","getLog","updateLog","StropheLogger","logIncoming","rawOutput","logOutgoing","RAYO_XMLNS","RayoConnectionPlugin","onRayo","roomPass","req","callResource","lastErrorStatus","resetLastErrorStatusRegExpr","lastErrorStatusRegExpr","FAILURE_REGEX","DEFAULT_STUN_SERVERS","FEATURE_JIGASI","token","disconnectInProgress","authenticatedUser","trace","errStatusCapture","createConnection","bosh","deploymentInfo","details","shard_changed","suspend_time","time_since_last_success","JitsiConnectionEvents","JitsiConnectionErrors","_initStrophePlugins","initFeaturesList","disableBeforeUnloadHandlers","enableOpusRed","enableRemb","enableTcc","enableLipSync","getConnection","connectionHandler","credentials","statusStr","_maybeSendDeploymentInfoStat","_sysMessageHandler","deleteHandler","sendDiscoInfo","_resetState","sendDeploymentInfo","_processDiscoInfoIdentities","anonymousConnectionFailed","connectionFailed","lastErrorMsg","wasIntentionalDisconnect","_getConnectionFailedReasonDetails","lastFailedRawMessage","_parseConnectionFailedMessage","speakerStatsComponentAddress","conferenceDurationComponentAddress","processLobbyFeatures","fr","endsWith","region","_onPrivateMessage","_connect","_addSysHandler","_onSystemMessage","foundIceServers","attaching","configDomain","hostname","onCreateResource","getRoomJid","mucNickname","muc","getJingleLog","getXmppLog","getSessions","disconnectListener","_cleanupXmppConnection","evType","sync","p2pStunServers","stunServers","iceTransportPolicy","headersArr","header","sendFacialExpressionEvent","expression","jsonString","users","created_timestamp","acceptedStatuses","aprops","authenticateAndUpgradeRole","rejectPromise","onLoginSuccessful","roomPassword","canceled","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","createDataChannel","_handleChannel","_wsUrl","_initWebSocket","ws","_startConnectionRetries","timeoutS","reload","isOpen","_retryTimeout","_stopConnectionRetries","_retryWebSocketConnection","closeEvent","createBridgeChannelClosedEvent","mode","sendEndpointStatsMessage","_send","colibriClass","msgPayload","sendSetLastNMessage","lastN","sendSelectedEndpointsMessage","endpointIds","selectedEndpoints","sendReceiverVideoConstraintMessage","maxFrameHeightPixels","sendNewReceiverVideoConstraintsMessage","sendVideoTypeMessage","sendSourceVideoTypeMessage","channel","onopen","onmessage","dominantSpeakerEndpoint","previousSpeakers","active","lastNEndpoints","videoConstraints","onclose","jsonObject","DEFAULT_CONSTRAINTS","ideal","audioOutputDeviceId","audioOutputChanged","disableAP","disableAEC","disableNS","disableAGC","featureDetectionAudioEl","isAudioOutputDeviceChangeAvailable","setSinkId","availableDevicesPollTimer","availableDevices","emptyFuncton","updateGrantedPermissions","um","audioTracksReceived","videoTracksReceived","getVideoTracks","grantedPermissions","sendDeviceListToAnalytics","deviceList","audioInputDeviceCount","audioOutputDeviceCount","videoInputDeviceCount","videoOutputDeviceCount","device","deviceId","groupId","label","updateKnownDevices","pds","newDevices","mediaDeviceInfoToJSON","facing","compareAvailableMediaDevices","rtcUtils","origAttachMediaStream","RTCPeerConnectionType","RTCPeerConnection","attachMediaStream","getStreamID","getTrackID","element","srcObject","isDeviceChangeAvailable","getAudioOutputDevice","ex","pcConstraints","googScreencastMinBitrate","googCpuOveruseDetection","screenObtainer","isDeviceListAvailable","enumerateDevices","_getUserMedia","umDevices","gumTimeout","timeoutExpired","_getDesktopMedia","_getMissingTracks","requestedDevices","missingDevices","audioDeviceRequested","videoDeviceRequested","obtainAudioAndVideoPermissions","otherOptions","mediaStreamsMetaData","maybeRequestDesktopDevice","desktopSharingSourceDevice","matchingDevice","maybeRequestCaptureDevices","requestedCaptureDevices","clonedeep","Resolutions","cameraDeviceId","facingMode","CameraFacingMode","micDeviceId","getConstraints","desktopStream","desktopAudioTracks","desktopAudioStream","desktopVideoTracks","desktopVideoStream","avStream","audioTracks","audioStream","effects","videoStream","stopMediaStream","deviceType","mediaStream","release","isDesktopSharingEnabled","setAudioOutputDevice","getCurrentlyAvailableMediaDevices","arePermissionsGrantedForAvailableDevices","getEventDataForActiveDevice","deviceData","setSuspendVideo","googSuspendBelowMinBitrate","trackHandler2Prop","JitsiTrack","streamInactiveHandler","trackMediaType","containers","_streamInactiveHandler","_setStream","_addMediaStreamInactiveHandler","onended","oninactive","_setHandler","_unregisterHandlers","videoTrack","getVideoType","isLocalAudioTrack","getStreamId","getTrack","getTrackLabel","getTrackId","getUsageLabel","_maybeFireTrackAttached","container","_onTrackAttach","RTCUtils","_attachTTFMTracker","detach","_onTrackDetach","isScreenSharing","newAudioLevel","getMSID","trackId","setAudioOutput","JitsiLocalTrack","rtcId","_setEffectInProgress","effect","_startStreamEffect","displaySurface","getSettings","metadata","maxEnabledResolution","_constraints","_prevSetMuted","_facingMode","_trackEnded","_hasSentData","_testDataSent","_realDeviceId","_trackMutedTS","_onDeviceListWillChange","oldRealDeviceId","_setRealDeviceIdFromDeviceList","_onAudioOutputDeviceChanged","_initNoDataFromSourceHandlers","_addStreamToConferenceAsUnmute","_addLocalTrackAsUnmute","_fireNoDataFromSourceEvent","isReceivingData","createNoDataFromSourceEvent","_isNoDataFromSourceEventsEnabled","_queueSetMuted","setMuted","_setMuted","_removeStreamFromConferenceAsMute","successCallback","_removeLocalTrackAsMute","_sendMuteStatus","_setTrackMuteStatus","logMuteInfo","_streamEffect","_stopStreamEffect","stopStream","streamOptions","getDeviceId","getCameraFacingMode","streamsInfo","streamInfo","cont","storedMSID","_originalStream","startEffect","stopEffect","_switchCamera","_switchStreamEffect","setEffect","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","sessionMedia","bLine","bundle","mids","msidSemantic","semantic","resStr","toUnifiedPlan","currentDesc","iceRestart","newDesc","oldDesc","newMLine","oldMLine","checkForIceRestart","newIceUfrag","newIcePwd","newMline","mLineForData","ssrc2group","createSourceGroupMap","bundleOnly","existingMline","resultSdp","PEER_MUTED_CHANGED","PEER_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","selectedMLine","toRawSDP","LocalSdpMunger","localEndpointId","_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","updateAssociatedRtxStream","primarySsrcInfo","rtxSsrc","previousRtxSSRC","RtxModifier","correspondingRtxSsrcs","ssrcMapping","modifyRtxSsrcs","sdpStr","sdpTransformer","primaryVideoSsrcs","correspondingRtxSsrc","stripRtx","fidGroups","SdpConsistency","clearVideoSsrcCache","injectRecvOnly","setPrimarySsrc","hasPrimarySsrcCached","makeVideoPrimarySsrcsConsistent","newPrimarySsrc","ttfmTrackerAudioAttached","ttfmTrackerVideoAttached","containerEvents","JitsiRemoteTrack","ownerEndpointId","_sourceName","hasBeenMuted","_bindTrackHandlers","_containerHandlers","_containerEventHandler","_onTrackMute","_onTrackUnmute","setMute","getSSRC","getSourceName","_setVideoType","_playCallback","getConnectionTimes","gumStart","gumEnd","gumDuration","ttfm","_getStatus","HD_BITRATE","SIM_LAYER_RIDS","TPCUtils","bitrateSettings","maxBitratesVideo","standardBitrates","low","standard","high","videoBitrates","encodingBitrates","localStreamEncodingsConfig","scaleResolutionDownBy","_getStreamEncodings","ensureCorrectOrderOfSsrcs","parsedSdp","reorderedSsrcs","findTransceiver","getTransceivers","insertUnifiedPlanSimulcastReceive","simulcast_03","simulcastLine","transceiverInit","sendEncodings","addTransceiver","calculateEncodingsActiveState","localVideoTrack","newHeight","encoding","isSharingLowFpsScreen","calculateEncodingsBitrates","desktopShareBitrate","desktopBitrate","presenterEnabled","transceiver","setEncodings","getParameters","encodings","setParameters","transceivers","updateEncodingsResolution","TraceablePeerConnection","audioTransferActive","_dtmfSender","_dtmfTonesQueue","videoTransferActive","remoteTracks","_addedStreams","localSSRCs","localUfrag","remoteUfrag","_dtlsTransport","_peerVideoTypeChanged","SignalingEvents","_peerMutedChanged","safeConstraints","rtcStatsSFUP2P","tpcUtils","statsinterval","_capScreenshareBitrate","_usesUnifiedPlan","_usesTransceiverCodecPreferences","interop","localSdpMunger","getLocalEndpointId","_senderVideoMaxHeight","what","onTrack","evt","_remoteTrackAdded","_remoteTrackRemoved","onaddstream","_remoteStreamAdded","onremovestream","_remoteStreamRemoved","ondatachannel","_processStat","stat","statValue","endTime","dumpSDP","isAddOperation","hasLocalSource","hasAnyTracksOfType","mediaTransferActive","_getReceiversByEndpointIds","endpoints","receivers","getRemoteTracks","remoteTrackIds","getReceivers","audioReceivers","getSynchronizationSources","tracks","getLocalVideoTrack","endpointTrackMap","mediaTrack","primarySsrcs","fidLines","getTargetVideoBitrates","findTrackById","RTC","isUserStreamById","onaddtrack","onremovetrack","streamAudioTracks","audioTrack","streamVideoTracks","remoteSDP","mediaLines","mls","trackSsrc","_createRemoteTrack","remoteTracksMap","existingTrack","isUserStream","_removeRemoteTrackById","_getRemoteTrackById","removedTracks","removedAudioTrack","removedVideoTrack","_removeRemoteTrack","toBeRemoved","_extractSSRCMap","groupsMap","groupSSRCs","_getSSRC","_injectSsrcGroupForUnifiedSimulcast","getters","audioMedia","changed","videoMedia","enforceSendRecv","_adjustRemoteMediaDirection","_isSharingScreen","_mungeCodecOrder","codecPreference","bitrates","hdBitrate","webrtcStream","_addStream","generateNewStreamSSRCInfo","rtxSsrcMapping","promiseChain","_assertTrackBelongs","webRtcStream","addStream","_removeStream","removeStream","doesBelong","defaultCodec","lowFps","findSenderByKind","getSenders","negotiationNeeded","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","_initializeDtlsTransport","onstatechange","currentDescription","firstSsrcs","newSsrcLines","filteredLines","ssrcId","cnameLine","replaceDefaultUnifiedPlanMsid","normalizePlanB","videoSender","preference","degradationPreference","encodingsEnabledState","maxBitrates","scaleFactor","layer","sendTones","tones","interToneGap","rtpSender","dtmf","localAudioTrack","createDTMFSender","ontonechange","_onToneChange","toneBuffer","insertDTMF","tone","peerTracks","_removePeerConnection","_createOfferOrAnswer","isOffer","logName","handleSuccess","resolveFn","rejectFn","_processLocalSSRCsMap","handleFailure","eventType","capabilities","setCodecPreferences","oaPromise","_extractPrimarySSRC","sourceIdentifier","newSSRCNum","oldSSRCNum","activeStreams","currNumSsrcs","peerConnectionIdCounter","rtcTrackIdCounter","_createLocalTracks","mediaStreamMetaData","metaData","peerConnections","_lastN","_lastNEndpoints","_maxFrameHeight","_selectedEndpoints","_lastNChangeListener","_onDeviceListChanged","_updateAudioOutputForAudioTracks","_videoType","BridgeVideoType","destroy","_channelOpenListener","tracksInfo","initializeBridgeChannel","logError","msgType","_receiverVideoConstraints","oldLastNEndpoints","leavingLastNEndpoints","enteringLastNEndpoints","setNewReceiverVideoConstraints","setVideoType","sendSourceVideoType","selectEndpoints","ids","signaling","encodedInsertableStreams","sdpSemantics","bundlePolicy","newConnection","traceablePeerConnection","addLocalTrack","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","ConnectionQualityEvents","_broadcastLocalStats","_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","DETECTOR_STATE_CHANGE","VAD_NOISY_DEVICE","VAD_REPORT_PUBLISHED","VAD_SCORE_PUBLISHED","VAD_TALK_WHILE_MUTED","NoAudioSignalDetection","_timeoutTrigger","_hasAudioInput","_audioLevel","_trackAdded","_clearTriggerTimeout","_handleAudioInputStateChange","DetectionEvents","_handleNoAudioInputDetection","_eventFired","_audioTrack","P2PDominantSpeakerDetection","myUserID","createAudioContext","AudioContextImpl","TrackVADEmitter","procNodeSampleRate","vadProcessor","jitsiLocalTrack","_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","E2E_RTT_CHANGED","E2E_PING_REQUEST","E2E_PING_RESPONSE","ParticipantWrapper","e2eping","requests","lastRequestId","clearIntervals","handleResponse","maybeSendAnalytics","isDataChannelOpen","pingIntervalMs","analyticsInterval","analyticsIntervalMs","requestId","requestMessage","timeSent","E2ePingEvents","createE2eRttEvent","E2ePing","participantJoined","participantLeft","messageReceived","dataChannelOpened","participantWrapper","handleRequest","Jvb121EventGenerator","_jvb121","evaluateStatus","oldStatus","getParticipantCount","ReceiverVideoConstraints","_defaultConstraints","defaultConstraints","onStageEndpoints","updateLastN","updateReceiveResolution","updateReceiverVideoConstraints","updateSelectedEndpoints","ReceiveVideoController","_rtc","startLastN","channelLastN","useNewBandwidthAllocationStrategy","remoteEndpointIds","oldConstraints","newConstraints","setPreferredReceiveMaxFrameHeight","setReceiverConstraints","isEndpointsFormat","isSourcesFormat","p2pSession","SendVideoController","layerSuspensionEnabled","enableLayerSuspension","_propagateSendMaxFrameHeight","_senderVideoConstraints","idealHeight","_getActiveMediaSession","sendMaxFrameHeight","selectSendMaxFrameHeight","activeMediaSession","preferredSendMaxFrameHeight","setPreferredSendMaxFrameHeight","getFocusRecordingUpdate","jibriStatus","recordingMode","sessionID","getHiddenDomainUpdate","liveStreamViewURLContainer","liveStreamViewURL","modeContainer","sessionIDContainer","getSessionIdFromIq","jibri","getSessionId","sessionIdContainer","isFromFocus","JibriSession","_connection","_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","_onFacialExpressionAdd","_updateStats","oldDominantSpeaker","newDominantSpeaker","savedUser","newStats","speakerStatsToUpdate","newParticipant","ComponentsVersions","addCommandListener","processVersions","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","nodeTree","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","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","resourceCreator","isAuthenticatedUser","re","codecSettings","codecSelection","_statsCurrentId","statisticsId","_onIceConnectionInterrupted","_onIceConnectionRestored","_onIceConnectionEstablished","_updateProperties","_sendConferenceJoinAnalyticsEvent","_removeLocalSourceOnReject","_updateRoomPresence","_registerRtcListeners","receiveVideoController","sendVideoController","_p2pConnStatusRtcMuteTimeout","_peerConnStatusRtcMuteTimeout","_peerConnStatusOutOfLastNTimeout","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","_sendBridgeVideoTypeMessage","localtrack","_desktopSharingFrameRate","isAuthEnabled","isLoggedIn","getAuthLogin","getExternalAuthUrl","urlForPopup","getPerformanceStats","longTasksStats","eventId","command","removeCommandListener","sendTextMessage","sendPrivateTextMessage","sendCommand","sendCommandOnce","removeCommand","nickKey","getTranscriber","localAudioTracks","getTranscriptionStatus","_fireAudioLevelChangeEvent","activeTpc","_fireMuteChangeEvent","actorId","_getInitialLocalTracks","trackType","isStartAudioMuted","isStartVideoMuted","muteHandler","audioLevelHandler","oldTrackBelongsToConference","_doReplaceTrack","_setupNewTrack","replaceTrackPromises","_setNewVideoType","videoTypeTagName","trackVideoType","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","_shouldBeInP2PMode","_rejectIncomingCall","_acceptP2PIncomingCall","_acceptJvbIncomingCall","_suspendMediaTransferForJvbConnection","webSocket","stopOptions","forceJvb121","p2pFailed","_stopP2PSession","isDTMFSupported","peerConnection","startTranscriber","stopTranscriber","getMeetingUniqueId","setStartMutedPolicy","policy","getStartMutedPolicy","removeLocalParticipantProperty","getLocalParticipantProperty","property","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","presenceChanged","muteStatusChanged","videoTypeChanged","getP2PConnectionState","startP2PSession","stopP2PSession","getSpeakerStats","sendFacialExpression","perf","toggleE2EE","setMediaEncryptionKey","isLobbySupported","disableLobby","joinLobby","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","JitsiMediaDevicesEvents","_logOutputDevice","permissions","_handlePermissionsChange","_permissionsApiSupported","query","_parsePermissionState","onchange","supported","permissionStatus","deviceID","isDevicePermissionGranted","isMultipleAudioInputSupported","emitEvent","stopActiveDevices","ACTIONS","ProxyConnectionPC","receiveAudio","receiveVideo","_peerConnection","_onError","_onRemoteStream","_onSendMessage","getPeerJid","processMessage","$jingle","_onSessionAccept","_onSessionInitiate","_onSessionTerminate","_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","_selfCloseConnection","xml","xmlDom","DOMParser","parseFromString","isVideo","convertVideoToDesktop","jitsiLocalTracks","createLocalTracks","stringifiedIq","XMLSerializer","serializeToString","onConnectionClosed","constants","recording","BUSY","RESOURCE_CONSTRAINT","UNEXPECTED_REQUEST","SERVICE_UNAVAILABLE","FILE","STREAM","sipVideoGW","detection","errors","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","global$1","WebSocketImplementation","getWebSocketImplementation","DOMParserImplementation","getDOMParserImplementation","getDummyXMLDOMDocument","createDocument","implementation","documentMode","doc","docStrings","ActiveXObject","_getIEXmlDom","safe_add$1","lsw","str2binl","bin","md5_cmn","cnt","md5_ff","md5_gg","md5_hh","md5_ii","core_md5","olda","oldb","oldc","oldd","MD5","hexdigest","binarray","hex_tab","binl2hex","fromCharCode","binl2str","SASLMechanism","isClientFirst","mechname","onStart","onChallenge","challenge","clientChallenge","utils","out","cookies","cookieName","expires","path","cookieObj","isObj","cookieValue","escape","unescape","cookie","core_sha1","olde","rol","safe_add","sha1_ft","sha1_kt","core_hmac_sha1","bkey","str2binb","ipad","opad","binb2b64","triplet","binb2str","SHA1","b64_hmac_sha1","str_hmac_sha1","str_sha1","abab","atob","accumulatedBits","chr","btoa","groupsOfSix","Builder","VERSION","NS","HTTPBIND","BOSH","CLIENT","AUTH","ROSTER","PROFILE","DISCO_INFO","DISCO_ITEMS","MUC","SASL","FRAMING","BIND","SESSION","STANZAS","XHTML_IM","XHTML","tags","css","validTag","validAttribute","validCSS","CONNECTING","CONNFAIL","AUTHENTICATING","AUTHFAIL","DISCONNECTED","DISCONNECTING","ATTACHED","REDIRECT","CONNTIMEOUT","BINDREQUIRED","ATTACHFAIL","ErrorCondition","BAD_FORMAT","CONFLICT","MISSING_JID_NODE","NO_AUTH_MECH","UNKNOWN_REASON","LogLevel","FATAL","ElementType","NORMAL","TEXT","CDATA","FRAGMENT","SECONDARY_TIMEOUT","addNamespace","forEachChild","elemName","childNodes","childNode","isTagEqual","_xmlGenerator","xmlGenerator","xmlTextNode","xmlescape","xmlunescape","createTextNode","xmlHtmlNode","html","loadXML","getText","nodeValue","copyElement","createHtml","cssText","cssAttrs","cssName","cssValue","createDocumentFragment","escapeNode","unescapeNode","getNodeFromJid","getDomainFromJid","bare","getBareJidFromJid","getResourceFromJid","_handleError","fatal","sourceURL","fileName","lineNumber","_console","getNamedItem","_requestId","_connectionPlugins","ptype","moreattrs","removeAttribute","impNode","xmlGen","importNode","newElem","h","fragment","innerHTML","xhtml","ns","matchBare","matchBareFromJid","Handler","getNamespace","elNamespace","ignoreNamespaceFragment","namespaceMatch","nsMatch","isMatch","elem_type","run","TimedHandler","period","lastCalled","Connection","setProtocol","_sasl_data","do_bind","do_session","mechanisms","timedHandlers","removeTimeds","removeHandlers","addTimeds","addHandlers","protocolErrorHandlers","_idleTimeout","_disconnectTimeout","authenticated","do_authentication","restored","_data","_uniqueId","_sasl_success_handler","_sasl_failure_handler","_sasl_challenge_handler","_onIdle","registerSASLMechanisms","F","WorkerWebsocket","Websocket","Bosh","_reset","_requests","getUniqueId","suffix","addProtocolErrorHandler","status_code","hold","route","authcid","disconnection_timeout","authzid","connect_callback","wind","_attach","restore","_sessionCachingSupported","_restore","sessionStorage","xmlInput","xmlOutput","nextValidRid","_queueData","timeoutHandler","deleteTimedHandler","addTimedHandler","iqtype","_sendRestart","thand","handRef","hand","SASLAnonymous","SASLExternal","SASLOAuthBearer","SASLXOAuth2","SASLPlain","SASLSHA1","registerSASLMechanism","Mechanism","_addSysTimedHandler","_onDisconnectTimeout","_disconnect","condition","statusChanged","_dataRecv","_reqToData","strip","_emptyQueue","cond","conflict","UNKOWN_REASON","_connect_cb","_callback","bodyWrap","hasFeatures","_no_auth_received","sortMechanismsByPriority","higher","swap","_attemptSASLAuth","_attemptLegacyAuth","mechanism_found","_sasl_success_cb","_sasl_failure_cb","_sasl_challenge_cb","_sasl_mechanism","request_auth_exchange","_onLegacyAuthIQResult","_auth2_cb","serverSignature","attribMatch","streamfeature_handlers","wrapper","_onStreamFeaturesAfterSASL","explicitResourceBinding","_onResourceBindResultIQ","jidNode","_establishSession","_onSessionResultIQ","auth_str","nonce","iter","Hi","U","U_old","authMessage","cnonce","clientKey","serverKey","clientSignature","test_cnonce","core","Request","sends","xmlData","origFunc","date","NaN","abort","dead","age","timeDead","xhr","_newXHR","getResponse","responseXML","documentElement","overrideMimeType","_conn","inactivity","keepalive","_cacheSession","_onRequestStateChange","_throttledRequestHandler","typ","_sendTerminate","_callProtocolErrorHandlers","reqStatus","_getRequestStatus","err_callback","HTTP","_hitError","time_elapsed","static","def","getAllResponseHeaders","valid_request","too_many_retries","_removeRequest","reqIs0","_restartRequest","_processRequest","primary_timeout","secondary_timeout","server_error","content_type","contentType","withCredentials","e2","sendFunc","customHeaders","backoff","new_service","host","pathname","_buildStream","_checkStreamError","connectstatus","errorString","_onOpen","_onInitialMessage","_handleStreamStart","streamStart","parsedMessage","see_uri","_replaceMessageHandler","_streamWrap","CLOSED","closeString","rawStanza","firstChild","startString","lmap","SharedWorker","_messageHandler","_onWorkerMessage","_attachCallback","method_name","strophe_js","_items","conn","_onDiscoInfo","_onDiscoItems","addIdentity","lang","var_name","addItem","call_back","items","_buildIQResult","query_attrs","iqresult","logging","autoSendCountOnEveryIncomingStanza","requestResponseInterval","_c","_NS","_isStreamManagementEnabled","_serverProcesssedStanzasCounter","_clientProcessedStanzasCounter","_clientSentStanzasCounter","_originalXMLOutput","_requestHandler","_incomingHandler","_requestResponseIntervalCount","_isSupported","_unacknowledgedStanzas","_acknowledgedStanzaListeners","addAcknowledgedStanzaListener","_resumeToken","_resuming","_originalConnect","_connectArgs","requestAcknowledgement","getOutgoingCounter","getIncomingCounter","_interceptConnectArgs","_originalOnStreamFeaturesAfterSASL","_originalDoDisconnect","_interceptDoDisconnect","_originalDisconnect","_interceptDisconnect","_resumeState","_storedJid","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","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","DNS","rnds","_sha","logDisabled_","deprecationWarnings_","extractVersion","uastring","wrapPeerConnectionEvent","eventNameToWrap","nativeAddEventListener","nativeEventName","modifiedEvent","handleEvent","_eventMap","nativeRemoveEventListener","unwrappedCb","disableLog","bool","disableWarnings","deprecated","oldMethod","newMethod","val","compactObject","isEmptyObject","walkStats","resultSet","filterStats","outbound","streamStatsType","filteredResult","trackStats","trackStat","shimGetUserMedia","browserDetails","constraintsToChrome_","cc","exact","oldname_","oc","mix","advanced","shimConstraints_","remap","face","getSupportedFacingModeLies","getSupportedConstraints","dev","shimError_","PermissionDeniedError","PermissionDismissedError","InvalidStateError","DevicesNotFoundError","ConstraintNotSatisfiedError","TrackStartError","MediaDeviceFailedDueToShutdown","MediaDeviceKillSwitchOn","TabCaptureError","ScreenCaptureError","DeviceCaptureError","webkitGetUserMedia","origGetUserMedia","DOMException","shimGetDisplayMedia","getSourceId","widthSpecified","heightSpecified","frameRateSpecified","shimMediaStream","webkitMediaStream","shimOnTrack","_ontrack","origSetRemoteDescription","_ontrackpoly","te","Event","dispatchEvent","shimGetSendersWithDtmf","shimSenderWithDtmf","_dtmf","_pc","_senders","origAddTrack","origRemoveTrack","origAddStream","origRemoveStream","origGetSenders","shimGetStats","origGetStats","selector","onSucc","onErr","fixChromeStats_","standardReport","standardStats","localcandidate","remotecandidate","makeMapStats","successCallbackWrapper_","shimSenderReceiverGetStats","origGetReceivers","srcElement","MediaStreamTrack","shimAddTrackRemoveTrackWithNative","getLocalStreams","_shimmedLocalStreams","existingSenders","newSenders","newSender","shimAddTrackRemoveTrack","origGetLocalStreams","nativeStreams","_reverseStreams","_streams","newStream","replaceInternalStreamId","internalId","externalStream","internalStream","replaceExternalStreamId","alreadyExists","oldStream","nativeMethod","methodObj","origSetLocalDescription","origLocalDescription","getOwnPropertyDescriptor","streamid","shimPeerConnection","webkitRTCPeerConnection","fixNegotiationNeeded","getConfiguration","nativeGetUserMedia","nativeGetSettings","applyConstraints","nativeApplyConstraints","preferredMediaSource","mediaSource","RTCTrackEvent","mozRTCPeerConnection","modernStatsTypes","inboundrtp","outboundrtp","candidatepair","nativeGetStats","shimSenderGetStats","shimReceiverGetStats","shimRemoveStream","shimRTCDataChannel","DataChannel","RTCDataChannel","shimAddTransceiver","origAddTransceiver","setParametersPromises","initParameters","shouldPerformCheck","encodingParam","maxFramerate","shimGetParameters","origGetParameters","shimCreateOffer","origCreateOffer","finally","shimCreateAnswer","origCreateAnswer","shimLocalStreamsAPI","_localStreams","_addTrack","shimRemoteStreamsAPI","getRemoteStreams","_remoteStreams","_onaddstream","_onaddstreampoly","shimCallbacksAPI","withCallback","shimConstraints","errcb","shimRTCIceServerUrls","OrigPeerConnection","newIceServers","generateCertificate","shimTrackEventTransceiver","shimCreateOfferLegacy","offerOptions","audioTransceiver","setDirection","videoTransceiver","shimAudioContext","shimRTCIceCandidate","NativeRTCIceCandidate","nativeCandidate","parsedCandidate","augmentedCandidate","toJSON","shimMaxMessageSize","_sctp","sctpInDescription","getRemoteFirefoxVersion","getCanSendMaxMessageSize","remoteIsFirefox","canSendMaxMessageSize","getMaxMessageSize","canSendMMS","remoteMMS","POSITIVE_INFINITY","shimSendThrowTypeError","wrapDcSend","dc","origDataChannelSend","origCreateDataChannel","dataChannel","shimConnectionState","checking","_onconnectionstatechange","origMethod","_connectionstatechangepoly","_lastConnectionState","newEvent","removeExtmapAllowMixed","nativeSRD","shimAddIceCandidateNullOrEmpty","nativeAddIceCandidate","adapter","shimChrome","shimFirefox","shimSafari","mozGetUserMedia","isSecureContext","RTCIceGatherer","commonShim","browserShim","adapterFactory","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","loaded","__webpack_modules__","amdO","getter","definition","globalThis","nmd","paths","__webpack_exports__"],"sourceRoot":""}