{ "version": 3, "sources": ["../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/adapters.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/logger.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/connection_monitor.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/internal.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/connection.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/subscription.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/subscription_guarantor.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/subscriptions.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/consumer.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/index.js", "../../../node_modules/twilio-video/lib/util/dynamicimport.js", "../../../node_modules/twilio-video/lib/vendor/loglevel.js", "../../../node_modules/twilio-video/package.json", "../../../node_modules/twilio-video/lib/util/constants.js", "../../../node_modules/twilio-video/lib/util/log.js", "../../../node_modules/twilio-video/lib/noisecancellationadapter.ts", "../../../node_modules/twilio-video/lib/media/track/noisecancellationimpl.ts", "../../../node_modules/twilio-video/lib/webrtc/util/index.js", "../../../node_modules/twilio-video/lib/util/sid.js", "../../../node_modules/twilio-video/lib/util/twiliowarning.js", "../../../node_modules/twilio-video/lib/util/index.js", "../../../node_modules/twilio-video/lib/webrtc/util/sdp.js", "../../../node_modules/twilio-video/lib/webrtc/getstats.js", "../../../node_modules/twilio-video/lib/webrtc/getusermedia.js", "../../../node_modules/twilio-video/lib/webrtc/mediastream.js", "../../../node_modules/twilio-video/lib/webrtc/mediastreamtrack.js", "../../../node_modules/twilio-video/lib/webrtc/rtcicecandidate.js", "../../../node_modules/twilio-video/lib/webrtc/rtcsessiondescription/chrome.js", "../../../node_modules/events/events.js", "../../../node_modules/twilio-video/lib/eventtarget.js", "../../../node_modules/twilio-video/lib/webrtc/util/latch.js", "../../../node_modules/twilio-video/lib/webrtc/rtcrtpsender.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/chrome.js", "../../../node_modules/twilio-video/lib/webrtc/rtcsessiondescription/firefox.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/firefox.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/safari.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/index.js", "../../../node_modules/twilio-video/lib/webrtc/rtcsessiondescription/index.js", "../../../node_modules/twilio-video/lib/webrtc/index.js", "../../../node_modules/twilio-video/lib/vendor/inherits.js", "../../../node_modules/twilio-video/lib/util/browserdetection.js", "../../../node_modules/twilio-video/lib/webaudio/detectsilence.js", "../../../node_modules/twilio-video/lib/webaudio/audiocontext.js", "../../../node_modules/twilio-video/lib/util/detectsilentaudio.js", "../../../node_modules/twilio-video/lib/util/localmediarestartdeferreds.js", "../../../node_modules/twilio-video/lib/eventemitter.js", "../../../node_modules/twilio-video/lib/media/track/index.js", "../../../node_modules/twilio-video/lib/media/track/mediatrack.js", "../../../node_modules/twilio-video/lib/media/track/audiotrack.js", "../../../node_modules/twilio-video/lib/util/detectsilentvideo.js", "../../../node_modules/twilio-video/lib/util/documentvisibilitymonitor.js", "../../../node_modules/twilio-video/lib/webaudio/workaround180748.js", "../../../node_modules/twilio-video/lib/queueingeventemitter.js", "../../../node_modules/twilio-video/lib/transceiver.js", "../../../node_modules/twilio-video/lib/media/track/transceiver.js", "../../../node_modules/twilio-video/lib/media/track/sender.js", "../../../node_modules/twilio-video/lib/media/track/localmediatrack.js", "../../../node_modules/twilio-video/lib/media/track/localaudiotrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/localaudiotrack.js", "../../../node_modules/twilio-video/lib/media/track/capturevideoframes.js", "../../../node_modules/twilio-video/lib/media/track/videoprocessoreventobserver.js", "../../../node_modules/twilio-video/lib/media/track/videotrack.js", "../../../node_modules/twilio-video/lib/media/track/localvideotrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/localvideotrack.js", "../../../node_modules/twilio-video/lib/data/transceiver.js", "../../../node_modules/twilio-video/lib/data/sender.js", "../../../node_modules/twilio-video/lib/media/track/localdatatrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/localdatatrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/index.js", "../../../node_modules/twilio-video/lib/createlocaltracks.ts", "../../../node_modules/twilio-video/lib/preflight/timer.ts", "../../../node_modules/twilio-video/lib/preflight/mos.ts", "../../../node_modules/twilio-video/lib/preflight/getCombinedConnectionStats.ts", "../../../node_modules/twilio-video/lib/statemachine.js", "../../../node_modules/twilio-video/lib/util/networkmonitor.js", "../../../node_modules/twilio-video/lib/util/timeout.js", "../../../node_modules/twilio-video/src/ws.js", "../../../node_modules/twilio-video/lib/twilioconnection.js", "../../../node_modules/twilio-video/lib/util/twilioerror.js", "../../../node_modules/twilio-video/lib/util/twilio-video-errors.js", "../../../node_modules/twilio-video/lib/preflight/getturncredentials.ts", "../../../node_modules/twilio-video/lib/preflight/makestat.ts", "../../../node_modules/twilio-video/lib/preflight/syntheticaudio.ts", "../../../node_modules/twilio-video/lib/preflight/syntheticvideo.ts", "../../../node_modules/twilio-video/lib/util/movingaveragedelta.js", "../../../node_modules/twilio-video/lib/util/eventobserver.js", "../../../node_modules/twilio-video/lib/util/insightspublisher/index.js", "../../../node_modules/twilio-video/lib/preflight/preflighttest.ts", "../../../node_modules/twilio-video/lib/util/cancelablepromise.js", "../../../node_modules/twilio-video/lib/cancelableroompromise.js", "../../../node_modules/twilio-video/lib/encodingparameters.js", "../../../node_modules/twilio-video/lib/util/validate.js", "../../../node_modules/twilio-video/lib/media/track/trackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localtrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localaudiotrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localdatatrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localvideotrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/remotemediatrack.js", "../../../node_modules/twilio-video/lib/media/track/remoteaudiotrack.js", "../../../node_modules/twilio-video/lib/media/track/remotetrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/remoteaudiotrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/remotedatatrack.js", "../../../node_modules/twilio-video/lib/media/track/remotedatatrackpublication.js", "../../../node_modules/twilio-video/lib/util/nullobserver.js", "../../../node_modules/twilio-video/lib/media/track/remotevideotrack.js", "../../../node_modules/twilio-video/lib/media/track/remotevideotrackpublication.js", "../../../node_modules/twilio-video/lib/participant.js", "../../../node_modules/twilio-video/lib/localparticipant.js", "../../../node_modules/twilio-video/lib/util/insightspublisher/null.js", "../../../node_modules/twilio-video/lib/networkqualityconfiguration.js", "../../../node_modules/twilio-video/lib/remoteparticipant.js", "../../../node_modules/twilio-video/lib/stats/trackstats.js", "../../../node_modules/twilio-video/lib/stats/localtrackstats.js", "../../../node_modules/twilio-video/lib/stats/localaudiotrackstats.js", "../../../node_modules/twilio-video/lib/stats/localvideotrackstats.js", "../../../node_modules/twilio-video/lib/stats/remotetrackstats.js", "../../../node_modules/twilio-video/lib/stats/remoteaudiotrackstats.js", "../../../node_modules/twilio-video/lib/stats/remotevideotrackstats.js", "../../../node_modules/twilio-video/lib/stats/statsreport.js", "../../../node_modules/twilio-video/lib/room.js", "../../../node_modules/twilio-video/lib/util/backoff.js", "../../../node_modules/twilio-video/lib/util/sdp/simulcast.js", "../../../node_modules/twilio-video/lib/util/sdp/index.js", "../../../node_modules/twilio-video/lib/util/filter.js", "../../../node_modules/twilio-video/lib/signaling/v2/icebox.js", "../../../node_modules/twilio-video/lib/signaling/v2/iceconnectionmonitor.js", "../../../node_modules/twilio-video/lib/data/transport.js", "../../../node_modules/twilio-video/lib/data/receiver.js", "../../../node_modules/twilio-video/lib/media/track/receiver.js", "../../../node_modules/twilio-video/lib/util/sdp/trackmatcher.js", "../../../node_modules/twilio-video/lib/util/sdp/issue8329.js", "../../../node_modules/twilio-video/lib/signaling/v2/peerconnection.js", "../../../node_modules/twilio-video/lib/signaling/v2/peerconnectionmanager.js", "../../../node_modules/twilio-video/lib/signaling/v2/mediasignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/dominantspeakersignaling.js", "../../../node_modules/twilio-video/lib/stats/icereport.js", "../../../node_modules/twilio-video/lib/stats/icereportfactory.js", "../../../node_modules/twilio-video/lib/stats/average.js", "../../../node_modules/twilio-video/lib/stats/senderorreceiverreport.js", "../../../node_modules/twilio-video/lib/stats/sum.js", "../../../node_modules/twilio-video/lib/stats/receiverreport.js", "../../../node_modules/twilio-video/lib/stats/senderreport.js", "../../../node_modules/twilio-video/lib/stats/peerconnectionreport.js", "../../../node_modules/twilio-video/lib/stats/senderorreceiverreportfactory.js", "../../../node_modules/twilio-video/lib/stats/receiverreportfactory.js", "../../../node_modules/twilio-video/lib/stats/senderreportfactory.js", "../../../node_modules/twilio-video/lib/stats/peerconnectionreportfactory.js", "../../../node_modules/twilio-video/lib/signaling/v2/networkqualitymonitor.js", "../../../node_modules/twilio-video/lib/util/asyncvar.js", "../../../node_modules/twilio-video/lib/signaling/v2/networkqualitysignaling.js", "../../../node_modules/twilio-video/lib/signaling/recording.js", "../../../node_modules/twilio-video/lib/signaling/v2/recording.js", "../../../node_modules/twilio-video/lib/signaling/room.js", "../../../node_modules/twilio-video/lib/stats/networkqualitybandwidthstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityfractionloststats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitylatencystats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitysendorrecvstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitysendstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityrecvstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitymediastats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityaudiostats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityvideostats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitystats.js", "../../../node_modules/twilio-video/lib/signaling/participant.js", "../../../node_modules/twilio-video/lib/signaling/remoteparticipant.js", "../../../node_modules/twilio-video/lib/signaling/track.js", "../../../node_modules/twilio-video/lib/signaling/remotetrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/remotetrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/remoteparticipant.js", "../../../node_modules/twilio-video/lib/signaling/v2/trackprioritysignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/trackswitchoffsignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/renderhintssignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/publisherhintsignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/room.js", "../../../node_modules/twilio-video/lib/signaling/v2/twilioconnectiontransport.js", "../../../node_modules/twilio-video/lib/signaling/v2/cancelableroomsignalingpromise.js", "../../../node_modules/twilio-video/lib/signaling/localparticipant.js", "../../../node_modules/twilio-video/lib/signaling/localtrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/localtrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/localparticipant.js", "../../../node_modules/twilio-video/lib/signaling/index.js", "../../../node_modules/twilio-video/lib/signaling/v2/index.js", "../../../node_modules/twilio-video/lib/connect.js", "../../../node_modules/twilio-video/lib/createlocaltrack.js", "../../../node_modules/twilio-video/lib/util/support.js", "../../../node_modules/twilio-video/lib/index.ts", "../../../node_modules/@rails/ujs/app/assets/javascripts/rails-ujs.esm.js", "../../../node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/snakeize.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable_stream_source_element.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/fetch_requests.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/index.js", "../../../node_modules/@rails/activestorage/app/assets/javascripts/activestorage.esm.js", "../../../node_modules/local-time/app/assets/javascripts/local-time.es2017-esm.js", "../../../node_modules/@hotwired/stimulus/dist/stimulus.js", "../../javascript/controllers/autofocus_controller.js", "../../../node_modules/@rails/actioncable/app/assets/javascripts/actioncable.esm.js", "../../javascript/controllers/cable_controller.js", "../../javascript/controllers/carousel_controller.js", "../../javascript/controllers/cart_controller.js", "../../javascript/controllers/clipboard_controller.js", "../../javascript/controllers/disable_controller.js", "../../javascript/controllers/disable_trix_attachments_controller.js", "../../javascript/controllers/dismissable_alert_controller.js", "../../javascript/debounce.js", "../../javascript/controllers/form_submit_controller.js", "../../javascript/controllers/json_parse_controller.js", "../../javascript/controllers/loading_indicator_controller.js", "../../javascript/controllers/modal_controller.js", "../../javascript/controllers/modal_creator_controller.js", "../../javascript/controllers/online_controller.js", "../../javascript/controllers/options_toggle_controller.js", "../../javascript/controllers/password_toggler_controller.js", "../../javascript/controllers/required_input_controller.js", "../../javascript/controllers/resizer_controller.js", "../../../node_modules/choices.js/public/assets/scripts/choices.mjs", "../../javascript/controllers/searchable_dropdown_controller.js", "../../javascript/controllers/stripe_controller.js", "../../javascript/controllers/tab_filters_controller.js", "../../javascript/controllers/timer_controller.js", "../../javascript/controllers/toggle_controller.js", "../../javascript/helpers.js", "../../javascript/uploader.js", "../../javascript/controllers/upload_controller.js", "../../javascript/controllers/video_chat_controller.js", "../../javascript/controllers/video_preview_controller.js", "stimulus_ns:/kp20/app/javascript/controllers", "../../../node_modules/trix/src/trix/config/attachments.js", "../../../node_modules/trix/src/trix/config/block_attributes.js", "../../../node_modules/trix/src/trix/config/browser.js", "../../../node_modules/trix/src/trix/config/dompurify.js", "../../../node_modules/trix/src/trix/config/lang.js", "../../../node_modules/trix/src/trix/config/file_size_formatting.js", "../../../node_modules/trix/src/trix/constants.js", "../../../node_modules/trix/src/trix/core/helpers/extend.js", "../../../node_modules/trix/src/trix/core/helpers/dom.js", "../../../node_modules/trix/src/trix/config/input.js", "../../../node_modules/trix/src/trix/config/key_names.js", "../../../node_modules/trix/src/trix/config/parser.js", "../../../node_modules/trix/src/trix/config/text_attributes.js", "../../../node_modules/trix/src/trix/config/toolbar.js", "../../../node_modules/trix/src/trix/config/undo.js", "../../../node_modules/trix/src/trix/config/css.js", "../../../node_modules/trix/src/trix/core/basic_object.js", "../../../node_modules/trix/src/trix/core/utilities/utf16_string.js", "../../../node_modules/trix/src/trix/core/object.js", "../../../node_modules/trix/src/trix/core/helpers/arrays.js", "../../../node_modules/trix/src/trix/core/helpers/bidi.js", "../../../node_modules/trix/src/trix/core/helpers/config.js", "../../../node_modules/trix/src/trix/core/helpers/custom_elements.js", "../../../node_modules/trix/src/trix/core/helpers/events.js", "../../../node_modules/trix/src/trix/core/helpers/functions.js", "../../../node_modules/trix/src/trix/core/helpers/objects.js", "../../../node_modules/trix/src/trix/core/helpers/ranges.js", "../../../node_modules/trix/src/trix/observers/selection_change_observer.js", "../../../node_modules/trix/src/trix/core/helpers/strings.js", "../../../node_modules/trix/src/trix/core/collections/hash.js", "../../../node_modules/trix/src/trix/core/collections/object_group.js", "../../../node_modules/trix/src/trix/core/collections/object_map.js", "../../../node_modules/trix/src/trix/core/collections/element_store.js", "../../../node_modules/trix/src/trix/core/utilities/operation.js", "../../../node_modules/trix/src/trix/views/object_view.js", "../../../node_modules/trix/src/trix/models/html_sanitizer.js", "../../../node_modules/trix/src/trix/views/attachment_view.js", "../../../node_modules/trix/src/trix/views/previewable_attachment_view.js", "../../../node_modules/trix/src/trix/views/piece_view.js", "../../../node_modules/trix/src/trix/views/text_view.js", "../../../node_modules/trix/src/trix/views/block_view.js", "../../../node_modules/trix/src/trix/views/document_view.js", "../../../node_modules/trix/src/trix/models/piece.js", "../../../node_modules/trix/src/trix/operations/image_preload_operation.js", "../../../node_modules/trix/src/trix/models/attachment.js", "../../../node_modules/trix/src/trix/models/attachment_piece.js", "../../../node_modules/trix/src/trix/models/string_piece.js", "../../../node_modules/trix/src/trix/models/splittable_list.js", "../../../node_modules/trix/src/trix/models/text.js", "../../../node_modules/trix/src/trix/models/block.js", "../../../node_modules/trix/src/trix/models/document.js", "../../../node_modules/trix/src/trix/models/html_parser.js", "../../../node_modules/trix/src/trix/core/serialization.js", "../../../node_modules/trix/src/trix/models/managed_attachment.js", "../../../node_modules/trix/src/trix/models/attachment_manager.js", "../../../node_modules/trix/src/trix/models/line_break_insertion.js", "../../../node_modules/trix/src/trix/models/composition.js", "../../../node_modules/trix/src/trix/models/undo_manager.js", "../../../node_modules/trix/src/trix/filters/filter.js", "../../../node_modules/trix/src/trix/filters/attachment_gallery_filter.js", "../../../node_modules/trix/src/trix/models/editor.js", "../../../node_modules/trix/src/trix/models/location_mapper.js", "../../../node_modules/trix/src/trix/models/point_mapper.js", "../../../node_modules/trix/src/trix/models/selection_manager.js", "../../../node_modules/trix/src/trix/controllers/attachment_editor_controller.js", "../../../node_modules/trix/src/trix/controllers/composition_controller.js", "../../../node_modules/trix/src/trix/controllers/controller.js", "../../../node_modules/trix/src/trix/observers/mutation_observer.js", "../../../node_modules/trix/src/trix/operations/file_verification_operation.js", "../../../node_modules/trix/src/trix/models/flaky_android_keyboard_detector.js", "../../../node_modules/trix/src/trix/controllers/input_controller.js", "../../../node_modules/trix/src/trix/controllers/level_0_input_controller.js", "../../../node_modules/trix/src/trix/controllers/level_2_input_controller.js", "../../../node_modules/trix/src/trix/controllers/toolbar_controller.js", "../../../node_modules/trix/src/trix/controllers/editor_controller.js", "../../../node_modules/trix/src/trix/elements/trix_toolbar_element.js", "../../../node_modules/trix/src/trix/elements/trix_editor_element.js", "../../../node_modules/trix/src/trix/trix.js", "../../../node_modules/@rails/actiontext/app/assets/javascripts/actiontext.esm.js", "../../javascript/application.js"], "sourcesContent": ["export default {\n logger: typeof console !== \"undefined\" ? console : undefined,\n WebSocket: typeof WebSocket !== \"undefined\" ? WebSocket : undefined,\n}\n", "import adapters from \"./adapters\"\n\n// The logger is disabled by default. You can enable it with:\n//\n// ActionCable.logger.enabled = true\n//\n// Example:\n//\n// import * as ActionCable from '@rails/actioncable'\n//\n// ActionCable.logger.enabled = true\n// ActionCable.logger.log('Connection Established.')\n//\n\nexport default {\n log(...messages) {\n if (this.enabled) {\n messages.push(Date.now())\n adapters.logger.log(\"[ActionCable]\", ...messages)\n }\n },\n}\n", "import logger from \"./logger\"\n\n// Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting\n// revival reconnections if things go astray. Internal class, not intended for direct user manipulation.\n\nconst now = () => new Date().getTime()\n\nconst secondsSince = time => (now() - time) / 1000\n\nclass ConnectionMonitor {\n constructor(connection) {\n this.visibilityDidChange = this.visibilityDidChange.bind(this)\n this.connection = connection\n this.reconnectAttempts = 0\n }\n\n start() {\n if (!this.isRunning()) {\n this.startedAt = now()\n delete this.stoppedAt\n this.startPolling()\n addEventListener(\"visibilitychange\", this.visibilityDidChange)\n logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`)\n }\n }\n\n stop() {\n if (this.isRunning()) {\n this.stoppedAt = now()\n this.stopPolling()\n removeEventListener(\"visibilitychange\", this.visibilityDidChange)\n logger.log(\"ConnectionMonitor stopped\")\n }\n }\n\n isRunning() {\n return this.startedAt && !this.stoppedAt\n }\n\n recordPing() {\n this.pingedAt = now()\n }\n\n recordConnect() {\n this.reconnectAttempts = 0\n this.recordPing()\n delete this.disconnectedAt\n logger.log(\"ConnectionMonitor recorded connect\")\n }\n\n recordDisconnect() {\n this.disconnectedAt = now()\n logger.log(\"ConnectionMonitor recorded disconnect\")\n }\n\n // Private\n\n startPolling() {\n this.stopPolling()\n this.poll()\n }\n\n stopPolling() {\n clearTimeout(this.pollTimeout)\n }\n\n poll() {\n this.pollTimeout = setTimeout(() => {\n this.reconnectIfStale()\n this.poll()\n }\n , this.getPollInterval())\n }\n\n getPollInterval() {\n const { staleThreshold, reconnectionBackoffRate } = this.constructor\n const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10))\n const jitterMax = this.reconnectAttempts === 0 ? 1.0 : reconnectionBackoffRate\n const jitter = jitterMax * Math.random()\n return staleThreshold * 1000 * backoff * (1 + jitter)\n }\n\n reconnectIfStale() {\n if (this.connectionIsStale()) {\n logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`)\n this.reconnectAttempts++\n if (this.disconnectedRecently()) {\n logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`)\n } else {\n logger.log(\"ConnectionMonitor reopening\")\n this.connection.reopen()\n }\n }\n }\n\n get refreshedAt() {\n return this.pingedAt ? this.pingedAt : this.startedAt\n }\n\n connectionIsStale() {\n return secondsSince(this.refreshedAt) > this.constructor.staleThreshold\n }\n\n disconnectedRecently() {\n return this.disconnectedAt && (secondsSince(this.disconnectedAt) < this.constructor.staleThreshold)\n }\n\n visibilityDidChange() {\n if (document.visibilityState === \"visible\") {\n setTimeout(() => {\n if (this.connectionIsStale() || !this.connection.isOpen()) {\n logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`)\n this.connection.reopen()\n }\n }\n , 200)\n }\n }\n\n}\n\nConnectionMonitor.staleThreshold = 6 // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)\nConnectionMonitor.reconnectionBackoffRate = 0.15\n\nexport default ConnectionMonitor\n", "export default {\n \"message_types\": {\n \"welcome\": \"welcome\",\n \"disconnect\": \"disconnect\",\n \"ping\": \"ping\",\n \"confirmation\": \"confirm_subscription\",\n \"rejection\": \"reject_subscription\"\n },\n \"disconnect_reasons\": {\n \"unauthorized\": \"unauthorized\",\n \"invalid_request\": \"invalid_request\",\n \"server_restart\": \"server_restart\",\n \"remote\": \"remote\"\n },\n \"default_mount_path\": \"/cable\",\n \"protocols\": [\n \"actioncable-v1-json\",\n \"actioncable-unsupported\"\n ]\n}\n", "import adapters from \"./adapters\"\nimport ConnectionMonitor from \"./connection_monitor\"\nimport INTERNAL from \"./internal\"\nimport logger from \"./logger\"\n\n// Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.\n\nconst {message_types, protocols} = INTERNAL\nconst supportedProtocols = protocols.slice(0, protocols.length - 1)\n\nconst indexOf = [].indexOf\n\nclass Connection {\n constructor(consumer) {\n this.open = this.open.bind(this)\n this.consumer = consumer\n this.subscriptions = this.consumer.subscriptions\n this.monitor = new ConnectionMonitor(this)\n this.disconnected = true\n }\n\n send(data) {\n if (this.isOpen()) {\n this.webSocket.send(JSON.stringify(data))\n return true\n } else {\n return false\n }\n }\n\n open() {\n if (this.isActive()) {\n logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`)\n return false\n } else {\n const socketProtocols = [...protocols, ...this.consumer.subprotocols || []]\n logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`)\n if (this.webSocket) { this.uninstallEventHandlers() }\n this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols)\n this.installEventHandlers()\n this.monitor.start()\n return true\n }\n }\n\n close({allowReconnect} = {allowReconnect: true}) {\n if (!allowReconnect) { this.monitor.stop() }\n // Avoid closing websockets in a \"connecting\" state due to Safari 15.1+ bug. See: https://github.com/rails/rails/issues/43835#issuecomment-1002288478\n if (this.isOpen()) {\n return this.webSocket.close()\n }\n }\n\n reopen() {\n logger.log(`Reopening WebSocket, current state is ${this.getState()}`)\n if (this.isActive()) {\n try {\n return this.close()\n } catch (error) {\n logger.log(\"Failed to reopen WebSocket\", error)\n }\n finally {\n logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`)\n setTimeout(this.open, this.constructor.reopenDelay)\n }\n } else {\n return this.open()\n }\n }\n\n getProtocol() {\n if (this.webSocket) {\n return this.webSocket.protocol\n }\n }\n\n isOpen() {\n return this.isState(\"open\")\n }\n\n isActive() {\n return this.isState(\"open\", \"connecting\")\n }\n\n triedToReconnect() {\n return this.monitor.reconnectAttempts > 0\n }\n\n // Private\n\n isProtocolSupported() {\n return indexOf.call(supportedProtocols, this.getProtocol()) >= 0\n }\n\n isState(...states) {\n return indexOf.call(states, this.getState()) >= 0\n }\n\n getState() {\n if (this.webSocket) {\n for (let state in adapters.WebSocket) {\n if (adapters.WebSocket[state] === this.webSocket.readyState) {\n return state.toLowerCase()\n }\n }\n }\n return null\n }\n\n installEventHandlers() {\n for (let eventName in this.events) {\n const handler = this.events[eventName].bind(this)\n this.webSocket[`on${eventName}`] = handler\n }\n }\n\n uninstallEventHandlers() {\n for (let eventName in this.events) {\n this.webSocket[`on${eventName}`] = function() {}\n }\n }\n\n}\n\nConnection.reopenDelay = 500\n\nConnection.prototype.events = {\n message(event) {\n if (!this.isProtocolSupported()) { return }\n const {identifier, message, reason, reconnect, type} = JSON.parse(event.data)\n switch (type) {\n case message_types.welcome:\n if (this.triedToReconnect()) {\n this.reconnectAttempted = true\n }\n this.monitor.recordConnect()\n return this.subscriptions.reload()\n case message_types.disconnect:\n logger.log(`Disconnecting. Reason: ${reason}`)\n return this.close({allowReconnect: reconnect})\n case message_types.ping:\n return this.monitor.recordPing()\n case message_types.confirmation:\n this.subscriptions.confirmSubscription(identifier)\n if (this.reconnectAttempted) {\n this.reconnectAttempted = false\n return this.subscriptions.notify(identifier, \"connected\", {reconnected: true})\n } else {\n return this.subscriptions.notify(identifier, \"connected\", {reconnected: false})\n }\n case message_types.rejection:\n return this.subscriptions.reject(identifier)\n default:\n return this.subscriptions.notify(identifier, \"received\", message)\n }\n },\n\n open() {\n logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`)\n this.disconnected = false\n if (!this.isProtocolSupported()) {\n logger.log(\"Protocol is unsupported. Stopping monitor and disconnecting.\")\n return this.close({allowReconnect: false})\n }\n },\n\n close(event) {\n logger.log(\"WebSocket onclose event\")\n if (this.disconnected) { return }\n this.disconnected = true\n this.monitor.recordDisconnect()\n return this.subscriptions.notifyAll(\"disconnected\", {willAttemptReconnect: this.monitor.isRunning()})\n },\n\n error() {\n logger.log(\"WebSocket onerror event\")\n }\n}\n\nexport default Connection\n", "// A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.\n// It provides a number of callbacks and a method for calling remote procedure calls on the corresponding\n// Channel instance on the server side.\n//\n// An example demonstrates the basic functionality:\n//\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\", {\n// connected() {\n// // Called once the subscription has been successfully completed\n// },\n//\n// disconnected({ willAttemptReconnect: boolean }) {\n// // Called when the client has disconnected with the server.\n// // The object will have an `willAttemptReconnect` property which\n// // says whether the client has the intention of attempting\n// // to reconnect.\n// },\n//\n// appear() {\n// this.perform('appear', {appearing_on: this.appearingOn()})\n// },\n//\n// away() {\n// this.perform('away')\n// },\n//\n// appearingOn() {\n// $('main').data('appearing-on')\n// }\n// })\n//\n// The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server\n// by calling the `perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away).\n// The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter.\n//\n// This is how the server component would look:\n//\n// class AppearanceChannel < ApplicationActionCable::Channel\n// def subscribed\n// current_user.appear\n// end\n//\n// def unsubscribed\n// current_user.disappear\n// end\n//\n// def appear(data)\n// current_user.appear on: data['appearing_on']\n// end\n//\n// def away\n// current_user.away\n// end\n// end\n//\n// The \"AppearanceChannel\" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.\n// The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the perform method.\n\nconst extend = function(object, properties) {\n if (properties != null) {\n for (let key in properties) {\n const value = properties[key]\n object[key] = value\n }\n }\n return object\n}\n\nexport default class Subscription {\n constructor(consumer, params = {}, mixin) {\n this.consumer = consumer\n this.identifier = JSON.stringify(params)\n extend(this, mixin)\n }\n\n // Perform a channel action with the optional data passed as an attribute\n perform(action, data = {}) {\n data.action = action\n return this.send(data)\n }\n\n send(data) {\n return this.consumer.send({command: \"message\", identifier: this.identifier, data: JSON.stringify(data)})\n }\n\n unsubscribe() {\n return this.consumer.subscriptions.remove(this)\n }\n}\n", "import logger from \"./logger\"\n\n// Responsible for ensuring channel subscribe command is confirmed, retrying until confirmation is received.\n// Internal class, not intended for direct user manipulation.\n\nclass SubscriptionGuarantor {\n constructor(subscriptions) {\n this.subscriptions = subscriptions\n this.pendingSubscriptions = []\n }\n\n guarantee(subscription) {\n if(this.pendingSubscriptions.indexOf(subscription) == -1){ \n logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`)\n this.pendingSubscriptions.push(subscription) \n }\n else {\n logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`)\n }\n this.startGuaranteeing()\n }\n\n forget(subscription) {\n logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`)\n this.pendingSubscriptions = (this.pendingSubscriptions.filter((s) => s !== subscription))\n }\n\n startGuaranteeing() {\n this.stopGuaranteeing()\n this.retrySubscribing()\n }\n \n stopGuaranteeing() {\n clearTimeout(this.retryTimeout)\n }\n\n retrySubscribing() {\n this.retryTimeout = setTimeout(() => {\n if (this.subscriptions && typeof(this.subscriptions.subscribe) === \"function\") {\n this.pendingSubscriptions.map((subscription) => {\n logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`)\n this.subscriptions.subscribe(subscription)\n })\n }\n }\n , 500)\n }\n}\n\nexport default SubscriptionGuarantor", "import Subscription from \"./subscription\"\nimport SubscriptionGuarantor from \"./subscription_guarantor\"\nimport logger from \"./logger\"\n\n// Collection class for creating (and internally managing) channel subscriptions.\n// The only method intended to be triggered by the user is ActionCable.Subscriptions#create,\n// and it should be called through the consumer like so:\n//\n// App = {}\n// App.cable = ActionCable.createConsumer(\"ws://example.com/accounts/1\")\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\")\n//\n// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.\n\nexport default class Subscriptions {\n constructor(consumer) {\n this.consumer = consumer\n this.guarantor = new SubscriptionGuarantor(this)\n this.subscriptions = []\n }\n\n create(channelName, mixin) {\n const channel = channelName\n const params = typeof channel === \"object\" ? channel : {channel}\n const subscription = new Subscription(this.consumer, params, mixin)\n return this.add(subscription)\n }\n\n // Private\n\n add(subscription) {\n this.subscriptions.push(subscription)\n this.consumer.ensureActiveConnection()\n this.notify(subscription, \"initialized\")\n this.subscribe(subscription)\n return subscription\n }\n\n remove(subscription) {\n this.forget(subscription)\n if (!this.findAll(subscription.identifier).length) {\n this.sendCommand(subscription, \"unsubscribe\")\n }\n return subscription\n }\n\n reject(identifier) {\n return this.findAll(identifier).map((subscription) => {\n this.forget(subscription)\n this.notify(subscription, \"rejected\")\n return subscription\n })\n }\n\n forget(subscription) {\n this.guarantor.forget(subscription)\n this.subscriptions = (this.subscriptions.filter((s) => s !== subscription))\n return subscription\n }\n\n findAll(identifier) {\n return this.subscriptions.filter((s) => s.identifier === identifier)\n }\n\n reload() {\n return this.subscriptions.map((subscription) =>\n this.subscribe(subscription))\n }\n\n notifyAll(callbackName, ...args) {\n return this.subscriptions.map((subscription) =>\n this.notify(subscription, callbackName, ...args))\n }\n\n notify(subscription, callbackName, ...args) {\n let subscriptions\n if (typeof subscription === \"string\") {\n subscriptions = this.findAll(subscription)\n } else {\n subscriptions = [subscription]\n }\n\n return subscriptions.map((subscription) =>\n (typeof subscription[callbackName] === \"function\" ? subscription[callbackName](...args) : undefined))\n }\n\n subscribe(subscription) {\n if (this.sendCommand(subscription, \"subscribe\")) {\n this.guarantor.guarantee(subscription)\n }\n }\n\n confirmSubscription(identifier) {\n logger.log(`Subscription confirmed ${identifier}`)\n this.findAll(identifier).map((subscription) =>\n this.guarantor.forget(subscription))\n }\n\n sendCommand(subscription, command) {\n const {identifier} = subscription\n return this.consumer.send({command, identifier})\n }\n}\n", "import Connection from \"./connection\"\nimport Subscriptions from \"./subscriptions\"\n\n// The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,\n// the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.\n// The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription\n// method.\n//\n// The following example shows how this can be set up:\n//\n// App = {}\n// App.cable = ActionCable.createConsumer(\"ws://example.com/accounts/1\")\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\")\n//\n// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.\n//\n// When a consumer is created, it automatically connects with the server.\n//\n// To disconnect from the server, call\n//\n// App.cable.disconnect()\n//\n// and to restart the connection:\n//\n// App.cable.connect()\n//\n// Any channel subscriptions which existed prior to disconnecting will\n// automatically resubscribe.\n\nexport default class Consumer {\n constructor(url) {\n this._url = url\n this.subscriptions = new Subscriptions(this)\n this.connection = new Connection(this)\n this.subprotocols = []\n }\n\n get url() {\n return createWebSocketURL(this._url)\n }\n\n send(data) {\n return this.connection.send(data)\n }\n\n connect() {\n return this.connection.open()\n }\n\n disconnect() {\n return this.connection.close({allowReconnect: false})\n }\n\n ensureActiveConnection() {\n if (!this.connection.isActive()) {\n return this.connection.open()\n }\n }\n\n addSubProtocol(subprotocol) {\n this.subprotocols = [...this.subprotocols, subprotocol]\n }\n}\n\nexport function createWebSocketURL(url) {\n if (typeof url === \"function\") {\n url = url()\n }\n\n if (url && !/^wss?:/i.test(url)) {\n const a = document.createElement(\"a\")\n a.href = url\n // Fix populating Location properties in IE. Otherwise, protocol will be blank.\n a.href = a.href\n a.protocol = a.protocol.replace(\"http\", \"ws\")\n return a.href\n } else {\n return url\n }\n}\n", "import Connection from \"./connection\"\nimport ConnectionMonitor from \"./connection_monitor\"\nimport Consumer, { createWebSocketURL } from \"./consumer\"\nimport INTERNAL from \"./internal\"\nimport Subscription from \"./subscription\"\nimport Subscriptions from \"./subscriptions\"\nimport SubscriptionGuarantor from \"./subscription_guarantor\"\nimport adapters from \"./adapters\"\nimport logger from \"./logger\"\n\nexport {\n Connection,\n ConnectionMonitor,\n Consumer,\n INTERNAL,\n Subscription,\n Subscriptions,\n SubscriptionGuarantor,\n adapters,\n createWebSocketURL,\n logger,\n}\n\nexport function createConsumer(url = getConfig(\"url\") || INTERNAL.default_mount_path) {\n return new Consumer(url)\n}\n\nexport function getConfig(name) {\n const element = document.head.querySelector(`meta[name='action-cable-${name}']`)\n if (element) {\n return element.getAttribute(\"content\")\n }\n}\n", "'use strict';\n\nmodule.exports = (function(scope) {\n const { location, URL } = scope;\n if ([location, URL].some(api => !api)) {\n return function dynamicImportNotSupported(module) {\n return Promise.reject(new Error(`Failed to import: ${module}: dynamicImport is not supported`));\n };\n }\n scope.__twilioVideoImportedModules = {\n // Imported module map.\n };\n return function dynamicImport(module) {\n if (module in scope.__twilioVideoImportedModules) {\n return Promise.resolve(scope.__twilioVideoImportedModules[module]);\n }\n // NOTE(mmalavalli): Calling import() directly can cause build issues in TypeScript and Webpack\n // (and probably other frameworks). So, we create a Function that calls import() in its body.\n // eslint-disable-next-line no-new-func\n return new Function('scope', `return import('${new URL(module, location)}').then(m => scope.__twilioVideoImportedModules['${module}'] = m);`)(scope);\n };\n}(globalThis));\n", "/**\n * Copyright (c) 2013 Tim Perry\n * Licensed under the MIT license.\n *\n * Copied from https://github.com/pimterry/loglevel (1.7.0)\n * and modified to remove browser and AMD module support, while keeping CommonJS.\n * It was causing a conflict when this is bundled using CommonJS, and then loaded via RequireJS.\n * The proper way to fix this module is to have a build that outputs CommonJS and AMD separately\n * which needs to be submitted to the original module's repo.\n */\n\n/* istanbul ignore file */\n/* eslint-disable */\n// Slightly dubious tricks to cut down minimized file size\nvar noop = function() {};\nvar undefinedType = \"undefined\";\nvar isIE = (typeof window !== undefinedType) && (typeof window.navigator !== undefinedType) && (\n /Trident\\/|MSIE /.test(window.navigator.userAgent)\n);\n\nvar logMethods = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\"\n];\n\n// Cross-browser bind equivalent that works at least back to IE6\nfunction bindMethod(obj, methodName) {\n var method = obj[methodName];\n if (typeof method.bind === 'function') {\n return method.bind(obj);\n } else {\n try {\n return Function.prototype.bind.call(method, obj);\n } catch (e) {\n // Missing bind shim or IE8 + Modernizr, fallback to wrapping\n return function() {\n return Function.prototype.apply.apply(method, [obj, arguments]);\n };\n }\n }\n}\n\n// Trace() doesn't print the message in IE, so for that case we need to wrap it\nfunction traceForIE() {\n if (console.log) {\n if (console.log.apply) {\n console.log.apply(console, arguments);\n } else {\n // In old IE, native console methods themselves don't have apply().\n Function.prototype.apply.apply(console.log, [console, arguments]);\n }\n }\n if (console.trace) console.trace();\n}\n\n// Build the best logging method possible for this env\n// Wherever possible we want to bind, not wrap, to preserve stack traces\nfunction realMethod(methodName) {\n if (methodName === 'debug') {\n methodName = 'log';\n }\n\n if (typeof console === undefinedType) {\n return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives\n } else if (methodName === 'trace' && isIE) {\n return traceForIE;\n } else if (console[methodName] !== undefined) {\n return bindMethod(console, methodName);\n } else if (console.log !== undefined) {\n return bindMethod(console, 'log');\n } else {\n return noop;\n }\n}\n\n// These private functions always need `this` to be set properly\n\nfunction replaceLoggingMethods(level, loggerName) {\n /*jshint validthis:true */\n for (var i = 0; i < logMethods.length; i++) {\n var methodName = logMethods[i];\n this[methodName] = (i < level) ?\n noop :\n this.methodFactory(methodName, level, loggerName);\n }\n\n // Define log.log as an alias for log.debug\n this.log = this.debug;\n}\n\n// In old IE versions, the console isn't present until you first open it.\n// We build realMethod() replacements here that regenerate logging methods\nfunction enableLoggingWhenConsoleArrives(methodName, level, loggerName) {\n return function () {\n if (typeof console !== undefinedType) {\n replaceLoggingMethods.call(this, level, loggerName);\n this[methodName].apply(this, arguments);\n }\n };\n}\n\n// By default, we use closely bound real methods wherever possible, and\n// otherwise we wait for a console to appear, and then try again.\nfunction defaultMethodFactory(methodName, level, loggerName) {\n /*jshint validthis:true */\n return realMethod(methodName) ||\n enableLoggingWhenConsoleArrives.apply(this, arguments);\n}\n\nfunction Logger(name, defaultLevel, factory) {\n var self = this;\n var currentLevel;\n\n var storageKey = \"loglevel\";\n if (typeof name === \"string\") {\n storageKey += \":\" + name;\n } else if (typeof name === \"symbol\") {\n storageKey = undefined;\n }\n\n function persistLevelIfPossible(levelNum) {\n var levelName = (logMethods[levelNum] || 'silent').toUpperCase();\n\n if (typeof window === undefinedType || !storageKey) return;\n\n // Use localStorage if available\n try {\n window.localStorage[storageKey] = levelName;\n return;\n } catch (ignore) {}\n\n // Use session cookie as fallback\n try {\n window.document.cookie =\n encodeURIComponent(storageKey) + \"=\" + levelName + \";\";\n } catch (ignore) {}\n }\n\n function getPersistedLevel() {\n var storedLevel;\n\n if (typeof window === undefinedType || !storageKey) return;\n\n try {\n storedLevel = window.localStorage[storageKey];\n } catch (ignore) {}\n\n // Fallback to cookies if local storage gives us nothing\n if (typeof storedLevel === undefinedType) {\n try {\n var cookie = window.document.cookie;\n var location = cookie.indexOf(\n encodeURIComponent(storageKey) + \"=\");\n if (location !== -1) {\n storedLevel = /^([^;]+)/.exec(cookie.slice(location))[1];\n }\n } catch (ignore) {}\n }\n\n // If the stored level is not valid, treat it as if nothing was stored.\n if (self.levels[storedLevel] === undefined) {\n storedLevel = undefined;\n }\n\n return storedLevel;\n }\n\n /*\n *\n * Public logger API - see https://github.com/pimterry/loglevel for details\n *\n */\n\n self.name = name;\n\n self.levels = { \"TRACE\": 0, \"DEBUG\": 1, \"INFO\": 2, \"WARN\": 3,\n \"ERROR\": 4, \"SILENT\": 5};\n\n self.methodFactory = factory || defaultMethodFactory;\n\n self.getLevel = function () {\n return currentLevel;\n };\n\n self.setLevel = function (level, persist) {\n if (typeof level === \"string\" && self.levels[level.toUpperCase()] !== undefined) {\n level = self.levels[level.toUpperCase()];\n }\n if (typeof level === \"number\" && level >= 0 && level <= self.levels.SILENT) {\n currentLevel = level;\n if (persist !== false) { // defaults to true\n persistLevelIfPossible(level);\n }\n replaceLoggingMethods.call(self, level, name);\n if (typeof console === undefinedType && level < self.levels.SILENT) {\n return \"No console available for logging\";\n }\n } else {\n throw \"log.setLevel() called with invalid level: \" + level;\n }\n };\n\n self.setDefaultLevel = function (level) {\n if (!getPersistedLevel()) {\n self.setLevel(level, false);\n }\n };\n\n self.enableAll = function(persist) {\n self.setLevel(self.levels.TRACE, persist);\n };\n\n self.disableAll = function(persist) {\n self.setLevel(self.levels.SILENT, persist);\n };\n\n // Initialize with the right level\n var initialLevel = getPersistedLevel();\n if (initialLevel == null) {\n initialLevel = defaultLevel == null ? \"WARN\" : defaultLevel;\n }\n self.setLevel(initialLevel, false);\n}\n\n/*\n *\n * Top-level API\n *\n */\n\nvar defaultLogger = new Logger();\n\nvar _loggersByName = {};\ndefaultLogger.getLogger = function getLogger(name) {\n if ((typeof name !== \"symbol\" && typeof name !== \"string\") || name === \"\") {\n throw new TypeError(\"You must supply a name when creating a logger.\");\n }\n\n var logger = _loggersByName[name];\n if (!logger) {\n logger = _loggersByName[name] = new Logger(\n name, defaultLogger.getLevel(), defaultLogger.methodFactory);\n }\n return logger;\n};\n\n// Grab the current global log variable in case of overwrite\nvar _log = (typeof window !== undefinedType) ? window.log : undefined;\ndefaultLogger.noConflict = function() {\n if (typeof window !== undefinedType &&\n window.log === defaultLogger) {\n window.log = _log;\n }\n\n return defaultLogger;\n};\n\ndefaultLogger.getLoggers = function getLoggers() {\n return _loggersByName;\n};\n\n// ES6 default export, for compatibility\ndefaultLogger['default'] = defaultLogger;\n\nmodule.exports = defaultLogger;\n", "{\n \"name\": \"twilio-video\",\n \"title\": \"Twilio Video\",\n \"description\": \"Twilio Video JavaScript Library\",\n \"version\": \"2.29.0\",\n \"homepage\": \"https://twilio.com\",\n \"author\": \"Mark Andrus Roberts \",\n \"contributors\": [\n \"Ryan Rowland \",\n \"Manjesh Malavalli \",\n \"Makarand Patwardhan \"\n ],\n \"keywords\": [\n \"twilio\",\n \"webrtc\",\n \"library\",\n \"javascript\",\n \"video\",\n \"rooms\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/twilio/twilio-video.js.git\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"^7.14.2\",\n \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n \"@babel/plugin-proposal-object-rest-spread\": \"^7.20.7\",\n \"@babel/preset-env\": \"^7.14.2\",\n \"@babel/preset-typescript\": \"^7.13.0\",\n \"@types/express\": \"^4.11.0\",\n \"@types/node\": \"^8.5.1\",\n \"@types/selenium-webdriver\": \"^3.0.8\",\n \"@types/ws\": \"^3.2.1\",\n \"@typescript-eslint/eslint-plugin\": \"^4.13.0\",\n \"@typescript-eslint/parser\": \"^4.0.0\",\n \"babel-cli\": \"^6.26.0\",\n \"babel-preset-es2015\": \"^6.24.1\",\n \"browserify\": \"^17.0.0\",\n \"cheerio\": \"^0.22.0\",\n \"cors\": \"^2.8.5\",\n \"electron\": \"^17.2.0\",\n \"envify\": \"^4.0.0\",\n \"eslint\": \"^6.2.1\",\n \"eslint-config-standard\": \"^14.0.0\",\n \"eslint-plugin-import\": \"^2.18.2\",\n \"eslint-plugin-node\": \"^9.1.0\",\n \"eslint-plugin-promise\": \"^4.2.1\",\n \"eslint-plugin-standard\": \"^4.0.1\",\n \"express\": \"^4.16.2\",\n \"glob\": \"^7.1.7\",\n \"ink-docstrap\": \"^1.3.2\",\n \"inquirer\": \"^7.0.0\",\n \"is-docker\": \"^2.0.0\",\n \"jsdoc\": \"^3.5.5\",\n \"jsdoc-babel\": \"^0.5.0\",\n \"json-loader\": \"^0.5.7\",\n \"karma\": \"6.4.1\",\n \"karma-browserify\": \"^8.0.0\",\n \"karma-chrome-launcher\": \"^2.0.0\",\n \"karma-edgium-launcher\": \"^4.0.0-0\",\n \"karma-electron\": \"^6.1.0\",\n \"karma-firefox-launcher\": \"^1.3.0\",\n \"karma-htmlfile-reporter\": \"^0.3.8\",\n \"karma-junit-reporter\": \"^1.2.0\",\n \"karma-mocha\": \"^1.3.0\",\n \"karma-safari-launcher\": \"^1.0.0\",\n \"karma-spec-reporter\": \"0.0.32\",\n \"karma-typescript\": \"^5.5.1\",\n \"karma-typescript-es6-transform\": \"^5.5.1\",\n \"mocha\": \"^3.2.0\",\n \"mock-require\": \"^3.0.3\",\n \"ncp\": \"^2.0.0\",\n \"node-http-server\": \"^8.1.2\",\n \"npm-run-all\": \"^4.0.2\",\n \"nyc\": \"^15.1.0\",\n \"regex-replace\": \"^2.3.1\",\n \"requirejs\": \"^2.3.3\",\n \"rimraf\": \"^2.6.1\",\n \"simple-git\": \"^1.126.0\",\n \"sinon\": \"^4.0.1\",\n \"ts-node\": \"4.0.1\",\n \"tslint\": \"5.8.0\",\n \"twilio\": \"^3.49.0\",\n \"twilio-release-tool\": \"^1.0.2\",\n \"typescript\": \"4.2.2\",\n \"uglify-js\": \"^2.8.22\",\n \"vinyl-fs\": \"^2.4.4\",\n \"vinyl-source-stream\": \"^1.1.0\",\n \"watchify\": \"^3.11.1\",\n \"webrtc-adapter\": \"^7.7.1\"\n },\n \"engines\": {\n \"node\": \">=0.12\"\n },\n \"license\": \"BSD-3-Clause\",\n \"main\": \"./es5/index.js\",\n \"types\": \"./tsdef/index.d.ts\",\n \"scripts\": {\n \"lint:js\": \"eslint ./lib ./test/*.js ./docker/**/*.js ./test/framework/*.js ./test/lib/*.js ./test/integration/** ./test/unit/** \",\n \"lint:ts\": \"eslint ./tsdef/*.ts ./lib/**/*.ts\",\n \"lint\": \"npm-run-all lint:js lint:ts\",\n \"patch-linkifyit-111\": \"regex-replace \\\"readonly linkify: LinkifyIt.LinkifyIt;\\\" \\\"readonly linkify: typeof LinkifyIt;\\\" \\\"node_modules/@types/markdown-it/lib/index.d.ts\\\" --filecontents\",\n \"printVersion\": \"node --version && npm --version\",\n \"test:unit\": \"npm-run-all printVersion build:es5 && nyc --report-dir=./coverage --include=lib/**/* --reporter=html --reporter=lcov --reporter=text mocha -r ts-node/register ./test/unit/*\",\n \"test:unit:quick\": \"nyc --report-dir=./coverage --include=lib/**/* --reporter=html --reporter=lcov mocha -r ts-node/register\",\n \"test:serversiderender\": \"mocha ./test/serversiderender/index.js\",\n \"test:integration:adapter\": \"node ./scripts/karma.js karma/integration.adapter.conf.js\",\n \"test:integration\": \"npm run build:es5 && node ./scripts/karma.js karma/integration.conf.js\",\n \"test:umd:install\": \"npm install puppeteer@19.2.2\",\n \"test:umd\": \"mocha ./test/umd/index.js\",\n \"test:crossbrowser:build:clean\": \"rimraf ./test/crossbrowser/lib ./test/crossbrowser/src/browser/index.js\",\n \"test:crossbrowser:build:lint\": \"cd ./test/crossbrowser && tslint --project tsconfig.json\",\n \"test:crossbrowser:build:tsc\": \"cd ./test/crossbrowser && tsc\",\n \"test:crossbrowser:build:browser\": \"cd ./test/crossbrowser && browserify lib/crossbrowser/src/browser/index.js > src/browser/index.js\",\n \"test:crossbrowser:build\": \"npm-run-all test:crossbrowser:build:*\",\n \"test:crossbrowser:test\": \"cd ./test/crossbrowser && mocha --compilers ts:ts-node/register test/integration/spec/**/*.ts\",\n \"test:crossbrowser\": \"npm-run-all test:crossbrowser:*\",\n \"test:sdkdriver:build:clean\": \"rimraf ./test/lib/sdkdriver/lib ./test/lib/sdkdriver/test/integration/browser/index.js\",\n \"test:sdkdriver:build:lint\": \"cd ./test/lib/sdkdriver && tslint --project tsconfig.json\",\n \"test:sdkdriver:build:tsc\": \"cd ./test/lib/sdkdriver && tsc --rootDir src\",\n \"test:sdkdriver:build\": \"npm-run-all test:sdkdriver:build:*\",\n \"test:sdkdriver:test:unit\": \"cd ./test/lib/sdkdriver && mocha --compilers ts:ts-node/register test/unit/spec/**/*.ts\",\n \"test:sdkdriver:test:integration:browser\": \"cd ./test/lib/sdkdriver/test/integration && browserify browser/browser.js > browser/index.js\",\n \"test:sdkdriver:test:integration:run\": \"cd ./test/lib/sdkdriver && mocha --compilers ts:ts-node/register test/integration/spec/**/*.ts\",\n \"test:sdkdriver:test:integration\": \"npm-run-all test:sdkdriver:test:integration:*\",\n \"test:sdkdriver:test\": \"npm-run-all test:sdkdriver:test:*\",\n \"test:sdkdriver\": \"npm-run-all test:sdkdriver:*\",\n \"test:framework:angular:install\": \"cd ./test/framework/twilio-video-angular && rimraf ./node_modules package-lock.json && npm install\",\n \"test:framework:angular:run\": \"mocha ./test/framework/twilio-video-angular.js\",\n \"test:framework:angular\": \"npm-run-all test:framework:angular:*\",\n \"test:framework:no-framework:run\": \"mocha ./test/framework/twilio-video-no-framework.js\",\n \"test:framework:no-framework\": \"npm-run-all test:framework:no-framework:*\",\n \"test:framework:react:install\": \"cd ./test/framework/twilio-video-react && rimraf ./node_modules package-lock.json && npm install\",\n \"test:framework:react:test\": \"node ./scripts/framework.js twilio-video-react\",\n \"test:framework:react:build\": \"cd ./test/framework/twilio-video-react && npm run build\",\n \"test:framework:react:run\": \"mocha ./test/framework/twilio-video-react.js\",\n \"test:framework:react\": \"npm-run-all test:framework:react:*\",\n \"test:framework:install\": \"npm install chromedriver && npm install selenium-webdriver && npm install geckodriver && npm install puppeteer\",\n \"test:framework\": \"npm-run-all test:framework:install test:framework:no-framework test:framework:react\",\n \"test\": \"npm-run-all test:unit test:integration\",\n \"build:es5\": \"rimraf ./es5 && mkdir -p es5 && npm run patch-linkifyit-111 && tsc tsdef/twilio-video-tests.ts --noEmit --lib es2018,dom && tsc\",\n \"build:js\": \"node ./scripts/build.js ./src/twilio-video.js ./LICENSE.md ./dist/twilio-video.js\",\n \"build:min.js\": \"uglifyjs ./dist/twilio-video.js -o ./dist/twilio-video.min.js --comments \\\"/^! twilio-video.js/\\\" -b beautify=false,ascii_only=true\",\n \"build\": \"npm-run-all clean lint docs test:unit test:integration build:es5 build:js build:min.js test:umd\",\n \"build:quick\": \"npm-run-all clean lint docs build:es5 build:js build:min.js\",\n \"docs\": \"node ./scripts/docs.js ./dist/docs\",\n \"watch\": \"tsc -w\",\n \"clean\": \"rimraf ./coverage ./es5 ./dist\"\n },\n \"dependencies\": {\n \"events\": \"^3.3.0\",\n \"util\": \"^0.12.4\",\n \"ws\": \"^7.4.6\",\n \"xmlhttprequest\": \"^1.8.0\"\n },\n \"browser\": {\n \"ws\": \"./src/ws.js\",\n \"xmlhttprequest\": \"./src/xmlhttprequest.js\"\n }\n}\n", "'use strict';\n/* eslint-disable camelcase */\nconst packageInfo = require('../../package.json');\nmodule.exports.SDK_NAME = `${packageInfo.name}.js`;\nmodule.exports.SDK_VERSION = packageInfo.version;\nmodule.exports.SDP_FORMAT = 'unified';\nmodule.exports.hardwareDevicePublisheriPad = {\n hwDeviceManufacturer: 'Apple',\n hwDeviceModel: 'iPad',\n hwDeviceType: 'tablet',\n platformName: 'iOS'\n};\n\nmodule.exports.hardwareDevicePublisheriPhone = {\n hwDeviceManufacturer: 'Apple',\n hwDeviceModel: 'iPhone',\n hwDeviceType: 'mobile',\n platformName: 'iOS'\n};\n\nmodule.exports.DEFAULT_ENVIRONMENT = 'prod';\nmodule.exports.DEFAULT_REALM = 'us1';\nmodule.exports.DEFAULT_REGION = 'gll';\nmodule.exports.DEFAULT_LOG_LEVEL = 'warn';\nmodule.exports.DEFAULT_LOGGER_NAME = 'twilio-video';\nmodule.exports.WS_SERVER = (environment, region) => {\n region = region === 'gll' ? 'global' : encodeURIComponent(region);\n return environment === 'prod'\n ? `wss://${region}.vss.twilio.com/signaling`\n : `wss://${region}.vss.${environment}.twilio.com/signaling`;\n};\nmodule.exports.PUBLISH_MAX_ATTEMPTS = 5;\nmodule.exports.PUBLISH_BACKOFF_JITTER = 10;\nmodule.exports.PUBLISH_BACKOFF_MS = 20;\n\n/**\n * Returns the appropriate indefinite article (\"a\" | \"an\").\n * @param {string} word - The word which determines whether \"a\" | \"an\" is returned\n * @returns {string} \"a\" if word's first letter is a vowel, \"an\" otherwise\n */\nfunction article(word) {\n // NOTE(mmalavalli): This will not be accurate for words like \"hour\",\n // which have consonants as their first character, but are pronounced like\n // vowels. We can address this issue if the need arises.\n return ['a', 'e', 'i', 'o', 'u'].includes(word.toLowerCase()[0]) ? 'an' : 'a';\n}\n\nmodule.exports.typeErrors = {\n ILLEGAL_INVOKE(name, context) {\n return new TypeError(`Illegal call to ${name}: ${context}`);\n },\n INVALID_TYPE(name, type) {\n return new TypeError(`${name} must be ${article(type)} ${type}`);\n },\n INVALID_VALUE(name, values) {\n return new RangeError(`${name} must be one of ${values.join(', ')}`);\n },\n REQUIRED_ARGUMENT(name) {\n return new TypeError(`${name} must be specified`);\n }\n};\n\nmodule.exports.DEFAULT_FRAME_RATE = 24;\nmodule.exports.DEFAULT_VIDEO_PROCESSOR_STATS_INTERVAL_MS = 10000;\n\nmodule.exports.DEFAULT_ICE_GATHERING_TIMEOUT_MS = 15000;\nmodule.exports.DEFAULT_SESSION_TIMEOUT_SEC = 30;\n\nmodule.exports.DEFAULT_NQ_LEVEL_LOCAL = 1;\nmodule.exports.DEFAULT_NQ_LEVEL_REMOTE = 0;\nmodule.exports.MAX_NQ_LEVEL = 3;\n\nmodule.exports.ICE_ACTIVITY_CHECK_PERIOD_MS = 1000;\nmodule.exports.ICE_INACTIVITY_THRESHOLD_MS = 3000;\n\nmodule.exports.iceRestartBackoffConfig = {\n factor: 1.1,\n min: 1,\n max: module.exports.DEFAULT_SESSION_TIMEOUT_SEC * 1000,\n jitter: 1\n};\n\nmodule.exports.reconnectBackoffConfig = {\n factor: 1.5,\n min: 80,\n jitter: 1\n};\n\nmodule.exports.subscriptionMode = {\n MODE_COLLABORATION: 'collaboration',\n MODE_GRID: 'grid',\n MODE_PRESENTATION: 'presentation'\n};\n\nmodule.exports.trackSwitchOffMode = {\n MODE_DISABLED: 'disabled',\n MODE_DETECTED: 'detected',\n MODE_PREDICTED: 'predicted'\n};\n\nmodule.exports.trackPriority = {\n PRIORITY_HIGH: 'high',\n PRIORITY_LOW: 'low',\n PRIORITY_STANDARD: 'standard'\n};\n\nmodule.exports.clientTrackSwitchOffControl = {\n MODE_AUTO: 'auto',\n MODE_MANUAL: 'manual'\n};\n\nmodule.exports.videoContentPreferencesMode = {\n MODE_AUTO: 'auto',\n MODE_MANUAL: 'manual'\n};\n", "/* eslint new-cap:0 */\n'use strict';\n\nconst defaultGetLogger = require('../vendor/loglevel').getLogger;\nconst constants = require('./constants');\nconst { DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME } = constants;\nconst E = require('./constants').typeErrors;\n\nlet deprecationWarningsByComponentConstructor;\n\nfunction getDeprecationWarnings(componentConstructor) {\n deprecationWarningsByComponentConstructor = deprecationWarningsByComponentConstructor || new Map();\n if (deprecationWarningsByComponentConstructor.has(componentConstructor)) {\n return deprecationWarningsByComponentConstructor.get(componentConstructor);\n }\n const deprecationWarnings = new Set();\n deprecationWarningsByComponentConstructor.set(componentConstructor, deprecationWarnings);\n return deprecationWarnings;\n}\n\n/**\n * Selectively outputs messages to console based on specified minimum module\n * specific log levels.\n *\n * NOTE: The values in the logLevels object passed to the constructor is changed\n * by subsequent calls to {@link Log#setLevels}.\n */\nclass Log {\n /**\n * Construct a new {@link Log} object.\n * @param {String} moduleName - Name of the logging module (webrtc/media/signaling)\n * @param {object} component - Component owning this instance of {@link Log}\n * @param {LogLevels} logLevels - Logging levels. See {@link LogLevels}\n * @param {String} loggerName - Name of the logger instance. Used when calling getLogger from loglevel module\n * @param {Function} [getLogger] - optional method used internally.\n */\n constructor(moduleName, component, logLevels, loggerName, getLogger) {\n if (typeof moduleName !== 'string') {\n throw E.INVALID_TYPE('moduleName', 'string');\n }\n\n if (!component) {\n throw E.REQUIRED_ARGUMENT('component');\n }\n\n if (typeof logLevels !== 'object') {\n logLevels = {};\n }\n\n getLogger = getLogger || defaultGetLogger;\n\n validateLogLevels(logLevels);\n\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _component: {\n value: component\n },\n _logLevels: {\n value: logLevels\n },\n _warnings: {\n value: new Set()\n },\n _loggerName: {\n get: function get() {\n let name = loggerName && typeof loggerName === 'string' ? loggerName : DEFAULT_LOGGER_NAME;\n\n if (!this._logLevelsEqual) {\n name = `${name}-${moduleName}`;\n }\n return name;\n }\n },\n _logger: {\n get: function get() {\n const logger = getLogger(this._loggerName);\n let level = this._logLevels[moduleName] || DEFAULT_LOG_LEVEL;\n\n // There is no 'off' in the logger module. It uses 'silent' instead\n level = level === 'off' ? 'silent' : level;\n\n logger.setDefaultLevel(level);\n return logger;\n }\n },\n _logLevelsEqual: {\n get: function get() {\n // True if all levels are the same\n return (new Set(Object.values(this._logLevels)).size) === 1;\n }\n },\n logLevel: {\n get: function get() {\n return Log.getLevelByName(logLevels[moduleName] || DEFAULT_LOG_LEVEL);\n }\n },\n name: { get: component.toString.bind(component) }\n });\n }\n\n /**\n * Get the log level (number) by its name (string)\n * @param {String} name - Name of the log level\n * @returns {Number} Requested log level\n * @throws {TwilioError} INVALID_LOG_LEVEL (32056)\n * @public\n */\n static getLevelByName(name) {\n if (!isNaN(name)) {\n return parseInt(name, 10);\n }\n name = name.toUpperCase();\n validateLogLevel(name);\n return Log[name];\n }\n\n /**\n * Create a child {@link Log} instance with this._logLevels\n * @param moduleName - Name of the logging module\n * @param component - Component owning this instance of {@link Log}\n * @returns {Log} this\n */\n createLog(moduleName, component) {\n let name = this._loggerName;\n // Grab the original logger name\n if (!this._logLevelsEqual) {\n name = name.substring(0, name.lastIndexOf('-'));\n }\n return new Log(moduleName, component, this._logLevels, name);\n }\n\n /**\n * Set new log levels.\n * This changes the levels for all its ancestors,\n * siblings, and children and descendants instances of {@link Log}.\n * @param {LogLevels} levels - New log levels\n * @throws {TwilioError} INVALID_ARGUMENT\n * @returns {Log} this\n */\n setLevels(levels) {\n validateLogLevels(levels);\n Object.assign(this._logLevels, levels);\n return this;\n }\n\n /**\n * Log a message using the logger method appropriate for the specified logLevel\n * @param {Number} logLevel - Log level of the message being logged\n * @param {Array} messages - Message(s) to log\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n log(logLevel, messages) {\n let name = Log._levels[logLevel];\n // eslint-disable-next-line no-use-before-define\n if (!name) { throw E.INVALID_VALUE('logLevel', LOG_LEVEL_VALUES); }\n\n name = name.toLowerCase();\n const prefix = [new Date().toISOString(), name, this.name];\n\n (this._logger[name] || function noop() {})(...prefix.concat(messages));\n\n return this;\n }\n\n /**\n * Log a debug message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n debug() {\n return this.log(Log.DEBUG, [].slice.call(arguments));\n }\n\n /**\n * Log a deprecation warning. Deprecation warnings are logged as warnings and\n * they are only ever logged once.\n * @param {String} deprecationWarning - The deprecation warning\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n deprecated(deprecationWarning) {\n const deprecationWarnings = getDeprecationWarnings(this._component.constructor);\n if (deprecationWarnings.has(deprecationWarning)) {\n return this;\n }\n deprecationWarnings.add(deprecationWarning);\n return this.warn(deprecationWarning);\n }\n\n /**\n * Log an info message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n info() {\n return this.log(Log.INFO, [].slice.call(arguments));\n }\n\n /**\n * Log a warn message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n warn() {\n return this.log(Log.WARN, [].slice.call(arguments));\n }\n\n /**\n * Log a warning once.\n * @param {String} warning\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n warnOnce(warning) {\n if (this._warnings.has(warning)) {\n return this;\n }\n this._warnings.add(warning);\n return this.warn(warning);\n }\n\n /**\n * Log an error message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n error() {\n return this.log(Log.ERROR, [].slice.call(arguments));\n }\n\n /**\n * Log an error message and throw an exception\n * @param {TwilioError} error - Error to throw\n * @param {String} customMessage - Custom message for the error\n * @public\n */\n throw(error, customMessage) {\n if (error.clone) {\n error = error.clone(customMessage);\n }\n\n this.log(Log.ERROR, error);\n throw error;\n }\n}\n\n// Singleton Constants\n/* eslint key-spacing:0 */\n/* istanbul ignore next */\nObject.defineProperties(Log, {\n DEBUG: { value: 0 },\n INFO: { value: 1 },\n WARN: { value: 2 },\n ERROR: { value: 3 },\n OFF: { value: 4 },\n _levels: {\n value: [\n 'DEBUG',\n 'INFO',\n 'WARN',\n 'ERROR',\n 'OFF',\n ]\n }\n});\n\nconst LOG_LEVELS_SET = {};\nconst LOG_LEVEL_VALUES = [];\n\nconst LOG_LEVEL_NAMES = Log._levels.map((level, i) => {\n LOG_LEVELS_SET[level] = true;\n LOG_LEVEL_VALUES.push(i);\n return level;\n});\n\nfunction validateLogLevel(level) {\n if (!(level in LOG_LEVELS_SET)) {\n throw E.INVALID_VALUE('level', LOG_LEVEL_NAMES);\n }\n}\n\nfunction validateLogLevels(levels) {\n Object.keys(levels).forEach(moduleName => {\n validateLogLevel(levels[moduleName].toUpperCase());\n });\n}\n\nmodule.exports = Log;\n", "'use strict';\n\nimport { AudioProcessor } from '../tsdef/AudioProcessor';\nimport { NoiseCancellationOptions } from '../tsdef/types';\n\nconst dynamicImport = require('./util/dynamicimport');\nconst Log = require('./util/log');\n\nconst PLUGIN_CONFIG = {\n krisp: {\n supportedVersion: '1.0.0',\n pluginFile: 'krispsdk.mjs'\n },\n rnnoise: {\n supportedVersion: '0.6.0',\n pluginFile: 'rnnoise_sdk.mjs'\n }\n};\n\n// AudioProcessor assumes following interface from the Plugin\ninterface NoiseCancellationPlugin {\n init(options: { rootDir: string }): Promise;\n isInitialized(): boolean;\n isConnected(): boolean;\n isEnabled(): boolean\n connect(input: MediaStream): MediaStream;\n disconnect(): void;\n enable(): void;\n disable(): void;\n destroy(): void;\n setLogging(enable: boolean):void;\n isSupported(audioContext: AudioContext):boolean;\n getVersion(): string;\n}\n\nconst ensureVersionSupported = ({ supportedVersion, plugin, log }: {supportedVersion: string, plugin: NoiseCancellationPlugin, log: typeof Log}) : void => {\n if (!plugin.getVersion || !plugin.isSupported) {\n throw new Error('Plugin does not export getVersion/isSupported api. Are you using old version of the plugin ?');\n }\n\n const pluginVersion = plugin.getVersion();\n log.debug(`Plugin Version = ${pluginVersion}`);\n const supportedVersions = supportedVersion.split('.').map(version => Number(version));\n const pluginVersions = pluginVersion.split('.').map(version => Number(version));\n if (supportedVersions.length !== 3 || pluginVersions.length !== 3) {\n throw new Error(`Unsupported Plugin version format: ${supportedVersion}, ${pluginVersion}`);\n }\n\n if (supportedVersions[0] !== pluginVersions[0]) {\n throw new Error(`Major version mismatch: [Plugin version ${pluginVersion}], [Supported Version ${supportedVersion}]`);\n }\n\n if (pluginVersions[1] < supportedVersions[1]) {\n throw new Error(`Minor version mismatch: [Plugin version ${pluginVersion}] < [Supported Version ${supportedVersion}]`);\n }\n\n const tempContext = new AudioContext();\n const isSupported = plugin.isSupported(tempContext);\n tempContext.close();\n if (!isSupported) {\n throw new Error('Noise Cancellation plugin is not supported on your browser');\n }\n};\n\nlet audioProcessors = new Map();\nexport async function createNoiseCancellationAudioProcessor(\n noiseCancellationOptions: NoiseCancellationOptions,\n log: typeof Log\n) : Promise {\n let audioProcessor = audioProcessors.get(noiseCancellationOptions.vendor);\n if (!audioProcessor) {\n let pluginConfig = PLUGIN_CONFIG[noiseCancellationOptions.vendor];\n if (!pluginConfig) {\n throw new Error(`Unsupported NoiseCancellationOptions.vendor: ${noiseCancellationOptions.vendor}`);\n }\n\n const { supportedVersion, pluginFile } = pluginConfig;\n const rootDir = noiseCancellationOptions.sdkAssetsPath;\n const sdkFilePath = `${rootDir}/${pluginFile}`;\n\n try {\n log.debug('loading noise cancellation sdk: ', sdkFilePath);\n const dynamicModule = await dynamicImport(sdkFilePath);\n log.debug('Loaded noise cancellation sdk:', dynamicModule);\n\n const plugin = dynamicModule.default as NoiseCancellationPlugin;\n ensureVersionSupported({\n supportedVersion,\n plugin,\n log\n });\n\n if (!plugin.isInitialized()) {\n log.debug('initializing noise cancellation sdk: ', rootDir);\n await plugin.init({ rootDir });\n log.debug('noise cancellation sdk initialized!');\n }\n\n audioProcessor = {\n vendor: noiseCancellationOptions.vendor,\n isInitialized: () => plugin.isInitialized(),\n isConnected: () => plugin.isConnected(),\n isEnabled: () => plugin.isEnabled(),\n disconnect: () => plugin.disconnect(),\n enable: () => plugin.enable(),\n disable: () => plugin.disable(),\n destroy: () => plugin.destroy(),\n setLogging: (enable: boolean) => plugin.setLogging(enable),\n connect: (sourceTrack: MediaStreamTrack) => {\n log.debug('connect: ', sourceTrack.id);\n if (plugin.isConnected()) {\n plugin.disconnect();\n }\n\n const mediaStream = plugin.connect(new MediaStream([sourceTrack]));\n if (!mediaStream) {\n throw new Error('Error connecting with noise cancellation sdk');\n }\n const cleanTrack = mediaStream.getAudioTracks()[0];\n if (!cleanTrack) {\n throw new Error('Error getting clean track from noise cancellation sdk');\n }\n plugin.enable();\n return cleanTrack;\n },\n };\n audioProcessors.set(noiseCancellationOptions.vendor, audioProcessor);\n\n } catch (er) {\n log.error(`Error loading noise cancellation sdk:${sdkFilePath}`, er);\n throw er;\n }\n }\n return audioProcessor;\n}\n", "'use strict';\n\nimport { NoiseCancellation, NoiseCancellationOptions, NoiseCancellationVendor } from '../../../tsdef/types';\nimport { AudioProcessor } from '../../../tsdef/AudioProcessor';\nimport { createNoiseCancellationAudioProcessor } from '../../noisecancellationadapter';\nconst Log = require('../../util/log');\n\n/**\n * {@link NoiseCancellation} interface provides methods to control noise cancellation at runtime. This interface is exposed\n * on {@link LocalAudioTrack} property `noiseCancellation`. It is available only when {@link NoiseCancellationOptions} are\n * specified when creating a {@link LocalAudioTrack}, and the plugin is successfully loaded.\n * @alias NoiseCancellation\n * @interface\n *\n * @example\n * const { connect, createLocalAudioTrack } = require('twilio-video');\n *\n * // Create a LocalAudioTrack with Krisp noise cancellation enabled.\n * const localAudioTrack = await createLocalAudioTrack({\n * noiseCancellationOptions: {\n * sdkAssetsPath: 'path/to/hosted/twilio/krisp/audio/plugin/1.0.0/dist',\n * vendor: 'krisp'\n * }\n * });\n *\n * if (!localAudioTrack.noiseCancellation) {\n * // If the Krisp audio plugin fails to load, then a warning message will be logged\n * // in the browser console, and the \"noiseCancellation\" property will be set to null.\n * // You can still use the LocalAudioTrack to join a Room. However, it will use the\n * // browser's noise suppression instead of the Krisp noise cancellation. Make sure\n * // the \"sdkAssetsPath\" provided in \"noiseCancellationOptions\" points to the correct\n * // hosted path of the plugin assets.\n * } else {\n * // Join a Room with the LocalAudioTrack.\n * const room = await connect('token', {\n * name: 'my-cool-room',\n * tracks: [localAudioTrack]\n * });\n *\n * if (!localAudioTrack.noiseCancellation.isEnabled) {\n * // Krisp noise cancellation is permanently disabled in Peer-to-Peer and Go Rooms.\n * }\n * }\n *\n * //\n * // Enable/disable noise cancellation.\n * // @param {boolean} enable - whether noise cancellation should be enabled\n * //\n * function setNoiseCancellation(enable) {\n * const { noiseCancellation } = localAudioTrack;\n * if (noiseCancellation) {\n * if (enable) {\n * // If enabled, then the LocalAudioTrack will use the Krisp noise\n * // cancellation instead of the browser's noise suppression.\n * noiseCancellation.enable();\n * } else {\n * // If disabled, then the LocalAudioTrack will use the browser's\n * // noise suppression instead of the Krisp noise cancellation.\n * noiseCancellation.disable();\n * }\n * }\n * }\n */\nexport class NoiseCancellationImpl implements NoiseCancellation {\n private _processor: AudioProcessor;\n private _sourceTrack: MediaStreamTrack;\n private _disabledPermanent: boolean;\n\n constructor(processor: AudioProcessor, originalTrack: MediaStreamTrack) {\n this._processor = processor;\n this._sourceTrack = originalTrack;\n this._disabledPermanent = false;\n }\n\n /**\n * Name of the noise cancellation vendor.\n * @type {NoiseCancellationVendor}\n */\n get vendor(): NoiseCancellationVendor {\n return this._processor.vendor;\n }\n\n /**\n * The underlying MediaStreamTrack of the {@link LocalAudioTrack}.\n * @type {MediaStreamTrack}\n */\n get sourceTrack(): MediaStreamTrack {\n return this._sourceTrack;\n }\n\n /**\n * Whether noise cancellation is enabled.\n * @type {boolean}\n */\n get isEnabled(): boolean {\n return this._processor.isEnabled();\n }\n\n /**\n * Enable noise cancellation.\n * @returns {Promise} Promise that resolves when the operation is complete\n * @throws {Error} Throws an error if noise cancellation is disabled permanently\n * for the {@link LocalAudioTrack}\n */\n enable() : Promise {\n if (this._disabledPermanent) {\n throw new Error(`${this.vendor} noise cancellation is disabled permanently for this track`);\n }\n\n this._processor.enable();\n return Promise.resolve();\n }\n\n /**\n * Disable noise cancellation.\n * @returns {Promise} Promise that resolves when the operation is complete\n */\n disable() : Promise {\n this._processor.disable();\n return Promise.resolve();\n }\n\n /**\n * @private\n */\n async reacquireTrack(reacquire: () => Promise) : Promise {\n const processorWasEnabled = this._processor.isEnabled();\n this._processor.disconnect();\n\n const track = await reacquire();\n this._sourceTrack = track;\n\n const processedTrack = await this._processor.connect(track);\n if (processorWasEnabled) {\n this._processor.enable();\n } else {\n this._processor.disable();\n }\n return processedTrack;\n }\n\n /**\n * @private\n */\n disablePermanently(): Promise {\n this._disabledPermanent = true;\n return this.disable();\n }\n\n\n /**\n * @private\n */\n stop(): void {\n this._processor.disconnect();\n this._sourceTrack.stop();\n }\n}\n\n\nexport async function applyNoiseCancellation(\n mediaStreamTrack: MediaStreamTrack,\n noiseCancellationOptions: NoiseCancellationOptions,\n log: typeof Log\n) : Promise<{ cleanTrack: MediaStreamTrack, noiseCancellation?: NoiseCancellation }> {\n try {\n const processor = await createNoiseCancellationAudioProcessor(noiseCancellationOptions, log);\n const cleanTrack = processor.connect(mediaStreamTrack);\n const noiseCancellation = new NoiseCancellationImpl(processor, mediaStreamTrack);\n return { cleanTrack, noiseCancellation };\n } catch (ex) {\n // in case of failures to load noise cancellation library just return original media stream.\n log.warn(`Failed to create noise cancellation. Returning normal audio track: ${ex}`);\n return { cleanTrack: mediaStreamTrack };\n }\n}\n", "'use strict';\n\n/**\n * Create a {@link Deferred}.\n * @returns {Deferred}\n */\nfunction defer() {\n const deferred = {};\n deferred.promise = new Promise((resolve, reject) => {\n deferred.resolve = resolve;\n deferred.reject = reject;\n });\n return deferred;\n}\n\n/**\n * Copy a method from a `source` prototype onto a `wrapper` prototype. Invoking\n * the method on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} methodName\n * @returns {undefined}\n */\nfunction delegateMethod(source, wrapper, target, methodName) {\n if (methodName in wrapper) {\n // Skip any methods already set.\n return;\n } else if (methodName.match(/^on[a-z]+$/)) {\n // Skip EventHandlers (these are handled in the constructor).\n return;\n }\n\n\n let isProperty = false;\n try {\n const propDesc = Object.getOwnPropertyDescriptor(source, methodName);\n isProperty = propDesc && !!propDesc.get;\n } catch (error) {\n // its okay to eat failure here.\n }\n\n // NOTE(mpatwardhan):skip properties. we are only interested in overriding\n // functions. we do not even want to evaluate `typeof source[methodName]` for properties\n // because getter would get invoked, and they might have side effects.\n // For example RTCPeerConnection.peerIdentity is a property that returns a promise.\n // calling typeof RTCPeerConnection.peerIdentity, would leak a promise, and in case it rejects\n // we see errors.\n if (isProperty) {\n return;\n }\n\n let type;\n try {\n type = typeof source[methodName];\n } catch (error) {\n // NOTE(mroberts): Attempting to check the type of non-function members\n // on the prototype throws an error for some types.\n }\n\n if (type !== 'function') {\n // Skip non-function members.\n return;\n }\n\n /* eslint no-loop-func:0 */\n wrapper[methodName] = function() {\n return this[target][methodName].apply(this[target], arguments);\n };\n}\n\n/**\n * Copy methods from a `source` prototype onto a `wrapper` prototype. Invoking\n * the methods on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction delegateMethods(source, wrapper, target) {\n for (const methodName in source) {\n delegateMethod(source, wrapper, target, methodName);\n }\n}\n\n/**\n * Finds the items in list1 that are not in list2.\n * @param {Array<*>|Map<*>|Set<*>} list1\n * @param {Array<*>|Map<*>|Set<*>} list2\n * @returns {Set}\n */\nfunction difference(list1, list2) {\n list1 = Array.isArray(list1) ? new Set(list1) : new Set(list1.values());\n list2 = Array.isArray(list2) ? new Set(list2) : new Set(list2.values());\n\n const difference = new Set();\n\n list1.forEach(item => {\n if (!list2.has(item)) {\n difference.add(item);\n }\n });\n\n return difference;\n}\n\n/**\n * Map a list to an array of arrays, and return the flattened result.\n * @param {Array<*>|Set<*>|Map<*>} list\n * @param {function(*): Array<*>} mapFn\n * @returns Array<*>\n */\nfunction flatMap(list, mapFn) {\n const listArray = list instanceof Map || list instanceof Set\n ? Array.from(list.values())\n : list;\n\n return listArray.reduce((flattened, item) => flattened.concat(mapFn(item)), []);\n}\n\n/**\n * Get the browser's user agent, if available.\n * @returns {?string}\n */\nfunction getUserAgent() {\n return typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string'\n ? navigator.userAgent\n : null;\n}\n\n/**\n * Guess the browser.\n * @param {string} [userAgent=navigator.userAgent]\n * @returns {?string} browser - \"chrome\", \"firefox\", \"safari\", or null\n */\nfunction guessBrowser(userAgent) {\n if (typeof userAgent === 'undefined') {\n userAgent = getUserAgent();\n }\n if (/Chrome|CriOS/.test(userAgent)) {\n return 'chrome';\n }\n if (/Firefox|FxiOS/.test(userAgent)) {\n return 'firefox';\n }\n if (/Safari|iPhone|iPad|iPod/.test(userAgent)) {\n return 'safari';\n }\n return null;\n}\n\n/**\n * Guess the browser version.\n * @param {string} [userAgent=navigator.userAgent]\n * @returns {?{major: number, minor: number}}\n */\nfunction guessBrowserVersion(userAgent) {\n if (typeof userAgent === 'undefined') {\n userAgent = getUserAgent();\n }\n const prefix = {\n chrome: 'Chrome|CriOS',\n firefox: 'Firefox|FxiOS',\n safari: 'Version'\n }[guessBrowser(userAgent)];\n\n if (!prefix) {\n return null;\n }\n const regex = new RegExp(`(${prefix})/([^\\\\s]+)`);\n const [, , match] = userAgent.match(regex) || [];\n\n if (!match) {\n return null;\n }\n const versions = match.split('.').map(Number);\n return {\n major: isNaN(versions[0]) ? null : versions[0],\n minor: isNaN(versions[1]) ? null : versions[1]\n };\n}\n\n/**\n * Check whether the current browser is iOS Chrome.\n * @param {string} [userAgent=navigator.userAgent]\n * @returns {boolean}\n */\nfunction isIOSChrome(userAgent) {\n if (typeof userAgent === 'undefined') {\n userAgent = getUserAgent();\n }\n return (/Mobi/.test(userAgent) && guessBrowser() === 'chrome' && /iPad|iPhone|iPod/.test(userAgent));\n}\n\n/**\n * Intercept an event that might otherwise be proxied on an EventTarget.\n * @param {EventTarget} target\n * @param {string} type\n * @returns {void}\n */\nfunction interceptEvent(target, type) {\n let currentListener = null;\n Object.defineProperty(target, 'on' + type, {\n get: function() {\n return currentListener;\n },\n set: function(newListener) {\n if (currentListener) {\n this.removeEventListener(type, currentListener);\n }\n\n if (typeof newListener === 'function') {\n currentListener = newListener;\n this.addEventListener(type, currentListener);\n } else {\n currentListener = null;\n }\n }\n });\n}\n\n/**\n * This is a function for turning a Promise into the kind referenced in the\n * Legacy Interface Extensions section of the WebRTC spec.\n * @param {Promise<*>} promise\n * @param {function<*>} onSuccess\n * @param {function} onFailure\n * @returns {Promise}\n */\nfunction legacyPromise(promise, onSuccess, onFailure) {\n return onSuccess\n ? promise.then(onSuccess, onFailure)\n : promise;\n}\n\n/**\n * Make a unique ID.\n * @return {string}\n */\nfunction makeUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n/**\n * For each property name on the `source` prototype, add getters and/or setters\n * to `wrapper` that proxy to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction proxyProperties(source, wrapper, target) {\n Object.getOwnPropertyNames(source).forEach(propertyName => {\n proxyProperty(source, wrapper, target, propertyName);\n });\n}\n\n/**\n * For the property name on the `source` prototype, add a getter and/or setter\n * to `wrapper` that proxies to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} propertyName\n * @returns {undefined}\n */\nfunction proxyProperty(source, wrapper, target, propertyName) {\n if (propertyName in wrapper) {\n // Skip any properties already set.\n return;\n } else if (propertyName.match(/^on[a-z]+$/)) {\n Object.defineProperty(wrapper, propertyName, {\n value: null,\n writable: true\n });\n\n target.addEventListener(\n propertyName.slice(2),\n (...args) => wrapper.dispatchEvent(...args)\n );\n\n return;\n }\n\n Object.defineProperty(wrapper, propertyName, {\n enumerable: true,\n get: function() {\n return target[propertyName];\n }\n });\n}\n\n/**\n * Check whether native WebRTC APIs are supported.\n * @returns {boolean}\n */\nfunction support() {\n return typeof navigator === 'object'\n && typeof navigator.mediaDevices === 'object'\n && typeof navigator.mediaDevices.getUserMedia === 'function'\n && typeof RTCPeerConnection === 'function';\n}\n\n/**\n * Create a Set of supported codecs for a certain kind of media.\n * @param {'audio'|'video'} kind\n * @returns {Promise>}\n */\nfunction createSupportedCodecsSet(kind) {\n if (typeof RTCRtpSender !== 'undefined'\n && typeof RTCRtpSender.getCapabilities === 'function') {\n return Promise.resolve(new Set(\n RTCRtpSender\n .getCapabilities(kind)\n .codecs\n .map(({ mimeType }) => mimeType.split('/')[1].toLowerCase())\n ));\n }\n if (typeof RTCPeerConnection === 'undefined'\n || typeof RTCPeerConnection.prototype === 'undefined'\n || typeof RTCPeerConnection.prototype.addTransceiver !== 'function'\n || typeof RTCPeerConnection.prototype.close !== 'function'\n || typeof RTCPeerConnection.prototype.createOffer !== 'function') {\n return Promise.resolve(new Set());\n }\n const pc = new RTCPeerConnection();\n pc.addTransceiver(kind);\n return pc.createOffer().then(({ sdp }) => {\n pc.close();\n return new Set((sdp.match(/^a=rtpmap:.+$/gm) || [])\n .map(line => line.match(/^a=rtpmap:.+ ([^/]+)/)[1].toLowerCase()));\n }, () => {\n pc.close();\n return new Set();\n });\n}\n\n// NOTE(mmalavalli): Cache the supported audio and video codecs here.\nconst supportedCodecs = new Map();\n\n/**\n * Check whether a given codec for a certain kind of media is supported.\n * @param {AudioCodec|VideoCodec} codec\n * @param {'audio'|'video'} kind\n * @returns {Promise}\n */\nfunction isCodecSupported(codec, kind) {\n const codecs = supportedCodecs.get(kind);\n if (codecs) {\n return Promise.resolve(codecs.has(codec.toLowerCase()));\n }\n return createSupportedCodecsSet(kind).then(codecs => {\n supportedCodecs.set(kind, codecs);\n return codecs.has(codec.toLowerCase());\n });\n}\n\n/**\n * Clear cached supported codecs (unit tests only).\n */\nfunction clearCachedSupportedCodecs() {\n supportedCodecs.clear();\n}\n\n/**\n * @typedef {object} Deferred\n * @property {Promise} promise\n * @property {function} reject\n * @property {function} resolve\n */\n\nexports.clearCachedSupportedCodecs = clearCachedSupportedCodecs;\nexports.defer = defer;\nexports.delegateMethods = delegateMethods;\nexports.difference = difference;\nexports.flatMap = flatMap;\nexports.guessBrowser = guessBrowser;\nexports.guessBrowserVersion = guessBrowserVersion;\nexports.isCodecSupported = isCodecSupported;\nexports.isIOSChrome = isIOSChrome;\nexports.interceptEvent = interceptEvent;\nexports.legacyPromise = legacyPromise;\nexports.makeUUID = makeUUID;\nexports.proxyProperties = proxyProperties;\nexports.support = support;\n", "const SID_CHARS = '1234567890abcdef';\nconst SID_CHAR_LENGTH = 32;\n// copied from: https://code.hq.twilio.com/flex/monkey/blob/0fdce2b6c52d6be0b17a5cdb92f0c54f119b8ea8/src/client/lib/sid.ts#L39\n\n/**\n * Generates a random sid using given prefix.\n * @param {string} prefix\n * @returns string\n */\nfunction createSID(prefix) {\n let result = '';\n for (let i = 0; i < SID_CHAR_LENGTH; i++) {\n result += SID_CHARS.charAt(Math.floor(Math.random() * SID_CHARS.length));\n }\n return `${prefix}${result}`;\n}\n\nexports.sessionSID = createSID('SS');\nexports.createSID = createSID;\n\n", "'use strict';\n\n/**\n * @private\n * Represents a warning encountered when\n * interacting with one of Twilio's services.\n */\n// eslint-disable-next-line\nconst TwilioWarning = {\n recordingMediaLost: 'recording-media-lost'\n};\n\nmodule.exports = TwilioWarning;\n", "'use strict';\n\nconst constants = require('./constants');\nconst { typeErrors: E, trackPriority } = constants;\nconst util = require('../webrtc/util');\nconst { sessionSID } = require('./sid');\nconst TwilioWarning = require('./twiliowarning');\n\n/**\n * Return the given {@link LocalTrack} or a new {@link LocalTrack} for the\n * given MediaStreamTrack.\n * @param {LocalTrack|MediaStreamTrack} track\n * @param {object} options\n * @returns {LocalTrack}\n * @throws {TypeError}\n */\nfunction asLocalTrack(track, options) {\n if (track instanceof options.LocalAudioTrack\n || track instanceof options.LocalVideoTrack\n || track instanceof options.LocalDataTrack) {\n return track;\n }\n if (track instanceof options.MediaStreamTrack) {\n return track.kind === 'audio'\n ? new options.LocalAudioTrack(track, options)\n : new options.LocalVideoTrack(track, options);\n }\n /* eslint new-cap:0 */\n throw E.INVALID_TYPE('track', 'LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n}\n\n/**\n * Create a new {@link LocalTrackPublication} for the given {@link LocalTrack}.\n * @param {LocalTrack} track\n * @param {LocalTrackPublicationSignaling} signaling\n * @param {function(track: LocalTrackPublication): void} unpublish\n * @param {object} options\n */\nfunction asLocalTrackPublication(track, signaling, unpublish, options) {\n const LocalTrackPublication = {\n audio: options.LocalAudioTrackPublication,\n video: options.LocalVideoTrackPublication,\n data: options.LocalDataTrackPublication\n }[track.kind];\n return new LocalTrackPublication(signaling, track, unpublish, options);\n}\n\n/**\n * Capitalize a word.\n * @param {string} word\n * @returns {string} capitalized\n */\nfunction capitalize(word) {\n return word[0].toUpperCase() + word.slice(1);\n}\n\n/**\n * Log deprecation warnings for the given events of an EventEmitter.\n * @param {string} name\n * @param {EventEmitter} emitter\n * @param {Map} events\n * @param {Log} log\n */\nfunction deprecateEvents(name, emitter, events, log) {\n const warningsShown = new Set();\n emitter.on('newListener', function newListener(event) {\n if (events.has(event) && !warningsShown.has(event)) {\n log.deprecated(`${name}#${event} has been deprecated and scheduled for removal in twilio-video.js@2.0.0.${events.get(event)\n ? ` Use ${name}#${events.get(event)} instead.`\n : ''}`);\n warningsShown.add(event);\n }\n if (warningsShown.size >= events.size) {\n emitter.removeListener('newListener', newListener);\n }\n });\n}\n\n/**\n * Finds the items in list1 that are not in list2.\n * @param {Array<*>|Map<*>|Set<*>} list1\n * @param {Array<*>|Map<*>|Set<*>} list2\n * @returns {Set}\n */\nfunction difference(list1, list2) {\n list1 = Array.isArray(list1) ? new Set(list1) : new Set(list1.values());\n list2 = Array.isArray(list2) ? new Set(list2) : new Set(list2.values());\n\n const difference = new Set();\n\n list1.forEach(item => {\n if (!list2.has(item)) {\n difference.add(item);\n }\n });\n\n return difference;\n}\n\n/**\n * Filter out the keys in an object with a given value.\n * @param {object} object - Object to be filtered\n * @param {*} [filterValue] - Value to be filtered out; If not specified, then\n * filters out all keys which have an explicit value of \"undefined\"\n * @returns {object} - Filtered object\n */\nfunction filterObject(object, filterValue) {\n return Object.keys(object).reduce((filtered, key) => {\n if (object[key] !== filterValue) {\n filtered[key] = object[key];\n }\n return filtered;\n }, {});\n}\n\n/**\n * Map a list to an array of arrays, and return the flattened result.\n * @param {Array<*>|Set<*>|Map<*>} list\n * @param {function(*): Array<*>} [mapFn]\n * @returns Array<*>\n */\nfunction flatMap(list, mapFn) {\n const listArray = list instanceof Map || list instanceof Set\n ? Array.from(list.values())\n : list;\n\n mapFn = mapFn || function mapFn(item) {\n return item;\n };\n\n return listArray.reduce((flattened, item) => {\n const mapped = mapFn(item);\n return flattened.concat(mapped);\n }, []);\n}\n\n/**\n * Get the user agent string, or return \"Unknown\".\n * @returns {string}\n */\nfunction getUserAgent() {\n return typeof navigator !== 'undefined' && navigator.userAgent\n ? navigator.userAgent\n : 'Unknown';\n}\n\n/**\n * Get the platform component of the user agent string.\n * Example:\n * Input - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36\n * Output - macintosh\n * @returns {string}\n */\nfunction getPlatform() {\n const userAgent = getUserAgent();\n const [, match = 'unknown'] = userAgent.match(/\\(([^)]+)\\)/) || [];\n const [platform] = match.split(';').map(entry => entry.trim());\n return platform.toLowerCase();\n}\n\n/**\n * Create a unique identifier.\n * @returns {string}\n */\nfunction makeUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n/**\n * Ensure that the given function is called once per tick.\n * @param {function} fn - Function to be executed\n * @returns {function} - Schedules the given function to be called on the next tick\n */\nfunction oncePerTick(fn) {\n let timeout = null;\n\n function nextTick() {\n timeout = null;\n fn();\n }\n\n return function scheduleNextTick() {\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(nextTick);\n };\n}\n\nfunction promiseFromEvents(operation, eventEmitter, successEvent, failureEvent) {\n return new Promise((resolve, reject) => {\n function onSuccess() {\n const args = [].slice.call(arguments);\n if (failureEvent) {\n eventEmitter.removeListener(failureEvent, onFailure);\n }\n resolve(...args);\n }\n function onFailure() {\n const args = [].slice.call(arguments);\n eventEmitter.removeListener(successEvent, onSuccess);\n reject(...args);\n }\n eventEmitter.once(successEvent, onSuccess);\n if (failureEvent) {\n eventEmitter.once(failureEvent, onFailure);\n }\n operation();\n });\n}\n\n/**\n * Traverse down multiple nodes on an object and return null if\n * any link in the path is unavailable.\n * @param {Object} obj - Object to traverse\n * @param {String} path - Path to traverse. Period-separated.\n * @returns {Any|null}\n */\nfunction getOrNull(obj, path) {\n return path.split('.').reduce((output, step) => {\n if (!output) { return null; }\n return output[step];\n }, obj);\n}\n\n/**\n * @typedef {object} Deferred\n * @property {Promise} promise\n * @property {function} reject\n * @property {function} resolve\n */\n\n/**\n * Create a {@link Deferred}.\n * @returns {Deferred}\n */\nfunction defer() {\n const deferred = {};\n deferred.promise = new Promise((resolve, reject) => {\n deferred.resolve = resolve;\n deferred.reject = reject;\n });\n return deferred;\n}\n\n/**\n * Copy a method from a `source` prototype onto a `wrapper` prototype. Invoking\n * the method on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} methodName\n * @returns {undefined}\n */\nfunction delegateMethod(source, wrapper, target, methodName) {\n if (methodName in wrapper) {\n // Skip any methods already set.\n return;\n } else if (methodName.match(/^on[a-z]+$/)) {\n // Skip EventHandlers (these are handled in the constructor).\n return;\n }\n\n let type;\n try {\n type = typeof source[methodName];\n } catch (error) {\n // NOTE(mroberts): Attempting to check the type of non-function members\n // on the prototype throws an error for some types.\n }\n\n if (type !== 'function') {\n // Skip non-function members.\n return;\n }\n\n /* eslint no-loop-func:0 */\n wrapper[methodName] = function(...args) {\n return this[target][methodName](...args);\n };\n}\n\n/**\n * Copy methods from a `source` prototype onto a `wrapper` prototype. Invoking\n * the methods on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction delegateMethods(source, wrapper, target) {\n for (const methodName in source) {\n delegateMethod(source, wrapper, target, methodName);\n }\n}\n\n/**\n * Determine whether two values are deeply equal.\n * @param {*} val1\n * @param {*} val2\n * @returns {boolean}\n */\nfunction isDeepEqual(val1, val2) {\n if (val1 === val2) {\n return true;\n }\n if (typeof val1 !== typeof val2) {\n return false;\n }\n if (val1 === null) {\n return val2 === null;\n }\n if (val2 === null) {\n return false;\n }\n if (Array.isArray(val1)) {\n return Array.isArray(val2)\n && val1.length === val2.length\n && val1.every((val, i) => isDeepEqual(val, val2[i]));\n }\n if (typeof val1 === 'object') {\n const val1Keys = Object.keys(val1).sort();\n const val2Keys = Object.keys(val2).sort();\n return !Array.isArray(val2)\n && isDeepEqual(val1Keys, val2Keys)\n && val1Keys.every(key => isDeepEqual(val1[key], val2[key]));\n }\n return false;\n}\n\n/**\n * Whether the given argument is a non-array object.\n * @param {*} object\n * @return {boolean}\n */\nfunction isNonArrayObject(object) {\n return typeof object === 'object' && !Array.isArray(object);\n}\n\n/**\n * For each property name on the `source` prototype, add getters and/or setters\n * to `wrapper` that proxy to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction proxyProperties(source, wrapper, target) {\n Object.getOwnPropertyNames(source).forEach(propertyName => {\n proxyProperty(source, wrapper, target, propertyName);\n });\n}\n\n/**\n * For the property name on the `source` prototype, add a getter and/or setter\n * to `wrapper` that proxies to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} propertyName\n * @returns {undefined}\n */\nfunction proxyProperty(source, wrapper, target, propertyName) {\n if (propertyName in wrapper) {\n // Skip any properties already set.\n return;\n } else if (propertyName.match(/^on[a-z]+$/)) {\n Object.defineProperty(wrapper, propertyName, {\n value: null,\n writable: true\n });\n\n target.addEventListener(propertyName.slice(2), function(...args) {\n wrapper.dispatchEvent(...args);\n });\n\n return;\n }\n\n Object.defineProperty(wrapper, propertyName, {\n enumerable: true,\n get() {\n return target[propertyName];\n }\n });\n}\n\n/**\n * This is a function for turning a Promise into the kind referenced in the\n * Legacy Interface Extensions section of the WebRTC spec.\n * @param {Promise<*>} promise\n * @param {function<*>} onSuccess\n * @param {function} onFailure\n * @returns {Promise}\n */\nfunction legacyPromise(promise, onSuccess, onFailure) {\n if (onSuccess) {\n return promise.then(result => {\n onSuccess(result);\n }, error => {\n onFailure(error);\n });\n }\n return promise;\n}\n\n/**\n * Build the {@link LogLevels} object.\n * @param {String|LogLevel} logLevel - Log level name or object\n * @returns {LogLevels}\n */\nfunction buildLogLevels(logLevel) {\n if (typeof logLevel === 'string') {\n return {\n default: logLevel,\n media: logLevel,\n signaling: logLevel,\n webrtc: logLevel\n };\n }\n return logLevel;\n}\n\n/**\n * Get the {@link Track}'s derived class name\n * @param {Track} track\n * @param {?boolean} [local=undefined]\n * @returns {string}\n */\nfunction trackClass(track, local) {\n local = local ? 'Local' : '';\n return `${local + (track.kind || '').replace(/\\w{1}/, m => m.toUpperCase())}Track`;\n}\n\n/**\n * Get the {@link TrackPublication}'s derived class name\n * @param {TrackPublication} publication\n * @param {?boolean} [local=undefined]\n * @returns {string}\n */\nfunction trackPublicationClass(publication, local) {\n local = local ? 'Local' : '';\n return `${local + (publication.kind || '').replace(/\\w{1}/, m => m.toUpperCase())}TrackPublication`;\n}\n\n/**\n * Sets all underscore-prefixed properties on `object` non-enumerable.\n * @param {Object} object\n * @returns {void}\n */\nfunction hidePrivateProperties(object) {\n Object.getOwnPropertyNames(object).forEach(name => {\n if (name.startsWith('_')) {\n hideProperty(object, name);\n }\n });\n}\n\n/**\n * Creates a new subclass which, in the constructor, sets all underscore-prefixed\n * properties and the given public properties non-enumerable. This is useful for\n * patching up classes like EventEmitter which may set properties like `_events`\n * and `domain`.\n * @param {Function} klass\n * @param {Array} props\n * @returns {Function} subclass\n */\nfunction hidePrivateAndCertainPublicPropertiesInClass(klass, props) {\n // NOTE(mroberts): We do this to avoid giving the class a name.\n return class extends klass {\n constructor(...args) {\n super(...args);\n hidePrivateProperties(this);\n hidePublicProperties(this, props);\n }\n };\n}\n\n/**\n * Hide a property of an object.\n * @param {object} object\n * @param {string} name\n */\nfunction hideProperty(object, name) {\n const descriptor = Object.getOwnPropertyDescriptor(object, name);\n descriptor.enumerable = false;\n Object.defineProperty(object, name, descriptor);\n}\n\n/**\n * Hide the given public properties of an object.\n * @param {object} object\n * @param {Array} [props=[]]\n */\nfunction hidePublicProperties(object, props = []) {\n props.forEach(name => {\n // eslint-disable-next-line no-prototype-builtins\n if (object.hasOwnProperty(name)) {\n hideProperty(object, name);\n }\n });\n}\n\n/**\n * Convert an Array of values to an Array of JSON values by calling\n * `valueToJSON` on each value.\n * @param {Array<*>} array\n * @returns {Array<*>}\n */\nfunction arrayToJSON(array) {\n return array.map(valueToJSON);\n}\n\n/**\n * Convert a Set of values to an Array of JSON values by calling `valueToJSON`\n * on each value.\n * @param {Set<*>} set\n * @returns {Array<*>}\n */\nfunction setToJSON(set) {\n return arrayToJSON([...set]);\n}\n\n/**\n * Convert a Map from strings to values to an object of JSON values by calling\n * `valueToJSON` on each value.\n * @param {Map} map\n * @returns {object}\n */\nfunction mapToJSON(map) {\n return [...map.entries()].reduce((json, [key, value]) => {\n return Object.assign({ [key]: valueToJSON(value) }, json);\n }, {});\n}\n\n/**\n * Convert an object to a JSON value by calling `valueToJSON` on its enumerable\n * keys.\n * @param {object} object\n * @returns {object}\n */\nfunction objectToJSON(object) {\n return Object.entries(object).reduce((json, [key, value]) => {\n return Object.assign({ [key]: valueToJSON(value) }, json);\n }, {});\n}\n\n/**\n * Convert a value into a JSON value.\n * @param {*} value\n * @returns {*}\n */\nfunction valueToJSON(value) {\n if (Array.isArray(value)) {\n return arrayToJSON(value);\n } else if (value instanceof Set) {\n return setToJSON(value);\n } else if (value instanceof Map) {\n return mapToJSON(value);\n } else if (value && typeof value === 'object') {\n return objectToJSON(value);\n }\n return value;\n}\n\nfunction createRoomConnectEventPayload(connectOptions) {\n function boolToString(val) {\n return val ? 'true' : 'false';\n }\n const payload = {\n sessionSID,\n\n // arrays props converted to lengths.\n iceServers: (connectOptions.iceServers || []).length,\n audioTracks: (connectOptions.tracks || []).filter(track => track.kind === 'audio').length,\n videoTracks: (connectOptions.tracks || []).filter(track => track.kind === 'video').length,\n dataTracks: (connectOptions.tracks || []).filter(track => track.kind === 'data').length,\n };\n\n // boolean properties.\n [['audio'], ['automaticSubscription'], ['enableDscp'], ['eventListener'], ['preflight'], ['video'], ['dominantSpeaker', 'enableDominantSpeaker']].forEach(([prop, eventProp]) => {\n eventProp = eventProp || prop;\n payload[eventProp] = boolToString(!!connectOptions[prop]);\n });\n\n // numbers properties.\n [['maxVideoBitrate'], ['maxAudioBitrate']].forEach(([prop, eventProp]) => {\n eventProp = eventProp || prop;\n if (typeof connectOptions[prop] === 'number') {\n payload[eventProp] = connectOptions[prop];\n } else if (!isNaN(Number(connectOptions[prop]))) {\n payload[eventProp] = Number(connectOptions[prop]);\n }\n });\n\n // string properties.\n [['iceTransportPolicy'], ['region'], ['name', 'roomName']].forEach(([prop, eventProp]) => {\n eventProp = eventProp || prop;\n if (typeof connectOptions[prop] === 'string') {\n payload[eventProp] = connectOptions[prop];\n } else if (typeof connectOptions[prop] === 'number' && prop === 'name') {\n payload[eventProp] = connectOptions[prop].toString();\n }\n });\n\n // array props stringified.\n ['preferredAudioCodecs', 'preferredVideoCodecs'].forEach(prop => {\n if (prop in connectOptions) {\n payload[prop] = JSON.stringify(connectOptions[prop]);\n }\n });\n\n if ('networkQuality' in connectOptions) {\n payload.networkQualityConfiguration = {};\n if (isNonArrayObject(connectOptions.networkQuality)) {\n ['local', 'remote'].forEach(prop => {\n if (typeof connectOptions.networkQuality[prop] === 'number') {\n payload.networkQualityConfiguration[prop] = connectOptions.networkQuality[prop];\n }\n });\n } else {\n payload.networkQualityConfiguration.remote = 0;\n payload.networkQualityConfiguration.local = connectOptions.networkQuality ? 1 : 0;\n }\n }\n\n if (connectOptions.bandwidthProfile && connectOptions.bandwidthProfile.video) {\n const videoBPOptions = connectOptions.bandwidthProfile.video || {};\n payload.bandwidthProfileOptions = {};\n ['mode', 'maxTracks', 'trackSwitchOffMode', 'dominantSpeakerPriority', 'maxSubscriptionBitrate', 'renderDimensions', 'contentPreferencesMode', 'clientTrackSwitchOffControl'].forEach(prop => {\n if (typeof videoBPOptions[prop] === 'number' || typeof videoBPOptions[prop] === 'string') {\n payload.bandwidthProfileOptions[prop] = videoBPOptions[prop];\n } else if (typeof videoBPOptions[prop] === 'boolean') {\n payload.bandwidthProfileOptions[prop] = boolToString(videoBPOptions[prop]);\n } else if (typeof videoBPOptions[prop] === 'object') {\n payload.bandwidthProfileOptions[prop] = JSON.stringify(videoBPOptions[prop]);\n }\n });\n }\n\n return {\n group: 'room',\n name: 'connect',\n level: 'info',\n payload\n };\n}\n\n/**\n * Create the bandwidth profile payload included in an RSP connect message.\n * @param {BandwidthProfileOptions} bandwidthProfile\n * @returns {object}\n */\nfunction createBandwidthProfilePayload(bandwidthProfile) {\n return createRSPPayload(bandwidthProfile, [\n { prop: 'video', type: 'object', transform: createBandwidthProfileVideoPayload }\n ]);\n}\n\n/**\n * Create the bandwidth profile video payload included in an RSP connect message.\n * @param {VideoBandwidthProfileOptions} bandwidthProfileVideo\n * @returns {object}\n */\nfunction createBandwidthProfileVideoPayload(bandwidthProfileVideo) {\n return createRSPPayload(bandwidthProfileVideo, [\n { prop: 'dominantSpeakerPriority', type: 'string', payloadProp: 'active_speaker_priority' },\n { prop: 'maxSubscriptionBitrate', type: 'number', payloadProp: 'max_subscription_bandwidth' },\n { prop: 'maxTracks', type: 'number', payloadProp: 'max_tracks' },\n { prop: 'mode', type: 'string' },\n { prop: 'renderDimensions', type: 'object', payloadProp: 'render_dimensions', transform: createRenderDimensionsPayload },\n { prop: 'trackSwitchOffMode', type: 'string', payloadProp: 'track_switch_off' }\n ]);\n}\n\n/**\n * Create the Media Signaling payload included in an RSP connect message.\n * @param {boolean} dominantSpeaker - whether to enable the Dominant Speaker\n * protocol or not\n * @param {boolean} networkQuality - whether to enable the Network Quality\n * protocol or not\n * @param {boolean} trackPriority - whether to enable the Track Priority\n * protocol or not\n * @param {boolean} trackSwitchOff - whether to enable the Track Switch-Off\n * protocol or not.\n * @param {boolean} renderHints - whether to enable the renderHints\n * protocol or not.\n * @returns {object}\n */\nfunction createMediaSignalingPayload(dominantSpeaker, networkQuality, trackPriority, trackSwitchOff, adaptiveSimulcast, renderHints) {\n const transports = { transports: [{ type: 'data-channel' }] };\n return Object.assign(\n dominantSpeaker\n // eslint-disable-next-line\n ? { active_speaker: transports }\n : {},\n networkQuality\n // eslint-disable-next-line\n ? { network_quality: transports }\n : {},\n renderHints\n // eslint-disable-next-line\n ? { render_hints: transports }\n : {},\n adaptiveSimulcast\n // eslint-disable-next-line\n ? { publisher_hints: transports }\n : {},\n trackPriority\n // eslint-disable-next-line\n ? { track_priority: transports }\n : {},\n trackSwitchOff\n // eslint-disable-next-line\n ? { track_switch_off: transports }\n : {}\n );\n}\n\n/**\n * Create {@link VideoTrack.Dimensions} RSP payload.\n * @param {VideoTrack.Dimensions} [dimensions]\n * @returns {object}\n */\nfunction createDimensionsPayload(dimensions) {\n return createRSPPayload(dimensions, [\n { prop: 'height', type: 'number' },\n { prop: 'width', type: 'number' }\n ]);\n}\n\n/**\n * Create {@link VideoRenderDimensions} RSP payload.\n * @param renderDimensions\n * @returns {object}\n */\nfunction createRenderDimensionsPayload(renderDimensions) {\n const { PRIORITY_HIGH, PRIORITY_LOW, PRIORITY_STANDARD } = trackPriority;\n return createRSPPayload(renderDimensions, [\n { prop: PRIORITY_HIGH, type: 'object', transform: createDimensionsPayload },\n { prop: PRIORITY_LOW, type: 'object', transform: createDimensionsPayload },\n { prop: PRIORITY_STANDARD, type: 'object', transform: createDimensionsPayload }\n ]);\n}\n\n/**\n * Create an RSP payload for the given object.\n * @param {object} object - object for which RSP payload is to be generated\n * @param {Array} propConversions - conversion rules for object properties;\n * they specify how object properties should be converted to their corresponding\n * RSP payload properties\n * @returns {object}\n */\nfunction createRSPPayload(object, propConversions) {\n return propConversions.reduce((payload, { prop, type, payloadProp = prop, transform = x => x }) => {\n return typeof object[prop] === type\n ? Object.assign({ [payloadProp]: transform(object[prop]) }, payload)\n : payload;\n }, {});\n}\n\n/**\n * Create the subscribe payload included in an RSP connect/update message.\n * @param {boolean} automaticSubscription - whether to subscribe to all RemoteTracks\n * @returns {object}\n */\nfunction createSubscribePayload(automaticSubscription) {\n return {\n rules: [{\n type: automaticSubscription ? 'include' : 'exclude',\n all: true\n }],\n revision: 1\n };\n}\n\nfunction createMediaWarningsPayload(notifyWarnings) {\n const mediaWarnings = {\n [TwilioWarning.recordingMediaLost]: 'recordings'\n };\n return notifyWarnings\n .map(twilioWarningName => mediaWarnings[twilioWarningName])\n .filter(name => !!name);\n}\n\n/**\n * Add random jitter to a given value in the range [-jitter, jitter].\n * @private\n * @param {number} value\n * @param {number} jitter\n * @returns {number} value + random(-jitter, +jitter)\n */\nfunction withJitter(value, jitter) {\n const rand = Math.random();\n return value - jitter + Math.floor(2 * jitter * rand + 0.5);\n}\n\n/**\n * Checks if the a number is in the range [min, max].\n * @private\n * @param {num} num\n * @param {number} min\n * @param {number} max\n * @return {boolean}\n */\nfunction inRange(num, min, max) {\n return min <= num && num <= max;\n}\n\n/**\n * returns true if given MediaStreamTrack is a screen share track\n * @private\n * @param {MediaStreamTrack} track\n * @returns {boolean}\n */\nfunction isChromeScreenShareTrack(track) {\n // NOTE(mpatwardhan): Chrome creates screen share tracks with label like: \"screen:69734272*\"\n // we will check for label that starts with \"screen:D\" where D being a digit.\n return util.guessBrowser() === 'chrome' && track.kind === 'video' && 'displaySurface' in track.getSettings();\n}\n\n/**\n * Returns a promise that resolve after timeoutMS have passed.\n * @param {number} timeoutMS - time to wait in milliseconds.\n * @returns {Promise}\n */\nfunction waitForSometime(timeoutMS = 10) {\n return new Promise(resolve => setTimeout(resolve, timeoutMS));\n}\n\n/**\n * Returns a promise that resolve after event is received\n * @returns {Promise}\n */\nfunction waitForEvent(eventTarget, event) {\n return new Promise(resolve => {\n eventTarget.addEventListener(event, function onevent(e) {\n eventTarget.removeEventListener(event, onevent);\n resolve(e);\n });\n });\n}\n\nexports.constants = constants;\nexports.createBandwidthProfilePayload = createBandwidthProfilePayload;\nexports.createMediaSignalingPayload = createMediaSignalingPayload;\nexports.createMediaWarningsPayload = createMediaWarningsPayload;\nexports.createRoomConnectEventPayload = createRoomConnectEventPayload;\nexports.createSubscribePayload = createSubscribePayload;\nexports.asLocalTrack = asLocalTrack;\nexports.asLocalTrackPublication = asLocalTrackPublication;\nexports.capitalize = capitalize;\nexports.deprecateEvents = deprecateEvents;\nexports.difference = difference;\nexports.filterObject = filterObject;\nexports.flatMap = flatMap;\nexports.getPlatform = getPlatform;\nexports.getUserAgent = getUserAgent;\nexports.hidePrivateProperties = hidePrivateProperties;\nexports.hidePrivateAndCertainPublicPropertiesInClass = hidePrivateAndCertainPublicPropertiesInClass;\nexports.isDeepEqual = isDeepEqual;\nexports.isNonArrayObject = isNonArrayObject;\nexports.inRange = inRange;\nexports.makeUUID = makeUUID;\nexports.oncePerTick = oncePerTick;\nexports.promiseFromEvents = promiseFromEvents;\nexports.getOrNull = getOrNull;\nexports.defer = defer;\nexports.delegateMethods = delegateMethods;\nexports.proxyProperties = proxyProperties;\nexports.legacyPromise = legacyPromise;\nexports.buildLogLevels = buildLogLevels;\nexports.trackClass = trackClass;\nexports.trackPublicationClass = trackPublicationClass;\nexports.valueToJSON = valueToJSON;\nexports.withJitter = withJitter;\nexports.isChromeScreenShareTrack = isChromeScreenShareTrack;\nexports.waitForSometime = waitForSometime;\nexports.waitForEvent = waitForEvent;\n", "/* globals RTCPeerConnection, RTCRtpTransceiver */\n\n'use strict';\n\nconst { flatMap, guessBrowser } = require('./');\n\n// NOTE(mmalavalli): We cache Chrome's sdpSemantics support in order to prevent\n// instantiation of more than one RTCPeerConnection.\nlet isSdpSemanticsSupported = null;\n\n/**\n * Check if Chrome supports specifying sdpSemantics for an RTCPeerConnection.\n * @return {boolean}\n */\nfunction checkIfSdpSemanticsIsSupported() {\n if (typeof isSdpSemanticsSupported === 'boolean') {\n return isSdpSemanticsSupported;\n }\n if (typeof RTCPeerConnection === 'undefined') {\n isSdpSemanticsSupported = false;\n return isSdpSemanticsSupported;\n }\n try {\n // eslint-disable-next-line no-new\n new RTCPeerConnection({ sdpSemantics: 'foo' });\n isSdpSemanticsSupported = false;\n } catch (e) {\n isSdpSemanticsSupported = true;\n }\n return isSdpSemanticsSupported;\n}\n\n// NOTE(mmalavalli): We cache Chrome's SDP format in order to prevent\n// instantiation of more than one RTCPeerConnection.\nlet chromeSdpFormat = null;\n\n/**\n * Clear cached Chrome's SDP format\n */\nfunction clearChromeCachedSdpFormat() {\n chromeSdpFormat = null;\n}\n\n/**\n * Get Chrome's default SDP format.\n * @returns {'planb'|'unified'}\n */\nfunction getChromeDefaultSdpFormat() {\n if (!chromeSdpFormat) {\n if (typeof RTCPeerConnection !== 'undefined'\n && 'addTransceiver' in RTCPeerConnection.prototype) {\n const pc = new RTCPeerConnection();\n try {\n pc.addTransceiver('audio');\n chromeSdpFormat = 'unified';\n } catch (e) {\n chromeSdpFormat = 'planb';\n }\n pc.close();\n } else {\n chromeSdpFormat = 'planb';\n }\n }\n return chromeSdpFormat;\n}\n\n/**\n * Get Chrome's SDP format.\n * @param {'plan-b'|'unified-plan'} [sdpSemantics]\n * @returns {'planb'|'unified'}\n */\nfunction getChromeSdpFormat(sdpSemantics) {\n if (!sdpSemantics || !checkIfSdpSemanticsIsSupported()) {\n return getChromeDefaultSdpFormat();\n }\n return {\n 'plan-b': 'planb',\n 'unified-plan': 'unified'\n }[sdpSemantics];\n}\n\n/**\n * Get Safari's default SDP format.\n * @returns {'planb'|'unified'}\n */\nfunction getSafariSdpFormat() {\n return typeof RTCRtpTransceiver !== 'undefined'\n && 'currentDirection' in RTCRtpTransceiver.prototype\n ? 'unified'\n : 'planb';\n}\n\n/**\n * Get the browser's default SDP format.\n * @param {'plan-b'|'unified-plan'} [sdpSemantics]\n * @returns {'planb'|'unified'}\n */\nfunction getSdpFormat(sdpSemantics) {\n return {\n chrome: getChromeSdpFormat(sdpSemantics),\n firefox: 'unified',\n safari: getSafariSdpFormat()\n }[guessBrowser()] || null;\n}\n\n/**\n * Match a pattern across lines, returning the first capture group for any\n * matches.\n * @param {string} pattern\n * @param {string} lines\n * @returns {Set} matches\n */\nfunction getMatches(pattern, lines) {\n const matches = lines.match(new RegExp(pattern, 'gm')) || [];\n return matches.reduce((results, line) => {\n const match = line.match(new RegExp(pattern));\n return match ? results.add(match[1]) : results;\n }, new Set());\n}\n\n/**\n * Get a Set of MediaStreamTrack IDs from an SDP.\n * @param {string} pattern\n * @param {string} sdp\n * @returns {Set}\n */\nfunction getTrackIds(pattern, sdp) {\n return getMatches(pattern, sdp);\n}\n\n/**\n * Get a Set of MediaStreamTrack IDs from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @returns {Set} trackIds\n */\nfunction getPlanBTrackIds(sdp) {\n return getTrackIds('^a=ssrc:[0-9]+ +msid:.+ +(.+) *$', sdp);\n}\n\n/**\n * Get a Set of MediaStreamTrack IDs from a Unified Plan SDP.\n * @param {string} sdp - Unified Plan SDP\n * @returns {Set} trackIds\n */\nfunction getUnifiedPlanTrackIds(sdp) {\n return getTrackIds('^a=msid:.+ +(.+) *$', sdp);\n}\n\n/**\n * Get a Set of SSRCs for a MediaStreamTrack from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @param {string} trackId - MediaStreamTrack ID\n * @returns {Set}\n */\nfunction getPlanBSSRCs(sdp, trackId) {\n const pattern = `^a=ssrc:([0-9]+) +msid:[^ ]+ +${trackId} *$`;\n return getMatches(pattern, sdp);\n}\n\n/**\n * Get the m= sections of a particular kind and direction from an sdp.\n * @param {string} sdp - sdp string\n * @param {string} [kind] - Pattern for matching kind\n * @param {string} [direction] - Pattern for matching direction\n * @returns {Array} mediaSections\n */\nfunction getMediaSections(sdp, kind = '.*', direction = '.*') {\n return sdp.split('\\r\\nm=').slice(1).map(mediaSection => `m=${mediaSection}`).filter(mediaSection => {\n const kindPattern = new RegExp(`m=${kind}`, 'gm');\n const directionPattern = new RegExp(`a=${direction}`, 'gm');\n return kindPattern.test(mediaSection) && directionPattern.test(mediaSection);\n });\n}\n\n/**\n * Get the Set of SSRCs announced in a MediaSection.\n * @param {string} mediaSection\n * @returns {Array} ssrcs\n */\nfunction getMediaSectionSSRCs(mediaSection) {\n return Array.from(getMatches('^a=ssrc:([0-9]+) +.*$', mediaSection));\n}\n\n/**\n * Get a Set of SSRCs for a MediaStreamTrack from a Unified Plan SDP.\n * @param {string} sdp - Unified Plan SDP\n * @param {string} trackId - MediaStreamTrack ID\n * @returns {Set}\n */\nfunction getUnifiedPlanSSRCs(sdp, trackId) {\n const mediaSections = getMediaSections(sdp);\n\n const msidAttrRegExp = new RegExp(`^a=msid:[^ ]+ +${trackId} *$`, 'gm');\n const matchingMediaSections = mediaSections.filter(mediaSection => mediaSection.match(msidAttrRegExp));\n\n return new Set(flatMap(matchingMediaSections, getMediaSectionSSRCs));\n}\n\n/**\n * Get a Map from MediaStreamTrack IDs to SSRCs from an SDP.\n * @param {function(string): Set} getTrackIds\n * @param {function(string, string): Set} getSSRCs\n * @param {string} sdp - SDP\n * @returns {Map>} trackIdsToSSRCs\n */\nfunction getTrackIdsToSSRCs(getTrackIds, getSSRCs, sdp) {\n return new Map(Array.from(getTrackIds(sdp)).map(trackId => [trackId, getSSRCs(sdp, trackId)]));\n}\n\n/**\n * Get a Map from MediaStreamTrack IDs to SSRCs from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @returns {Map>} trackIdsToSSRCs\n */\nfunction getPlanBTrackIdsToSSRCs(sdp) {\n return getTrackIdsToSSRCs(getPlanBTrackIds, getPlanBSSRCs, sdp);\n}\n\n/**\n * Get a Map from MediaStreamTrack IDs to SSRCs from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @returns {Map>} trackIdsToSSRCs\n */\nfunction getUnifiedPlanTrackIdsToSSRCs(sdp) {\n return getTrackIdsToSSRCs(getUnifiedPlanTrackIds, getUnifiedPlanSSRCs, sdp);\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the SDP itself. This method\n * ensures that SSRCs never change once announced.\n * @param {function(string): Map>} getTrackIdsToSSRCs\n * @param {Map>} trackIdsToSSRCs\n * @param {string} sdp - SDP\n * @returns {strinng} updatedSdp - updated SDP\n */\nfunction updateTrackIdsToSSRCs(getTrackIdsToSSRCs, trackIdsToSSRCs, sdp) {\n const newTrackIdsToSSRCs = getTrackIdsToSSRCs(sdp);\n const newSSRCsToOldSSRCs = new Map();\n\n // NOTE(mroberts): First, update a=ssrc attributes.\n newTrackIdsToSSRCs.forEach((ssrcs, trackId) => {\n if (!trackIdsToSSRCs.has(trackId)) {\n trackIdsToSSRCs.set(trackId, ssrcs);\n return;\n }\n const oldSSRCs = Array.from(trackIdsToSSRCs.get(trackId));\n const newSSRCs = Array.from(ssrcs);\n oldSSRCs.forEach((oldSSRC, i) => {\n const newSSRC = newSSRCs[i];\n newSSRCsToOldSSRCs.set(newSSRC, oldSSRC);\n const pattern = `^a=ssrc:${newSSRC} (.*)$`;\n const replacement = `a=ssrc:${oldSSRC} $1`;\n sdp = sdp.replace(new RegExp(pattern, 'gm'), replacement);\n });\n });\n\n // NOTE(mroberts): Then, update a=ssrc-group attributes.\n const pattern = '^(a=ssrc-group:[^ ]+ +)(.*)$';\n const matches = sdp.match(new RegExp(pattern, 'gm')) || [];\n matches.forEach(line => {\n const match = line.match(new RegExp(pattern));\n if (!match) {\n return;\n }\n const prefix = match[1];\n const newSSRCs = match[2];\n const oldSSRCs = newSSRCs.split(' ').map(newSSRC => {\n const oldSSRC = newSSRCsToOldSSRCs.get(newSSRC);\n return oldSSRC ? oldSSRC : newSSRC;\n }).join(' ');\n sdp = sdp.replace(match[0], prefix + oldSSRCs);\n });\n\n return sdp;\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the Plan B SDP itself. This\n * method ensures that SSRCs never change once announced.\n * @param {Map>} trackIdsToSSRCs\n * @param {string} sdp - Plan B SDP\n * @returns {string} updatedSdp - updated Plan B SDP\n */\nfunction updatePlanBTrackIdsToSSRCs(trackIdsToSSRCs, sdp) {\n return updateTrackIdsToSSRCs(getPlanBTrackIdsToSSRCs, trackIdsToSSRCs, sdp);\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the Plan B SDP itself. This\n * method ensures that SSRCs never change once announced.\n * @param {Map>} trackIdsToSSRCs\n * @param {string} sdp - Plan B SDP\n * @returns {string} updatedSdp - updated Plan B SDP\n */\nfunction updateUnifiedPlanTrackIdsToSSRCs(trackIdsToSSRCs, sdp) {\n return updateTrackIdsToSSRCs(getUnifiedPlanTrackIdsToSSRCs, trackIdsToSSRCs, sdp);\n}\n\nexports.clearChromeCachedSdpFormat = clearChromeCachedSdpFormat;\nexports.getSdpFormat = getSdpFormat;\nexports.getMediaSections = getMediaSections;\nexports.getPlanBTrackIds = getPlanBTrackIds;\nexports.getUnifiedPlanTrackIds = getUnifiedPlanTrackIds;\nexports.getPlanBSSRCs = getPlanBSSRCs;\nexports.getUnifiedPlanSSRCs = getUnifiedPlanSSRCs;\nexports.updatePlanBTrackIdsToSSRCs = updatePlanBTrackIdsToSSRCs;\nexports.updateUnifiedPlanTrackIdsToSSRCs = updateUnifiedPlanTrackIdsToSSRCs;\n", "'use strict';\n\nconst { flatMap, guessBrowser, guessBrowserVersion } = require('./util');\nconst { getSdpFormat } = require('./util/sdp');\n\nconst guess = guessBrowser();\nconst guessVersion = guessBrowserVersion();\nconst isChrome = guess === 'chrome';\nconst isFirefox = guess === 'firefox';\nconst isSafari = guess === 'safari';\n\nconst chromeMajorVersion = isChrome ? guessVersion.major : null;\n\nconst CHROME_LEGACY_MAX_AUDIO_LEVEL = 32767;\n\n/**\n * Get the standardized {@link RTCPeerConnection} statistics.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options] - Used for testing\n * @returns {Promise.}\n */\nfunction getStats(peerConnection, options) {\n if (!(peerConnection && typeof peerConnection.getStats === 'function')) {\n return Promise.reject(new Error('Given PeerConnection does not support getStats'));\n }\n return _getStats(peerConnection, options);\n}\n\n/**\n * getStats() implementation.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options] - Used for testing\n * @returns {Promise.}\n */\nfunction _getStats(peerConnection, options) {\n const localAudioTracks = getTracks(peerConnection, 'audio', 'local');\n const localVideoTracks = getTracks(peerConnection, 'video', 'local');\n const remoteAudioTracks = getTracks(peerConnection, 'audio');\n const remoteVideoTracks = getTracks(peerConnection, 'video');\n\n const statsResponse = {\n activeIceCandidatePair: null,\n localAudioTrackStats: [],\n localVideoTrackStats: [],\n remoteAudioTrackStats: [],\n remoteVideoTrackStats: []\n };\n\n const trackStatsPromises = flatMap([\n [localAudioTracks, 'localAudioTrackStats', false],\n [localVideoTracks, 'localVideoTrackStats', false],\n [remoteAudioTracks, 'remoteAudioTrackStats', true],\n [remoteVideoTracks, 'remoteVideoTrackStats', true]\n ], ([tracks, statsArrayName, isRemote]) => {\n return tracks.map(track => {\n return getTrackStats(peerConnection, track, Object.assign({ isRemote }, options)).then(trackStatsArray => {\n trackStatsArray.forEach(trackStats => {\n trackStats.trackId = track.id;\n statsResponse[statsArrayName].push(trackStats);\n });\n });\n });\n });\n\n return Promise.all(trackStatsPromises).then(() => {\n return getActiveIceCandidatePairStats(peerConnection, options);\n }).then(activeIceCandidatePairStatsReport => {\n statsResponse.activeIceCandidatePair = activeIceCandidatePairStatsReport;\n return statsResponse;\n });\n}\n\n/**\n * Generate the {@link StandardizedActiveIceCandidatePairStatsReport} for the\n * {@link RTCPeerConnection}.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options]\n * @returns {Promise}\n */\nfunction getActiveIceCandidatePairStats(peerConnection, options = {}) {\n if (typeof options.testForChrome !== 'undefined' || isChrome\n || typeof options.testForSafari !== 'undefined' || isSafari) {\n return peerConnection.getStats().then(\n standardizeChromeOrSafariActiveIceCandidatePairStats);\n }\n if (typeof options.testForFirefox !== 'undefined' || isFirefox) {\n return peerConnection.getStats().then(standardizeFirefoxActiveIceCandidatePairStats);\n }\n return Promise.reject(new Error('RTCPeerConnection#getStats() not supported'));\n}\n\n/**\n * Standardize the active RTCIceCandidate pair's statistics in Chrome or Safari.\n * @param {RTCStatsReport} stats\n * @returns {?StandardizedActiveIceCandidatePairStatsReport}\n */\nfunction standardizeChromeOrSafariActiveIceCandidatePairStats(stats) {\n const activeCandidatePairStats = Array.from(stats.values()).find(\n ({ nominated, type }) => type === 'candidate-pair' && nominated\n );\n\n if (!activeCandidatePairStats) {\n return null;\n }\n\n const activeLocalCandidateStats = stats.get(activeCandidatePairStats.localCandidateId);\n const activeRemoteCandidateStats = stats.get(activeCandidatePairStats.remoteCandidateId);\n\n const standardizedCandidateStatsKeys = [\n { key: 'candidateType', type: 'string' },\n { key: 'ip', type: 'string' },\n { key: 'port', type: 'number' },\n { key: 'priority', type: 'number' },\n { key: 'protocol', type: 'string' },\n { key: 'url', type: 'string' }\n ];\n\n const standardizedLocalCandidateStatsKeys = standardizedCandidateStatsKeys.concat([\n { key: 'deleted', type: 'boolean' },\n { key: 'relayProtocol', type: 'string' }\n ]);\n\n const standatdizedLocalCandidateStatsReport = activeLocalCandidateStats\n ? standardizedLocalCandidateStatsKeys.reduce((report, { key, type }) => {\n report[key] = typeof activeLocalCandidateStats[key] === type\n ? activeLocalCandidateStats[key]\n : key === 'deleted' ? false : null;\n return report;\n }, {})\n : null;\n\n const standardizedRemoteCandidateStatsReport = activeRemoteCandidateStats\n ? standardizedCandidateStatsKeys.reduce((report, { key, type }) => {\n report[key] = typeof activeRemoteCandidateStats[key] === type\n ? activeRemoteCandidateStats[key]\n : null;\n return report;\n }, {})\n : null;\n\n return [\n { key: 'availableIncomingBitrate', type: 'number' },\n { key: 'availableOutgoingBitrate', type: 'number' },\n { key: 'bytesReceived', type: 'number' },\n { key: 'bytesSent', type: 'number' },\n { key: 'consentRequestsSent', type: 'number' },\n { key: 'currentRoundTripTime', type: 'number' },\n { key: 'lastPacketReceivedTimestamp', type: 'number' },\n { key: 'lastPacketSentTimestamp', type: 'number' },\n { key: 'nominated', type: 'boolean' },\n { key: 'priority', type: 'number' },\n { key: 'readable', type: 'boolean' },\n { key: 'requestsReceived', type: 'number' },\n { key: 'requestsSent', type: 'number' },\n { key: 'responsesReceived', type: 'number' },\n { key: 'responsesSent', type: 'number' },\n { key: 'retransmissionsReceived', type: 'number' },\n { key: 'retransmissionsSent', type: 'number' },\n { key: 'state', type: 'string', fixup: state => { return state === 'inprogress' ? 'in-progress' : state; } },\n { key: 'totalRoundTripTime', type: 'number' },\n { key: 'transportId', type: 'string' },\n { key: 'writable', type: 'boolean' }\n ].reduce((report, { key, type, fixup }) => {\n report[key] = typeof activeCandidatePairStats[key] === type\n ? (fixup ? fixup(activeCandidatePairStats[key]) : activeCandidatePairStats[key])\n : null;\n return report;\n }, {\n localCandidate: standatdizedLocalCandidateStatsReport,\n remoteCandidate: standardizedRemoteCandidateStatsReport\n });\n}\n\n/**\n * Standardize the active RTCIceCandidate pair's statistics in Firefox.\n * @param {RTCStatsReport} stats\n * @returns {?StandardizedActiveIceCandidatePairStatsReport}\n */\nfunction standardizeFirefoxActiveIceCandidatePairStats(stats) {\n const activeCandidatePairStats = Array.from(stats.values()).find(\n ({ nominated, type }) => type === 'candidate-pair' && nominated\n );\n\n if (!activeCandidatePairStats) {\n return null;\n }\n\n const activeLocalCandidateStats = stats.get(activeCandidatePairStats.localCandidateId);\n const activeRemoteCandidateStats = stats.get(activeCandidatePairStats.remoteCandidateId);\n\n const standardizedCandidateStatsKeys = [\n { key: 'candidateType', type: 'string' },\n { key: 'ip', ffKeys: ['address', 'ipAddress'], type: 'string' },\n { key: 'port', ffKeys: ['portNumber'], type: 'number' },\n { key: 'priority', type: 'number' },\n { key: 'protocol', ffKeys: ['transport'], type: 'string' },\n { key: 'url', type: 'string' }\n ];\n\n const standardizedLocalCandidateStatsKeys = standardizedCandidateStatsKeys.concat([\n { key: 'deleted', type: 'boolean' },\n { key: 'relayProtocol', type: 'string' }\n ]);\n\n const candidateTypes = {\n host: 'host',\n peerreflexive: 'prflx',\n relayed: 'relay',\n serverreflexive: 'srflx'\n };\n\n const standatdizedLocalCandidateStatsReport = activeLocalCandidateStats\n ? standardizedLocalCandidateStatsKeys.reduce((report, { ffKeys, key, type }) => {\n const localStatKey = ffKeys && ffKeys.find(key => key in activeLocalCandidateStats) || key;\n report[key] = typeof activeLocalCandidateStats[localStatKey] === type\n ? localStatKey === 'candidateType'\n ? candidateTypes[activeLocalCandidateStats[localStatKey]] || activeLocalCandidateStats[localStatKey]\n : activeLocalCandidateStats[localStatKey]\n : localStatKey === 'deleted' ? false : null;\n return report;\n }, {})\n : null;\n\n const standardizedRemoteCandidateStatsReport = activeRemoteCandidateStats\n ? standardizedCandidateStatsKeys.reduce((report, { ffKeys, key, type }) => {\n const remoteStatKey = ffKeys && ffKeys.find(key => key in activeRemoteCandidateStats) || key;\n report[key] = typeof activeRemoteCandidateStats[remoteStatKey] === type\n ? remoteStatKey === 'candidateType'\n ? candidateTypes[activeRemoteCandidateStats[remoteStatKey]] || activeRemoteCandidateStats[remoteStatKey]\n : activeRemoteCandidateStats[remoteStatKey]\n : null;\n return report;\n }, {})\n : null;\n\n return [\n { key: 'availableIncomingBitrate', type: 'number' },\n { key: 'availableOutgoingBitrate', type: 'number' },\n { key: 'bytesReceived', type: 'number' },\n { key: 'bytesSent', type: 'number' },\n { key: 'consentRequestsSent', type: 'number' },\n { key: 'currentRoundTripTime', type: 'number' },\n { key: 'lastPacketReceivedTimestamp', type: 'number' },\n { key: 'lastPacketSentTimestamp', type: 'number' },\n { key: 'nominated', type: 'boolean' },\n { key: 'priority', type: 'number' },\n { key: 'readable', type: 'boolean' },\n { key: 'requestsReceived', type: 'number' },\n { key: 'requestsSent', type: 'number' },\n { key: 'responsesReceived', type: 'number' },\n { key: 'responsesSent', type: 'number' },\n { key: 'retransmissionsReceived', type: 'number' },\n { key: 'retransmissionsSent', type: 'number' },\n { key: 'state', type: 'string' },\n { key: 'totalRoundTripTime', type: 'number' },\n { key: 'transportId', type: 'string' },\n { key: 'writable', type: 'boolean' }\n ].reduce((report, { key, type }) => {\n report[key] = typeof activeCandidatePairStats[key] === type\n ? activeCandidatePairStats[key]\n : null;\n return report;\n }, {\n localCandidate: standatdizedLocalCandidateStatsReport,\n remoteCandidate: standardizedRemoteCandidateStatsReport\n });\n}\n\n/**\n * Get local/remote audio/video MediaStreamTracks.\n * @param {RTCPeerConnection} peerConnection - The RTCPeerConnection\n * @param {string} kind - 'audio' or 'video'\n * @param {string} [localOrRemote] - 'local' or 'remote'\n * @returns {Array}\n */\nfunction getTracks(peerConnection, kind, localOrRemote) {\n const getSendersOrReceivers = localOrRemote === 'local' ? 'getSenders' : 'getReceivers';\n if (peerConnection[getSendersOrReceivers]) {\n return peerConnection[getSendersOrReceivers]()\n .map(({ track }) => track)\n .filter(track => track && track.kind === kind);\n }\n const getStreams = localOrRemote === 'local' ? 'getLocalStreams' : 'getRemoteStreams';\n const getTracks = kind === 'audio' ? 'getAudioTracks' : 'getVideoTracks';\n return flatMap(peerConnection[getStreams](), stream => stream[getTracks]());\n}\n\n/**\n * Get the standardized statistics for a particular MediaStreamTrack.\n * @param {RTCPeerConnection} peerConnection\n * @param {MediaStreamTrack} track\n * @param {object} [options] - Used for testing\n * @returns {Promise.>}\n */\nfunction getTrackStats(peerConnection, track, options = {}) {\n if (typeof options.testForChrome !== 'undefined' || isChrome) {\n return chromeOrSafariGetTrackStats(peerConnection, track, options);\n }\n if (typeof options.testForFirefox !== 'undefined' || isFirefox) {\n return firefoxGetTrackStats(peerConnection, track, options);\n }\n if (typeof options.testForSafari !== 'undefined' || isSafari) {\n if (typeof options.testForSafari !== 'undefined' || getSdpFormat() === 'unified') {\n return chromeOrSafariGetTrackStats(peerConnection, track, options);\n }\n // NOTE(syerrapragada): getStats() is not supported on\n // Safari versions where plan-b is the SDP format\n // due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601\n return Promise.reject(new Error([\n 'getStats() is not supported on this version of Safari',\n 'due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601'\n ].join(' ')));\n }\n return Promise.reject(new Error('RTCPeerConnection#getStats() not supported'));\n}\n\n/**\n * Get the standardized statistics for a particular MediaStreamTrack in Chrome or Safari.\n * @param {RTCPeerConnection} peerConnection\n * @param {MediaStreamTrack} track\n * @param {object} options - Used for testing\n * @returns {Promise.>}\n */\nfunction chromeOrSafariGetTrackStats(peerConnection, track, options) {\n if (chromeMajorVersion && chromeMajorVersion < 67) {\n return new Promise((resolve, reject) => {\n peerConnection.getStats(response => {\n resolve([standardizeChromeLegacyStats(response, track)]);\n }, null, reject);\n });\n }\n return peerConnection.getStats(track).then(response => {\n return standardizeChromeOrSafariStats(response, options);\n });\n}\n\n/**\n * Get the standardized statistics for a particular MediaStreamTrack in Firefox.\n * @param {RTCPeerConnection} peerConnection\n * @param {MediaStreamTrack} track\n * @param {object} options\n * @returns {Promise.>}\n */\nfunction firefoxGetTrackStats(peerConnection, track, options) {\n return peerConnection.getStats(track).then(response => {\n return [standardizeFirefoxStats(response, options)];\n });\n}\n\n/**\n * Standardize the MediaStreamTrack's legacy statistics in Chrome.\n * @param {RTCStatsResponse} response\n * @param {MediaStreamTrack} track\n * @returns {StandardizedTrackStatsReport}\n */\nfunction standardizeChromeLegacyStats(response, track) {\n const ssrcReport = response.result().find(report => {\n return report.type === 'ssrc' && report.stat('googTrackId') === track.id;\n });\n\n let standardizedStats = {};\n\n if (ssrcReport) {\n standardizedStats.timestamp = Math.round(Number(ssrcReport.timestamp));\n standardizedStats = ssrcReport.names().reduce((stats, name) => {\n switch (name) {\n case 'googCodecName':\n stats.codecName = ssrcReport.stat(name);\n break;\n case 'googRtt':\n stats.roundTripTime = Number(ssrcReport.stat(name));\n break;\n case 'googJitterReceived':\n stats.jitter = Number(ssrcReport.stat(name));\n break;\n case 'googFrameWidthInput':\n stats.frameWidthInput = Number(ssrcReport.stat(name));\n break;\n case 'googFrameHeightInput':\n stats.frameHeightInput = Number(ssrcReport.stat(name));\n break;\n case 'googFrameWidthSent':\n stats.frameWidthSent = Number(ssrcReport.stat(name));\n break;\n case 'googFrameHeightSent':\n stats.frameHeightSent = Number(ssrcReport.stat(name));\n break;\n case 'googFrameWidthReceived':\n stats.frameWidthReceived = Number(ssrcReport.stat(name));\n break;\n case 'googFrameHeightReceived':\n stats.frameHeightReceived = Number(ssrcReport.stat(name));\n break;\n case 'googFrameRateInput':\n stats.frameRateInput = Number(ssrcReport.stat(name));\n break;\n case 'googFrameRateSent':\n stats.frameRateSent = Number(ssrcReport.stat(name));\n break;\n case 'googFrameRateReceived':\n stats.frameRateReceived = Number(ssrcReport.stat(name));\n break;\n case 'ssrc':\n stats[name] = ssrcReport.stat(name);\n break;\n case 'bytesReceived':\n case 'bytesSent':\n case 'packetsLost':\n case 'packetsReceived':\n case 'packetsSent':\n case 'audioInputLevel':\n case 'audioOutputLevel':\n stats[name] = Number(ssrcReport.stat(name));\n break;\n }\n\n return stats;\n }, standardizedStats);\n }\n\n return standardizedStats;\n}\n\n/**\n * Standardize the MediaStreamTrack's statistics in Chrome or Safari.\n * @param {RTCStatsResponse} response\n * @param {object} options - Used for testing\n * @returns {Array}\n */\nfunction standardizeChromeOrSafariStats(response, { simulateExceptionWhileStandardizingStats = false }) {\n if (simulateExceptionWhileStandardizingStats) {\n throw new Error('Error while gathering stats');\n }\n let inbound = null;\n\n // NOTE(mpatwardhan): We should expect more than one \"outbound-rtp\" stats for a\n // VP8 simulcast MediaStreamTrack.\n const outbound = [];\n\n let remoteInbound = null;\n let remoteOutbound = null;\n let track = null;\n let codec = null;\n let localMedia = null;\n\n response.forEach(stat => {\n const { type } = stat;\n switch (type) {\n case 'inbound-rtp':\n inbound = stat;\n break;\n case 'outbound-rtp':\n outbound.push(stat);\n break;\n case 'media-source':\n localMedia = stat;\n break;\n case 'track':\n track = stat;\n break;\n case 'codec':\n codec = stat;\n break;\n case 'remote-inbound-rtp':\n remoteInbound = stat;\n break;\n case 'remote-outbound-rtp':\n remoteOutbound = stat;\n break;\n }\n });\n\n const isRemote = track ? track.remoteSource : !localMedia;\n const mainSources = isRemote ? [inbound] : outbound;\n const stats = [];\n const remoteSource = isRemote ? remoteOutbound : remoteInbound; // remote rtp stats\n\n mainSources.forEach(source => {\n const standardizedStats = {};\n const statSources = [\n source, // local rtp stats\n localMedia,\n track,\n codec,\n remoteSource && remoteSource.ssrc === source.ssrc ? remoteSource : null, // remote rtp stats\n ];\n\n function getStatValue(name) {\n const sourceFound = statSources.find(statSource => {\n return statSource && typeof statSource[name] !== 'undefined';\n }) || null;\n\n return sourceFound ? sourceFound[name] : null;\n }\n\n const ssrc = getStatValue('ssrc');\n if (typeof ssrc === 'number') {\n standardizedStats.ssrc = String(ssrc);\n }\n\n const timestamp = getStatValue('timestamp');\n standardizedStats.timestamp = Math.round(timestamp);\n\n let mimeType = getStatValue('mimeType');\n if (typeof mimeType === 'string') {\n mimeType = mimeType.split('/');\n standardizedStats.codecName = mimeType[mimeType.length - 1];\n }\n\n const roundTripTime = getStatValue('roundTripTime');\n if (typeof roundTripTime === 'number') {\n standardizedStats.roundTripTime = Math.round(roundTripTime * 1000);\n }\n\n const jitter = getStatValue('jitter');\n if (typeof jitter === 'number') {\n standardizedStats.jitter = Math.round(jitter * 1000);\n }\n\n const frameWidth = getStatValue('frameWidth');\n if (typeof frameWidth === 'number') {\n if (isRemote) {\n standardizedStats.frameWidthReceived = frameWidth;\n } else {\n standardizedStats.frameWidthSent = frameWidth;\n standardizedStats.frameWidthInput = track ? track.frameWidth : localMedia.width;\n }\n }\n\n const frameHeight = getStatValue('frameHeight');\n if (typeof frameHeight === 'number') {\n if (isRemote) {\n standardizedStats.frameHeightReceived = frameHeight;\n } else {\n standardizedStats.frameHeightSent = frameHeight;\n standardizedStats.frameHeightInput = track ? track.frameHeight : localMedia.height;\n }\n }\n\n const framesPerSecond = getStatValue('framesPerSecond');\n if (typeof framesPerSecond === 'number') {\n standardizedStats[isRemote ? 'frameRateReceived' : 'frameRateSent'] = framesPerSecond;\n }\n\n const bytesReceived = getStatValue('bytesReceived');\n if (typeof bytesReceived === 'number') {\n standardizedStats.bytesReceived = bytesReceived;\n }\n\n const bytesSent = getStatValue('bytesSent');\n if (typeof bytesSent === 'number') {\n standardizedStats.bytesSent = bytesSent;\n }\n\n const packetsLost = getStatValue('packetsLost');\n if (typeof packetsLost === 'number') {\n standardizedStats.packetsLost = packetsLost;\n }\n\n const packetsReceived = getStatValue('packetsReceived');\n if (typeof packetsReceived === 'number') {\n standardizedStats.packetsReceived = packetsReceived;\n }\n\n const packetsSent = getStatValue('packetsSent');\n if (typeof packetsSent === 'number') {\n standardizedStats.packetsSent = packetsSent;\n }\n\n let audioLevel = getStatValue('audioLevel');\n if (typeof audioLevel === 'number') {\n audioLevel = Math.round(audioLevel * CHROME_LEGACY_MAX_AUDIO_LEVEL);\n if (isRemote) {\n standardizedStats.audioOutputLevel = audioLevel;\n } else {\n standardizedStats.audioInputLevel = audioLevel;\n }\n }\n\n const totalPacketSendDalay = getStatValue('totalPacketSendDelay');\n if (typeof totalPacketSendDalay === 'number') {\n standardizedStats.totalPacketSendDelay = totalPacketSendDalay;\n }\n\n const totalEncodeTime = getStatValue('totalEncodeTime');\n if (typeof totalEncodeTime === 'number') {\n standardizedStats.totalEncodeTime = totalEncodeTime;\n }\n\n const framesEncoded = getStatValue('framesEncoded');\n if (typeof framesEncoded === 'number') {\n standardizedStats.framesEncoded = framesEncoded;\n }\n\n const estimatedPlayoutTimestamp = getStatValue('estimatedPlayoutTimestamp');\n if (typeof estimatedPlayoutTimestamp === 'number') {\n standardizedStats.estimatedPlayoutTimestamp = estimatedPlayoutTimestamp;\n }\n\n const totalDecodeTime = getStatValue('totalDecodeTime');\n if (typeof totalDecodeTime === 'number') {\n standardizedStats.totalDecodeTime = totalDecodeTime;\n }\n\n const framesDecoded = getStatValue('framesDecoded');\n if (typeof framesDecoded === 'number') {\n standardizedStats.framesDecoded = framesDecoded;\n }\n\n const jitterBufferDelay = getStatValue('jitterBufferDelay');\n if (typeof jitterBufferDelay === 'number') {\n standardizedStats.jitterBufferDelay = jitterBufferDelay;\n }\n\n const jitterBufferEmittedCount = getStatValue('jitterBufferEmittedCount');\n if (typeof jitterBufferEmittedCount === 'number') {\n standardizedStats.jitterBufferEmittedCount = jitterBufferEmittedCount;\n }\n\n stats.push(standardizedStats);\n });\n\n return stats;\n}\n\n/**\n * Standardize the MediaStreamTrack's statistics in Firefox.\n * @param {RTCStatsReport} response\n * @param {object} options - Used for testing\n * @returns {StandardizedTrackStatsReport}\n */\nfunction standardizeFirefoxStats(response = new Map(), { isRemote, simulateExceptionWhileStandardizingStats = false }) {\n if (simulateExceptionWhileStandardizingStats) {\n throw new Error('Error while gathering stats');\n }\n // NOTE(mroberts): If getStats is called on a closed RTCPeerConnection,\n // Firefox returns undefined instead of an RTCStatsReport. We workaround this\n // here. See the following bug for more details:\n //\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1377225\n //\n\n let inbound = null;\n let outbound = null;\n\n // NOTE(mmalavalli): Starting from Firefox 63, RTC{Inbound, Outbound}RTPStreamStats.isRemote\n // will be deprecated, followed by its removal in Firefox 66. Also, trying to\n // access members of the remote RTC{Inbound, Outbound}RTPStreamStats without\n // using RTCStatsReport.get(remoteId) will trigger console warnings. So, we\n // no longer depend on \"isRemote\", and we call RTCStatsReport.get(remoteId)\n // to access the remote RTC{Inbound, Outbound}RTPStreamStats.\n //\n // Source: https://blog.mozilla.org/webrtc/getstats-isremote-65/\n //\n response.forEach(stat => {\n const { isRemote, remoteId, type } = stat;\n if (isRemote) {\n return;\n }\n switch (type) {\n case 'inbound-rtp':\n inbound = stat;\n outbound = response.get(remoteId);\n break;\n case 'outbound-rtp':\n outbound = stat;\n inbound = response.get(remoteId);\n break;\n }\n });\n\n const first = isRemote ? inbound : outbound;\n const second = isRemote ? outbound : inbound;\n\n function getStatValue(name) {\n if (first && typeof first[name] !== 'undefined') {\n return first[name];\n }\n if (second && typeof second[name] !== 'undefined') {\n return second[name];\n }\n return null;\n }\n\n const standardizedStats = {};\n const timestamp = getStatValue('timestamp');\n standardizedStats.timestamp = Math.round(timestamp);\n\n const ssrc = getStatValue('ssrc');\n if (typeof ssrc === 'number') {\n standardizedStats.ssrc = String(ssrc);\n }\n\n const bytesSent = getStatValue('bytesSent');\n if (typeof bytesSent === 'number') {\n standardizedStats.bytesSent = bytesSent;\n }\n\n const packetsLost = getStatValue('packetsLost');\n if (typeof packetsLost === 'number') {\n standardizedStats.packetsLost = packetsLost;\n }\n\n const packetsSent = getStatValue('packetsSent');\n if (typeof packetsSent === 'number') {\n standardizedStats.packetsSent = packetsSent;\n }\n\n const roundTripTime = getStatValue('roundTripTime');\n if (typeof roundTripTime === 'number') {\n // roundTripTime is double - measured in seconds.\n // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteinboundrtpstreamstats-roundtriptime\n // cover it to milliseconds (and make it integer)\n standardizedStats.roundTripTime = Math.round(roundTripTime * 1000);\n }\n\n const jitter = getStatValue('jitter');\n if (typeof jitter === 'number') {\n standardizedStats.jitter = Math.round(jitter * 1000);\n }\n\n const frameRateSent = getStatValue('framerateMean');\n if (typeof frameRateSent === 'number') {\n standardizedStats.frameRateSent = Math.round(frameRateSent);\n }\n\n const bytesReceived = getStatValue('bytesReceived');\n if (typeof bytesReceived === 'number') {\n standardizedStats.bytesReceived = bytesReceived;\n }\n\n const packetsReceived = getStatValue('packetsReceived');\n if (typeof packetsReceived === 'number') {\n standardizedStats.packetsReceived = packetsReceived;\n }\n\n const frameRateReceived = getStatValue('framerateMean');\n if (typeof frameRateReceived === 'number') {\n standardizedStats.frameRateReceived = Math.round(frameRateReceived);\n }\n\n const totalPacketSendDalay = getStatValue('totalPacketSendDelay');\n if (typeof totalPacketSendDalay === 'number') {\n standardizedStats.totalPacketSendDelay = totalPacketSendDalay;\n }\n\n const totalEncodeTime = getStatValue('totalEncodeTime');\n if (typeof totalEncodeTime === 'number') {\n standardizedStats.totalEncodeTime = totalEncodeTime;\n }\n\n const framesEncoded = getStatValue('framesEncoded');\n if (typeof framesEncoded === 'number') {\n standardizedStats.framesEncoded = framesEncoded;\n }\n\n const estimatedPlayoutTimestamp = getStatValue('estimatedPlayoutTimestamp');\n if (typeof estimatedPlayoutTimestamp === 'number') {\n standardizedStats.estimatedPlayoutTimestamp = estimatedPlayoutTimestamp;\n }\n\n const totalDecodeTime = getStatValue('totalDecodeTime');\n if (typeof totalDecodeTime === 'number') {\n standardizedStats.totalDecodeTime = totalDecodeTime;\n }\n\n const framesDecoded = getStatValue('framesDecoded');\n if (typeof framesDecoded === 'number') {\n standardizedStats.framesDecoded = framesDecoded;\n }\n\n const jitterBufferDelay = getStatValue('jitterBufferDelay');\n if (typeof jitterBufferDelay === 'number') {\n standardizedStats.jitterBufferDelay = jitterBufferDelay;\n }\n\n const jitterBufferEmittedCount = getStatValue('jitterBufferEmittedCount');\n if (typeof jitterBufferEmittedCount === 'number') {\n standardizedStats.jitterBufferEmittedCount = jitterBufferEmittedCount;\n }\n\n return standardizedStats;\n}\n\n/**\n * Standardized RTCIceCandidate statistics.\n * @typedef {object} StandardizedIceCandidateStatsReport\n * @property {'host'|'prflx'|'relay'|'srflx'} candidateType\n * @property {string} ip\n * @property {number} port\n * @property {number} priority\n * @property {'tcp'|'udp'} protocol\n * @property {string} url\n */\n\n/**\n * Standardized local RTCIceCandidate statistics.\n * @typedef {StandardizedIceCandidateStatsReport} StandardizedLocalIceCandidateStatsReport\n * @property {boolean} [deleted=false]\n * @property {'tcp'|'tls'|'udp'} relayProtocol\n */\n\n/**\n * Standardized active RTCIceCandidate pair statistics.\n * @typedef {object} StandardizedActiveIceCandidatePairStatsReport\n * @property {number} availableIncomingBitrate\n * @property {number} availableOutgoingBitrate\n * @property {number} bytesReceived\n * @property {number} bytesSent\n * @property {number} consentRequestsSent\n * @property {number} currentRoundTripTime\n * @property {number} lastPacketReceivedTimestamp\n * @property {number} lastPacketSentTimestamp\n * @property {StandardizedLocalIceCandidateStatsReport} localCandidate\n * @property {boolean} nominated\n * @property {number} priority\n * @property {boolean} readable\n * @property {StandardizedIceCandidateStatsReport} remoteCandidate\n * @property {number} requestsReceived\n * @property {number} requestsSent\n * @property {number} responsesReceived\n * @property {number} responsesSent\n * @property {number} retransmissionsReceived\n * @property {number} retransmissionsSent\n * @property {'frozen'|'waiting'|'in-progress'|'failed'|'succeeded'} state\n * @property {number} totalRoundTripTime\n * @property {string} transportId\n * @property {boolean} writable\n */\n\n/**\n * Standardized {@link RTCPeerConnection} statistics.\n * @typedef {Object} StandardizedStatsResponse\n * @property {StandardizedActiveIceCandidatePairStatsReport} activeIceCandidatePair - Stats for active ICE candidate pair\n * @property Array localAudioTrackStats - Stats for local audio MediaStreamTracks\n * @property Array localVideoTrackStats - Stats for local video MediaStreamTracks\n * @property Array remoteAudioTrackStats - Stats for remote audio MediaStreamTracks\n * @property Array remoteVideoTrackStats - Stats for remote video MediaStreamTracks\n */\n\n/**\n * Standardized MediaStreamTrack statistics.\n * @typedef {Object} StandardizedTrackStatsReport\n * @property {string} trackId - MediaStreamTrack ID\n * @property {string} ssrc - SSRC of the MediaStreamTrack\n * @property {number} timestamp - The Unix timestamp in milliseconds\n * @property {string} [codecName] - Name of the codec used to encode the MediaStreamTrack's media\n * @property {number} [roundTripTime] - Round trip time in milliseconds\n * @property {number} [jitter] - Jitter in milliseconds\n * @property {number} [frameWidthInput] - Width in pixels of the local video MediaStreamTrack's captured frame\n * @property {number} [frameHeightInput] - Height in pixels of the local video MediaStreamTrack's captured frame\n * @property {number} [frameWidthSent] - Width in pixels of the local video MediaStreamTrack's encoded frame\n * @property {number} [frameHeightSent] - Height in pixels of the local video MediaStreamTrack's encoded frame\n * @property {number} [frameWidthReceived] - Width in pixels of the remote video MediaStreamTrack's received frame\n * @property {number} [frameHeightReceived] - Height in pixels of the remote video MediaStreamTrack's received frame\n * @property {number} [frameRateInput] - Captured frames per second of the local video MediaStreamTrack\n * @property {number} [frameRateSent] - Frames per second of the local video MediaStreamTrack's encoded video\n * @property {number} [frameRateReceived] - Frames per second of the remote video MediaStreamTrack's received video\n * @property {number} [bytesReceived] - Number of bytes of the remote MediaStreamTrack's media received\n * @property {number} [bytesSent] - Number of bytes of the local MediaStreamTrack's media sent\n * @property {number} [packetsLost] - Number of packets of the MediaStreamTrack's media lost\n * @property {number} [packetsReceived] - Number of packets of the remote MediaStreamTrack's media received\n * @property {number} [packetsSent] - Number of packets of the local MediaStreamTrack's media sent\n * @property {number} [totalPacketSendDelay] - The total number of seconds that the local MediaStreamTrack's packets\n * have spent buffered locally before being sent over the network\n * @property {number} [totalEncodeTime] - The total number of seconds spent on encoding the local MediaStreamTrack's frames\n * @property {number} [framesEncoded] - The total number of frames of the local MediaStreamTrack that have been encoded sor far\n * @property {number} [estimatedPlayoutTimestamp] - The estimated playout time of the remote MediaStreamTrack\n * @property {number} [totalDecodeTime] - The total number of seconds spent on decoding the remote MediaStreamTrack's frames\n * @property {number} [framesDecoded] - The total number of frames of the remote MediaStreamTrack that have been decoded sor far\n * @property {number} [jitterBufferDelay] - The sum of the time, in seconds, each audio sample or a video frame of the remote\n * MediaStreamTrack takes from the time the first packet is received by the jitter buffer to the time it exits the jitter buffer\n * @property {number} [jitterBufferEmittedCount] - The total number of audio samples or video frames that have come out of the jitter buffer\n * @property {AudioLevel} [audioInputLevel] - The {@link AudioLevel} of the local audio MediaStreamTrack\n * @property {AudioLevel} [audioOutputLevel] - The {@link AudioLevel} of the remote video MediaStreamTrack\n */\n\nmodule.exports = getStats;\n", "/* globals navigator */\n'use strict';\n\n/**\n * This function is very similar to navigator.mediaDevices.getUserMedia\n * except that if no MediaStreamConstraints are provided, then bot audio and video\n * are requested.\n * @function getUserMedia\n * @param {MediaStreamConstraints} [constraints={audio:true,video:true}] - the\n * MediaStreamConstraints object specifying what kind of MediaStream to\n * request from the browser (by default both audio and video)\n * @returns Promise\n */\nfunction getUserMedia(constraints = { audio: true, video: true }) {\n if (typeof navigator === 'object'\n && typeof navigator.mediaDevices === 'object'\n && typeof navigator.mediaDevices.getUserMedia === 'function') {\n return navigator.mediaDevices.getUserMedia(constraints);\n }\n return Promise.reject(new Error('getUserMedia is not supported'));\n}\n\nmodule.exports = getUserMedia;\n", "/* globals MediaStream */\n'use strict';\n\nif (typeof MediaStream === 'function') {\n module.exports = MediaStream;\n} else {\n module.exports = function MediaStream() {\n throw new Error('MediaStream is not supported');\n };\n}\n", "/* global MediaStreamTrack */\n'use strict';\n\nif (typeof MediaStreamTrack === 'function') {\n module.exports = MediaStreamTrack;\n} else {\n module.exports = function MediaStreamTrack() {\n throw new Error('MediaStreamTrack is not supported');\n };\n}\n", "/* global RTCIceCandidate */\n'use strict';\n\nif (typeof RTCIceCandidate === 'function') {\n module.exports = RTCIceCandidate;\n} else {\n module.exports = function RTCIceCandidate() {\n throw new Error('RTCIceCandidate is not supported');\n };\n}\n", "/* globals RTCSessionDescription */\n'use strict';\n\n// This class wraps Chrome's RTCSessionDescription implementation. It provides\n// one piece of functionality not currently present in Chrome, namely\n//\n// 1. Rollback support\n// https://bugs.chromium.org/p/webrtc/issues/detail?id=4676\n//\nclass ChromeRTCSessionDescription {\n constructor(descriptionInitDict) {\n this.descriptionInitDict = descriptionInitDict;\n\n // If this constructor is called with an object with a .type property set to\n // \"rollback\", we should not call Chrome's RTCSessionDescription constructor,\n // because this would throw an RTCSdpType error.\n const description = descriptionInitDict && descriptionInitDict.type === 'rollback'\n ? null\n : new RTCSessionDescription(descriptionInitDict);\n\n Object.defineProperties(this, {\n _description: {\n get: function() {\n return description;\n }\n }\n });\n }\n\n get sdp() {\n return this._description ? this._description.sdp : this.descriptionInitDict.sdp;\n }\n\n get type() {\n return this._description ? this._description.type : this.descriptionInitDict.type;\n }\n}\n\nmodule.exports = ChromeRTCSessionDescription;\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", "'use strict';\n\nconst { EventEmitter } = require('events');\n\nclass EventTarget {\n constructor() {\n Object.defineProperties(this, {\n _eventEmitter: {\n value: new EventEmitter()\n }\n });\n }\n\n dispatchEvent(event) {\n return this._eventEmitter.emit(event.type, event);\n }\n\n addEventListener() {\n return this._eventEmitter.addListener(...arguments);\n }\n\n removeEventListener() {\n return this._eventEmitter.removeListener(...arguments);\n }\n}\n\nmodule.exports = EventTarget;\n", "'use strict';\n\nconst { defer } = require('./');\n\nconst states = {\n high: new Set(['low']),\n low: new Set(['high'])\n};\n\n/**\n * Construct a {@link Latch}.\n * @class\n * @classdesc A {@link Latch} has two states (\"high\" and \"low\") and methods for\n * transitioning between them ({@link Latch#raise} and {@link Latch#lower}).\n * @param {string} [initialState=\"low\"] - either \"high\" or \"low\"\n */\nclass Latch {\n constructor(initialState = 'low') {\n let state = initialState;\n Object.defineProperties(this, {\n _state: {\n set: function(_state) {\n if (state !== _state) {\n state = _state;\n const whenDeferreds = this._whenDeferreds.get(state);\n whenDeferreds.forEach(deferred => deferred.resolve(this));\n whenDeferreds.clear();\n }\n },\n get: function() {\n return state;\n }\n },\n _whenDeferreds: {\n value: new Map([\n ['high', new Set()],\n ['low', new Set()]\n ])\n }\n });\n }\n\n get state() {\n return this._state;\n }\n\n /**\n * Transition to \"low\".\n * @returns {this}\n * @throws {Error}\n */\n lower() {\n return this.transition('low');\n }\n\n /**\n * Transition to \"high\".\n * @returns {this}\n * @throws {Error}\n */\n raise() {\n return this.transition('high');\n }\n\n /**\n * Transition to a new state.\n * @param {string} newState\n * @returns {this}\n * @throws {Error}\n */\n transition(newState) {\n if (!states[this.state].has(newState)) {\n throw createUnreachableStateError(this.state, newState);\n }\n this._state = newState;\n return this;\n }\n\n /**\n * Return a Promise that resolves when the {@link Latch} transitions to\n * the specified state.\n * @param {string} state\n * @returns {Promise}\n */\n when(state) {\n if (this.state === state) {\n return Promise.resolve(this);\n }\n if (!states[this.state].has(state)) {\n return Promise.reject(createUnreachableStateError(this.state, state));\n }\n const deferred = defer();\n this._whenDeferreds.get(state).add(deferred);\n return deferred.promise;\n }\n}\n\n/**\n * Create an unreachable state Error.\n * @param {string} from - state to be transitioned from\n * @param {string} to - state to be transitioned to\n * @return {Error}\n */\nfunction createUnreachableStateError(from, to) {\n return new Error(`Cannot transition from \"${from}\" to \"${to}\"`);\n}\n\nmodule.exports = Latch;\n", "'use strict';\n\n/**\n * RTCRtpSender shim.\n * @param {MediaStreamTrack} track\n * @property {MediaStreamTrack} track\n */\nclass RTCRtpSenderShim {\n constructor(track) {\n Object.defineProperties(this, {\n track: {\n enumerable: true,\n value: track,\n writable: true\n }\n });\n }\n}\n\n// NOTE(mmalavalli): Because of the way we will be using this shim, there\n// are a couple of use cases that will not be covered:\n//\n// /* Case 1 */\n// const sender = pc.addTrack(track);\n// assert.equal(sender.track, track);\n// pc.removeTrack(sender);\n// assert.equal(sender.track, null); /* Error */\n//\n// /* Case 2 */\n// const sender = pc.addTrack(track);\n// const senders1 = new Set(pc.getSenders());\n// assert(senders1.has(sender));\n// pc.removeTrack(track);\n// const senders2 = new Set(pc.getSenders());\n// assert(senders2.has(sender)); /* Error */\n//\n// For now, since we only use senders for passing them to RTCPeerConnection#removeTrack(),\n// we will omit handling these use cases for now, and revisit them when we start\n// using the RTCRtpSender APIs.\n\nmodule.exports = RTCRtpSenderShim;\n", "/* globals RTCDataChannel, RTCPeerConnection, RTCSessionDescription */\n'use strict';\n\nconst ChromeRTCSessionDescription = require('../rtcsessiondescription/chrome');\nconst EventTarget = require('../../eventtarget');\nconst Latch = require('../util/latch');\nconst MediaStream = require('../mediastream');\nconst RTCRtpSenderShim = require('../rtcrtpsender');\nconst { getSdpFormat, updatePlanBTrackIdsToSSRCs, updateUnifiedPlanTrackIdsToSSRCs } = require('../util/sdp');\nconst { delegateMethods, interceptEvent, isIOSChrome, legacyPromise, proxyProperties } = require('../util');\n\nconst isUnifiedPlan = getSdpFormat() === 'unified';\n\n// NOTE(mroberts): This class wraps Chrome's RTCPeerConnection implementation.\n// It provides some functionality not currently present in Chrome, namely the\n// abilities to\n//\n// 1. Rollback, per the workaround suggested here:\n// https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3\n//\n// 2. Listen for track events, per the adapter.js workaround.\n//\n// 3. Set iceTransportPolicy.\n//\nclass ChromeRTCPeerConnection extends EventTarget {\n constructor(configuration = {}, constraints) {\n super();\n\n const newConfiguration = Object.assign(configuration.iceTransportPolicy\n ? { iceTransports: configuration.iceTransportPolicy }\n : {}, configuration);\n\n interceptEvent(this, 'datachannel');\n interceptEvent(this, 'signalingstatechange');\n const sdpFormat = getSdpFormat(newConfiguration.sdpSemantics);\n const peerConnection = new RTCPeerConnection(newConfiguration, constraints);\n\n Object.defineProperties(this, {\n _appliedTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _localStream: {\n value: new MediaStream()\n },\n _peerConnection: {\n value: peerConnection\n },\n _pendingLocalOffer: {\n value: null,\n writable: true\n },\n _pendingRemoteOffer: {\n value: null,\n writable: true\n },\n _rolledBackTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _sdpFormat: {\n value: sdpFormat\n },\n _senders: {\n value: new Map()\n },\n _signalingStateLatch: {\n value: new Latch()\n },\n _tracksToSSRCs: {\n value: new Map(),\n writable: true\n }\n });\n\n peerConnection.addEventListener('datachannel', event => {\n shimDataChannel(event.channel);\n this.dispatchEvent(event);\n });\n\n peerConnection.addEventListener('signalingstatechange', (...args) => {\n if (peerConnection.signalingState === 'stable') {\n this._appliedTracksToSSRCs = new Map(this._tracksToSSRCs);\n }\n if (!this._pendingLocalOffer && !this._pendingRemoteOffer) {\n this.dispatchEvent(...args);\n }\n });\n\n peerConnection.ontrack = () => {\n // NOTE(mroberts): adapter.js's \"track\" event shim only kicks off if we set\n // the ontrack property of the RTCPeerConnection.\n };\n\n if (typeof peerConnection.addTrack !== 'function') {\n peerConnection.addStream(this._localStream);\n }\n proxyProperties(RTCPeerConnection.prototype, this, peerConnection);\n }\n\n get localDescription() {\n return this._pendingLocalOffer ? this._pendingLocalOffer : this._peerConnection.localDescription;\n }\n\n get remoteDescription() {\n return this._pendingRemoteOffer ? this._pendingRemoteOffer : this._peerConnection.remoteDescription;\n }\n\n get signalingState() {\n if (this._pendingLocalOffer) {\n return 'have-local-offer';\n } else if (this._pendingRemoteOffer) {\n return 'have-remote-offer';\n }\n return this._peerConnection.signalingState;\n }\n\n // NOTE(mmalavalli): This shim supports our limited case of adding\n // all MediaStreamTracks to one MediaStream. It has been implemented this\n // keeping in mind that this is to be maintained only until \"addTrack\" is\n // supported natively in Chrome.\n addTrack(track, ...rest) {\n if (typeof this._peerConnection.addTrack === 'function') {\n return this._peerConnection.addTrack(track, ...rest);\n }\n if (this._peerConnection.signalingState === 'closed') {\n throw new Error(`Cannot add MediaStreamTrack [${track.id}, \n ${track.kind}]: RTCPeerConnection is closed`);\n }\n\n let sender = this._senders.get(track);\n if (sender && sender.track) {\n throw new Error(`Cannot add MediaStreamTrack ['${track.id}, \n ${track.kind}]: RTCPeerConnection already has it`);\n }\n this._peerConnection.removeStream(this._localStream);\n this._localStream.addTrack(track);\n this._peerConnection.addStream(this._localStream);\n sender = new RTCRtpSenderShim(track);\n this._senders.set(track, sender);\n return sender;\n }\n\n // NOTE(mmalavalli): This shim supports our limited case of removing\n // MediaStreamTracks from one MediaStream. It has been implemented this\n // keeping in mind that this is to be maintained only until \"removeTrack\" is\n // supported natively in Chrome.\n removeTrack(sender) {\n if (this._peerConnection.signalingState === 'closed') {\n throw new Error('Cannot remove MediaStreamTrack: RTCPeerConnection is closed');\n }\n if (typeof this._peerConnection.addTrack === 'function') {\n try {\n return this._peerConnection.removeTrack(sender);\n } catch (e) {\n // NOTE(mhuynh): Do nothing. In Chrome, will throw if a 'sender was not\n // created by this peer connection'. This behavior does not seem to be\n // spec compliant, so a temporary shim is introduced. A bug has been filed,\n // and is tracked here:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=860853\n }\n } else {\n const { track } = sender;\n if (!track) {\n return;\n }\n sender = this._senders.get(track);\n if (sender && sender.track) {\n sender.track = null;\n this._peerConnection.removeStream(this._localStream);\n this._localStream.removeTrack(track);\n this._peerConnection.addStream(this._localStream);\n }\n }\n }\n\n getSenders() {\n if (typeof this._peerConnection.addTrack === 'function') {\n return this._peerConnection.getSenders();\n }\n return Array.from(this._senders.values());\n }\n\n addIceCandidate(candidate, ...rest) {\n let promise;\n\n if (this.signalingState === 'have-remote-offer') {\n // NOTE(mroberts): Because the ChromeRTCPeerConnection simulates the\n // \"have-remote-offer\" signalingStates, we only want to invoke the true\n // addIceCandidates method when the remote description has been applied.\n promise = this._signalingStateLatch.when('low').then(() =>\n this._peerConnection.addIceCandidate(candidate));\n } else {\n promise = this._peerConnection.addIceCandidate(candidate);\n }\n\n return rest.length > 0\n ? legacyPromise(promise, ...rest)\n : promise;\n }\n\n // NOTE(mroberts): The WebRTC spec does not specify that close should throw an\n // Error; however, in Chrome it does. We workaround this by checking the\n // signalingState manually.\n close() {\n if (this.signalingState !== 'closed') {\n this._pendingLocalOffer = null;\n this._pendingRemoteOffer = null;\n this._peerConnection.close();\n }\n }\n\n // NOTE(mroberts): Because we workaround Chrome's lack of rollback support by\n // \"faking\" setRemoteDescription, we cannot create an answer until we actually\n // apply the remote description. This means, once you call createAnswer, you\n // can no longer rollback. This is acceptable for our use case because we will\n // apply the newly-created answer almost immediately; however, this may be\n // unacceptable for other use cases.\n createAnswer(...args) {\n let promise;\n\n if (this._pendingRemoteOffer) {\n promise = this._peerConnection.setRemoteDescription(this._pendingRemoteOffer).then(() => {\n // NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection\n // and the underlying RTCPeerConnection implementation have converged. We\n // can unblock any pending calls to addIceCandidate now.\n this._signalingStateLatch.lower();\n return this._peerConnection.createAnswer();\n }).then(answer => {\n this._pendingRemoteOffer = null;\n\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new ChromeRTCSessionDescription({\n type: 'answer',\n sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, answer.sdp)\n });\n }, error => {\n this._pendingRemoteOffer = null;\n throw error;\n });\n } else {\n promise = this._peerConnection.createAnswer().then(answer => {\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new ChromeRTCSessionDescription({\n type: 'answer',\n sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, answer.sdp)\n });\n });\n }\n\n return args.length > 1\n ? legacyPromise(promise, ...args)\n : promise;\n }\n\n createOffer(...args) {\n const [arg1, arg2, arg3] = args;\n const options = arg3 || arg1 || {};\n\n if (isIOSChrome()) {\n // NOTE (joma): From SafariRTCPeerConnection in order to support iOS Chrome.\n if (options.offerToReceiveVideo && !this._audioTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'audio'))) {\n delete options.offerToReceiveAudio;\n try {\n this._audioTransceiver = isUnifiedPlan\n ? this.addTransceiver('audio', { direction: 'recvonly' })\n : this.addTransceiver('audio');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'video'))) {\n delete options.offerToReceiveVideo;\n try {\n this._videoTransceiver = isUnifiedPlan\n ? this.addTransceiver('video', { direction: 'recvonly' })\n : this.addTransceiver('video');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n }\n\n const promise = this._peerConnection.createOffer(options).then(offer => {\n // NOTE(mmalavalli): If createOffer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new ChromeRTCSessionDescription({\n type: offer.type,\n sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, offer.sdp)\n });\n });\n\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n\n createDataChannel(label, dataChannelDict) {\n dataChannelDict = shimDataChannelInit(dataChannelDict);\n const dataChannel = this._peerConnection.createDataChannel(label, dataChannelDict);\n shimDataChannel(dataChannel);\n return dataChannel;\n }\n\n setLocalDescription(...args) {\n const [description, arg1, arg2] = args;\n\n // NOTE(mmalavalli): If setLocalDescription() is called immediately after rolling back,\n // then we need to restore the rolled back tracks to SSRCs Map.\n if (this._rolledBackTracksToSSRCs.size > 0) {\n this._tracksToSSRCs = new Map(this._rolledBackTracksToSSRCs);\n this._rolledBackTracksToSSRCs.clear();\n }\n\n const promise = setDescription(this, true, description);\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n\n setRemoteDescription(...args) {\n const [description, arg1, arg2] = args;\n\n // NOTE(mmalavalli): If setRemoteDescription() is called immediately after rolling back,\n // then we no longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n const promise = setDescription(this, false, description);\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n}\n\ndelegateMethods(\n RTCPeerConnection.prototype,\n ChromeRTCPeerConnection.prototype,\n '_peerConnection');\n\n// NOTE(mroberts): We workaround Chrome's lack of rollback support, per the\n// workaround suggested here: https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3\n// Namely, we \"fake\" setting the local or remote description and instead buffer\n// it. If we receive or create an answer, then we will actually apply the\n// description. Until we receive or create an answer, we will be able to\n// \"rollback\" by simply discarding the buffer description.\nfunction setDescription(peerConnection, local, description) {\n function setPendingLocalOffer(offer) {\n if (local) {\n peerConnection._pendingLocalOffer = offer;\n } else {\n peerConnection._pendingRemoteOffer = offer;\n }\n }\n\n function clearPendingLocalOffer() {\n if (local) {\n peerConnection._pendingLocalOffer = null;\n } else {\n peerConnection._pendingRemoteOffer = null;\n }\n }\n\n const pendingLocalOffer = local ? peerConnection._pendingLocalOffer : peerConnection._pendingRemoteOffer;\n const pendingRemoteOffer = local ? peerConnection._pendingRemoteOffer : peerConnection._pendingLocalOffer;\n const intermediateState = local ? 'have-local-offer' : 'have-remote-offer';\n const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription';\n let promise;\n\n if (!local && pendingRemoteOffer && description.type === 'answer') {\n promise = setRemoteAnswer(peerConnection, description);\n } else if (description.type === 'offer') {\n if (peerConnection.signalingState !== intermediateState && peerConnection.signalingState !== 'stable') {\n // NOTE(mroberts): Error message copied from Firefox.\n return Promise.reject(new Error(`Cannot set ${local ? 'local' : 'remote'} offer in state ${peerConnection.signalingState}`));\n }\n\n // We need to save this local offer in case of a rollback. We also need to\n // check to see if the signalingState between the ChromeRTCPeerConnection\n // and the underlying RTCPeerConnection implementation are about to diverge.\n // If so, we need to ensure subsequent calls to addIceCandidate will block.\n if (!pendingLocalOffer && peerConnection._signalingStateLatch.state === 'low') {\n peerConnection._signalingStateLatch.raise();\n }\n const previousSignalingState = peerConnection.signalingState;\n setPendingLocalOffer(unwrap(description));\n promise = Promise.resolve();\n\n // Only dispatch a signalingstatechange event if we transitioned.\n if (peerConnection.signalingState !== previousSignalingState) {\n promise.then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n\n } else if (description.type === 'rollback') {\n if (peerConnection.signalingState !== intermediateState) {\n // NOTE(mroberts): Error message copied from Firefox.\n promise = Promise.reject(new Error(`Cannot rollback ${local ? 'local' : 'remote'} description in ${peerConnection.signalingState}`));\n } else {\n // Reset the pending offer.\n clearPendingLocalOffer();\n\n // NOTE(mmalavalli): We store the rolled back tracks to SSRCs Map here in case\n // setLocalDescription() is called immediately after a rollback (without calling\n // createOffer() or createAnswer()), in which case this roll back is not due to a\n // glare scenario and this Map should be restored.\n peerConnection._rolledBackTracksToSSRCs = new Map(peerConnection._tracksToSSRCs);\n peerConnection._tracksToSSRCs = new Map(peerConnection._appliedTracksToSSRCs);\n\n promise = Promise.resolve();\n promise.then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n }\n\n return promise || peerConnection._peerConnection[setLocalDescription](unwrap(description));\n}\n\nfunction setRemoteAnswer(peerConnection, answer) {\n // Apply the pending local offer.\n const pendingLocalOffer = peerConnection._pendingLocalOffer;\n return peerConnection._peerConnection.setLocalDescription(pendingLocalOffer).then(() => {\n peerConnection._pendingLocalOffer = null;\n return peerConnection.setRemoteDescription(answer);\n }).then(() => {\n // NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection\n // and the underlying RTCPeerConnection implementation have converged. We\n // can unblock any pending calls to addIceCandidate now.\n peerConnection._signalingStateLatch.lower();\n });\n}\n\n/**\n * Whether a ChromeRTCPeerConnection has any RTCRtpReceivers(s) for the given\n * MediaStreamTrack kind.\n * @param {ChromeRTCPeerConnection} peerConnection\n * @param {'audio' | 'video'} kind\n * @returns {boolean}\n */\nfunction hasReceiversForTracksOfKind(peerConnection, kind) {\n return !!peerConnection.getTransceivers().find(({ receiver = {} }) => {\n const { track = {} } = receiver;\n return track.kind === kind;\n });\n}\n\nfunction unwrap(description) {\n if (description instanceof ChromeRTCSessionDescription) {\n if (description._description) {\n return description._description;\n }\n }\n return new RTCSessionDescription(description);\n}\n\n/**\n * Check whether or not we need to apply our maxPacketLifeTime shim. We are\n * pretty conservative: we'll only apply it if the legacy maxRetransmitTime\n * property is available _and_ the standard maxPacketLifeTime property is _not_\n * available (the thinking being that Chrome will land the standards-compliant\n * property).\n * @returns {boolean}\n */\nfunction needsMaxPacketLifeTimeShim() {\n return 'maxRetransmitTime' in RTCDataChannel.prototype\n && !('maxPacketLifeTime' in RTCDataChannel.prototype);\n}\n\n/**\n * Shim an RTCDataChannelInit dictionary (if necessary). This function returns\n * a copy of the original RTCDataChannelInit.\n * @param {RTCDataChannelInit} dataChannelDict\n * @returns {RTCDataChannelInit}\n */\nfunction shimDataChannelInit(dataChannelDict) {\n dataChannelDict = Object.assign({}, dataChannelDict);\n if (needsMaxPacketLifeTimeShim() && 'maxPacketLifeTime' in dataChannelDict) {\n dataChannelDict.maxRetransmitTime = dataChannelDict.maxPacketLifeTime;\n }\n return dataChannelDict;\n}\n\n/**\n * Shim an RTCDataChannel (if necessary). This function mutates the\n * RTCDataChannel.\n * @param {RTCDataChannel} dataChannel\n * @returns {RTCDataChannel}\n */\nfunction shimDataChannel(dataChannel) {\n Object.defineProperty(dataChannel, 'maxRetransmits', {\n value: dataChannel.maxRetransmits === 65535\n ? null\n : dataChannel.maxRetransmits\n });\n if (needsMaxPacketLifeTimeShim()) {\n // NOTE(mroberts): We can rename `maxRetransmitTime` to `maxPacketLifeTime`.\n //\n // https://bugs.chromium.org/p/chromium/issues/detail?id=696681\n //\n Object.defineProperty(dataChannel, 'maxPacketLifeTime', {\n value: dataChannel.maxRetransmitTime === 65535\n ? null\n : dataChannel.maxRetransmitTime\n });\n }\n return dataChannel;\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the SDP itself. This method\n * ensures that SSRCs never change once announced.\n * @param {'planb'|'unified'} sdpFormat\n * @param {Map>} tracksToSSRCs\n * @param {string} sdp - an SDP whose format is determined by `sdpSemantics`\n * @returns {string} updatedSdp - updated SDP\n */\nfunction updateTrackIdsToSSRCs(sdpFormat, tracksToSSRCs, sdp) {\n return sdpFormat === 'unified'\n ? updateUnifiedPlanTrackIdsToSSRCs(tracksToSSRCs, sdp)\n : updatePlanBTrackIdsToSSRCs(tracksToSSRCs, sdp);\n}\n\nmodule.exports = ChromeRTCPeerConnection;\n", "/* globals RTCSessionDescription */\n'use strict';\n\nmodule.exports = RTCSessionDescription;\n", "/* globals RTCPeerConnection */\n'use strict';\n\nconst EventTarget = require('../../eventtarget');\nconst FirefoxRTCSessionDescription = require('../rtcsessiondescription/firefox');\nconst { updateUnifiedPlanTrackIdsToSSRCs: updateTracksToSSRCs } = require('../util/sdp');\nconst { delegateMethods, interceptEvent, legacyPromise, proxyProperties } = require('../util');\n\n// NOTE(mroberts): This class wraps Firefox's RTCPeerConnection implementation.\n// It provides some functionality not currently present in Firefox, namely the\n// abilities to\n//\n// 1. Call setLocalDescription and setRemoteDescription with new offers in\n// signalingStates \"have-local-offer\" and \"have-remote-offer\",\n// respectively.\n//\n// 2. The ability to call createOffer in signalingState \"have-local-offer\".\n//\n// Both of these are implemented using rollbacks to workaround the following\n// bug:\n//\n// https://bugzilla.mozilla.org/show_bug.cgi?id=1072388\n//\n// We also provide a workaround for a bug where Firefox may change the\n// previously-negotiated DTLS role in an answer, which breaks Chrome:\n//\n// https://bugzilla.mozilla.org/show_bug.cgi?id=1240897\n//\nclass FirefoxRTCPeerConnection extends EventTarget {\n constructor(configuration) {\n super();\n\n interceptEvent(this, 'signalingstatechange');\n\n /* eslint new-cap:0 */\n const peerConnection = new RTCPeerConnection(configuration);\n\n Object.defineProperties(this, {\n _initiallyNegotiatedDtlsRole: {\n value: null,\n writable: true\n },\n _isClosed: {\n value: false,\n writable: true\n },\n _peerConnection: {\n value: peerConnection\n },\n _rollingBack: {\n value: false,\n writable: true\n },\n _tracksToSSRCs: {\n value: new Map()\n },\n\n // NOTE(mmalavalli): Firefox throws a TypeError when the PeerConnection's\n // prototype's \"peerIdentity\" property is accessed. In order to overcome\n // this, we ignore this property while delegating methods.\n // Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=1363815\n peerIdentity: {\n enumerable: true,\n value: Promise.resolve({\n idp: '',\n name: ''\n })\n }\n });\n\n let previousSignalingState;\n\n peerConnection.addEventListener('signalingstatechange', (...args) => {\n if (!this._rollingBack && this.signalingState !== previousSignalingState) {\n previousSignalingState = this.signalingState;\n\n // NOTE(mmalavalli): In Firefox, 'signalingstatechange' event is\n // triggered synchronously in the same tick after\n // RTCPeerConnection#close() is called. So we mimic Chrome's behavior\n // by triggering 'signalingstatechange' on the next tick.\n if (this._isClosed) {\n setTimeout(() => this.dispatchEvent(...args));\n } else {\n this.dispatchEvent(...args);\n }\n }\n });\n\n proxyProperties(RTCPeerConnection.prototype, this, peerConnection);\n }\n\n get iceGatheringState() {\n return this._isClosed ? 'complete' : this._peerConnection.iceGatheringState;\n }\n\n get localDescription() {\n return overwriteWithInitiallyNegotiatedDtlsRole(this._peerConnection.localDescription, this._initiallyNegotiatedDtlsRole);\n }\n\n get signalingState() {\n return this._isClosed ? 'closed' : this._peerConnection.signalingState;\n }\n\n createAnswer(...args) {\n let promise;\n\n promise = this._peerConnection.createAnswer().then(answer => {\n saveInitiallyNegotiatedDtlsRole(this, answer);\n return overwriteWithInitiallyNegotiatedDtlsRole(answer, this._initiallyNegotiatedDtlsRole);\n });\n\n return typeof args[0] === 'function'\n ? legacyPromise(promise, ...args)\n : promise;\n }\n\n // NOTE(mroberts): The WebRTC spec allows you to call createOffer from any\n // signalingState other than \"closed\"; however, Firefox has not yet implemented\n // this (https://bugzilla.mozilla.org/show_bug.cgi?id=1072388). We workaround\n // this by rolling back if we are in state \"have-local-offer\" or\n // \"have-remote-offer\". This is acceptable for our use case because we will\n // apply the newly-created offer almost immediately; however, this may be\n // unacceptable for other use cases.\n createOffer(...args) {\n const [arg1, arg2, arg3] = args;\n const options = arg3 || arg1 || {};\n let promise;\n\n if (this.signalingState === 'have-local-offer' ||\n this.signalingState === 'have-remote-offer') {\n const local = this.signalingState === 'have-local-offer';\n promise = rollback(this, local, () => this.createOffer(options));\n } else {\n promise = this._peerConnection.createOffer(options);\n }\n\n promise = promise.then(offer => {\n return new FirefoxRTCSessionDescription({\n type: offer.type,\n sdp: updateTracksToSSRCs(this._tracksToSSRCs, offer.sdp)\n });\n });\n\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n\n // NOTE(mroberts): While Firefox will reject the Promise returned by\n // setLocalDescription when called from signalingState \"have-local-offer\" with\n // an answer, it still updates the .localDescription property. We workaround\n // this by explicitly handling this case.\n setLocalDescription(...args) {\n const [description, ...rest] = args;\n let promise;\n\n if (description && description.type === 'answer' && this.signalingState === 'have-local-offer') {\n promise = Promise.reject(new Error('Cannot set local answer in state have-local-offer'));\n }\n\n if (promise) {\n return args.length > 1\n ? legacyPromise(promise, ...rest)\n : promise;\n }\n\n return this._peerConnection.setLocalDescription(...args);\n }\n\n // NOTE(mroberts): The WebRTC spec allows you to call setRemoteDescription with\n // an offer multiple times in signalingState \"have-remote-offer\"; however,\n // Firefox has not yet implemented this (https://bugzilla.mozilla.org/show_bug.cgi?id=1072388).\n // We workaround this by rolling back if we are in state \"have-remote-offer\".\n // This is acceptable for our use case; however, this may be unacceptable for\n // other use cases.\n //\n // While Firefox will reject the Promise returned by setRemoteDescription when\n // called from signalingState \"have-remote-offer\" with an answer, it sill\n // updates the .remoteDescription property. We workaround this by explicitly\n // handling this case.\n setRemoteDescription(...args) {\n const [description, ...rest] = args;\n\n let promise;\n\n if (description && this.signalingState === 'have-remote-offer') {\n if (description.type === 'answer') {\n promise = Promise.reject(new Error('Cannot set remote answer in state have-remote-offer'));\n } else if (description.type === 'offer') {\n promise = rollback(this, false, () => this._peerConnection.setRemoteDescription(description));\n }\n }\n\n if (!promise) {\n promise = this._peerConnection.setRemoteDescription(description);\n }\n\n promise = promise.then(() => saveInitiallyNegotiatedDtlsRole(this, description, true));\n\n return args.length > 1\n ? legacyPromise(promise, ...rest)\n : promise;\n }\n\n // NOTE(mroberts): The WebRTC spec specifies that the PeerConnection's internal\n // isClosed slot should immediately be set to true; however, in Firefox it\n // occurs in the next tick. We workaround this by tracking isClosed manually.\n close() {\n if (this.signalingState !== 'closed') {\n this._isClosed = true;\n this._peerConnection.close();\n }\n }\n}\n\ndelegateMethods(\n RTCPeerConnection.prototype,\n FirefoxRTCPeerConnection.prototype,\n '_peerConnection');\n\nfunction rollback(peerConnection, local, onceRolledBack) {\n const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription';\n peerConnection._rollingBack = true;\n return peerConnection._peerConnection[setLocalDescription](new FirefoxRTCSessionDescription({\n type: 'rollback'\n })).then(onceRolledBack).then(result => {\n peerConnection._rollingBack = false;\n return result;\n }, error => {\n peerConnection._rollingBack = false;\n throw error;\n });\n}\n\n/**\n * Extract the initially negotiated DTLS role out of an RTCSessionDescription's\n * sdp property and save it on the FirefoxRTCPeerConnection if and only if\n *\n * 1. A DTLS role was not already saved on the FirefoxRTCPeerConnection, and\n * 2. The description is an answer.\n *\n * @private\n * @param {FirefoxRTCPeerConnection} peerConnection\n * @param {RTCSessionDescription} description\n * @param {boolean} [remote=false] - if true, save the inverse of the DTLS role,\n * e.g. \"active\" instead of \"passive\" and vice versa\n * @returns {undefined}\n */\nfunction saveInitiallyNegotiatedDtlsRole(peerConnection, description, remote) {\n // NOTE(mroberts): JSEP specifies that offers always offer \"actpass\" as the\n // DTLS role. We need to inspect answers to figure out the negotiated DTLS\n // role.\n if (peerConnection._initiallyNegotiatedDtlsRole || description.type === 'offer') {\n return;\n }\n\n const match = description.sdp.match(/a=setup:([a-z]+)/);\n if (!match) {\n return;\n }\n\n const dtlsRole = match[1];\n peerConnection._initiallyNegotiatedDtlsRole = remote ? {\n active: 'passive',\n passive: 'active'\n }[dtlsRole] : dtlsRole;\n}\n\n/**\n * Overwrite the DTLS role in the sdp property of an RTCSessionDescription if\n * and only if\n *\n * 1. The description is an answer, and\n * 2. A DTLS role is provided.\n *\n * @private\n * @param {RTCSessionDescription} [description]\n * @param {string} [dtlsRole] - one of \"active\" or \"passive\"\n * @returns {?RTCSessionDescription} description\n */\nfunction overwriteWithInitiallyNegotiatedDtlsRole(description, dtlsRole) {\n if (description && description.type === 'answer' && dtlsRole) {\n return new FirefoxRTCSessionDescription({\n type: description.type,\n sdp: description.sdp.replace(/a=setup:[a-z]+/g, 'a=setup:' + dtlsRole)\n });\n }\n return description;\n}\n\nmodule.exports = FirefoxRTCPeerConnection;\n", "/* globals RTCPeerConnection, RTCSessionDescription */\n'use strict';\n\nconst EventTarget = require('../../eventtarget');\nconst Latch = require('../util/latch');\nconst { getSdpFormat, updatePlanBTrackIdsToSSRCs, updateUnifiedPlanTrackIdsToSSRCs } = require('../util/sdp');\nconst { delegateMethods, interceptEvent, proxyProperties } = require('../util');\n\nconst isUnifiedPlan = getSdpFormat() === 'unified';\n\nconst updateTrackIdsToSSRCs = isUnifiedPlan\n ? updateUnifiedPlanTrackIdsToSSRCs\n : updatePlanBTrackIdsToSSRCs;\n\nclass SafariRTCPeerConnection extends EventTarget {\n constructor(configuration) {\n super();\n\n interceptEvent(this, 'datachannel');\n interceptEvent(this, 'iceconnectionstatechange');\n interceptEvent(this, 'signalingstatechange');\n interceptEvent(this, 'track');\n\n const peerConnection = new RTCPeerConnection(configuration);\n\n Object.defineProperties(this, {\n _appliedTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _audioTransceiver: {\n value: null,\n writable: true\n },\n _isClosed: {\n value: false,\n writable: true\n },\n _peerConnection: {\n value: peerConnection\n },\n _pendingLocalOffer: {\n value: null,\n writable: true\n },\n _pendingRemoteOffer: {\n value: null,\n writable: true\n },\n _rolledBackTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _signalingStateLatch: {\n value: new Latch()\n },\n _tracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _videoTransceiver: {\n value: null,\n writable: true\n }\n });\n\n peerConnection.addEventListener('datachannel', event => {\n shimDataChannel(event.channel);\n this.dispatchEvent(event);\n });\n\n peerConnection.addEventListener('iceconnectionstatechange', (...args) => {\n if (this._isClosed) {\n return;\n }\n this.dispatchEvent(...args);\n });\n\n peerConnection.addEventListener('signalingstatechange', (...args) => {\n if (this._isClosed) {\n return;\n }\n if (peerConnection.signalingState === 'stable') {\n this._appliedTracksToSSRCs = new Map(this._tracksToSSRCs);\n }\n if (!this._pendingLocalOffer && !this._pendingRemoteOffer) {\n this.dispatchEvent(...args);\n }\n });\n\n // NOTE(syerrapragada): This ensures that SafariRTCPeerConnection's \"remoteDescription\", when accessed\n // in an RTCTrackEvent listener, will point to the underlying RTCPeerConnection's\n // \"remoteDescription\". Before this fix, this was still pointing to \"_pendingRemoteOffer\"\n // even though a new remote RTCSessionDescription had already been applied.\n peerConnection.addEventListener('track', event => {\n this._pendingRemoteOffer = null;\n this.dispatchEvent(event);\n });\n\n proxyProperties(RTCPeerConnection.prototype, this, peerConnection);\n\n }\n\n get localDescription() {\n return this._pendingLocalOffer || this._peerConnection.localDescription;\n }\n\n get iceConnectionState() {\n return this._isClosed ? 'closed' : this._peerConnection.iceConnectionState;\n }\n\n get iceGatheringState() {\n return this._isClosed ? 'complete' : this._peerConnection.iceGatheringState;\n }\n\n get remoteDescription() {\n return this._pendingRemoteOffer || this._peerConnection.remoteDescription;\n }\n\n get signalingState() {\n if (this._isClosed) {\n return 'closed';\n } else if (this._pendingLocalOffer) {\n return 'have-local-offer';\n } else if (this._pendingRemoteOffer) {\n return 'have-remote-offer';\n }\n return this._peerConnection.signalingState;\n }\n\n addIceCandidate(candidate) {\n if (this.signalingState === 'have-remote-offer') {\n return this._signalingStateLatch.when('low').then(() => this._peerConnection.addIceCandidate(candidate));\n }\n return this._peerConnection.addIceCandidate(candidate);\n }\n\n createOffer(options) {\n options = Object.assign({}, options);\n\n // NOTE(mroberts): In general, this is not the way to do this; however, it's\n // good enough for our application.\n if (options.offerToReceiveVideo && !this._audioTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'audio'))) {\n delete options.offerToReceiveAudio;\n try {\n this._audioTransceiver = isUnifiedPlan\n ? this.addTransceiver('audio', { direction: 'recvonly' })\n : this.addTransceiver('audio');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'video'))) {\n delete options.offerToReceiveVideo;\n try {\n this._videoTransceiver = isUnifiedPlan\n ? this.addTransceiver('video', { direction: 'recvonly' })\n : this.addTransceiver('video');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n return this._peerConnection.createOffer(options).then(offer => {\n // NOTE(mmalavalli): If createOffer() is called immediately after rolling back,\n // then we no longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new RTCSessionDescription({\n type: offer.type,\n sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, offer.sdp)\n });\n });\n }\n\n createAnswer(options) {\n if (this._pendingRemoteOffer) {\n return this._peerConnection.setRemoteDescription(this._pendingRemoteOffer).then(() => {\n this._signalingStateLatch.lower();\n return this._peerConnection.createAnswer();\n }).then(answer => {\n this._pendingRemoteOffer = null;\n\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return isUnifiedPlan ? new RTCSessionDescription({\n type: answer.type,\n sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, answer.sdp)\n }) : answer;\n }, error => {\n this._pendingRemoteOffer = null;\n throw error;\n });\n }\n\n return this._peerConnection.createAnswer(options).then(answer => {\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return isUnifiedPlan ? new RTCSessionDescription({\n type: answer.type,\n sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, answer.sdp)\n }) : answer;\n });\n }\n\n createDataChannel(label, dataChannelDict) {\n const dataChannel = this._peerConnection.createDataChannel(label, dataChannelDict);\n shimDataChannel(dataChannel);\n return dataChannel;\n }\n\n removeTrack(sender) {\n sender.replaceTrack(null);\n this._peerConnection.removeTrack(sender);\n }\n\n setLocalDescription(description) {\n // NOTE(mmalavalli): If setLocalDescription() is called immediately after rolling back,\n // then we need to restore the rolled back tracks to SSRCs Map.\n if (this._rolledBackTracksToSSRCs.size > 0) {\n this._tracksToSSRCs = new Map(this._rolledBackTracksToSSRCs);\n this._rolledBackTracksToSSRCs.clear();\n }\n return setDescription(this, true, description);\n }\n\n setRemoteDescription(description) {\n // NOTE(mmalavalli): If setRemoteDescription() is called immediately after rolling back,\n // then we no longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n return setDescription(this, false, description);\n }\n\n close() {\n if (this._isClosed) {\n return;\n }\n this._isClosed = true;\n this._peerConnection.close();\n setTimeout(() => {\n this.dispatchEvent(new Event('iceconnectionstatechange'));\n this.dispatchEvent(new Event('signalingstatechange'));\n });\n }\n}\n\ndelegateMethods(\n RTCPeerConnection.prototype,\n SafariRTCPeerConnection.prototype,\n '_peerConnection');\n\nfunction setDescription(peerConnection, local, description) {\n function setPendingLocalOffer(offer) {\n if (local) {\n peerConnection._pendingLocalOffer = offer;\n } else {\n peerConnection._pendingRemoteOffer = offer;\n }\n }\n\n function clearPendingLocalOffer() {\n if (local) {\n peerConnection._pendingLocalOffer = null;\n } else {\n peerConnection._pendingRemoteOffer = null;\n }\n }\n\n const pendingLocalOffer = local ? peerConnection._pendingLocalOffer : peerConnection._pendingRemoteOffer;\n const pendingRemoteOffer = local ? peerConnection._pendingRemoteOffer : peerConnection._pendingLocalOffer;\n const intermediateState = local ? 'have-local-offer' : 'have-remote-offer';\n const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription';\n\n if (!local && pendingRemoteOffer && description.type === 'answer') {\n return setRemoteAnswer(peerConnection, description);\n } else if (description.type === 'offer') {\n if (peerConnection.signalingState !== intermediateState && peerConnection.signalingState !== 'stable') {\n return Promise.reject(new Error(`Cannot set ${local ? 'local' : 'remote'}\n offer in state ${peerConnection.signalingState}`));\n }\n\n if (!pendingLocalOffer && peerConnection._signalingStateLatch.state === 'low') {\n peerConnection._signalingStateLatch.raise();\n }\n const previousSignalingState = peerConnection.signalingState;\n setPendingLocalOffer(description);\n\n // Only dispatch a signalingstatechange event if we transitioned.\n if (peerConnection.signalingState !== previousSignalingState) {\n return Promise.resolve().then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n\n return Promise.resolve();\n } else if (description.type === 'rollback') {\n if (peerConnection.signalingState !== intermediateState) {\n return Promise.reject(new Error(`Cannot rollback \n ${local ? 'local' : 'remote'} description in ${peerConnection.signalingState}`));\n }\n clearPendingLocalOffer();\n\n // NOTE(mmalavalli): We store the rolled back tracks to SSRCs Map here in case\n // setLocalDescription() is called immediately aftera rollback (without calling\n // createOffer() or createAnswer()), in which case this roll back is not due to\n // a glare scenario and this Map should be restored.\n peerConnection._rolledBackTracksToSSRCs = new Map(peerConnection._tracksToSSRCs);\n peerConnection._tracksToSSRCs = new Map(peerConnection._appliedTracksToSSRCs);\n\n return Promise.resolve().then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n\n return peerConnection._peerConnection[setLocalDescription](description);\n}\n\nfunction setRemoteAnswer(peerConnection, answer) {\n const pendingLocalOffer = peerConnection._pendingLocalOffer;\n return peerConnection._peerConnection.setLocalDescription(pendingLocalOffer).then(() => {\n peerConnection._pendingLocalOffer = null;\n return peerConnection.setRemoteDescription(answer);\n }).then(() => peerConnection._signalingStateLatch.lower());\n}\n\n/**\n * Whether a SafariRTCPeerConnection has any RTCRtpReceivers(s) for the given\n * MediaStreamTrack kind.\n * @param {SafariRTCPeerConnection} peerConnection\n * @param {'audio' | 'video'} kind\n * @returns {boolean}\n */\nfunction hasReceiversForTracksOfKind(peerConnection, kind) {\n return !!peerConnection.getTransceivers().find(({ receiver = {} }) => {\n const { track = {} } = receiver;\n return track.kind === kind;\n });\n}\n\n/**\n * Shim an RTCDataChannel. This function mutates the RTCDataChannel.\n * @param {RTCDataChannel} dataChannel\n * @returns {RTCDataChannel}\n */\nfunction shimDataChannel(dataChannel) {\n return Object.defineProperties(dataChannel, {\n maxPacketLifeTime: {\n value: dataChannel.maxPacketLifeTime === 65535\n ? null\n : dataChannel.maxPacketLifeTime\n },\n maxRetransmits: {\n value: dataChannel.maxRetransmits === 65535\n ? null\n : dataChannel.maxRetransmits\n }\n });\n}\n\nmodule.exports = SafariRTCPeerConnection;\n", "'use strict';\n\nif (typeof RTCPeerConnection === 'function') {\n const { guessBrowser } = require('../util');\n switch (guessBrowser()) {\n case 'chrome':\n module.exports = require('./chrome');\n break;\n case 'firefox':\n module.exports = require('./firefox');\n break;\n case 'safari':\n module.exports = require('./safari');\n break;\n default:\n module.exports = RTCPeerConnection;\n break;\n }\n} else {\n module.exports = function RTCPeerConnection() {\n throw new Error('RTCPeerConnection is not supported');\n };\n}\n", "/* globals RTCSessionDescription */\n'use strict';\n\nif (typeof RTCSessionDescription === 'function') {\n const { guessBrowser } = require('../util');\n switch (guessBrowser()) {\n case 'chrome':\n module.exports = require('./chrome');\n break;\n case 'firefox':\n module.exports = require('./firefox');\n break;\n default:\n module.exports = RTCSessionDescription;\n break;\n }\n} else {\n module.exports = function RTCSessionDescription() {\n throw new Error('RTCSessionDescription is not supported');\n };\n}\n", "'use strict';\n\nconst WebRTC = {};\n\nObject.defineProperties(WebRTC, {\n getStats: {\n enumerable: true,\n value: require('./getstats')\n },\n getUserMedia: {\n enumerable: true,\n value: require('./getusermedia')\n },\n MediaStream: {\n enumerable: true,\n value: require('./mediastream')\n },\n MediaStreamTrack: {\n enumerable: true,\n value: require('./mediastreamtrack')\n },\n RTCIceCandidate: {\n enumerable: true,\n value: require('./rtcicecandidate')\n },\n RTCPeerConnection: {\n enumerable: true,\n value: require('./rtcpeerconnection')\n },\n RTCSessionDescription: {\n enumerable: true,\n value: require('./rtcsessiondescription')\n }\n});\n\nmodule.exports = WebRTC;\n", "/**\n * Copyright (c) 2011-2022 Isaac Z. Schlueter\n * Licensed under the ISC License.\n *\n * Copied from https://github.com/isaacs/inherits (2.0.4)\n*/\n\nmodule.exports = function inherits(ctor, superCtor) {\n if (ctor && superCtor) {\n ctor.super_ = superCtor;\n if (typeof Object.create === 'function') {\n // implementation from standard node.js 'util' module\n ctor.prototype = Object.create(superCtor.prototype, {\n constructor: {\n value: ctor,\n enumerable: false,\n writable: true,\n configurable: true\n }\n });\n } else {\n // old school shim for old browsers\n class TempCtor {\n constructor() { }\n }\n TempCtor.prototype = superCtor.prototype;\n ctor.prototype = new TempCtor();\n ctor.prototype.constructor = ctor;\n }\n }\n};\n", "/* globals chrome, navigator */\n'use strict';\n\n/**\n * Check whether the current browser is an Android device.\n * @returns {boolean}\n */\nfunction isAndroid() {\n return /Android/.test(navigator.userAgent);\n}\n\n/**\n * Detects whether or not a device is an Apple touch screen device.\n * @returns {boolean}\n */\nfunction hasTouchScreen() {\n return !!(navigator && navigator.maxTouchPoints && navigator.maxTouchPoints > 2);\n}\n\n/**\n * Detects whether or not a device is an iPad.\n * @returns {boolean}\n */\nfunction isIpad() {\n return hasTouchScreen() && window.screen.width >= 744 && (/Macintosh/i.test(navigator.userAgent)\n || /iPad/.test(navigator.userAgent)\n || /iPad/.test(navigator.platform));\n}\n\n/**\n * Detects whether or not a device is an iPhone.\n * @returns {boolean}\n */\nfunction isIphone() {\n return hasTouchScreen() && window.screen.width <= 476 && (/Macintosh/i.test(navigator.userAgent)\n || /iPhone/.test(navigator.userAgent)\n || /iPhone/.test(navigator.platform));\n}\n\n/**\n * Check whether the current device is an iOS device.\n * @returns {boolean}\n */\nfunction isIOS() {\n return isIpad() || isIphone();\n}\n\n/**\n * Check whether the current browser is a mobile browser\n * @returns {boolean}\n */\nfunction isMobile() {\n return /Mobi/.test(navigator.userAgent);\n}\n\n/**\n * Check whether the current browser is non-Chromium Edge.\n * @param {string} browser\n * @returns {boolean}\n */\nfunction isNonChromiumEdge(browser) {\n return browser === 'chrome' && /Edge/.test(navigator.userAgent) && (\n typeof chrome === 'undefined' || typeof chrome.runtime === 'undefined'\n );\n}\n\n/**\n * Get the name of the rebranded Chromium browser, if any. Re-branded Chrome's user\n * agent has the following format:\n * / () / () Chrome/ [Mobile] Safari/\n * @param browser\n * @returns {?string} Name of the rebranded Chrome browser, or null if the browser\n * is either not Chrome or vanilla Chrome.\n */\nfunction rebrandedChromeBrowser(browser) {\n // If the browser is not Chrome based, then it is not a rebranded Chrome browser.\n if (browser !== 'chrome') {\n return null;\n }\n\n // Latest desktop Brave browser has a \"brave\" property in navigator.\n if ('brave' in navigator) {\n return 'brave';\n }\n\n // Remove the \"(.+)\" entries from the user agent thereby retaining only the\n // [/] entries.\n const parenthesizedSubstrings = getParenthesizedSubstrings(navigator.userAgent);\n const nameAndVersions = parenthesizedSubstrings.reduce(\n (userAgent, substring) => userAgent.replace(substring, ''),\n navigator.userAgent\n );\n\n // Extract the potential browser s by ignoring the first two names, which\n // point to and .\n const matches = nameAndVersions.match(/[^\\s]+/g) || [];\n const [/* source */, /* engine */, ...browserNames] = matches.map(nameAndVersion => {\n return nameAndVersion.split('/')[0].toLowerCase();\n });\n\n // Extract the that is not expected to be present in the vanilla Chrome\n // browser, which indicates the rebranded name (ex: \"edg[e]\", \"electron\"). If null,\n // then this is a vanilla Chrome browser.\n return browserNames.find(name => {\n return !['chrome', 'mobile', 'safari'].includes(name);\n }) || null;\n}\n\n/**\n * Get the name of the mobile webkit based browser, if any.\n * @param browser\n * @returns {?string} Name of the mobile webkit based browser, or null if the browser\n * is either not webkit based or mobile safari.\n */\nfunction mobileWebKitBrowser(browser) {\n if (browser !== 'safari') {\n return null;\n }\n if ('brave' in navigator) {\n return 'brave';\n }\n\n return ['edge', 'edg'].find(name => {\n return navigator.userAgent.toLowerCase().includes(name);\n }) || null;\n}\n\n/**\n * Get the top level parenthesized substrings within a given string. Unmatched\n * parentheses are ignored.\n * Ex: \"abc) (def) gh(ij) (kl (mn)o) (pqr\" => [\"(def)\", \"(ij)\", \"(kl (mn)o)\"]\n * @param {string} string\n * @returns {string[]}\n */\nfunction getParenthesizedSubstrings(string) {\n const openParenthesisPositions = [];\n const substrings = [];\n for (let i = 0; i < string.length; i++) {\n if (string[i] === '(') {\n openParenthesisPositions.push(i);\n } else if (string[i] === ')' && openParenthesisPositions.length > 0) {\n const openParenthesisPosition = openParenthesisPositions.pop();\n if (openParenthesisPositions.length === 0) {\n substrings.push(string.substring(openParenthesisPosition, i + 1));\n }\n }\n }\n return substrings;\n}\n\nmodule.exports = {\n isAndroid,\n isIOS,\n isIpad,\n isIphone,\n isMobile,\n isNonChromiumEdge,\n mobileWebKitBrowser,\n rebrandedChromeBrowser\n};\n", "'use strict';\n\n/**\n * Return a Promise that resolves after `timeout` milliseconds.\n * @param {?number} [timeout=0]\n * @returns {Promise}\n */\nfunction delay(timeout) {\n timeout = typeof timeout === 'number' ? timeout : 0;\n return new Promise(resolve => setTimeout(resolve, timeout));\n}\n\n/**\n * Attempt to detect silence. The Promise returned by this function returns\n * false as soon as audio is detected or true after `timeout` milliseconds.\n * @param {AudioContext} audioContext\n * @param {MediaStream} stream\n * @param {?number} [timeout=250]\n * @returns {Promise}\n */\nfunction detectSilence(audioContext, stream, timeout) {\n timeout = typeof timeout === 'number' ? timeout : 250;\n\n const source = audioContext.createMediaStreamSource(stream);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = 2048;\n source.connect(analyser);\n\n const samples = new Uint8Array(analyser.fftSize);\n\n let timeoutDidFire = false;\n setTimeout(() => { timeoutDidFire = true; }, timeout);\n\n /**\n * We can't use async/await yet, so I need to factor this out.\n * @returns {Promise}\n */\n function doDetectSilence() {\n if (timeoutDidFire) {\n return Promise.resolve(true);\n }\n analyser.getByteTimeDomainData(samples);\n // NOTE(mpatwardhan): An audio MediaStreamTrack can be silent either due to all samples\n // being equal to 128 or all samples being equal to 0.\n return samples.some(sample => sample !== 128 && sample !== 0)\n ? Promise.resolve(false)\n : delay().then(doDetectSilence);\n }\n\n return doDetectSilence().then(isSilent => {\n source.disconnect();\n return isSilent;\n }, error => {\n source.disconnect();\n throw error;\n });\n}\n\nmodule.exports = detectSilence;\n", "/* globals webkitAudioContext, AudioContext */\n'use strict';\n\nconst NativeAudioContext = typeof AudioContext !== 'undefined'\n ? AudioContext\n : typeof webkitAudioContext !== 'undefined'\n ? webkitAudioContext\n : null;\n\n/**\n * @interface AudioContextFactoryOptions\n * @property {AudioContext} [AudioContext] - The AudioContext constructor\n */\n\n/**\n * {@link AudioContextFactory} ensures we construct at most one AudioContext\n * at a time, and that it is eventually closed when we no longer need it.\n * @property {AudioContextFactory} AudioContextFactory - The\n * {@link AudioContextFactory} constructor\n */\nclass AudioContextFactory {\n /**\n * @param {AudioContextFactoryOptions} [options]\n */\n constructor(options) {\n options = Object.assign({\n AudioContext: NativeAudioContext\n }, options);\n Object.defineProperties(this, {\n _AudioContext: {\n value: options.AudioContext\n },\n _audioContext: {\n value: null,\n writable: true\n },\n _holders: {\n value: new Set()\n },\n AudioContextFactory: {\n enumerable: true,\n value: AudioContextFactory\n }\n });\n }\n\n /**\n * Each call to {@link AudioContextFactory#getOrCreate} should be paired with a\n * call to {@link AudioContextFactory#release}. Calling this increments an\n * internal reference count.\n * @param {*} holder - The object to hold a reference to the AudioContext\n * @returns {?AudioContext}\n */\n getOrCreate(holder) {\n if (!this._holders.has(holder)) {\n this._holders.add(holder);\n if (this._AudioContext && !this._audioContext) {\n try {\n this._audioContext = new this._AudioContext();\n } catch (error) {\n // Do nothing;\n }\n }\n }\n return this._audioContext;\n }\n\n /**\n * Decrement the internal reference count. If it reaches zero, close and destroy\n * the AudioContext.\n * @param {*} holder - The object that held a reference to the AudioContext\n * @returns {void}\n */\n release(holder) {\n if (this._holders.has(holder)) {\n this._holders.delete(holder);\n if (!this._holders.size && this._audioContext) {\n this._audioContext.close();\n this._audioContext = null;\n }\n }\n }\n}\n\nmodule.exports = new AudioContextFactory();\n", "'use strict';\n\nconst detectSilence = require('../webaudio/detectsilence');\n\nconst N_ATTEMPTS = 3;\nconst ATTEMPT_DURATION_MS = 250;\n\n/**\n * Detect whether the audio stream rendered by the given HTMLVideoElement is silent.\n * @param {HTMLAudioElement} el\n * @returns {Promise} true if silent, false if not.\n */\nfunction detectSilentAudio(el) {\n // NOTE(mmalavalli): We have to delay require-ing AudioContextFactory, because\n // it exports a default instance whose constructor calls Object.assign.\n const AudioContextFactory = require('../webaudio/audiocontext');\n const holder = {};\n const audioContext = AudioContextFactory.getOrCreate(holder);\n\n let attemptsLeft = N_ATTEMPTS;\n\n function doCheckSilence() {\n attemptsLeft--;\n return detectSilence(audioContext, el.srcObject, ATTEMPT_DURATION_MS).then(isSilent => {\n if (!isSilent) {\n return false;\n }\n if (attemptsLeft > 0) {\n return doCheckSilence();\n }\n return true;\n }).catch(() => {\n // NOTE(mmalavalli): If an error is thrown while detect silence, the audio\n // stream is assumed to be silent.\n return true;\n });\n }\n\n // Resolve the returned Promise with true if 3 consecutive attempts\n // to detect silent audio are successful.\n return doCheckSilence().finally(() => {\n AudioContextFactory.release(holder);\n });\n}\n\nmodule.exports = detectSilentAudio;\n", "'use strict';\n\nconst { defer } = require('./');\n\n/**\n * This is a pair of Deferreds that are set whenever local media is muted and\n * resolved whenever local media is unmuted/ended and restarted if necessary.\n */\nclass LocalMediaRestartDeferreds {\n /**\n * Constructor.\n */\n constructor() {\n Object.defineProperties(this, {\n _audio: {\n value: defer(),\n writable: true\n },\n _video: {\n value: defer(),\n writable: true\n }\n });\n\n // Initially, resolve both the Deferreds.\n this._audio.resolve();\n this._video.resolve();\n }\n\n /**\n * Resolve the Deferred for audio or video.\n * @param {'audio'|'video'} kind\n */\n resolveDeferred(kind) {\n if (kind === 'audio') {\n this._audio.resolve();\n } else {\n this._video.resolve();\n }\n }\n\n /**\n * Start the Deferred for audio or video.\n * @param {'audio' | 'video'} kind\n */\n startDeferred(kind) {\n if (kind === 'audio') {\n this._audio = defer();\n } else {\n this._video = defer();\n }\n }\n\n /**\n * Wait until the Deferred for audio or video is resolved.\n * @param {'audio'|'video'} kind\n * @returns {Promise}\n */\n whenResolved(kind) {\n return kind === 'audio' ? this._audio.promise : this._video.promise;\n }\n}\n\nmodule.exports = new LocalMediaRestartDeferreds();\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\nconst { hidePrivateAndCertainPublicPropertiesInClass } = require('./util');\n\nmodule.exports = hidePrivateAndCertainPublicPropertiesInClass(EventEmitter, ['domain']);\n", "'use strict';\n\nconst EventEmitter = require('../../eventemitter');\nconst { buildLogLevels, valueToJSON } = require('../../util');\nconst DEFAULT_LOG_LEVEL = require('../../util/constants').DEFAULT_LOG_LEVEL;\nconst Log = require('../../util/log');\n\nlet nInstances = 0;\n\n/**\n * A {@link Track} represents a stream of audio, video, or data.\n * @extends EventEmitter\n * @property {Track.Kind} kind - The {@link Track}'s kind\n * @property {string} name - The {@link Track}'s name\n */\nclass Track extends EventEmitter {\n /**\n * Construct a {@link Track}.\n * @param {Track.ID} id - The {@link Track}'s ID\n * @param {Track.Kind} kind - The {@link Track}'s kind\n * @param {{ log: Log, name: ?string }} options\n */\n constructor(id, kind, options) {\n options = Object.assign({\n name: id,\n log: null,\n logLevel: DEFAULT_LOG_LEVEL\n }, options);\n\n super();\n\n const name = String(options.name);\n\n const logLevels = buildLogLevels(options.logLevel);\n const log = options.log\n ? options.log.createLog('media', this)\n : new Log('media', this, logLevels, options.loggerName);\n\n Object.defineProperties(this, {\n _instanceId: {\n value: ++nInstances\n },\n _log: {\n value: log\n },\n kind: {\n enumerable: true,\n value: kind\n },\n name: {\n enumerable: true,\n value: name\n }\n });\n }\n\n toJSON() {\n return valueToJSON(this);\n }\n}\n\n/**\n * The {@link Track} ID is a string identifier for the {@link Track}.\n * @typedef {string} Track.ID\n */\n\n/**\n * The {@link Track} kind is either \"audio\", \"video\", or \"data\".\n * @typedef {string} Track.Kind\n */\n\n/**\n * The {@link Track}'s priority can be \"low\", \"standard\", or \"high\".\n * @typedef {string} Track.Priority\n */\n\n/**\n * The {@link Track} SID is a unique string identifier for the {@link Track}\n * that is published to a {@link Room}.\n * @typedef {string} Track.SID\n */\n\n/**\n * A {@link DataTrack} is a {@link LocalDataTrack} or {@link RemoteDataTrack}.\n * @typedef {LocalDataTrack|RemoteDataTrack} DataTrack\n */\n\n/**\n * A {@link LocalTrack} is a {@link LocalAudioTrack}, {@link LocalVideoTrack},\n * or {@link LocalDataTrack}.\n * @typedef {LocalAudioTrack|LocalVideoTrack|LocalDataTrack} LocalTrack\n */\n\n/**\n * {@link LocalTrack} options\n * @typedef {object} LocalTrackOptions\n * @property {LogLevel|LogLevels} logLevel - Log level for 'media' modules\n * @property {string} [name] - The {@link LocalTrack}'s name; by default,\n * it is set to the {@link LocalTrack}'s ID.\n */\n\n/**\n * A {@link RemoteTrack} is a {@link RemoteAudioTrack},\n * {@link RemoteVideoTrack}, or {@link RemoteDataTrack}.\n * @typedef {RemoteAudioTrack|RemoteVideoTrack|RemoteDataTrack} RemoteTrack\n */\n\nmodule.exports = Track;\n", "'use strict';\n\nconst { isIOS } = require('../../util/browserdetection');\nconst { MediaStream } = require('../../webrtc');\n\nconst { waitForEvent, waitForSometime } = require('../../util');\nconst localMediaRestartDeferreds = require('../../util/localmediarestartdeferreds');\nconst Track = require('./');\n\n/**\n * A {@link MediaTrack} represents audio or video that can be sent to or\n * received from a {@link Room}.\n * @extends Track\n * @property {Track.ID} id - This {@link Track}'s ID\n * @property {boolean} isStarted - Whether or not the {@link MediaTrack} has\n * started\n * @property {boolean} isEnabled - Whether or not the {@link MediaTrack} is\n * enabled (i.e., whether it is paused or muted)\n * @property {Track.Kind} kind - The kind of the underlying\n * MediaStreamTrack, \"audio\" or \"video\"\n * @property {MediaStreamTrack} mediaStreamTrack - The underlying\n * MediaStreamTrack\n * @emits MediaTrack#disabled\n * @emits MediaTrack#enabled\n * @emits MediaTrack#started\n */\nclass MediaTrack extends Track {\n /**\n * Construct a {@link MediaTrack}.\n * @param {MediaTrackTransceiver} mediaTrackTransceiver\n * @param {{log: Log}} options\n */\n constructor(mediaTrackTransceiver, options) {\n options = Object.assign({\n playPausedElementsIfNotBackgrounded: isIOS()\n && typeof document === 'object'\n && typeof document.addEventListener === 'function'\n && typeof document.visibilityState === 'string'\n }, options);\n\n super(mediaTrackTransceiver.id, mediaTrackTransceiver.kind, options);\n let isStarted = false;\n\n options = Object.assign({\n MediaStream\n }, options);\n\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _attachments: {\n value: new Set()\n },\n _dummyEl: {\n value: null,\n writable: true\n },\n _elShims: {\n value: new WeakMap()\n },\n _isStarted: {\n get() {\n return isStarted;\n },\n set(_isStarted) {\n isStarted = _isStarted;\n }\n },\n _playPausedElementsIfNotBackgrounded: {\n value: options.playPausedElementsIfNotBackgrounded\n },\n _shouldShimAttachedElements: {\n value: options.workaroundWebKitBug212780\n || options.playPausedElementsIfNotBackgrounded\n },\n _unprocessedTrack: {\n value: null,\n writable: true\n },\n _MediaStream: {\n value: options.MediaStream\n },\n isStarted: {\n enumerable: true,\n get() {\n return isStarted;\n }\n },\n mediaStreamTrack: {\n enumerable: true,\n get() {\n return this._unprocessedTrack || mediaTrackTransceiver.track;\n }\n },\n processedTrack: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n\n this._initialize();\n }\n\n /**\n * @private\n */\n _start() {\n this._log.debug('Started');\n this._isStarted = true;\n if (this._dummyEl) {\n this._dummyEl.oncanplay = null;\n }\n // eslint-disable-next-line no-use-before-define\n this.emit('started', this);\n }\n\n /**\n * @private\n */\n _initialize() {\n const self = this;\n\n this._log.debug('Initializing');\n this._dummyEl = this._createElement();\n\n this.mediaStreamTrack.addEventListener('ended', function onended() {\n self._end();\n self.mediaStreamTrack.removeEventListener('ended', onended);\n });\n\n if (this._dummyEl) {\n this._dummyEl.muted = true;\n this._dummyEl.oncanplay = this._start.bind(this, this._dummyEl);\n\n // NOTE(csantos): We always want to attach the original mediaStreamTrack for dummyEl\n this._attach(this._dummyEl, this.mediaStreamTrack);\n\n this._attachments.delete(this._dummyEl);\n }\n }\n\n /**\n * @private\n */\n _end() {\n this._log.debug('Ended');\n if (this._dummyEl) {\n this._dummyEl.remove();\n this._dummyEl.srcObject = null;\n this._dummyEl.oncanplay = null;\n this._dummyEl = null;\n }\n }\n\n attach(el) {\n if (typeof el === 'string') {\n el = this._selectElement(el);\n } else if (!el) {\n el = this._createElement();\n }\n this._log.debug('Attempting to attach to element:', el);\n el = this._attach(el);\n\n if (this._shouldShimAttachedElements && !this._elShims.has(el)) {\n const onUnintentionallyPaused = this._playPausedElementsIfNotBackgrounded\n ? () => playIfPausedAndNotBackgrounded(el, this._log)\n : null;\n this._elShims.set(el, shimMediaElement(el, onUnintentionallyPaused));\n }\n return el;\n }\n\n /**\n * Attach the provided MediaStreamTrack to the media element.\n * @param el - The media element to attach to\n * @param mediaStreamTrack - The MediaStreamTrack to attach. If this is\n * not provided, it uses the processedTrack if it exists\n * or it defaults to the current mediaStreamTrack\n * @private\n */\n _attach(el, mediaStreamTrack = this.processedTrack || this.mediaStreamTrack) {\n let mediaStream = el.srcObject;\n if (!(mediaStream instanceof this._MediaStream)) {\n mediaStream = new this._MediaStream();\n }\n\n const getTracks = mediaStreamTrack.kind === 'audio'\n ? 'getAudioTracks'\n : 'getVideoTracks';\n\n mediaStream[getTracks]().forEach(track => {\n mediaStream.removeTrack(track);\n });\n mediaStream.addTrack(mediaStreamTrack);\n\n // NOTE(mpatwardhan): resetting `srcObject` here, causes flicker (JSDK-2641), but it lets us\n // to sidestep the a chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1052353\n //\n el.srcObject = mediaStream;\n el.autoplay = true;\n el.playsInline = true;\n\n if (!this._attachments.has(el)) {\n this._attachments.add(el);\n }\n\n return el;\n }\n\n /**\n * @private\n */\n _selectElement(selector) {\n const el = document.querySelector(selector);\n\n if (!el) {\n throw new Error(`Selector matched no element: ${selector}`);\n }\n\n return el;\n }\n\n /**\n * @private\n */\n _updateElementsMediaStreamTrack() {\n this._log.debug('Reattaching all elements to update mediaStreamTrack');\n this._getAllAttachedElements().forEach(el => this._attach(el));\n }\n\n /**\n * @private\n */\n _createElement() {\n return typeof document !== 'undefined'\n ? document.createElement(this.kind)\n : null;\n }\n\n detach(el) {\n let els;\n\n if (typeof el === 'string') {\n els = [this._selectElement(el)];\n } else if (!el) {\n els = this._getAllAttachedElements();\n } else {\n els = [el];\n }\n\n this._log.debug('Attempting to detach from elements:', els);\n this._detachElements(els);\n return el ? els[0] : els;\n }\n\n /**\n * @private\n */\n _detachElements(elements) {\n return elements.map(this._detachElement.bind(this));\n }\n\n /**\n * @private\n */\n _detachElement(el) {\n if (!this._attachments.has(el)) {\n return el;\n }\n const mediaStream = el.srcObject;\n if (mediaStream instanceof this._MediaStream) {\n mediaStream.removeTrack(this.processedTrack || this.mediaStreamTrack);\n }\n this._attachments.delete(el);\n\n if (this._shouldShimAttachedElements && this._elShims.has(el)) {\n const shim = this._elShims.get(el);\n shim.unShim();\n this._elShims.delete(el);\n }\n\n return el;\n }\n\n /**\n * @private\n */\n _getAllAttachedElements() {\n const els = [];\n\n this._attachments.forEach(el => {\n els.push(el);\n });\n\n return els;\n }\n}\n\n/**\n * Play an HTMLMediaElement if it is paused and not backgrounded.\n * @private\n * @param {HTMLMediaElement} el\n * @param {Log} log\n * @returns {void}\n */\nfunction playIfPausedAndNotBackgrounded(el, log) {\n const tag = el.tagName.toLowerCase();\n log.warn('Unintentionally paused:', el);\n\n // NOTE(mmalavalli): When the element is unintentionally paused, we wait one\n // second for the \"onvisibilitychange\" event on the HTMLDocument to see if the\n // app will be backgrounded. If not, then the element can be safely played.\n Promise.race([\n waitForEvent(document, 'visibilitychange'),\n waitForSometime(1000)\n ]).then(() => {\n if (document.visibilityState === 'visible') {\n // NOTE(mmalavalli): We play the inadvertently paused elements only after\n // the LocalAudioTrack is unmuted to work around WebKit Bug 213853.\n //\n // Bug: https://bugs.webkit.org/show_bug.cgi?id=213853\n //\n localMediaRestartDeferreds.whenResolved('audio').then(() => {\n log.info(`Playing unintentionally paused <${tag}> element`);\n log.debug('Element:', el);\n return el.play();\n }).then(() => {\n log.info(`Successfully played unintentionally paused <${tag}> element`);\n log.debug('Element:', el);\n }).catch(error => {\n log.warn(`Error while playing unintentionally paused <${tag}> element:`, { error, el });\n });\n }\n });\n}\n\n/**\n * Shim the pause() and play() methods of the given HTMLMediaElement so that\n * we can detect if it was paused unintentionally.\n * @param {HTMLMediaElement} el\n * @param {?function} [onUnintentionallyPaused=null]\n * @returns {{pausedIntentionally: function, unShim: function}}\n */\nfunction shimMediaElement(el, onUnintentionallyPaused = null) {\n const origPause = el.pause;\n const origPlay = el.play;\n\n let pausedIntentionally = false;\n\n el.pause = () => {\n pausedIntentionally = true;\n return origPause.call(el);\n };\n\n el.play = () => {\n pausedIntentionally = false;\n return origPlay.call(el);\n };\n\n const onPause = onUnintentionallyPaused ? () => {\n if (!pausedIntentionally) {\n onUnintentionallyPaused();\n }\n } : null;\n\n if (onPause) {\n el.addEventListener('pause', onPause);\n }\n\n return {\n pausedIntentionally() {\n return pausedIntentionally;\n },\n unShim() {\n el.pause = origPause;\n el.play = origPlay;\n if (onPause) {\n el.removeEventListener('pause', onPause);\n }\n }\n };\n}\n\nmodule.exports = MediaTrack;\n", "'use strict';\n\nconst MediaTrack = require('./mediatrack');\n\n/**\n * An {@link AudioTrack} is a {@link Track} representing audio.\n * @extends Track\n * @property {boolean} isStarted - Whether or not the {@link AudioTrack} has\n * started; if the {@link AudioTrack} started, there is enough audio data to\n * begin playback\n * @property {boolean} isEnabled - Whether or not the {@link AudioTrack} is\n * enabled; if the {@link AudioTrack} is not enabled, it is \"muted\"\n * @property {Track.Kind} kind - \"audio\"\n * @property {MediaStreamTrack} mediaStreamTrack - An audio MediaStreamTrack\n * @property {?MediaStreamTrack} processedTrack - The source of processed audio samples.\n * It is always null as audio processing is not currently supported.\n * @emits AudioTrack#disabled\n * @emits AudioTrack#enabled\n * @emits AudioTrack#started\n */\nclass AudioTrack extends MediaTrack {\n /**\n * Construct an {@link AudioTrack}.\n * @param {MediaTrackTransceiver} mediaTrackTransceiver\n * @param {{log: Log}} options\n */\n constructor(mediaTrackTransceiver, options) {\n super(mediaTrackTransceiver, options);\n }\n\n /**\n * Create an HTMLAudioElement and attach the {@link AudioTrack} to it.\n *\n * The HTMLAudioElement's srcObject will be set to a new\n * MediaStream containing the {@link AudioTrack}'s MediaStreamTrack.\n *\n * @returns {HTMLAudioElement} audioElement\n * @example\n * const Video = require('twilio-video');\n *\n * Video.createLocalAudioTrack().then(function(audioTrack) {\n * const audioElement = audioTrack.attach();\n * document.body.appendChild(audioElement);\n * });\n *//**\n * Attach the {@link AudioTrack} to an existing HTMLMediaElement. The\n * HTMLMediaElement could be an HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link AudioTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link MediaTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {HTMLMediaElement} mediaElement - The HTMLMediaElement to attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalAudioTrack().then(function(audioTrack) {\n * audioTrack.attach(videoElement);\n * });\n *//**\n * Attach the {@link AudioTrack} to an HTMLMediaElement selected by\n * document.querySelector. The HTMLMediaElement could be an\n * HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link AudioTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link AudioTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {string} selector - A query selector for the HTMLMediaElement to\n * attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * videoElement.id = 'my-video-element';\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalAudioTrack().then(function(track) {\n * track.attach('#my-video-element');\n * });\n */\n attach() {\n return super.attach.apply(this, arguments);\n }\n\n /**\n * Detach the {@link AudioTrack} from all previously attached HTMLMediaElements.\n * @returns {Array} mediaElements\n * @example\n * const mediaElements = audioTrack.detach();\n * mediaElements.forEach(mediaElement => mediaElement.remove());\n *//**\n * Detach the {@link AudioTrack} from a previously attached HTMLMediaElement.\n * @param {HTMLMediaElement} mediaElement - One of the HTMLMediaElements to\n * which the {@link AudioTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const videoElement = document.getElementById('my-video-element');\n * audioTrack.detach(videoElement).remove();\n *//**\n * Detach the {@link AudioTrack} from a previously attached HTMLMediaElement\n * specified by document.querySelector.\n * @param {string} selector - The query selector of HTMLMediaElement to which\n * the {@link AudioTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * audioTrack.detach('#my-video-element').remove();\n */\n detach() {\n return super.detach.apply(this, arguments);\n }\n}\n\n/**\n * The {@link AudioTrack} was disabled, i.e. \"muted\".\n * @param {AudioTrack} track - The {@link AudioTrack} that was disabled\n * @event AudioTrack#disabled\n */\n\n/**\n * The {@link AudioTrack} was enabled, i.e. \"unmuted\".\n * @param {AudioTrack} track - The {@link AudioTrack} that was enabled\n * @event AudioTrack#enabled\n */\n\n/**\n * The {@link AudioTrack} started. This means there is enough audio data to\n * begin playback.\n * @param {AudioTrack} track - The {@link AudioTrack} that started\n * @event AudioTrack#started\n */\n\nmodule.exports = AudioTrack;\n", "'use strict';\n\n// Cached copy of the used to check silent video frames.\nlet canvas = null;\n\nconst N_SAMPLES = 3;\nconst SAMPLE_HEIGHT = 50;\nconst SAMPLE_INTERVAL_MS = 250;\nconst SAMPLE_WIDTH = 50;\n\n/**\n * Check whether the current video frame is silent by selecting a 50x50\n * sample and calculating the max value of the pixel data. If it is 0, then\n * the frame is considered to be silent.\n * @private\n * @param {HTMLVideoElement} el\n * @returns {boolean} true if silent, false if not\n */\nfunction checkSilence(el) {\n try {\n const context = canvas.getContext('2d');\n context.drawImage(el, 0, 0, SAMPLE_WIDTH, SAMPLE_HEIGHT);\n const frame = context.getImageData(0, 0, SAMPLE_WIDTH, SAMPLE_HEIGHT);\n const frameDataWithoutAlpha = frame.data.filter((item, i) => (i + 1) % 4);\n const max = Math.max.apply(Math, frameDataWithoutAlpha);\n return max === 0;\n } catch (ex) {\n // eslint-disable-next-line no-console\n console.log('Error checking silence: ', ex);\n return false;\n }\n\n}\n\n/**\n * Detect whether the video stream rendered by the given HTMLVideoElement is silent.\n * @param {HTMLVideoElement} el\n * @returns {Promise} true if silent, false if not.\n */\nfunction detectSilentVideo(el) {\n // Create the canvas when detectSilentVideo() is called for the\n // first time.\n canvas = canvas || document.createElement('canvas');\n\n // Resolve the returned Promise with true if 3 consecutive sample\n // frames from the video being played by the HTMLVideoElement are\n // silent.\n return new Promise(resolve => {\n let samplesLeft = N_SAMPLES;\n setTimeout(function doCheckSilence() {\n samplesLeft--;\n if (!checkSilence(el)) {\n return resolve(false);\n }\n if (samplesLeft > 0) {\n return setTimeout(doCheckSilence, SAMPLE_INTERVAL_MS);\n }\n return resolve(true);\n }, SAMPLE_INTERVAL_MS);\n });\n}\n\nmodule.exports = detectSilentVideo;\n", "'use strict';\n\n/**\n * The {@link DocumentVisibilityMonitor} monitors the visibility state of the DOM\n * and executes the attached listeners in phase order when the DOM is visible.\n */\nclass DocumentVisibilityMonitor {\n /**\n * Constructor.\n * @param {number} [nPhases=1] - the number of phases\n */\n constructor(nPhases = 1) {\n Object.defineProperties(this, {\n _listeners: {\n value: []\n },\n _onVisibilityChange: {\n value: () => {\n this._emitVisible(document.visibilityState === 'visible');\n }\n }\n });\n\n for (let i = 0; i < nPhases; i++) {\n this._listeners.push([]);\n }\n }\n\n\n /**\n * clears the state.\n */\n clear() {\n const nPhases = this._listeners.length;\n for (let i = 0; i < nPhases; i++) {\n this._listeners[i] = [];\n }\n }\n\n _listenerCount() {\n return this._listeners.reduce((count, phaseListeners) => count + phaseListeners.length, 0);\n }\n\n /**\n * Call all the listeners. Makes sure that all listeners for a given phase\n * are executed before calling the listeners of the next phase.\n * @private\n */\n _emitVisible(isVisible) {\n let promise = Promise.resolve();\n for (let phase = 1; phase <= this._listeners.length; phase++) {\n promise = promise.then(() => this._emitVisiblePhase(phase, isVisible));\n }\n return promise;\n }\n\n /**\n * Call all the listeners for a given phase.\n * @private\n */\n _emitVisiblePhase(phase, isVisible) {\n const phaseListeners = this._listeners[phase - 1];\n return Promise.all(phaseListeners.map(listener => {\n const ret = listener(isVisible);\n return ret instanceof Promise ? ret : Promise.resolve(ret);\n }));\n }\n\n /**\n * Start listening to the DOM visibility state change.\n * @private\n */\n _start() {\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n /**\n * Stop listening to the DOM visibility state change.\n * @private\n */\n _stop() {\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n /**\n * Listen for the DOM visibility changes at the given phase.\n * @param {number} phase\n * @param {function} listener\n * @returns {this}\n */\n onVisibilityChange(phase, listener) {\n if (typeof phase !== 'number' || phase <= 0 || phase > this._listeners.length) {\n throw new Error('invalid phase: ', phase);\n }\n const phaseListeners = this._listeners[phase - 1];\n phaseListeners.push(listener);\n if (this._listenerCount() === 1) {\n this._start();\n }\n return this;\n }\n\n /**\n * Stop listening for the DOM visibility change at the given phase.\n * @param {number} phase\n * @param {function} listener\n * @returns {this}\n */\n offVisibilityChange(phase, listener) {\n if (typeof phase !== 'number' || phase <= 0 || phase > this._listeners.length) {\n throw new Error('invalid phase: ', phase);\n }\n\n const phaseListeners = this._listeners[phase - 1];\n const index = phaseListeners.indexOf(listener);\n if (index !== -1) {\n phaseListeners.splice(index, 1);\n if (this._listenerCount() === 0) {\n this._stop();\n }\n }\n return this;\n }\n}\n\nmodule.exports = new DocumentVisibilityMonitor(2);\n", "'use strict';\n\nconst detectSilence = require('./detectsilence');\n\n/**\n * This function attempts to workaround WebKit Bug 180748. It does so by\n *\n * 1. Calling `getUserMedia`, and\n * 2. Checking to see if the resulting MediaStream is silent.\n * 3. If so, repeat Step 1; otherwise, return the MediaStream.\n *\n * The function only repeats up to `n` times, and it only waits `timeout`\n * milliseconds when detecting silence. Assuming `getUserMedia` is\n * instantaneous, in the best case, this function returns a Promise that\n * resolves immediately; in the worst case, this function returns a Promise that\n * resolves in `n` * `timeout` milliseconds.\n *\n * @param {Log} log\n * @param {function(MediaStreamConstraints): Promise} getUserMedia\n * @param {MediaStreamConstraints} constraints\n * @param {number} [n=3]\n * @param {number} [timeout=250]\n * @returns Promise\n */\nfunction workaround(log, getUserMedia, constraints, n, timeout) {\n n = typeof n === 'number' ? n : 3;\n let retry = 0;\n\n // NOTE(mroberts): We have to delay require-ing AudioContextFactory, because\n // it exports a default instance whose constructor calls Object.assign.\n const AudioContextFactory = require('./audiocontext');\n const holder = {};\n const audioContext = AudioContextFactory.getOrCreate(holder);\n\n /**\n * We can't use async/await yet, so I need to factor this out.\n * @returns {Promise}\n */\n function doWorkaround() {\n return getUserMedia(constraints).then(stream => {\n const isSilentPromise = constraints.audio\n ? detectSilence(audioContext, stream, timeout).catch(err => {\n log.warn('Encountered an error while detecting silence', err);\n return true;\n })\n : Promise.resolve(false);\n return isSilentPromise.then(isSilent => {\n if (!isSilent) {\n log.info('Got a non-silent audio MediaStreamTrack; returning it.');\n return stream;\n } else if (n <= 0) {\n log.warn('Got a silent audio MediaStreamTrack. Normally we would try \\\nto get a new one, but we\\'ve run out of retries; returning it anyway.');\n return stream;\n }\n log.warn(`Got a silent audio MediaStreamTrack. Stopping all \\\nMediaStreamTracks and calling getUserMedia again. This is retry \\\n#${++retry}.`);\n stream.getTracks().forEach(track => track.stop());\n n--;\n return doWorkaround();\n });\n });\n }\n\n return doWorkaround().then(stream => {\n AudioContextFactory.release(holder);\n return stream;\n }, error => {\n AudioContextFactory.release(holder);\n throw error;\n });\n}\n\nmodule.exports = workaround;\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\n/**\n * A {@link QueueingEventEmitter} can queue events until a listener has been\n * added.\n * @extends EventEmitter\n */\nclass QueueingEventEmitter extends EventEmitter {\n /**\n * Construct a {@link QueueingEventEmitter}\n */\n constructor() {\n super();\n Object.defineProperties(this, {\n _queuedEvents: {\n value: new Map()\n }\n });\n }\n\n /**\n * Emit any queued events.\n * @returns {boolean} true if every event had listeners, false otherwise\n *//**\n * Emit any queued events matching the event name.\n * @param {string} event\n * @returns {boolean} true if every event had listeners, false otherwise\n */\n dequeue(event) {\n let result = true;\n if (!event) {\n this._queuedEvents.forEach(function(_, queuedEvent) {\n result = this.dequeue(queuedEvent) && result;\n }, this);\n return result;\n }\n const queue = this._queuedEvents.get(event) || [];\n this._queuedEvents.delete(event);\n return queue.reduce((result, args) => this.emit(...[event].concat(args)) && result, result);\n }\n\n /**\n * If the event has listeners, emit the event; otherwise, queue the event.\n * @param {string} event\n * @param {...*} args\n * @returns {boolean} true if the event had listeners, false if the event was queued\n */\n queue() {\n const args = [].slice.call(arguments);\n if (this.emit(...args)) {\n return true;\n }\n const event = args[0];\n if (!this._queuedEvents.has(event)) {\n this._queuedEvents.set(event, []);\n }\n this._queuedEvents.get(event).push(args.slice(1));\n return false;\n }\n}\n\nmodule.exports = QueueingEventEmitter;\n", "'use strict';\n\nconst QueueingEventEmitter = require('./queueingeventemitter');\n\n/**\n * A {@link TrackTransceiver} represents either one or more local RTCRtpSenders\n * or RTCDataChannels, or a single RTCRtpReceiver or remote RTCDataChannel.\n * @extends QueueingEventEmitter\n * @property {Track.ID} id\n * @property {Track.kind} kind\n */\nclass TrackTransceiver extends QueueingEventEmitter {\n /**\n * Construct a {@link TrackTransceiver}.\n * @param {Track.ID} id\n * @param {Track.kind} kind\n */\n constructor(id, kind) {\n super();\n Object.defineProperties(this, {\n id: {\n enumerable: true,\n value: id\n },\n kind: {\n enumerable: true,\n value: kind\n }\n });\n }\n\n /**\n * Stop the {@link TrackTransceiver}.\n * #emits TrackTransceiver#stopped\n * @returns {void}\n */\n stop() {\n this.emit('stopped');\n }\n}\n\n/**\n * The {@link TrackTransceiver} was stopped.\n * @event TrackTransceiver#stopped\n */\n\nmodule.exports = TrackTransceiver;\n", "'use strict';\n\nconst TrackTransceiver = require('../../transceiver');\n\n/**\n * A {@link MediaTrackTransceiver} represents either one or more local\n * RTCRtpSenders, or a single RTCRtpReceiver.\n * @extends TrackTransceiver\n * @property {MediaStreamTrack} track\n */\nclass MediaTrackTransceiver extends TrackTransceiver {\n /**\n * Construct a {@link MediaTrackTransceiver}.\n * @param {Track.ID} id - The MediaStreamTrack ID signaled through RSP/SDP\n * @param {MediaStreamTrack} mediaStreamTrack\n */\n constructor(id, mediaStreamTrack) {\n super(id, mediaStreamTrack.kind);\n Object.defineProperties(this, {\n _track: {\n value: mediaStreamTrack,\n writable: true\n },\n enabled: {\n enumerable: true,\n get() {\n return this._track.enabled;\n }\n },\n readyState: {\n enumerable: true,\n get() {\n return this._track.readyState;\n }\n },\n track: {\n enumerable: true,\n get() {\n return this._track;\n }\n }\n });\n }\n\n stop() {\n this.track.stop();\n super.stop();\n }\n}\n\nmodule.exports = MediaTrackTransceiver;\n", "'use strict';\n\nconst MediaTrackTransceiver = require('./transceiver');\n\n/**\n * A {@link MediaTrackSender} represents one or more local RTCRtpSenders.\n * @extends MediaTrackTransceiver\n * @emits MediaTrackSender#replaced\n */\nclass MediaTrackSender extends MediaTrackTransceiver {\n /**\n * Construct a {@link MediaTrackSender}.\n * @param {MediaStreamTrack} mediaStreamTrack\n */\n constructor(mediaStreamTrack) {\n super(mediaStreamTrack.id, mediaStreamTrack);\n Object.defineProperties(this, {\n _clones: {\n value: new Set()\n },\n _eventsToReemitters: {\n value: new Map([\n ['mute', () => this.queue('muted')],\n ['unmute', () => this.queue('unmuted')]\n ])\n },\n _senders: {\n value: new Set()\n },\n _senderToPublisherHintCallbacks: {\n value: new Map()\n },\n isPublishing: {\n enumerable: true,\n get() {\n return !!this._clones.size;\n }\n },\n muted: {\n enumerable: true,\n get() {\n return this._track.muted;\n }\n }\n });\n\n this._reemitMediaStreamTrackEvents();\n }\n\n /**\n * @private\n */\n _reemitMediaStreamTrackEvents(mediaStreamTrack = this._track) {\n const { _eventsToReemitters: eventsToReemitters, _track: track } = this;\n eventsToReemitters.forEach((reemitter, event) => mediaStreamTrack.addEventListener(event, reemitter));\n if (track !== mediaStreamTrack) {\n eventsToReemitters.forEach((reemitter, event) => track.removeEventListener(event, reemitter));\n if (track.muted !== mediaStreamTrack.muted) {\n const reemitter = eventsToReemitters.get(mediaStreamTrack.muted ? 'mute' : 'unmute');\n reemitter();\n }\n }\n }\n\n /**\n * Return a new {@link MediaTrackSender} containing a clone of the underlying\n * MediaStreamTrack. No RTCRtpSenders are copied.\n * @returns {MediaTrackSender}\n */\n clone() {\n const clone = new MediaTrackSender(this.track.clone());\n this._clones.add(clone);\n return clone;\n }\n\n /**\n * Remove a cloned {@link MediaTrackSender}.\n * @returns {void}\n */\n removeClone(clone) {\n this._clones.delete(clone);\n }\n\n /**\n * Set the given MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack\n * @returns {Promise}\n */\n setMediaStreamTrack(mediaStreamTrack) {\n const clones = Array.from(this._clones);\n const senders = Array.from(this._senders);\n return Promise.all(clones.map(clone => {\n return clone.setMediaStreamTrack(mediaStreamTrack.clone());\n }).concat(senders.map(sender => {\n return this._replaceTrack(sender, mediaStreamTrack);\n }))).finally(() => {\n this._reemitMediaStreamTrackEvents(mediaStreamTrack);\n this._track = mediaStreamTrack;\n });\n }\n\n /**\n * Add an RTCRtpSender.\n * @param {RTCRtpSender} sender\n * @param {?()=>Promise} publisherHintCallback\n * @returns {this}\n */\n addSender(sender, publisherHintCallback) {\n this._senders.add(sender);\n if (publisherHintCallback) {\n this._senderToPublisherHintCallbacks.set(sender, publisherHintCallback);\n }\n return this;\n }\n\n /**\n * Remove an RTCRtpSender.\n * @param {RTCRtpSender} sender\n * @returns {this}\n */\n removeSender(sender) {\n this._senders.delete(sender);\n this._senderToPublisherHintCallbacks.delete(sender);\n return this;\n }\n\n /**\n * Applies given encodings, or resets encodings if none specified.\n * @param {Array<{enabled: boolean, layer_index: number}>|null} encodings\n * @returns {Promise}\n */\n setPublisherHint(encodings) {\n // Note(mpatwardhan): since publisher hint applies only to group rooms we only look at 1st call callback.\n const [publisherHintCallback] = Array.from(this._senderToPublisherHintCallbacks.values());\n return publisherHintCallback ? publisherHintCallback(encodings) : Promise.resolve('COULD_NOT_APPLY_HINT');\n }\n\n _replaceTrack(sender, mediaStreamTrack) {\n return sender.replaceTrack(mediaStreamTrack).then(replaceTrackResult => {\n // clear any publisherHints and apply default encodings.\n this.setPublisherHint(null).catch(() => {});\n this.emit('replaced');\n return replaceTrackResult;\n });\n }\n}\n\n/**\n * The {@link MediaTrackSender}'s underlying MediaStreamTrack was muted.\n * @event MediaTrackSender#muted\n */\n\n/**\n * The {@link MediaTrackSender} replaced the underlying MediaStreamTrack.\n * @event MediaTrackSender#replaced\n */\n\n/**\n * The {@link MediaTrackSender}'s underlying MediaStreamTrack was unmuted.\n * @event MediaTrackSender#unmuted\n */\n\nmodule.exports = MediaTrackSender;\n", "/* eslint new-cap:0 */\n'use strict';\n\nconst { getUserMedia } = require('../../webrtc');\nconst { isIOS } = require('../../util/browserdetection');\n\nconst { capitalize, defer, waitForSometime, waitForEvent } = require('../../util');\nconst { typeErrors: { ILLEGAL_INVOKE } } = require('../../util/constants');\nconst detectSilentAudio = require('../../util/detectsilentaudio');\nconst detectSilentVideo = require('../../util/detectsilentvideo');\nconst documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');\nconst localMediaRestartDeferreds = require('../../util/localmediarestartdeferreds');\nconst gUMSilentTrackWorkaround = require('../../webaudio/workaround180748');\nconst MediaTrackSender = require('./sender');\n\nfunction mixinLocalMediaTrack(AudioOrVideoTrack) {\n /**\n * A {@link LocalMediaTrack} represents audio or video that your\n * {@link LocalParticipant} is sending to a {@link Room}. As such, it can be\n * enabled and disabled with {@link LocalMediaTrack#enable} and\n * {@link LocalMediaTrack#disable} or stopped completely with\n * {@link LocalMediaTrack#stop}.\n * @emits LocalMediaTrack#muted\n * @emits LocalMediaTrack#stopped\n * @emits LocalMediaTrack#unmuted\n */\n return class LocalMediaTrack extends AudioOrVideoTrack {\n /**\n * Construct a {@link LocalMediaTrack} from a MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack - The underlying MediaStreamTrack\n * @param {LocalTrackOptions} [options] - {@link LocalTrack} options\n */\n constructor(mediaStreamTrack, options) {\n const workaroundWebKitBug1208516 = isIOS()\n && typeof document === 'object'\n && typeof document.addEventListener === 'function'\n && typeof document.visibilityState === 'string';\n\n options = Object.assign({\n getUserMedia,\n isCreatedByCreateLocalTracks: false,\n workaroundWebKitBug1208516,\n gUMSilentTrackWorkaround\n }, options);\n\n const mediaTrackSender = new MediaTrackSender(mediaStreamTrack);\n const { kind } = mediaTrackSender;\n\n super(mediaTrackSender, options);\n\n Object.defineProperties(this, {\n _constraints: {\n value: typeof options[kind] === 'object'\n ? options[kind]\n : {},\n writable: true\n },\n _getUserMedia: {\n value: options.getUserMedia\n },\n _gUMSilentTrackWorkaround: {\n value: options.gUMSilentTrackWorkaround\n },\n _eventsToReemitters: {\n value: new Map([\n ['muted', () => this.emit('muted', this)],\n ['unmuted', () => this.emit('unmuted', this)]\n ])\n },\n _workaroundWebKitBug1208516: {\n value: options.workaroundWebKitBug1208516\n },\n _workaroundWebKitBug1208516Cleanup: {\n value: null,\n writable: true\n },\n _didCallEnd: {\n value: false,\n writable: true\n },\n _isCreatedByCreateLocalTracks: {\n value: options.isCreatedByCreateLocalTracks\n },\n _noiseCancellation: {\n value: options.noiseCancellation || null\n },\n _trackSender: {\n value: mediaTrackSender\n },\n id: {\n enumerable: true,\n value: mediaTrackSender.id\n },\n isEnabled: {\n enumerable: true,\n get() {\n return mediaTrackSender.enabled;\n }\n },\n isMuted: {\n enumerable: true,\n get() {\n return mediaTrackSender.muted;\n }\n },\n isStopped: {\n enumerable: true,\n get() {\n return mediaTrackSender.readyState === 'ended';\n }\n }\n });\n\n // NOTE(mpatwardhan): As a workaround for WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=208516,\n // upon foregrounding, re-acquire new MediaStreamTrack if the existing one is ended or muted.\n if (this._workaroundWebKitBug1208516) {\n this._workaroundWebKitBug1208516Cleanup = restartWhenInadvertentlyStopped(this);\n }\n\n this._reemitTrackSenderEvents();\n }\n\n /**\n * @private\n */\n _end() {\n if (this._didCallEnd) {\n return;\n }\n super._end.call(this);\n this._didCallEnd = true;\n this._eventsToReemitters.forEach((reemitter, event) => this._trackSender.removeListener(event, reemitter));\n this.emit('stopped', this);\n }\n\n /**\n * @private\n */\n _initialize() {\n if (this._didCallEnd) {\n this._didCallEnd = false;\n }\n if (this._eventsToReemitters) {\n this._reemitTrackSenderEvents();\n }\n super._initialize.call(this);\n }\n\n /**\n * @private\n */\n _reacquireTrack(constraints) {\n const {\n _getUserMedia: getUserMedia,\n _gUMSilentTrackWorkaround: gUMSilentTrackWorkaround,\n _log: log,\n mediaStreamTrack: { kind }\n } = this;\n\n log.info('Re-acquiring the MediaStreamTrack');\n log.debug('Constraints:', constraints);\n\n const gUMConstraints = Object.assign({\n audio: false,\n video: false\n }, { [kind]: constraints });\n\n const gUMPromise = this._workaroundWebKitBug1208516Cleanup\n ? gUMSilentTrackWorkaround(log, getUserMedia, gUMConstraints)\n : getUserMedia(gUMConstraints);\n\n return gUMPromise.then(mediaStream => {\n return mediaStream.getTracks()[0];\n });\n }\n\n /**\n * @private\n */\n _reemitTrackSenderEvents() {\n this._eventsToReemitters.forEach((reemitter, event) => this._trackSender.on(event, reemitter));\n this._trackSender.dequeue('muted');\n this._trackSender.dequeue('unmuted');\n }\n\n /**\n * @private\n */\n _restart(constraints) {\n const { _log: log } = this;\n constraints = constraints || this._constraints;\n\n // NOTE(mmalavalli): If we try and restart a silent MediaStreamTrack\n // without stopping it first, then a NotReadableError is raised in case of\n // video, or the restarted audio will still be silent. Hence, we stop the\n // MediaStreamTrack here.\n this._stop();\n\n return this._reacquireTrack(constraints).catch(error => {\n log.error('Failed to re-acquire the MediaStreamTrack:', { error, constraints });\n throw error;\n }).then(newMediaStreamTrack => {\n log.info('Re-acquired the MediaStreamTrack');\n log.debug('MediaStreamTrack:', newMediaStreamTrack);\n this._constraints = Object.assign({}, constraints);\n return this._setMediaStreamTrack(newMediaStreamTrack);\n });\n }\n\n /**\n * @private\n */\n _setMediaStreamTrack(mediaStreamTrack) {\n // NOTE(mpatwardhan): Preserve the value of the \"enabled\" flag.\n mediaStreamTrack.enabled = this.mediaStreamTrack.enabled;\n\n // NOTE(mmalavalli): Stop the current MediaStreamTrack. If not already\n // stopped, this should fire a \"stopped\" event.\n this._stop();\n\n // NOTE(csantos): If there's an unprocessedTrack, this means RTCRtpSender has\n // the processedTrack already set, we don't want to replace that.\n return (this._unprocessedTrack ? Promise.resolve().then(() => {\n this._unprocessedTrack = mediaStreamTrack;\n }) : this._trackSender.setMediaStreamTrack(mediaStreamTrack).catch(error => {\n this._log.warn('setMediaStreamTrack failed:', { error, mediaStreamTrack });\n })).then(() => {\n this._initialize();\n this._getAllAttachedElements().forEach(el => this._attach(el));\n });\n }\n\n /**\n * @private\n */\n _stop() {\n this.mediaStreamTrack.stop();\n this._end();\n return this;\n }\n\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n if (enabled !== this.mediaStreamTrack.enabled) {\n this._log.info(`${enabled ? 'En' : 'Dis'}abling`);\n this.mediaStreamTrack.enabled = enabled;\n this.emit(enabled ? 'enabled' : 'disabled', this);\n }\n return this;\n }\n\n disable() {\n return this.enable(false);\n }\n\n restart(constraints) {\n const { kind } = this;\n if (!this._isCreatedByCreateLocalTracks) {\n return Promise.reject(ILLEGAL_INVOKE('restart', 'can only be called on a'\n + ` Local${capitalize(kind)}Track that is created using createLocalTracks`\n + ` or createLocal${capitalize(kind)}Track.`));\n }\n if (this._workaroundWebKitBug1208516Cleanup) {\n this._workaroundWebKitBug1208516Cleanup();\n this._workaroundWebKitBug1208516Cleanup = null;\n }\n let promise = this._restart(constraints);\n\n if (this._workaroundWebKitBug1208516) {\n promise = promise.finally(() => {\n this._workaroundWebKitBug1208516Cleanup = restartWhenInadvertentlyStopped(this);\n });\n }\n return promise;\n }\n\n stop() {\n this._log.info('Stopping');\n if (this._workaroundWebKitBug1208516Cleanup) {\n this._workaroundWebKitBug1208516Cleanup();\n this._workaroundWebKitBug1208516Cleanup = null;\n }\n return this._stop();\n }\n };\n}\n\n/**\n * Restart the given {@link LocalMediaTrack} if it has been inadvertently stopped.\n * @private\n * @param {LocalAudioTrack|LocalVideoTrack} localMediaTrack\n * @returns {function} Clean up listeners attached by the workaround\n */\nfunction restartWhenInadvertentlyStopped(localMediaTrack) {\n const {\n _log: log,\n kind,\n _noiseCancellation: noiseCancellation\n } = localMediaTrack;\n\n const detectSilence = {\n audio: detectSilentAudio,\n video: detectSilentVideo\n }[kind];\n\n const getSourceMediaStreamTrack = () => noiseCancellation\n ? noiseCancellation.sourceTrack\n : localMediaTrack.mediaStreamTrack;\n\n let { _dummyEl: el } = localMediaTrack;\n let mediaStreamTrack = getSourceMediaStreamTrack();\n let trackChangeInProgress = null;\n\n function checkSilence() {\n // The dummy element is paused, so play it and then detect silence.\n return el.play().then(() => detectSilence(el)).then(isSilent => {\n if (isSilent) {\n log.warn('Silence detected');\n } else {\n log.info('Non-silence detected');\n }\n return isSilent;\n }).catch(error => {\n log.warn('Failed to detect silence:', error);\n }).finally(() => {\n // Pause the dummy element again, if there is no processed track.\n if (!localMediaTrack.processedTrack) {\n el.pause();\n }\n });\n }\n\n function shouldReacquireTrack() {\n const {\n _workaroundWebKitBug1208516Cleanup,\n isStopped\n } = localMediaTrack;\n\n const isInadvertentlyStopped = isStopped && !!_workaroundWebKitBug1208516Cleanup;\n const { muted } = getSourceMediaStreamTrack();\n\n // NOTE(mmalavalli): Restart the LocalMediaTrack if:\n // 1. The app is foregrounded, and\n // 2. A restart is not already in progress, and\n // 3. The LocalMediaTrack is either muted, inadvertently stopped or silent\n return Promise.resolve().then(() => {\n return document.visibilityState === 'visible'\n && !trackChangeInProgress\n && (muted || isInadvertentlyStopped || checkSilence());\n });\n }\n\n function maybeRestart() {\n return Promise.race([\n waitForEvent(mediaStreamTrack, 'unmute'),\n waitForSometime(50)\n ]).then(() => shouldReacquireTrack()).then(shouldReacquire => {\n if (shouldReacquire && !trackChangeInProgress) {\n trackChangeInProgress = defer();\n localMediaTrack._restart().finally(() => {\n el = localMediaTrack._dummyEl;\n removeMediaStreamTrackListeners();\n mediaStreamTrack = getSourceMediaStreamTrack();\n addMediaStreamTrackListeners();\n trackChangeInProgress.resolve();\n trackChangeInProgress = null;\n }).catch(error => {\n log.error('failed to restart track: ', error);\n });\n }\n\n // NOTE(mmalavalli): If the MediaStreamTrack ends before the DOM is visible,\n // then this makes sure that visibility callback for phase 2 is called only\n // after the MediaStreamTrack is re-acquired.\n const promise = (trackChangeInProgress && trackChangeInProgress.promise) || Promise.resolve();\n return promise.finally(() => localMediaRestartDeferreds.resolveDeferred(kind));\n }).catch(ex => {\n log.error(`error in maybeRestart: ${ex.message}`);\n });\n }\n\n function onMute() {\n const { _log: log, kind } = localMediaTrack;\n log.info('Muted');\n log.debug('LocalMediaTrack:', localMediaTrack);\n\n // NOTE(mmalavalli): When a LocalMediaTrack is muted without the app being\n // backgrounded, and the inadvertently paused elements are played before it\n // is restarted, it never gets unmuted due to the WebKit Bug 213853. Hence,\n // setting this Deferred will make sure that the inadvertently paused elements\n // are played only after the LocalMediaTrack is unmuted.\n //\n // Bug: https://bugs.webkit.org/show_bug.cgi?id=213853\n //\n localMediaRestartDeferreds.startDeferred(kind);\n }\n\n function addMediaStreamTrackListeners() {\n mediaStreamTrack.addEventListener('ended', maybeRestart);\n mediaStreamTrack.addEventListener('mute', onMute);\n mediaStreamTrack.addEventListener('unmute', maybeRestart);\n }\n\n function removeMediaStreamTrackListeners() {\n mediaStreamTrack.removeEventListener('ended', maybeRestart);\n mediaStreamTrack.removeEventListener('mute', onMute);\n mediaStreamTrack.removeEventListener('unmute', maybeRestart);\n }\n\n // NOTE(mpatwardhan): listen for document visibility callback on phase 1.\n // this ensures that we acquire media tracks before RemoteMediaTrack\n // tries to `play` them (in phase 2). This order is important because\n // play can fail on safari if audio is not being captured.\n let onVisibilityChange = isVisible => {\n return isVisible ? maybeRestart() : false;\n };\n documentVisibilityMonitor.onVisibilityChange(1, onVisibilityChange);\n addMediaStreamTrackListeners();\n\n return () => {\n documentVisibilityMonitor.offVisibilityChange(1, onVisibilityChange);\n removeMediaStreamTrackListeners();\n };\n}\n\nmodule.exports = mixinLocalMediaTrack;\n", "'use strict';\n\nconst { isIOS } = require('../../util/browserdetection');\nconst detectSilentAudio = require('../../util/detectsilentaudio');\nconst { isIOSChrome } = require('../../webrtc/util');\nconst AudioTrack = require('./audiotrack');\nconst mixinLocalMediaTrack = require('./localmediatrack');\n\nconst LocalMediaAudioTrack = mixinLocalMediaTrack(AudioTrack);\n\n/**\n * A {@link LocalAudioTrack} is an {@link AudioTrack} representing audio that\n * your {@link LocalParticipant} can publish to a {@link Room}. It can be\n * enabled and disabled with {@link LocalAudioTrack#enable} and\n * {@link LocalAudioTrack#disable} or stopped completely with\n * {@link LocalAudioTrack#stop}.\n * @extends AudioTrack\n * @property {Track.ID} id - The {@link LocalAudioTrack}'s ID\n * @property {boolean} isMuted - Whether or not the audio source has stopped sending samples to the\n * {@link LocalAudioTrack}; This can happen when the microphone is taken over by another application,\n * mainly on mobile devices; When this property toggles, then muted and unmuted\n * events are fired appropriately\n * @property {boolean} isStopped - Whether or not the {@link LocalAudioTrack} is\n * stopped\n * @property {NoiseCancellation?} noiseCancellation - When a LocalAudioTrack is created\n * with {@link NoiseCancellationOptions}, this property provides interface\n * to enable or disable the noise cancellation at runtime.\n * @emits LocalAudioTrack#disabled\n * @emits LocalAudioTrack#enabled\n * @emits LocalAudioTrack#muted\n * @emits LocalAudioTrack#started\n * @emits LocalAudioTrack#stopped\n * @emits LocalAudioTrack#unmuted\n */\nclass LocalAudioTrack extends LocalMediaAudioTrack {\n /**\n * Construct a {@link LocalAudioTrack} from a MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack - An audio MediaStreamTrack\n * @param {LocalTrackOptions} [options] - {@link LocalTrack} options\n */\n constructor(mediaStreamTrack, options) {\n const noiseCancellation = options?.noiseCancellation || null;\n super(mediaStreamTrack, options);\n\n const { _log: log } = this;\n const { label: defaultDeviceLabel = '' } = mediaStreamTrack;\n const { deviceId: defaultDeviceId = '', groupId: defaultGroupId = '' } = mediaStreamTrack.getSettings();\n\n Object.defineProperties(this, {\n _currentDefaultDeviceInfo: {\n value: { deviceId: defaultDeviceId, groupId: defaultGroupId, label: defaultDeviceLabel },\n writable: true\n },\n _defaultDeviceCaptureMode: {\n value: !isIOS()\n && this._isCreatedByCreateLocalTracks\n && typeof navigator === 'object'\n && typeof navigator.mediaDevices === 'object'\n && typeof navigator.mediaDevices.addEventListener === 'function'\n && typeof navigator.mediaDevices.enumerateDevices === 'function'\n ? options?.defaultDeviceCaptureMode || 'auto'\n : 'manual'\n },\n _onDeviceChange: {\n value: () => {\n navigator.mediaDevices.enumerateDevices().then(deviceInfos => {\n // NOTE(mmalavalli): In Chrome, when the default device changes, and we restart the LocalAudioTrack with\n // device ID \"default\", it will not switch to the new default device unless all LocalAudioTracks capturing\n // from the old default device are stopped. So, we restart the LocalAudioTrack with the actual device ID of\n // the new default device instead.\n const defaultDeviceInfo = deviceInfos.find(({ deviceId, kind }) => {\n return kind === 'audioinput' && deviceId !== 'default';\n });\n\n if (defaultDeviceInfo && ['deviceId', 'groupId'].some(prop => {\n return defaultDeviceInfo[prop] !== this._currentDefaultDeviceInfo[prop];\n })) {\n log.info('Default device changed, restarting the LocalAudioTrack');\n log.debug(`Old default device: \"${this._currentDefaultDeviceInfo.deviceId}\" => \"${this._currentDefaultDeviceInfo.label}\"`);\n log.debug(`New default device: \"${defaultDeviceInfo.deviceId}\" => \"${defaultDeviceInfo.label}\"`);\n this._currentDefaultDeviceInfo = defaultDeviceInfo;\n this._restartDefaultDevice().catch(error => log.warn(`Failed to restart: ${error.message}`));\n }\n }, error => {\n log.warn(`Failed to run enumerateDevices(): ${error.message}`);\n });\n }\n },\n _restartOnDefaultDeviceChangeCleanup: {\n value: null,\n writable: true\n },\n noiseCancellation: {\n enumerable: true,\n value: noiseCancellation,\n writable: false\n },\n });\n\n log.debug('defaultDeviceCaptureMode:', this._defaultDeviceCaptureMode);\n this._maybeRestartOnDefaultDeviceChange();\n }\n\n toString() {\n return `[LocalAudioTrack #${this._instanceId}: ${this.id}]`;\n }\n\n attach(el) {\n el = super.attach.call(this, el);\n el.muted = true;\n return el;\n }\n\n /**\n * @private\n */\n _end() {\n return super._end.apply(this, arguments);\n }\n\n /**\n * @private\n */\n _maybeRestartOnDefaultDeviceChange() {\n const { _constraints: constraints, _defaultDeviceCaptureMode: defaultDeviceCaptureMode, _log: log } = this;\n const mediaStreamTrack = this.noiseCancellation ? this.noiseCancellation.sourceTrack : this.mediaStreamTrack;\n const { deviceId } = mediaStreamTrack.getSettings();\n\n const isNotEqualToCapturedDeviceIdOrEqualToDefault = requestedDeviceId => {\n return requestedDeviceId !== deviceId || requestedDeviceId === 'default';\n };\n\n const isCapturingFromDefaultDevice = (function checkIfCapturingFromDefaultDevice(deviceIdConstraint = {}) {\n if (typeof deviceIdConstraint === 'string') {\n return isNotEqualToCapturedDeviceIdOrEqualToDefault(deviceIdConstraint);\n } else if (Array.isArray(deviceIdConstraint)) {\n return deviceIdConstraint.every(isNotEqualToCapturedDeviceIdOrEqualToDefault);\n } else if (deviceIdConstraint.exact) {\n return checkIfCapturingFromDefaultDevice(deviceIdConstraint.exact);\n } else if (deviceIdConstraint.ideal) {\n return checkIfCapturingFromDefaultDevice(deviceIdConstraint.ideal);\n }\n return true;\n }(constraints.deviceId));\n\n if (defaultDeviceCaptureMode === 'auto' && isCapturingFromDefaultDevice) {\n if (!this._restartOnDefaultDeviceChangeCleanup) {\n log.info('LocalAudioTrack will be restarted if the default device changes');\n navigator.mediaDevices.addEventListener('devicechange', this._onDeviceChange);\n this._restartOnDefaultDeviceChangeCleanup = () => {\n log.info('Cleaning up the listener to restart the LocalAudioTrack if the default device changes');\n navigator.mediaDevices.removeEventListener('devicechange', this._onDeviceChange);\n this._restartOnDefaultDeviceChangeCleanup = null;\n };\n }\n } else {\n log.info('LocalAudioTrack will NOT be restarted if the default device changes');\n if (this._restartOnDefaultDeviceChangeCleanup) {\n this._restartOnDefaultDeviceChangeCleanup();\n }\n }\n }\n\n /**\n * @private\n */\n _reacquireTrack(constraints) {\n this._log.debug('_reacquireTrack: ', constraints);\n if (this.noiseCancellation) {\n return this.noiseCancellation.reacquireTrack(() => {\n return super._reacquireTrack.call(this, constraints);\n });\n }\n\n return super._reacquireTrack.call(this, constraints);\n }\n\n /**\n * @private\n */\n _restartDefaultDevice() {\n const constraints = Object.assign({}, this._constraints);\n const restartConstraints = Object.assign({}, constraints, { deviceId: this._currentDefaultDeviceInfo.deviceId });\n return this.restart(restartConstraints).then(() => {\n // NOTE(mmalavalli): Since we used the new default device's ID while restarting the LocalAudioTrack,\n // we reset the constraints to the original constraints so that the default device detection logic in\n // _maybeRestartOnDefaultDeviceChange() still works.\n this._constraints = constraints;\n this._maybeRestartOnDefaultDeviceChange();\n });\n }\n\n /**\n * NOTE(mmalavalli): On iOS 17 Chrome, a LocalAudioTrack with Krisp Noise Cancellation\n * enabled that is restarted due to foregrounding the browser is silent for as-of-yet\n * unknown reason. We work around this by discarding the Krisp MediaStreamTrack and using\n * the source MediaStreamTrack. (VIDEO-13006)\n * @private\n */\n _setMediaStreamTrack(mediaStreamTrack) {\n const { _log: log, noiseCancellation } = this;\n let promise = super._setMediaStreamTrack.call(this, mediaStreamTrack);\n\n if (isIOSChrome() && !!noiseCancellation) {\n log.debug('iOS Chrome detected, checking if the restarted Krisp audio is silent');\n promise = promise.then(() => detectSilentAudio(this._dummyEl)).then(isSilent => {\n log.debug(`Krisp audio is ${isSilent ? 'silent, using source audio' : 'not silent'}`);\n return isSilent && noiseCancellation.disablePermanently().then(() => {\n return super._setMediaStreamTrack.call(this, noiseCancellation.sourceTrack);\n });\n });\n }\n\n return promise;\n }\n\n /**\n * Disable the {@link LocalAudioTrack}. This is equivalent to muting the audio source.\n * @returns {this}\n * @fires LocalAudioTrack#disabled\n */\n disable() {\n return super.disable.apply(this, arguments);\n }\n\n /**\n * Enable the {@link LocalAudioTrack}. This is equivalent to unmuting the audio source.\n * @returns {this}\n * @fires LocalAudioTrack#enabled\n *//**\n * Enable or disable the {@link LocalAudioTrack}. This is equivalent to unmuting or muting\n * the audio source respectively.\n * @param {boolean} [enabled] - Specify false to disable the\n * {@link LocalAudioTrack}\n * @returns {this}\n * @fires LocalAudioTrack#disabled\n * @fires LocalAudioTrack#enabled\n */\n enable() {\n return super.enable.apply(this, arguments);\n }\n\n /**\n * Restart the {@link LocalAudioTrack}. This stops the existing MediaStreamTrack\n * and creates a new MediaStreamTrack. If the {@link LocalAudioTrack} is being published\n * to a {@link Room}, then all the {@link RemoteParticipant}s will start receiving media\n * from the newly created MediaStreamTrack. You can access the new MediaStreamTrack via\n * the mediaStreamTrack property. If you want to listen to events on\n * the MediaStreamTrack directly, please do so in the \"started\" event handler. Also,\n * the {@link LocalAudioTrack}'s ID is no longer guaranteed to be the same as the\n * underlying MediaStreamTrack's ID.\n * @param {MediaTrackConstraints} [constraints] - The optional MediaTrackConstraints\n * for restarting the {@link LocalAudioTrack}; If not specified, then the current MediaTrackConstraints\n * will be used; If {} (empty object) is specified, then the default MediaTrackConstraints\n * will be used\n * @returns {Promise} Rejects with a TypeError if the {@link LocalAudioTrack} was not created\n * using an one of createLocalAudioTrack, createLocalTracks or connect;\n * Also rejects with the DOMException\n * raised by getUserMedia when it fails\n * @fires LocalAudioTrack#stopped\n * @fires LocalAudioTrack#started\n * @example\n * const { connect, createLocalAudioTrack } = require('twilio-video');\n *\n * // Create a LocalAudioTrack that captures audio from a USB microphone.\n * createLocalAudioTrack({ deviceId: 'usb-mic-id' }).then(function(localAudioTrack) {\n * return connect('token', {\n * name: 'my-cool-room',\n * tracks: [localAudioTrack]\n * });\n * }).then(function(room) {\n * // Restart the LocalAudioTrack to capture audio from the default microphone.\n * const localAudioTrack = Array.from(room.localParticipant.audioTracks.values())[0].track;\n * return localAudioTrack.restart({ deviceId: 'default-mic-id' });\n * });\n */\n restart() {\n return super.restart.apply(this, arguments);\n }\n\n /**\n * Calls stop on the underlying MediaStreamTrack. If you choose to stop a\n * {@link LocalAudioTrack}, you should unpublish it after stopping.\n * @returns {this}\n * @fires LocalAudioTrack#stopped\n */\n stop() {\n if (this.noiseCancellation) {\n this.noiseCancellation.stop();\n }\n if (this._restartOnDefaultDeviceChangeCleanup) {\n this._restartOnDefaultDeviceChangeCleanup();\n }\n return super.stop.apply(this, arguments);\n }\n}\n\n/**\n * The {@link LocalAudioTrack} was disabled, i.e. the audio source was muted by the user.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was\n * disabled\n * @event LocalAudioTrack#disabled\n */\n\n/**\n * The {@link LocalAudioTrack} was enabled, i.e. the audio source was unmuted by the user.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was enabled\n * @event LocalAudioTrack#enabled\n */\n\n/**\n * The {@link LocalAudioTrack} was muted because the audio source stopped sending samples, most\n * likely due to another application taking said audio source, especially on mobile devices.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was muted\n * @event LocalAudioTrack#muted\n */\n\n/**\n * The {@link LocalAudioTrack} started. This means there is enough audio data to\n * begin playback.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that started\n * @event LocalAudioTrack#started\n */\n\n/**\n * The {@link LocalAudioTrack} stopped, either because {@link LocalAudioTrack#stop}\n * or {@link LocalAudioTrack#restart} was called or because the underlying\n * MediaStreamTrack ended.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that stopped\n * @event LocalAudioTrack#stopped\n */\n\n/**\n * The {@link LocalAudioTrack} was unmuted because the audio source resumed sending samples,\n * most likely due to the application that took over the said audio source has released it\n * back to the application, especially on mobile devices. This event is also fired when\n * {@link LocalAudioTrack#restart} is called on a muted {@link LocalAudioTrack} with a\n * new audio source.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was unmuted\n * @event LocalAudioTrack#unmuted\n */\n\nmodule.exports = LocalAudioTrack;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): Remove this when we go to the next major version. This is\n// only in place so that we can support ES6 classes without requiring `new`.\n'use strict';\n\nconst inherits = require('../../../vendor/inherits');\nconst LocalAudioTrackClass = require('../localaudiotrack');\n\nfunction LocalAudioTrack(mediaStreamTrack, options) {\n const track = new LocalAudioTrackClass(mediaStreamTrack, options);\n Object.setPrototypeOf(track, LocalAudioTrack.prototype);\n return track;\n}\n\ninherits(LocalAudioTrack, LocalAudioTrackClass);\n\nmodule.exports = LocalAudioTrack;\n", "/* globals MediaStreamTrackGenerator, MediaStreamTrackProcessor, TransformStream */\n'use strict';\n\nconst { DEFAULT_FRAME_RATE } = require('../../util/constants');\n\nfunction captureVideoFramesSetInterval(videoEl, processVideoFrame) {\n const [track] = videoEl.srcObject.getVideoTracks();\n const { frameRate = DEFAULT_FRAME_RATE } = track.getSettings();\n let sampleInterval;\n\n const readable = new ReadableStream({\n start(controller) {\n sampleInterval = setInterval(\n () => controller.enqueue(),\n 1000 / frameRate\n );\n }\n });\n\n const transformer = new TransformStream({\n transform() {\n return processVideoFrame();\n }\n });\n\n readable\n .pipeThrough(transformer)\n .pipeTo(new WritableStream())\n .then(() => { /* noop */ });\n\n return () => {\n clearInterval(sampleInterval);\n };\n}\n\nfunction captureVideoFramesInsertableStreams(videoEl, processVideoFrame, videoFrameType) {\n const [track] = videoEl.srcObject.getVideoTracks();\n const { readable } = new MediaStreamTrackProcessor({ track });\n const generator = new MediaStreamTrackGenerator({ kind: 'video' });\n let shouldStop = false;\n\n const transformer = new TransformStream({\n transform(videoFrame, controller) {\n const promise = videoFrameType === 'videoframe'\n ? processVideoFrame(videoFrame)\n : Promise.resolve(videoFrame.close())\n .then(processVideoFrame);\n return promise.finally(() => {\n if (shouldStop) {\n controller.terminate();\n }\n });\n }\n });\n\n readable\n .pipeThrough(transformer)\n .pipeTo(generator.writable)\n .then(() => { /* noop */ });\n\n return () => {\n shouldStop = true;\n };\n}\n\nmodule.exports = typeof MediaStreamTrackGenerator === 'function' && typeof MediaStreamTrackProcessor === 'function'\n ? captureVideoFramesInsertableStreams\n : captureVideoFramesSetInterval;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\nconst { DEFAULT_VIDEO_PROCESSOR_STATS_INTERVAL_MS } = require('../../util/constants');\n\n/**\n * VideoProcessorEventObserver listens to {@link VideoProcessor} related events\n * and re-emits them as a generic event with some additional information.\n * @extends EventEmitter\n * @emits VideoProcessorEventObserver#event\n */\nclass VideoProcessorEventObserver extends EventEmitter {\n\n /**\n * Constructor.\n * @param {Log} log\n */\n constructor(log) {\n super();\n\n Object.defineProperties(this, {\n _lastStatsSaveTime: {\n value: null,\n writable: true\n },\n _lastStatsPublishTime: {\n value: null,\n writable: true\n },\n _log: {\n value: log\n },\n _processorInfo: {\n value: null,\n writable: true\n },\n _stats: {\n value: null,\n writable: true\n }\n });\n\n this.on('add', info => {\n this._lastStatsSaveTime = Date.now();\n this._lastStatsPublishTime = Date.now();\n this._processorInfo = info;\n this._stats = [];\n this._reemitEvent('add', this._getEventData());\n });\n\n this.on('remove', () => {\n const data = this._getEventData();\n this._lastStatsSaveTime = null;\n this._lastStatsPublishTime = null;\n this._processorInfo = null;\n this._stats = null;\n this._reemitEvent('remove', data);\n });\n\n this.on('start', () => {\n this._reemitEvent('start', this._getEventData());\n });\n\n this.on('stop', message => {\n this._reemitEvent('stop', Object.assign({ message }, this._getEventData()));\n });\n\n this.on('stats', () => this._maybeEmitStats());\n }\n\n /**\n * @private\n */\n _getEventData() {\n if (!this._processorInfo) {\n return {};\n }\n\n const {\n processor,\n captureHeight,\n captureWidth,\n inputFrameRate,\n isRemoteVideoTrack,\n inputFrameBufferType,\n outputFrameBufferContextType\n } = this._processorInfo;\n const data = { captureHeight, captureWidth, inputFrameRate, isRemoteVideoTrack, inputFrameBufferType, outputFrameBufferContextType };\n data.name = processor._name || 'VideoProcessor';\n\n ['assetsPath', 'blurFilterRadius', 'debounce', 'fitType', 'isSimdEnabled', 'maskBlurRadius', 'pipeline', 'version'].forEach(prop => {\n const val = processor[`_${prop}`];\n if (typeof val !== 'undefined') {\n data[prop] = val;\n }\n });\n\n Object.keys(data).forEach(prop => {\n const val = data[prop];\n if (typeof val === 'boolean') {\n data[prop] = val ? 'true' : 'false';\n }\n });\n\n return data;\n }\n\n /**\n * Save stats every second. If a specific time interval has elapsed,\n * the stats event will be emitted\n * @private\n */\n _maybeEmitStats() {\n if (!this._stats || !this._processorInfo) {\n return;\n }\n const benchmark = this._processorInfo.processor._benchmark;\n if (!benchmark) {\n return;\n }\n const now = Date.now();\n if (now - this._lastStatsSaveTime < 1000) {\n return;\n }\n\n const entry = { outputFrameRate: benchmark.getRate('totalProcessingDelay') };\n ['captureFrameDelay', 'imageCompositionDelay', 'inputImageResizeDelay', 'processFrameDelay', 'segmentationDelay'].forEach(name => {\n entry[name] = benchmark.getAverageDelay(name);\n });\n this._lastStatsSaveTime = now;\n this._stats.push(entry);\n\n if (now - this._lastStatsPublishTime < DEFAULT_VIDEO_PROCESSOR_STATS_INTERVAL_MS) {\n return;\n }\n this._lastStatsPublishTime = now;\n const stats = this._stats.splice(0);\n const averages = stats.reduce((averages, current, n) => {\n Object.keys(entry).forEach(name => {\n if (!averages[name]) {\n averages[name] = 0;\n }\n averages[name] = ((averages[name] * n) + current[name]) / (n + 1);\n });\n return averages;\n }, {});\n\n Object.keys(averages).forEach(name => {\n averages[name] = parseFloat(averages[name].toFixed(2));\n });\n this._reemitEvent('stats', Object.assign({}, averages, this._getEventData()));\n }\n\n /**\n * @private\n */\n _reemitEvent(name, data) {\n this._log.debug(`VideoProcessor:${name}`, data);\n this.emit('event', { name, data });\n }\n}\n\nmodule.exports = VideoProcessorEventObserver;\n", "'use strict';\n\nconst MediaTrack = require('./mediatrack');\nconst captureVideoFrames = require('./capturevideoframes');\nconst VideoProcessorEventObserver = require('./videoprocessoreventobserver');\nconst { guessBrowser } = require('../../webrtc/util');\nconst { DEFAULT_FRAME_RATE } = require('../../util/constants');\n\n/**\n * A {@link VideoTrack} is a {@link Track} representing video.\n * @extends Track\n * @property {boolean} isStarted - Whether or not the {@link VideoTrack} has\n * started; if the {@link VideoTrack} started, there is enough video data to\n * begin playback\n * @property {boolean} isEnabled - Whether or not the {@link VideoTrack} is\n * enabled; if the {@link VideoTrack} is not enabled, it is \"paused\"\n * @property {VideoTrack.Dimensions} dimensions - The {@link VideoTrack}'s\n * {@link VideoTrack.Dimensions}\n * @property {Track.Kind} kind - \"video\"\n * @property {MediaStreamTrack} mediaStreamTrack - A video MediaStreamTrack\n * @property {?MediaStreamTrack} processedTrack - The source of processed video frames.\n * It is null if no VideoProcessor has been added.\n * @property {?VideoProcessor} processor - A {@link VideoProcessor} that is currently\n * processing video frames. It is null if video frames are not being processed.\n * @emits VideoTrack#dimensionsChanged\n * @emits VideoTrack#disabled\n * @emits VideoTrack#enabled\n * @emits VideoTrack#started\n */\nclass VideoTrack extends MediaTrack {\n /**\n * Construct a {@link VideoTrack}.\n * @param {MediaTrackTransceiver} mediaTrackTransceiver\n * @param {{log: Log}} options\n */\n constructor(mediaTrackTransceiver, options) {\n super(mediaTrackTransceiver, options);\n Object.defineProperties(this, {\n _isCapturing: {\n value: false,\n writable: true\n },\n _inputFrame: {\n value: null,\n writable: true\n },\n _outputFrame: {\n value: null,\n writable: true\n },\n _processorEventObserver: {\n value: null,\n writable: true,\n },\n _processorOptions: {\n value: {},\n writable: true,\n },\n _stopCapture: {\n value: () => {},\n writable: true\n },\n _unmuteHandler: {\n value: null,\n writable: true\n },\n dimensions: {\n enumerable: true,\n value: {\n width: null,\n height: null\n }\n },\n processor: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n\n this._processorEventObserver = new (options.VideoProcessorEventObserver || VideoProcessorEventObserver)(this._log);\n\n return this;\n }\n\n /**\n * @private\n */\n _checkIfCanCaptureFrames(isPublishing = false) {\n let canCaptureFrames = true;\n let message = '';\n const { enabled, readyState } = this.mediaStreamTrack;\n\n if (!enabled) {\n canCaptureFrames = false;\n message = 'MediaStreamTrack is disabled';\n }\n if (readyState === 'ended') {\n canCaptureFrames = false;\n message = 'MediaStreamTrack is ended';\n }\n if (!this.processor) {\n canCaptureFrames = false;\n message = 'VideoProcessor not detected.';\n }\n if (!this._attachments.size && !isPublishing) {\n canCaptureFrames = false;\n message = 'VideoTrack is not publishing and there is no attached element.';\n }\n\n if (message) {\n this._log.debug(message);\n }\n return { canCaptureFrames, message };\n }\n\n /**\n * @private\n */\n _captureFrames() {\n if (this._isCapturing) {\n this._log.debug('Ignoring captureFrames call. Capture is already in progress');\n return;\n }\n if (!this._checkIfCanCaptureFrames().canCaptureFrames) {\n this._isCapturing = false;\n this._log.debug('Cannot capture frames. Ignoring captureFrames call.');\n return;\n }\n this._isCapturing = true;\n this._processorEventObserver.emit('start');\n this._log.debug('Start capturing frames');\n\n const { inputFrameBufferType } = this._processorOptions;\n\n this._dummyEl.play().then(() => {\n const process = videoFrame => {\n const checkResult = this._checkIfCanCaptureFrames();\n if (!checkResult.canCaptureFrames) {\n if (videoFrame) {\n videoFrame.close();\n }\n this._isCapturing = false;\n this._stopCapture();\n this._processorEventObserver.emit('stop', checkResult.message);\n this._log.debug('Cannot capture frames. Stopping capturing frames.');\n return Promise.resolve();\n }\n const { width = 0, height = 0 } = this.mediaStreamTrack.getSettings();\n // Setting the canvas' dimension triggers a redraw.\n // Only set it if it has changed.\n if (this._outputFrame && this._outputFrame.width !== width) {\n this._outputFrame.width = width;\n this._outputFrame.height = height;\n }\n if (this._inputFrame) {\n if (this._inputFrame.width !== width) {\n this._inputFrame.width = width;\n this._inputFrame.height = height;\n }\n this._inputFrame.getContext('2d').drawImage(\n this._dummyEl,\n 0,\n 0,\n width,\n height\n );\n }\n const input = videoFrame || (\n ['video', 'videoframe'].includes(inputFrameBufferType)\n ? this._dummyEl\n : this._inputFrame\n );\n let result = null;\n\n try {\n result = this.processor.processFrame(input, this._outputFrame);\n } catch (ex) {\n this._log.debug('Exception detected after calling processFrame.', ex);\n }\n return ((result instanceof Promise) ? result : Promise.resolve(result))\n .then(() => {\n if (this._outputFrame) {\n if (typeof this.processedTrack.requestFrame === 'function') {\n this.processedTrack.requestFrame();\n }\n this._processorEventObserver.emit('stats');\n }\n });\n };\n this._stopCapture = captureVideoFrames(\n this._dummyEl,\n process,\n inputFrameBufferType\n );\n }).catch(error => this._log.error(\n 'Video element cannot be played',\n { error, track: this }\n ));\n }\n\n /**\n * @private\n */\n _initialize() {\n super._initialize();\n if (this._dummyEl) {\n this._dummyEl.onloadedmetadata = () => {\n if (dimensionsChanged(this, this._dummyEl)) {\n this.dimensions.width = this._dummyEl.videoWidth;\n this.dimensions.height = this._dummyEl.videoHeight;\n }\n };\n this._dummyEl.onresize = () => {\n if (dimensionsChanged(this, this._dummyEl)) {\n this.dimensions.width = this._dummyEl.videoWidth;\n this.dimensions.height = this._dummyEl.videoHeight;\n if (this.isStarted) {\n this._log.debug('Dimensions changed:', this.dimensions);\n this.emit(VideoTrack.DIMENSIONS_CHANGED, this);\n }\n }\n };\n }\n }\n\n /**\n * @private\n */\n _restartProcessor() {\n const processor = this.processor;\n if (processor) {\n const processorOptions = Object.assign({}, this._processorOptions);\n this.removeProcessor(processor);\n this.addProcessor(processor, processorOptions);\n }\n }\n\n /**\n * @private\n */\n _start(dummyEl) {\n this.dimensions.width = dummyEl.videoWidth;\n this.dimensions.height = dummyEl.videoHeight;\n\n this._log.debug('Dimensions:', this.dimensions);\n this.emit(VideoTrack.DIMENSIONS_CHANGED, this);\n return super._start.call(this, dummyEl);\n }\n\n /**\n * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.\n * @param {AddProcessorOptions} [options] - {@link AddProcessorOptions} to provide.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * videoTrack.addProcessor(new GrayScaleProcessor(100));\n * });\n */\n addProcessor(processor, options) {\n if (!processor || typeof processor.processFrame !== 'function') {\n throw new Error('Received an invalid VideoProcessor from addProcessor.');\n }\n if (this.processor) {\n throw new Error('A VideoProcessor has already been added.');\n }\n if (!this._dummyEl) {\n throw new Error('VideoTrack has not been initialized.');\n }\n\n this._log.debug('Adding VideoProcessor to the VideoTrack', processor);\n\n if (!this._unmuteHandler) {\n this._unmuteHandler = () => {\n this._log.debug('mediaStreamTrack unmuted');\n // NOTE(csantos): On certain scenarios where mediaStreamTrack is coming from muted to unmuted state,\n // the processedTrack doesn't unmutes automatically although enabled is already set to true.\n // This is a terminal state for the processedTrack and should be restarted. (VIDEO-4176)\n if (this.processedTrack.muted) {\n this._log.debug('mediaStreamTrack is unmuted but processedTrack is muted. Restarting processor.');\n this._restartProcessor();\n }\n };\n this.mediaStreamTrack.addEventListener('unmute', this._unmuteHandler);\n }\n\n this._processorOptions = options || {};\n let { inputFrameBufferType, outputFrameBufferContextType } = this._processorOptions;\n if (typeof OffscreenCanvas === 'undefined' && inputFrameBufferType === 'offscreencanvas') {\n throw new Error('OffscreenCanvas is not supported by this browser.');\n }\n if (inputFrameBufferType\n && inputFrameBufferType !== 'videoframe'\n && inputFrameBufferType !== 'video'\n && inputFrameBufferType !== 'canvas'\n && inputFrameBufferType !== 'offscreencanvas') {\n throw new Error(`Invalid inputFrameBufferType of ${inputFrameBufferType}`);\n }\n if (!inputFrameBufferType) {\n inputFrameBufferType = typeof OffscreenCanvas === 'undefined' ? 'canvas' : 'offscreencanvas';\n }\n\n const { width = 0, height = 0, frameRate = DEFAULT_FRAME_RATE } = this.mediaStreamTrack.getSettings();\n if (inputFrameBufferType === 'offscreencanvas') {\n this._inputFrame = new OffscreenCanvas(width, height);\n }\n if (inputFrameBufferType === 'canvas') {\n this._inputFrame = document.createElement('canvas');\n }\n if (this._inputFrame) {\n this._inputFrame.width = width;\n this._inputFrame.height = height;\n }\n\n this._outputFrame = document.createElement('canvas');\n this._outputFrame.width = width;\n this._outputFrame.height = height;\n\n // NOTE(csantos): Initialize the rendering context for future renders. This also ensures\n // that the correct type is used and on Firefox, it throws an exception if you try to capture\n // frames prior calling getContext https://bugzilla.mozilla.org/show_bug.cgi?id=1572422\n outputFrameBufferContextType = outputFrameBufferContextType || '2d';\n\n const ctx = this._outputFrame.getContext(outputFrameBufferContextType);\n if (!ctx) {\n throw new Error(`Cannot get outputFrameBufferContextType: ${outputFrameBufferContextType}.`);\n }\n\n // NOTE(csantos): Zero FPS means we can control when to render the next frame by calling requestFrame.\n // Some browsers such as Firefox doesn't support requestFrame so we will use default, which is an undefined value.\n // This means, the browser will use the highest FPS available.\n const targetFps = typeof CanvasCaptureMediaStreamTrack !== 'undefined' && CanvasCaptureMediaStreamTrack.prototype &&\n // eslint-disable-next-line\n typeof CanvasCaptureMediaStreamTrack.prototype.requestFrame === 'function' ? 0 : undefined;\n\n this.processedTrack = this._outputFrame.captureStream(targetFps).getTracks()[0];\n this.processedTrack.enabled = this.mediaStreamTrack.enabled;\n this.processor = processor;\n\n this._processorEventObserver.emit('add', {\n processor,\n captureHeight: height,\n captureWidth: width,\n inputFrameRate: frameRate,\n isRemoteVideoTrack: this.toString().includes('RemoteVideoTrack'),\n inputFrameBufferType,\n outputFrameBufferContextType\n });\n this._updateElementsMediaStreamTrack();\n this._captureFrames();\n return this;\n }\n\n /**\n * Create an HTMLVideoElement and attach the {@link VideoTrack} to it.\n *\n * The HTMLVideoElement's srcObject will be set to a new\n * MediaStream containing the {@link VideoTrack}'s MediaStreamTrack.\n *\n * @returns {HTMLVideoElement} videoElement\n * @example\n * const Video = require('twilio-video');\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * const videoElement = videoTrack.attach();\n * document.body.appendChild(videoElement);\n * });\n *//**\n * Attach the {@link VideoTrack} to an existing HTMLMediaElement. The\n * HTMLMediaElement could be an HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link VideoTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link MediaTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {HTMLMediaElement} mediaElement - The HTMLMediaElement to attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * videoTrack.attach(videoElement);\n * });\n *//**\n * Attach the {@link VideoTrack} to an HTMLMediaElement selected by\n * document.querySelector. The HTMLMediaElement could be an\n * HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link VideoTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link VideoTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {string} selector - A query selector for the HTMLMediaElement to\n * attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * videoElement.id = 'my-video-element';\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalVideoTrack().then(function(track) {\n * track.attach('#my-video-element');\n * });\n */\n attach() {\n const result = super.attach.apply(this, arguments);\n if (this.processor) {\n this._captureFrames();\n }\n return result;\n }\n\n /**\n * Detach the {@link VideoTrack} from all previously attached HTMLMediaElements.\n * @returns {Array} mediaElements\n * @example\n * const mediaElements = videoTrack.detach();\n * mediaElements.forEach(mediaElement => mediaElement.remove());\n *//**\n * Detach the {@link VideoTrack} from a previously attached HTMLMediaElement.\n * @param {HTMLMediaElement} mediaElement - One of the HTMLMediaElements to\n * which the {@link VideoTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const videoElement = document.getElementById('my-video-element');\n * videoTrack.detach(videoElement).remove();\n *//**\n * Detach the {@link VideoTrack} from a previously attached HTMLMediaElement\n * specified by document.querySelector.\n * @param {string} selector - The query selector of HTMLMediaElement to which\n * the {@link VideoTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * videoTrack.detach('#my-video-element').remove();\n */\n detach() {\n return super.detach.apply(this, arguments);\n }\n\n /**\n * Remove the previously added {@link VideoProcessor} using `addProcessor` API.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to remove.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * const grayScaleProcessor = new GrayScaleProcessor(100);\n * videoTrack.addProcessor(grayScaleProcessor);\n * document.getElementById('remove-button').onclick = () => videoTrack.removeProcessor(grayScaleProcessor);\n * });\n */\n removeProcessor(processor) {\n if (!processor) {\n throw new Error('Received an invalid VideoProcessor from removeProcessor.');\n }\n if (!this.processor) {\n throw new Error('No existing VideoProcessor detected.');\n }\n if (processor !== this.processor) {\n throw new Error('The provided VideoProcessor is different than the existing one.');\n }\n\n this._processorEventObserver.emit('remove');\n this._log.debug('Removing VideoProcessor from the VideoTrack', processor);\n this._stopCapture();\n this._stopCapture = () => {};\n this.mediaStreamTrack.removeEventListener('unmute', this._unmuteHandler);\n this._processorOptions = {};\n this._unmuteHandler = null;\n this._isCapturing = false;\n\n this.processor = null;\n this.processedTrack = null;\n this._inputFrame = null;\n this._outputFrame = null;\n\n this._updateElementsMediaStreamTrack();\n return this;\n }\n}\n\nVideoTrack.DIMENSIONS_CHANGED = 'dimensionsChanged';\n\nfunction dimensionsChanged(track, elem) {\n return track.dimensions.width !== elem.videoWidth\n || track.dimensions.height !== elem.videoHeight;\n}\n\n/**\n * A {@link VideoTrack}'s width and height.\n * @typedef {object} VideoTrack.Dimensions\n * @property {?number} width - The {@link VideoTrack}'s width or null if the\n * {@link VideoTrack} has not yet started\n * @property {?number} height - The {@link VideoTrack}'s height or null if the\n * {@link VideoTrack} has not yet started\n */\n\n/**\n * A {@link VideoProcessor}, when added via {@link VideoTrack#addProcessor},\n * is used to process incoming video frames before\n * sending to the encoder or renderer.\n * @typedef {object} VideoProcessor\n * @property {function} processFrame - A callback to receive input and output frame buffers for processing.\n * The input frame buffer contains the original video frame which can be used for additional processing\n * such as applying filters to it. The output frame buffer is used to receive the processed video frame\n * before sending to the encoder or renderer.\n *\n * Any exception raised (either synchronously or asynchronously) in `processFrame` will result in the frame being dropped.\n * This callback has the following signature:

\n * processFrame(
\n *   inputFrameBuffer: OffscreenCanvas | HTMLCanvasElement | HTMLVideoElement | VideoFrame,
\n *   outputFrameBuffer: HTMLCanvasElement
\n * ): Promise<void> | void;\n *\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n */\n\n/**\n * Possible options to provide to {@link LocalVideoTrack#addProcessor} and {@link RemoteVideoTrack#addProcessor}.\n * @typedef {object} AddProcessorOptions\n * @property {string} [inputFrameBufferType=\"offscreencanvas\"] - This option allows you to specify what kind of input you want to receive in your\n * Video Processor. The default is `offscreencanvas` and will fallback to a regular `canvas` if the browser does not support it.\n * Possible values include the following.\n *
\n *
\n * `videoframe` - Your Video Processor will receive a [VideoFrame](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame).\n * On browsers that do not support `VideoFrame`, it will receive an [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement) instead.\n *
\n *
\n * `offscreencanvas` - Your Video Processor will receive an [OffscreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas)\n * which is good for canvas-related processing that can be rendered off screen.\n *
\n *
\n * `canvas` - Your Video Processor will receive an [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement).\n * This is recommended on browsers that doesn't support `OffscreenCanvas`, or if you need to render the frame on the screen.\n *
\n *
\n * `video` - Your Video Processor will receive an [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement).\n * Use this option if you are processing the frame using WebGL or if you only need to [draw](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage)\n * the frame directly to your output canvas.\n * @property {string} [outputFrameBufferContextType=\"2d\"] - The SDK needs the [context type](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext)\n * that your Video Processor uses in order to properly generate the processed track. For example, if your Video Processor uses WebGL2 (`canvas.getContext('webgl2')`),\n * you should set `outputFrameBufferContextType` to `webgl2`. If you're using Canvas 2D processing (`canvas.getContext('2d')`),\n * you should set `outputFrameBufferContextType` to `2d`. If the output frame is an [ImageBitmap](https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap),\n * you should set `outputFrameBufferContextType` to `bitmaprenderer`.\n */\n\n/**\n * The {@link VideoTrack}'s dimensions changed.\n * @param {VideoTrack} track - The {@link VideoTrack} whose dimensions changed\n * @event VideoTrack#dimensionsChanged\n */\n\n/**\n * The {@link VideoTrack} was disabled, i.e. \"paused\".\n * @param {VideoTrack} track - The {@link VideoTrack} that was disabled\n * @event VideoTrack#disabled\n */\n\n/**\n * The {@link VideoTrack} was enabled, i.e. \"unpaused\".\n * @param {VideoTrack} track - The {@link VideoTrack} that was enabled\n * @event VideoTrack#enabled\n */\n\n/**\n * The {@link VideoTrack} started. This means there is enough video data to\n * begin playback.\n * @param {VideoTrack} track - The {@link VideoTrack} that started\n * @event VideoTrack#started\n */\n\nmodule.exports = VideoTrack;\n", "'use strict';\n\nconst { isIOS } = require('../../util/browserdetection');\nconst detectSilentVideo = require('../../util/detectsilentvideo');\nconst mixinLocalMediaTrack = require('./localmediatrack');\nconst VideoTrack = require('./videotrack');\n\nconst LocalMediaVideoTrack = mixinLocalMediaTrack(VideoTrack);\n\n/**\n * A {@link LocalVideoTrack} is a {@link VideoTrack} representing video that\n * your {@link LocalParticipant} can publish to a {@link Room}. It can be\n * enabled and disabled with {@link LocalVideoTrack#enable} and\n * {@link LocalVideoTrack#disable} or stopped completely with\n * {@link LocalVideoTrack#stop}.\n * @extends VideoTrack\n * @property {Track.ID} id - The {@link LocalVideoTrack}'s ID\n * @property {boolean} isMuted - Whether or not the video source has stopped sending frames to the\n * {@link LocalVideoTrack}; This can happen when the camera is taken over by another application,\n * mainly on mobile devices; When this property toggles, then muted and unmuted\n * events are fired appropriately\n * @property {boolean} isStopped - Whether or not the {@link LocalVideoTrack} is\n * stopped\n * @emits LocalVideoTrack#disabled\n * @emits LocalVideoTrack#enabled\n * @emits LocalVideoTrack#muted\n * @emits LocalVideoTrack#started\n * @emits LocalVideoTrack#stopped\n * @emits LocalVideoTrack#unmuted\n */\nclass LocalVideoTrack extends LocalMediaVideoTrack {\n /**\n * Construct a {@link LocalVideoTrack} from a MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack - The underlying MediaStreamTrack\n * @param {LocalTrackOptions} [options] - {@link LocalTrack} options\n */\n constructor(mediaStreamTrack, options) {\n options = Object.assign({\n workaroundSilentLocalVideo: isIOS()\n && typeof document !== 'undefined'\n && typeof document.createElement === 'function'\n }, options);\n\n super(mediaStreamTrack, options);\n\n Object.defineProperties(this, {\n _workaroundSilentLocalVideo: {\n value: options.workaroundSilentLocalVideo\n ? workaroundSilentLocalVideo\n : null\n },\n _workaroundSilentLocalVideoCleanup: {\n value: null,\n writable: true\n }\n });\n\n // NOTE(mmalavalli): In iOS Safari, we work around a bug where local video\n // MediaStreamTracks are silent (even though they are enabled, live and unmuted)\n // after accepting/rejecting a phone call.\n if (this._workaroundSilentLocalVideo) {\n this._workaroundSilentLocalVideoCleanup = this._workaroundSilentLocalVideo(this, document);\n }\n }\n\n toString() {\n return `[LocalVideoTrack #${this._instanceId}: ${this.id}]`;\n }\n\n /**\n * @private\n */\n _checkIfCanCaptureFrames() {\n return super._checkIfCanCaptureFrames.call(this, this._trackSender.isPublishing);\n }\n\n /**\n * @private\n */\n _end() {\n return super._end.apply(this, arguments);\n }\n\n /**\n * @private\n */\n _setSenderMediaStreamTrack(useProcessed) {\n const unprocessedTrack = this.mediaStreamTrack;\n const mediaStreamTrack = useProcessed ? this.processedTrack : unprocessedTrack;\n\n return this._trackSender.setMediaStreamTrack(mediaStreamTrack)\n .catch(error => this._log.warn(\n 'setMediaStreamTrack failed on LocalVideoTrack RTCRtpSender', { error, mediaStreamTrack }))\n .then(() => {\n this._unprocessedTrack = useProcessed ? unprocessedTrack : null;\n });\n }\n\n /**\n * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.\n * @param {AddProcessorOptions} [options] - {@link AddProcessorOptions} to provide.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const localVideoTrack = Array.from(room.localParticipant.videoTracks.values())[0].track;\n * localVideoTrack.addProcessor(new GrayScaleProcessor(100));\n */\n addProcessor() {\n this._log.debug('Adding VideoProcessor to the LocalVideoTrack');\n const result = super.addProcessor.apply(this, arguments);\n\n if (!this.processedTrack) {\n return this._log.warn('Unable to add a VideoProcessor to the LocalVideoTrack');\n }\n\n this._log.debug('Updating LocalVideoTrack\\'s MediaStreamTrack with the processed MediaStreamTrack', this.processedTrack);\n this._setSenderMediaStreamTrack(true);\n\n return result;\n }\n\n /**\n * Remove the previously added {@link VideoProcessor} using `addProcessor` API.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to remove.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const localVideoTrack = Array.from(room.localParticipant.videoTracks.values())[0].track;\n * const grayScaleProcessor = new GrayScaleProcessor(100);\n * localVideoTrack.addProcessor(grayScaleProcessor);\n *\n * document.getElementById('remove-button').onclick = () => localVideoTrack.removeProcessor(grayScaleProcessor);\n */\n removeProcessor() {\n this._log.debug('Removing VideoProcessor from the LocalVideoTrack');\n const result = super.removeProcessor.apply(this, arguments);\n\n this._log.debug('Updating LocalVideoTrack\\'s MediaStreamTrack with the original MediaStreamTrack');\n this._setSenderMediaStreamTrack()\n .then(() => this._updateElementsMediaStreamTrack());\n\n return result;\n }\n\n /**\n * Disable the {@link LocalVideoTrack}. This is equivalent to pausing a video source.\n * If a {@link VideoProcessor} is added, then processedTrack is also disabled.\n * @returns {this}\n * @fires VideoTrack#disabled\n */\n disable() {\n const result = super.disable.apply(this, arguments);\n if (this.processedTrack) {\n this.processedTrack.enabled = false;\n }\n return result;\n }\n\n /**\n * Enable the {@link LocalVideoTrack}. This is equivalent to unpausing the video source.\n * If a {@link VideoProcessor} is added, then processedTrack is also enabled.\n * @returns {this}\n * @fires VideoTrack#enabled\n *//**\n * Enable or disable the {@link LocalVideoTrack}. This is equivalent to unpausing or pausing\n * the video source respectively. If a {@link VideoProcessor} is added, then processedTrack\n * is also enabled or disabled.\n * @param {boolean} [enabled] - Specify false to disable the\n * {@link LocalVideoTrack}\n * @returns {this}\n * @fires VideoTrack#disabled\n * @fires VideoTrack#enabled\n */\n enable(enabled = true) {\n const result = super.enable.apply(this, arguments);\n if (this.processedTrack) {\n this.processedTrack.enabled = enabled;\n\n if (enabled) {\n this._captureFrames();\n this._log.debug('Updating LocalVideoTrack\\'s MediaStreamTrack with the processed MediaStreamTrack', this.processedTrack);\n this._setSenderMediaStreamTrack(true);\n }\n }\n return result;\n }\n\n /**\n * Restart the {@link LocalVideoTrack}. This stops the existing MediaStreamTrack\n * and creates a new MediaStreamTrack. If the {@link LocalVideoTrack} is being published\n * to a {@link Room}, then all the {@link RemoteParticipant}s will start receiving media\n * from the newly created MediaStreamTrack. You can access the new MediaStreamTrack via\n * the mediaStreamTrack property. If you want to listen to events on\n * the MediaStreamTrack directly, please do so in the \"started\" event handler. Also,\n * the {@link LocalVideoTrack}'s ID is no longer guaranteed to be the same as the\n * underlying MediaStreamTrack's ID.\n * @param {MediaTrackConstraints} [constraints] - The optional MediaTrackConstraints\n * for restarting the {@link LocalVideoTrack}; If not specified, then the current MediaTrackConstraints\n * will be used; If {} (empty object) is specified, then the default MediaTrackConstraints\n * will be used\n * @returns {Promise} Rejects with a TypeError if the {@link LocalVideoTrack} was not created\n * using an one of createLocalVideoTrack, createLocalTracks or connect;\n * Also rejects with the DOMException\n * raised by getUserMedia when it fails\n * @fires LocalVideoTrack#stopped\n * @fires LocalVideoTrack#started\n * @example\n * const { connect, createLocalVideoTrack } = require('twilio-video');\n *\n * // Create a LocalVideoTrack that captures video from the front-facing camera.\n * createLocalVideoTrack({ facingMode: 'user' }).then(function(localVideoTrack) {\n * return connect('token', {\n * name: 'my-cool-room',\n * tracks: [localVideoTrack]\n * });\n * }).then(function(room) {\n * // Restart the LocalVideoTrack to capture video from the back-facing camera.\n * const localVideoTrack = Array.from(room.localParticipant.videoTracks.values())[0].track;\n * return localVideoTrack.restart({ facingMode: 'environment' });\n * });\n */\n restart() {\n if (this._workaroundSilentLocalVideoCleanup) {\n this._workaroundSilentLocalVideoCleanup();\n this._workaroundSilentLocalVideoCleanup = null;\n }\n\n const promise = super.restart.apply(this, arguments);\n if (this.processor) {\n promise.then(() => {\n this._restartProcessor();\n });\n }\n\n if (this._workaroundSilentLocalVideo) {\n promise.finally(() => {\n this._workaroundSilentLocalVideoCleanup = this._workaroundSilentLocalVideo(this, document);\n });\n }\n return promise;\n }\n\n /**\n * Calls stop on the underlying MediaStreamTrack. If you choose to stop a\n * {@link LocalVideoTrack}, you should unpublish it after stopping.\n * @returns {this}\n * @fires LocalVideoTrack#stopped\n */\n stop() {\n if (this._workaroundSilentLocalVideoCleanup) {\n this._workaroundSilentLocalVideoCleanup();\n this._workaroundSilentLocalVideoCleanup = null;\n }\n return super.stop.apply(this, arguments);\n }\n}\n\n/**\n * Work around a bug where local video MediaStreamTracks are silent (even though\n * they are enabled, live and unmuted) after accepting/rejecting a phone call.\n * @private\n * @param {LocalVideoTrack} localVideoTrack\n * @param {HTMLDocument} doc\n * @returns {function} Cleans up listeners attached by the workaround\n */\nfunction workaroundSilentLocalVideo(localVideoTrack, doc) {\n const { _log: log } = localVideoTrack;\n let { _dummyEl: el, mediaStreamTrack } = localVideoTrack;\n\n function onUnmute() {\n if (!localVideoTrack.isEnabled) {\n return;\n }\n log.info('Unmuted, checking silence');\n\n // The dummy element is paused, so play it and then detect silence.\n el.play().then(() => detectSilentVideo(el, doc)).then(isSilent => {\n if (!isSilent) {\n log.info('Non-silent frames detected, so no need to restart');\n return;\n }\n log.warn('Silence detected, restarting');\n\n // NOTE(mmalavalli): If we try and restart a silent MediaStreamTrack\n // without stopping it first, then a NotReadableError is raised. Hence,\n // we stop the MediaStreamTrack here.\n localVideoTrack._stop();\n\n // Restart the LocalVideoTrack.\n // eslint-disable-next-line consistent-return\n return localVideoTrack._restart();\n }).catch(error => {\n log.warn('Failed to detect silence and restart:', error);\n }).finally(() => {\n // If silent frames were not detected, then pause the dummy element again,\n // if there is no processed track.\n el = localVideoTrack._dummyEl;\n if (el && !el.paused && !localVideoTrack.processedTrack) {\n el.pause();\n }\n\n // Reset the unmute handler.\n mediaStreamTrack.removeEventListener('unmute', onUnmute);\n mediaStreamTrack = localVideoTrack.mediaStreamTrack;\n mediaStreamTrack.addEventListener('unmute', onUnmute);\n });\n }\n\n // Set the unmute handler.\n mediaStreamTrack.addEventListener('unmute', onUnmute);\n\n return () => {\n mediaStreamTrack.removeEventListener('unmute', onUnmute);\n };\n}\n\n/**\n * The {@link LocalVideoTrack} was disabled, i.e. the video source was paused by the user.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was\n * disabled\n * @event LocalVideoTrack#disabled\n */\n\n/**\n * The {@link LocalVideoTrack} was enabled, i.e. the video source was unpaused by the user.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was enabled\n * @event LocalVideoTrack#enabled\n */\n\n/**\n * The {@link LocalVideoTrack} was muted because the video source stopped sending frames, most\n * likely due to another application taking said video source, especially on mobile devices.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was muted\n * @event LocalVideoTrack#muted\n */\n\n/**\n * The {@link LocalVideoTrack} started. This means there is enough video data\n * to begin playback.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that started\n * @event LocalVideoTrack#started\n */\n\n/**\n * The {@link LocalVideoTrack} stopped, either because {@link LocalVideoTrack#stop}\n * or {@link LocalVideoTrack#restart} was called or because the underlying\n * MediaStreamTrack ended.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that stopped\n * @event LocalVideoTrack#stopped\n */\n\n/**\n * The {@link LocalVideoTrack} was unmuted because the video source resumed sending frames,\n * most likely due to the application that took over the said video source has released it\n * back to the application, especially on mobile devices. This event is also fired when\n * {@link LocalVideoTrack#restart} is called on a muted {@link LocalVideoTrack} with a\n * new video source.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was unmuted\n * @event LocalVideoTrack#unmuted\n */\n\nmodule.exports = LocalVideoTrack;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): Remove this when we go to the next major version. This is\n// only in place so that we can support ES6 classes without requiring `new`.\n'use strict';\n\nconst inherits = require('../../../vendor/inherits');\n\nconst LocalVideoTrackClass = require('../localvideotrack');\n\nfunction LocalVideoTrack(mediaStreamTrack, options) {\n const track = new LocalVideoTrackClass(mediaStreamTrack, options);\n Object.setPrototypeOf(track, LocalVideoTrack.prototype);\n return track;\n}\n\ninherits(LocalVideoTrack, LocalVideoTrackClass);\n\nmodule.exports = LocalVideoTrack;\n", "'use strict';\n\nconst TrackTransceiver = require('../transceiver');\n\n/**\n * A {@link DataTrackTransceiver} represents either one or more local\n * RTCDataChannels or a single remote RTCDataChannel. It can be used to send or\n * receive data.\n * @extends TrackTransceiver\n * @property {string} id\n * @property {string} kind - \"data\"\n * @property {?number} maxPacketLifeTime\n * @property {?number} maxRetransmits\n * @property {boolean} ordered\n */\nclass DataTrackTransceiver extends TrackTransceiver {\n /**\n * Construct a {@link DataTrackTransceiver}.\n * @param {string} id\n * @param {?number} maxPacketLifeTime\n * @param {?number} maxRetransmits\n * @param {boolean} ordered\n */\n constructor(id, maxPacketLifeTime, maxRetransmits, ordered) {\n super(id, 'data');\n Object.defineProperties(this, {\n maxPacketLifeTime: {\n enumerable: true,\n value: maxPacketLifeTime\n },\n maxRetransmits: {\n enumerable: true,\n value: maxRetransmits\n },\n ordered: {\n enumerable: true,\n value: ordered\n }\n });\n }\n}\n\nmodule.exports = DataTrackTransceiver;\n", "'use strict';\n\nconst DataTrackTransceiver = require('./transceiver');\nconst makeUUID = require('../util').makeUUID;\n\n/**\n * A {@link DataTrackSender} represents a {@link DataTrackTransceiver} over\n * which data can be sent. Internally, it uses a collection of RTCDataChannels\n * to send data.\n * @extends DataTrackTransceiver\n */\nclass DataTrackSender extends DataTrackTransceiver {\n /**\n * Construct a {@link DataTrackSender}.\n * @param {?number} maxPacketLifeTime\n * @param {?number} maxRetransmits\n * @param {boolean} ordered\n */\n constructor(maxPacketLifeTime, maxRetransmtis, ordered) {\n super(makeUUID(), maxPacketLifeTime, maxRetransmtis, ordered);\n Object.defineProperties(this, {\n _clones: {\n value: new Set()\n },\n _dataChannels: {\n value: new Set()\n }\n });\n }\n\n /**\n * Add a cloned {@link DataTrackSender}.\n * @private\n * @returns {void}\n */\n _addClone(clone) {\n this._clones.add(clone);\n }\n\n /**\n * Remove a cloned {@link DataTrackSender}.\n * @returns {void}\n */\n removeClone(clone) {\n this._clones.delete(clone);\n }\n\n /**\n * Add an RTCDataChannel to the {@link DataTrackSender}.\n * @param {RTCDataChannel} dataChannel\n * @returns {this}\n */\n addDataChannel(dataChannel) {\n this._dataChannels.add(dataChannel);\n return this;\n }\n\n\n /**\n * Return a new {@link DataTrackSender}. Any message sent over this\n * {@link DataTrackSender} will also be sent over the clone. Whenever this\n * {@link DataTrackSender} is stopped, so to will the clone.\n * @returns {DataTrackSender}\n */\n clone() {\n const clone = new DataTrackSender(\n this.maxPacketLifeTime,\n this.maxRetransmits,\n this.ordered);\n this._addClone(clone);\n clone.once('stopped', () => this.removeClone(clone));\n return clone;\n }\n\n /**\n * Remove an RTCDataChannel from the {@link DataTrackSender}.\n * @param {RTCDataChannel} dataChannel\n * @returns {this}\n */\n removeDataChannel(dataChannel) {\n this._dataChannels.delete(dataChannel);\n return this;\n }\n\n /**\n * Send data over the {@link DataTrackSender}. Internally, this calls\n * send over each of the underlying RTCDataChannels.\n * @param {string|Blob|ArrayBuffer|ArrayBufferView} data\n * @returns {this}\n */\n send(data) {\n this._dataChannels.forEach(dataChannel => {\n try {\n dataChannel.send(data);\n } catch (error) {\n // Do nothing.\n }\n });\n this._clones.forEach(clone => {\n try {\n clone.send(data);\n } catch (error) {\n // Do nothing.\n }\n });\n return this;\n }\n\n stop() {\n this._dataChannels.forEach(dataChannel => dataChannel.close());\n this._clones.forEach(clone => clone.stop());\n super.stop();\n }\n}\n\nmodule.exports = DataTrackSender;\n", "'use strict';\n\nconst Track = require('./');\nconst DefaultDataTrackSender = require('../../data/sender');\n\n/**\n * A {@link LocalDataTrack} is a {@link Track} representing data that your\n * {@link LocalParticipant} can publish to a {@link Room}.\n * @extends Track\n * @property {Track.ID} id - The {@link LocalDataTrack}'s ID\n * @property {Track.Kind} kind - \"data\"\n * @property {?number} maxPacketLifeTime - If non-null, this represents a time\n * limit (in milliseconds) during which the {@link LocalDataTrack} will send\n * or re-send data if not acknowledged on the underlying RTCDataChannel(s).\n * @property {?number} maxRetransmits - If non-null, this represents the number\n * of times the {@link LocalDataTrack} will resend data if not successfully\n * delivered on the underlying RTCDataChannel(s).\n * @property {boolean} ordered - true if data on the {@link LocalDataTrack} is\n * guaranteed to be sent in order.\n * @property {boolean} reliable - This is true if both\n * maxPacketLifeTime and maxRetransmits are set to\n * null. In other words, if this is true, there is no bound on packet lifetime\n * or the number of times the {@link LocalDataTrack} will attempt to send\n * data, ensuring \"reliable\" transmission.\n * @example\n * var Video = require('twilio-video');\n *\n * var localDataTrack = new Video.LocalDataTrack();\n * window.addEventListener('mousemove', function(event) {\n * localDataTrack.send(JSON.stringify({\n * x: e.clientX,\n * y: e.clientY\n * }));\n * });\n *\n * var token1 = getAccessToken();\n * Video.connect(token1, {\n * name: 'my-cool-room',\n * tracks: [localDataTrack]\n * });\n *\n * var token2 = getAccessToken();\n * Video.connect(token2, {\n * name: 'my-cool-room',\n * tracks: []\n * }).then(function(room) {\n * room.on('trackSubscribed', function(track) {\n * track.on('message', function(message) {\n * console.log(JSON.parse(message)); // { x: , y: }\n * });\n * });\n * });\n */\nclass LocalDataTrack extends Track {\n /**\n * Construct a {@link LocalDataTrack}.\n * @param {LocalDataTrackOptions} [options] - {@link LocalDataTrack} options\n */\n constructor(options) {\n options = Object.assign({\n DataTrackSender: DefaultDataTrackSender,\n maxPacketLifeTime: null,\n maxRetransmits: null,\n ordered: true\n }, options);\n\n const DataTrackSender = options.DataTrackSender;\n const dataTrackSender = new DataTrackSender(\n options.maxPacketLifeTime,\n options.maxRetransmits,\n options.ordered);\n\n super(dataTrackSender.id, 'data', options);\n\n Object.defineProperties(this, {\n _trackSender: {\n value: dataTrackSender\n },\n id: {\n enumerable: true,\n value: dataTrackSender.id\n },\n maxPacketLifeTime: {\n enumerable: true,\n value: options.maxPacketLifeTime\n },\n maxRetransmits: {\n enumerable: true,\n value: options.maxRetransmits\n },\n ordered: {\n enumerable: true,\n value: options.ordered\n },\n reliable: {\n enumerable: true,\n value: options.maxPacketLifeTime === null\n && options.maxRetransmits === null\n }\n });\n }\n\n /**\n * Send a message over the {@link LocalDataTrack}.\n * @param {string|Blob|ArrayBuffer|ArrayBufferView} data\n * @returns {void}\n */\n send(data) {\n this._trackSender.send(data);\n }\n}\n\n/**\n * {@link LocalDataTrack} options\n * @typedef {LocalTrackOptions} LocalDataTrackOptions\n * @property {?number} [maxPacketLifeTime=null] - Set this to limit the time\n * (in milliseconds) during which the LocalDataTrack will send or re-send data\n * if not successfully delivered on the underlying RTCDataChannel(s). It is an\n * error to specify both this and maxRetransmits.\n * @property {?number} [maxRetransmits=null] - Set this to limit the number of\n * times the {@link LocalDataTrack} will send or re-send data if not\n * acknowledged on the underlying RTCDataChannel(s). It is an error to specify\n * both this and maxPacketLifeTime.\n * @property {boolean} [ordered=true] - Set this to false to allow data on the\n * LocalDataTrack to be sent out-of-order.\n */\n\nmodule.exports = LocalDataTrack;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): Remove this when we go to the next major version. This is\n// only in place so that we can support ES6 classes without requiring `new`.\n'use strict';\n\nconst inherits = require('../../../vendor/inherits');\n\nconst LocalDataTrackClass = require('../localdatatrack');\n\nfunction LocalDataTrack(options) {\n const track = new LocalDataTrackClass(options);\n Object.setPrototypeOf(track, LocalDataTrack.prototype);\n return track;\n}\n\ninherits(LocalDataTrack, LocalDataTrackClass);\n\nmodule.exports = LocalDataTrack;\n", "'use strict';\n\nmodule.exports = {\n LocalAudioTrack: require('./localaudiotrack'),\n LocalVideoTrack: require('./localvideotrack'),\n LocalDataTrack: require('./localdatatrack')\n};\n", "/* eslint-disable @typescript-eslint/no-explicit-any */\n'use strict';\n\nimport {\n CreateLocalAudioTrackOptions,\n CreateLocalTrackOptions,\n CreateLocalTracksOptions,\n DefaultDeviceCaptureMode,\n LocalTrack,\n NoiseCancellationOptions\n} from '../tsdef';\n\nimport { applyNoiseCancellation } from './media/track/noisecancellationimpl';\n\nconst { buildLogLevels } = require('./util');\nconst { getUserMedia, MediaStreamTrack } = require('./webrtc');\n\nconst {\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack\n} = require('./media/track/es5');\n\nconst Log = require('./util/log');\nconst { DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME, typeErrors: { INVALID_VALUE } } = require('./util/constants');\nconst workaround180748 = require('./webaudio/workaround180748');\n\n// This is used to make out which createLocalTracks() call a particular Log\n// statement belongs to. Each call to createLocalTracks() increments this\n// counter.\nlet createLocalTrackCalls = 0;\n\n\ntype ExtraLocalTrackOption = CreateLocalTrackOptions & { isCreatedByCreateLocalTracks?: boolean };\ntype ExtraLocalAudioTrackOption = ExtraLocalTrackOption & { defaultDeviceCaptureMode? : DefaultDeviceCaptureMode };\ntype ExtraLocalTrackOptions = { audio: ExtraLocalAudioTrackOption; video: ExtraLocalTrackOption; };\n\ninterface InternalOptions extends CreateLocalTracksOptions {\n getUserMedia: any;\n LocalAudioTrack: any;\n LocalDataTrack: any;\n LocalVideoTrack: any;\n MediaStreamTrack: any;\n Log: any;\n}\n\n/**\n * Request {@link LocalTrack}s. By default, it requests a\n * {@link LocalAudioTrack} and a {@link LocalVideoTrack}.\n * Note that on mobile browsers, the camera can be reserved by only one {@link LocalVideoTrack}\n * at any given time. If you attempt to create a second {@link LocalVideoTrack}, video frames\n * will no longer be supplied to the first {@link LocalVideoTrack}.\n * @alias module:twilio-video.createLocalTracks\n * @param {CreateLocalTracksOptions} [options]\n * @returns {Promise>}\n * @example\n * var Video = require('twilio-video');\n * // Request audio and video tracks\n * Video.createLocalTracks().then(function(localTracks) {\n * var localMediaContainer = document.getElementById('local-media-container-id');\n * localTracks.forEach(function(track) {\n * localMediaContainer.appendChild(track.attach());\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n * // Request just the default audio track\n * Video.createLocalTracks({ audio: true }).then(function(localTracks) {\n * return Video.connect('my-token', {\n * name: 'my-cool-room',\n * tracks: localTracks\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n * // Request the audio and video tracks with custom names\n * Video.createLocalTracks({\n * audio: { name: 'microphone' },\n * video: { name: 'camera' }\n * }).then(function(localTracks) {\n * localTracks.forEach(function(localTrack) {\n * console.log(localTrack.name);\n * });\n * });\n *\n * @example\n * var Video = require('twilio-video');\n * var localTracks;\n *\n * // Pre-acquire tracks to display camera preview.\n * Video.createLocalTracks().then(function(tracks) {\n * localTracks = tracks;\n * var localVideoTrack = localTracks.find(track => track.kind === 'video');\n * divContainer.appendChild(localVideoTrack.attach());\n * })\n *\n * // Later, join the Room with the pre-acquired LocalTracks.\n * Video.connect('token', {\n * name: 'my-cool-room',\n * tracks: localTracks\n * });\n *\n */\nexport async function createLocalTracks(options?: CreateLocalTracksOptions): Promise {\n const isAudioVideoAbsent =\n !(options && ('audio' in options || 'video' in options));\n\n const fullOptions: InternalOptions = {\n audio: isAudioVideoAbsent,\n getUserMedia,\n loggerName: DEFAULT_LOGGER_NAME,\n logLevel: DEFAULT_LOG_LEVEL,\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack,\n MediaStreamTrack,\n Log,\n video: isAudioVideoAbsent,\n ...options,\n };\n\n const logComponentName = `[createLocalTracks #${++createLocalTrackCalls}]`;\n const logLevels = buildLogLevels(fullOptions.logLevel);\n const log = new fullOptions.Log('default', logComponentName, logLevels, fullOptions.loggerName);\n\n const localTrackOptions = Object.assign({ log }, fullOptions);\n\n // NOTE(mmalavalli): The Room \"name\" in \"options\" was being used\n // as the LocalTrack name in asLocalTrack(). So we pass a copy of\n // \"options\" without the \"name\".\n // NOTE(joma): CreateLocalTracksOptions type does not really have a \"name\" property when used publicly by customers.\n // But we are passing this property when used internally by other JS files.\n // We can update this \"any\" type once those JS files are converted to TS.\n delete (localTrackOptions as any).name;\n\n if (fullOptions.audio === false && fullOptions.video === false) {\n log.info('Neither audio nor video requested, so returning empty LocalTracks');\n return [];\n }\n\n if (fullOptions.tracks) {\n log.info('Adding user-provided LocalTracks');\n log.debug('LocalTracks:', fullOptions.tracks);\n return fullOptions.tracks;\n }\n\n const extraLocalTrackOptions: ExtraLocalTrackOptions = {\n audio: typeof fullOptions.audio === 'object' && fullOptions.audio.name\n ? { name: fullOptions.audio.name }\n : { defaultDeviceCaptureMode: 'auto' },\n video: typeof fullOptions.video === 'object' && fullOptions.video.name\n ? { name: fullOptions.video.name }\n : {}\n };\n\n extraLocalTrackOptions.audio.isCreatedByCreateLocalTracks = true;\n extraLocalTrackOptions.video.isCreatedByCreateLocalTracks = true;\n\n let noiseCancellationOptions: NoiseCancellationOptions | undefined;\n\n if (typeof fullOptions.audio === 'object') {\n if (typeof fullOptions.audio.workaroundWebKitBug1208516 === 'boolean') {\n extraLocalTrackOptions.audio.workaroundWebKitBug1208516 = fullOptions.audio.workaroundWebKitBug1208516;\n }\n\n if ('noiseCancellationOptions' in fullOptions.audio) {\n noiseCancellationOptions = fullOptions.audio.noiseCancellationOptions;\n delete fullOptions.audio.noiseCancellationOptions;\n }\n\n if (!('defaultDeviceCaptureMode' in fullOptions.audio)) {\n extraLocalTrackOptions.audio.defaultDeviceCaptureMode = 'auto';\n } else if (['auto', 'manual'].every(mode => mode !== (fullOptions.audio as CreateLocalAudioTrackOptions).defaultDeviceCaptureMode)) {\n // eslint-disable-next-line new-cap\n throw INVALID_VALUE('CreateLocalAudioTrackOptions.defaultDeviceCaptureMode', ['auto', 'manual']);\n } else {\n extraLocalTrackOptions.audio.defaultDeviceCaptureMode = fullOptions.audio.defaultDeviceCaptureMode;\n }\n }\n\n if (typeof fullOptions.video === 'object' && typeof fullOptions.video.workaroundWebKitBug1208516 === 'boolean') {\n extraLocalTrackOptions.video.workaroundWebKitBug1208516 = fullOptions.video.workaroundWebKitBug1208516;\n }\n\n if (typeof fullOptions.audio === 'object') {\n delete fullOptions.audio.name;\n }\n if (typeof fullOptions.video === 'object') {\n delete fullOptions.video.name;\n }\n\n const mediaStreamConstraints = {\n audio: fullOptions.audio,\n video: fullOptions.video\n };\n\n const workaroundWebKitBug180748 = typeof fullOptions.audio === 'object' && fullOptions.audio.workaroundWebKitBug180748;\n\n try {\n const mediaStream = await (workaroundWebKitBug180748\n ? workaround180748(log, fullOptions.getUserMedia, mediaStreamConstraints)\n : fullOptions.getUserMedia(mediaStreamConstraints));\n\n const mediaStreamTracks = [\n ...mediaStream.getAudioTracks(),\n ...mediaStream.getVideoTracks(),\n ];\n\n log.info('Call to getUserMedia successful; got tracks:', mediaStreamTracks);\n\n return await Promise.all(\n mediaStreamTracks.map(async mediaStreamTrack => {\n if (mediaStreamTrack.kind === 'audio' && noiseCancellationOptions) {\n const { cleanTrack, noiseCancellation } = await applyNoiseCancellation(mediaStreamTrack, noiseCancellationOptions, log);\n return new localTrackOptions.LocalAudioTrack(cleanTrack, {\n ...extraLocalTrackOptions.audio,\n ...localTrackOptions,\n noiseCancellation\n });\n } else if (mediaStreamTrack.kind === 'audio') {\n return new localTrackOptions.LocalAudioTrack(mediaStreamTrack, {\n ...extraLocalTrackOptions.audio,\n ...localTrackOptions,\n });\n }\n return new localTrackOptions.LocalVideoTrack(mediaStreamTrack, {\n ...extraLocalTrackOptions.video,\n ...localTrackOptions,\n });\n })\n );\n } catch (error) {\n log.warn('Call to getUserMedia failed:', error);\n throw error;\n }\n}\n\n/**\n * {@link createLocalTracks} options\n * @typedef {object} CreateLocalTracksOptions\n * @property {boolean|CreateLocalTrackOptions|CreateLocalAudioTrackOptions} [audio=true] - Whether or not to\n * get local audio with getUserMedia when tracks\n * are not provided.\n * @property {LogLevel|LogLevels} [logLevel='warn'] - (deprecated: use [Video.Logger](module-twilio-video.html) instead.\n * See [examples](module-twilio-video.html#.connect) for details)\n * Set the default log verbosity\n * of logging. Passing a {@link LogLevel} string will use the same\n * level for all components. Pass a {@link LogLevels} to set specific log\n * levels.\n * @property {string} [loggerName='twilio-video'] - The name of the logger. Use this name when accessing the logger used by the SDK.\n * See [examples](module-twilio-video.html#.connect) for details.\n * @property {boolean|CreateLocalTrackOptions} [video=true] - Whether or not to\n * get local video with getUserMedia when tracks\n * are not provided.\n */\n", "import type { TimeMeasurement } from '../../tsdef/PreflightTypes';\n\nexport class Timer {\n // eslint-disable-next-line no-undefined\n private _end: number | undefined = undefined;\n private _start: number;\n\n constructor() {\n this.start();\n }\n\n start() : this {\n this._start = Date.now();\n return this;\n }\n\n stop(): this {\n this._end = Date.now();\n return this;\n }\n\n getTimeMeasurement() : TimeMeasurement {\n return {\n start: this._start,\n end: this._end,\n // eslint-disable-next-line no-undefined\n duration: this._end === undefined ? undefined : this._end - this._start\n };\n }\n}\n", "const r0 = 94.768; // Constant used in computing \"rFactor\".\n// copied from https://code.hq.twilio.com/client/sdk-frd/blob/master/voice/voice-mos-calculation.md\nexport function calculateMOS(rtt: number, jitter: number, fractionLost: number): number {\n // Compute the effective latency.\n const effectiveLatency: number = rtt + (jitter * 2) + 10;\n\n // Compute the initial \"rFactor\" from effective latency.\n let rFactor = 0;\n switch (true) {\n case effectiveLatency < 160:\n rFactor = r0 - (effectiveLatency / 40);\n break;\n case effectiveLatency < 1000:\n rFactor = r0 - ((effectiveLatency - 120) / 10);\n break;\n }\n\n // Adjust \"rFactor\" with the fraction of packets lost.\n switch (true) {\n case fractionLost <= (rFactor / 2.5):\n rFactor = Math.max(rFactor - fractionLost * 2.5, 6.52);\n break;\n default:\n rFactor = 0;\n break;\n }\n\n // Compute MOS from \"rFactor\".\n const mos: number = 1 +\n (0.035 * rFactor) +\n (0.000007 * rFactor) *\n (rFactor - 60) *\n (100 - rFactor);\n\n return mos;\n}\n\nexport function mosToScore(mosValue: number|null|undefined): number {\n let score = 0;\n if (!mosValue) {\n score = 0;\n } else if (mosValue > 4.2) {\n score = 5;\n } else if (mosValue > 4.0) {\n score = 4;\n } else if (mosValue > 3.6) {\n score = 3;\n } else if (mosValue > 3) {\n score = 2;\n } else {\n score = 1;\n }\n return score;\n}\n", "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { RTCIceCandidateStats, SelectedIceCandidatePairStats, } from '../../tsdef/PreflightTypes';\nimport type { RTCIceCandidatePairStats } from './rtctypes';\n\ninterface RTCStatsReport {\n [x: string]: any;\n forEach(callbackfn: (value: any, key: string, parent: RTCStatsReport) => void, thisArg?: any): void;\n}\nexport interface CombinedConnectionStats {\n timestamp: number;\n bytesSent: number;\n bytesReceived: number;\n packets: number;\n packetsLost: number;\n roundTripTime: number;\n jitter: number;\n selectedIceCandidatePairStats: SelectedIceCandidatePairStats | null;\n iceCandidateStats: RTCIceCandidateStats[];\n}\n\nfunction getStatValues(report: RTCStatsReport, statName: string, kind: string[], reportTypes: string[]): number[] {\n let results: number[] = [];\n report.forEach(stat => {\n if (\n (reportTypes.length === 0 || reportTypes.includes(stat.type)) &&\n (kind.length === 0 || kind.includes(stat.kind)) &&\n typeof stat[statName] === 'number') {\n results.push(stat[statName]);\n }\n });\n return results;\n}\n\nexport async function getCombinedConnectionStats({ publisher, subscriber }: { publisher: RTCPeerConnection, subscriber: RTCPeerConnection }): Promise {\n const [publisherStats, subscriberStats] = await Promise.all([publisher, subscriber].map(pc => pc.getStats()));\n\n const timestamps = getStatValues(subscriberStats, 'timestamp', ['audio'], ['inbound-rtp']);\n const timestamp = timestamps.length > 0 ? timestamps[0] : 0;\n\n // jitter: subscriber, inbound-rtp, audio\n // Note: chrome has jitter for video, but not Safari.\n // Note: also jitter values are in seconds, but chrome's video jitter values were found to be are too big to be in seconds.\n const jitter = getStatValues(subscriberStats, 'jitter', ['audio'], ['inbound-rtp']).reduce((a, b) => Math.max(a, b), 0);\n\n // packets, packetsLost:\n // subscriber, audio, inbound-rtp,\n // subscriber, video, inbound-rtp\n const packets = getStatValues(subscriberStats, 'packetsReceived', ['audio', 'video'], ['inbound-rtp']).reduce((a, b) => a + b, 0);\n const packetsLost = getStatValues(subscriberStats, 'packetsLost', ['audio', 'video'], ['inbound-rtp']).reduce((a, b) => a + b, 0);\n\n // roundTripTime: publisher, audio, remote-inbound-rtp\n // publisher, video, remote-inbound-rtp\n const trackRoundTripTime = getStatValues(publisherStats, 'roundTripTime', ['audio', 'video'], ['remote-inbound-rtp']).reduce((a, b) => Math.max(a, b), 0);\n\n // currentRoundTripTime. subscriber, 'candidate-pair'\n const currentRoundTripTime = getStatValues(subscriberStats, 'currentRoundTripTime', [], ['candidate-pair']).reduce((a, b) => Math.max(a, b), 0);\n const roundTripTime = (currentRoundTripTime || trackRoundTripTime) * 1000;\n\n const bytesSent = getStatValues(publisherStats, 'bytesSent', [], ['candidate-pair']).reduce((a, b) => a + b, 0);\n const bytesReceived = getStatValues(subscriberStats, 'bytesReceived', [], ['candidate-pair']).reduce((a, b) => a + b, 0);\n\n const selectedIceCandidatePairStats = extractSelectedActiveCandidatePair(subscriberStats);\n\n const iceCandidateStats: RTCIceCandidateStats[] = [];\n subscriberStats.forEach(stat => {\n if (stat.type === 'local-candidate' || stat.type === 'remote-candidate') {\n iceCandidateStats.push(makeStandardCandidateStats(stat));\n }\n });\n return { timestamp, jitter, packets, packetsLost, roundTripTime, bytesSent, bytesReceived, selectedIceCandidatePairStats, iceCandidateStats };\n}\n\n\nfunction makeStandardCandidateStats(input: any) : RTCIceCandidateStats {\n const standardizedCandidateStatsKeys = [\n { key: 'transportId', type: 'string' },\n { key: 'candidateType', type: 'string' },\n { key: 'port', altKeys: ['portNumber'], type: 'number' },\n { key: 'address', altKeys: ['ip', 'ipAddress'], type: 'string' },\n { key: 'priority', type: 'number' },\n { key: 'protocol', altKeys: ['transport'], type: 'string' },\n { key: 'url', type: 'string' },\n { key: 'relayProtocol', type: 'string' },\n ];\n\n return standardizedCandidateStatsKeys.reduce(function(report: any, keyInfo) {\n let keysToLookFor = [keyInfo.key];\n if (keyInfo.altKeys) {\n keysToLookFor = keysToLookFor.concat(keyInfo.altKeys);\n }\n var key = keysToLookFor.find(key => key in input);\n if (key && typeof input[key] === keyInfo.type) {\n report[keyInfo.key] = input[key];\n }\n\n return report;\n }, {});\n}\n\nfunction extractSelectedActiveCandidatePair(stats: RTCStatsReport) : SelectedIceCandidatePairStats | null {\n let selectedCandidatePairId:string|null = null;\n const candidatePairs: RTCIceCandidatePairStats[] = [];\n stats.forEach(stat => {\n if (stat.type === 'transport' && stat.selectedCandidatePairId) {\n selectedCandidatePairId = stat.selectedCandidatePairId;\n } else if (stat.type === 'candidate-pair') {\n candidatePairs.push(stat);\n }\n });\n\n const activeCandidatePairStatsFound = candidatePairs.find(pair =>\n // Firefox\n pair.selected ||\n // Spec-compliant way\n (selectedCandidatePairId && pair.id === selectedCandidatePairId)\n );\n\n if (!activeCandidatePairStatsFound) {\n return null;\n }\n\n const activeCandidatePairStats = activeCandidatePairStatsFound as RTCIceCandidatePairStats;\n const activeLocalCandidateStats = stats.get(activeCandidatePairStats.localCandidateId);\n const activeRemoteCandidateStats = stats.get(activeCandidatePairStats.remoteCandidateId);\n if (!activeLocalCandidateStats || !activeRemoteCandidateStats) {\n return null;\n }\n\n return {\n localCandidate: makeStandardCandidateStats(activeLocalCandidateStats),\n remoteCandidate: makeStandardCandidateStats(activeRemoteCandidateStats)\n };\n}\n\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\nconst util = require('./util');\n\n/**\n * {@link StateMachine} represents a state machine. The state machine supports a\n * reentrant locking mechanism to allow asynchronous state transitions to ensure\n * they have not been preempted. Calls to {@link StateMachine#takeLock} are\n * guaranteed to be resolved in FIFO order.\n * @extends EventEmitter\n * @property {boolean} isLocked - whether or not the {@link StateMachine} is\n * locked performing asynchronous state transition\n * @property {string} state - the current state\n * @emits {@link StateMachine#stateChanged}\n */\nclass StateMachine extends EventEmitter {\n /**\n * Construct a {@link StateMachine}.\n * @param {string} initialState - the intiial state\n * @param {object} states\n */\n constructor(initialState, states) {\n super();\n let lock = null;\n let state = initialState;\n states = transformStates(states);\n Object.defineProperties(this, {\n _lock: {\n get() {\n return lock;\n },\n set(_lock) {\n lock = _lock;\n }\n },\n _reachableStates: {\n value: reachable(states)\n },\n _state: {\n get() {\n return state;\n },\n set(_state) {\n state = _state;\n }\n },\n _states: {\n value: states\n },\n _whenDeferreds: {\n value: new Set()\n },\n isLocked: {\n enumerable: true,\n get() {\n return lock !== null;\n }\n },\n state: {\n enumerable: true,\n get() {\n return state;\n }\n }\n });\n\n this.on('stateChanged', state => {\n this._whenDeferreds.forEach(deferred => {\n deferred.when(state, deferred.resolve, deferred.reject);\n });\n });\n }\n\n /**\n * Returns a promise whose executor function is called on each state change.\n * @param {function(state: string, resolve: function, reject: function): void} when\n * @returns {Promise.<*>}\n * @private\n */\n _whenPromise(when) {\n if (typeof when !== 'function') {\n return Promise.reject(new Error('when() executor must be a function'));\n }\n\n const deferred = util.defer();\n\n deferred.when = when;\n this._whenDeferreds.add(deferred);\n\n return deferred.promise.then(payload => {\n this._whenDeferreds.delete(deferred);\n return payload;\n }, error => {\n this._whenDeferreds.delete(deferred);\n throw error;\n });\n }\n\n /**\n * This method takes a lock and passes the {@link StateMachine#Key} to your\n * transition function. You may perform zero or more state transitions in your\n * transition function, but you should check for preemption in each tick. You\n * may also reenter the lock. Once the Promise returned by your transition\n * function resolves or rejects, this method releases the lock it acquired for\n * you.\n * @param {string} name - a name for the lock\n * @param {function(StateMachine#Key): Promise} transitionFunction\n * @returns {Promise}\n */\n // NOTE(mroberts): This method is named after a Haskell function:\n // https://hackage.haskell.org/package/base-4.8.2.0/docs/Control-Exception.html#v:bracket\n bracket(name, transitionFunction) {\n let key;\n const self = this;\n\n function releaseLock(error) {\n if (self.hasLock(key)) {\n self.releaseLockCompletely(key);\n }\n if (error) {\n throw error;\n }\n }\n\n return this.takeLock(name).then(function gotKey(_key) {\n key = _key;\n return transitionFunction(key);\n }).then(function success(result) {\n releaseLock();\n return result;\n }, releaseLock);\n }\n\n /**\n * Check whether or not a {@link StateMachine#Key} matches the lock.\n * @param {StateMachine#Key} key\n * @returns {boolean}\n */\n hasLock(key) {\n return this._lock === key;\n }\n\n /**\n * Preempt any pending state transitions and immediately transition to the new\n * state. If a lock name is specified, take the lock and return the\n * {@link StateMachine#Key}.\n * @param {string} newState\n * @param {?string} [name=null] - a name for the lock\n * @param {Array<*>} [payload=[]]\n * @returns {?StateMachine#Key}\n */\n preempt(newState, name, payload) {\n // 1. Check that the new state is valid.\n if (!isValidTransition(this._states, this.state, newState)) {\n throw new Error(`Cannot transition from \"${this.state}\" to \"${newState}\"`);\n }\n\n // 2. Release the old lock, if any.\n let oldLock;\n if (this.isLocked) {\n oldLock = this._lock;\n this._lock = null;\n }\n\n // 3. Take the lock, if requested.\n let key = null;\n if (name) {\n key = this.takeLockSync(name);\n }\n\n // 4. If a lock wasn't requested, take a \"preemption\" lock in order to\n // maintain FIFO order of those taking locks.\n const preemptionKey = key ? null : this.takeLockSync('preemption');\n\n // 5. Transition.\n this.transition(newState, key || preemptionKey, payload);\n\n // 6. Preempt anyone blocked on the old lock.\n if (oldLock) {\n oldLock.resolve();\n }\n\n // 7. Release the \"preemption\" lock, if we took it.\n if (preemptionKey) {\n this.releaseLock(preemptionKey);\n }\n\n return key;\n }\n\n /**\n * Release a lock. This method succeeds only if the {@link StateMachine} is\n * still locked and has not been preempted.\n * @param {StateMachine#Key} key\n * @throws Error\n */\n releaseLock(key) {\n if (!this.isLocked) {\n throw new Error(`Could not release the lock for ${key.name} because the StateMachine is not locked`);\n } else if (!this.hasLock(key)) {\n throw new Error(`Could not release the lock for ${key.name} because ${this._lock.name} has the lock`);\n }\n if (key.depth === 0) {\n this._lock = null;\n key.resolve();\n } else {\n key.depth--;\n }\n }\n\n /**\n * Release a lock completely, even if it has been reentered. This method\n * succeeds only if the {@link StateMachine} is still locked and has not been\n * preempted.\n * @param {StateMachine#Key} key\n * @throws Error\n */\n releaseLockCompletely(key) {\n if (!this.isLocked) {\n throw new Error(`Could not release the lock for ${key.name} because the StateMachine is not locked`);\n } else if (!this.hasLock(key)) {\n throw new Error(`Could not release the lock for ${key.name} because ${this._lock.name} has the lock`);\n }\n key.depth = 0;\n this._lock = null;\n key.resolve();\n }\n\n /**\n * Take a lock, returning a Promise for the {@link StateMachine#Key}. You should\n * take a lock anytime you intend to perform asynchronous transitions. Calls to\n * this method are guaranteed to be resolved in FIFO order. You may reenter\n * a lock by passing its {@link StateMachine#Key}.\n * @param {string|StateMachine#Key} nameOrKey - a name for the lock or an\n * existing {@link StateMachine#Key}\n * @returns {Promise}\n */\n takeLock(nameOrKey) {\n // Reentrant lock\n if (typeof nameOrKey === 'object') {\n const key = nameOrKey;\n return new Promise(resolve => {\n resolve(this.takeLockSync(key));\n });\n }\n\n // New lock\n const name = nameOrKey;\n if (this.isLocked) {\n var takeLock = this.takeLock.bind(this, name);\n return this._lock.promise.then(takeLock);\n }\n return Promise.resolve(this.takeLockSync(name));\n }\n\n /**\n * Take a lock, returning the {@Link StateMachine#Key}. This method throws if\n * the {@link StateMachine} is locked or the wrong {@link StateMachine#Key} is\n * provided. You may reenter a lock by passing its {@link StateMachine#Key}.\n * @param {string|StateMachine#Key} nameOrKey - a name for the lock or an\n * existing {@link StateMachine#Key}\n * @returns {object}\n * @throws Error\n */\n takeLockSync(nameOrKey) {\n const key = typeof nameOrKey === 'string' ? null : nameOrKey;\n const name = key ? key.name : nameOrKey;\n\n if (key && !this.hasLock(key) || !key && this.isLocked) {\n throw new Error(`Could not take the lock for ${name} because the lock for ${this._lock.name} was not released`);\n }\n\n // Reentrant lock\n if (key) {\n key.depth++;\n return key;\n }\n\n // New lock\n const lock = makeLock(name);\n this._lock = lock;\n return lock;\n }\n\n /**\n * Transition to a new state. If the {@link StateMachine} is locked, you must\n * provide the {@link StateMachine#Key}. An invalid state or the wrong\n * {@link StateMachine#Key} will throw an error.\n * @param {string} newState\n * @param {?StateMachine#Key} [key=null]\n * @param {Array<*>} [payload=[]]\n * @throws {Error}\n */\n transition(newState, key, payload) {\n payload = payload || [];\n\n // 1. If we're locked, required the key.\n if (this.isLocked) {\n if (!key) {\n throw new Error('You must provide the key in order to ' +\n 'transition');\n } else if (!this.hasLock(key)) {\n throw new Error(`Could not transition using the key for ${key.name} because ${this._lock.name} has the lock`);\n }\n } else if (key) {\n throw new Error(`Key provided for ${key.name}, but the StateMachine was not locked (possibly due to preemption)`);\n }\n\n // 2. Check that the new state is valid.\n if (!isValidTransition(this._states, this.state, newState)) {\n throw new Error(`Cannot transition from \"${this.state}\" to \"${newState}\"`);\n }\n\n // 3. Update the state and emit an event.\n this._state = newState;\n this.emit(...['stateChanged', newState].concat(payload));\n }\n\n /**\n * Attempt to transition to a new state. Unlike {@link StateMachine#transition},\n * this method does not throw.\n * @param {string} newState\n * @param {?StateMachine#Key} [key=null]\n * @param {Array<*>} [payload=[]]\n * @returns {boolean}\n */\n tryTransition(newState, key, payload) {\n try {\n this.transition(newState, key, payload);\n } catch (error) {\n return false;\n }\n return true;\n }\n\n /**\n * Return a Promise that resolves when the {@link StateMachine} transitions to\n * the specified state. If the {@link StateMachine} transitions such that the\n * requested state becomes unreachable, the Promise rejects.\n * @param {string} state\n * @returns {Promise}\n */\n when(state) {\n if (this.state === state) {\n return Promise.resolve(this);\n } else if (!isValidTransition(this._reachableStates, this.state, state)) {\n return Promise.reject(createUnreachableError(this.state, state));\n }\n return this._whenPromise((newState, resolve, reject) => {\n if (newState === state) {\n resolve(this);\n } else if (!isValidTransition(this._reachableStates, newState, state)) {\n reject(createUnreachableError(newState, state));\n }\n });\n }\n}\n\n/**\n * @event StateMachine#stateChanged\n * @param {string} newState\n */\n\n/**\n * Check if a transition is valid.\n * @private\n * @param {Map<*, Set<*>>} graph\n * @param {*} from\n * @param {*} to\n * @returns {boolean}\n */\nfunction isValidTransition(graph, from, to) {\n return graph.get(from).has(to);\n}\n\n/**\n * @typedef {object} StateMachine#Key\n */\n\nfunction makeLock(name) {\n const lock = util.defer();\n lock.name = name;\n lock.depth = 0;\n return lock;\n}\n\n/**\n * Compute the transitive closure of a graph (i.e. what nodes are reachable from\n * where).\n * @private\n * @param {Map<*, Set<*>>} graph\n * @returns {Map<*, Set<*>>}\n */\nfunction reachable(graph) {\n return Array.from(graph.keys()).reduce((newGraph, from) => newGraph.set(from, reachableFrom(graph, from)), new Map());\n}\n\n/**\n * Compute the Set of node reachable from a particular node in the graph.\n * @private\n * @param {Map<*, Set<*>>} graph\n * @param {*} from\n * @param {Set<*>} [to]\n * @returns {Set<*>}\n */\nfunction reachableFrom(graph, from, to) {\n to = to || new Set();\n graph.get(from).forEach(node => {\n if (!to.has(node)) {\n to.add(node);\n reachableFrom(graph, node, to).forEach(to.add, to);\n }\n });\n return to;\n}\n\nfunction transformStates(states) {\n const newStates = new Map();\n for (const key in states) {\n newStates.set(key, new Set(states[key]));\n }\n return newStates;\n}\n\n/**\n * Create an \"unreachable state\" Error.\n * @param {string} here\n * @param {string} there\n * @returns {Error}\n */\nfunction createUnreachableError(here, there) {\n return new Error(`\"${there}\" cannot be reached from \"${here}\"`);\n}\n\nmodule.exports = StateMachine;\n", "'use strict';\n\n/**\n * Monitor the network connection status to detect interruptions and handoffs.\n */\nclass NetworkMonitor {\n /**\n * Construct a {@link NetworkMonitor}.\n * @param {function} onNetworkChanged\n * @param {*} [options]\n */\n constructor(onNetworkChanged, options) {\n options = Object.assign({\n navigator,\n window,\n }, options);\n\n const nav = options.navigator;\n const connection = nav.connection || { type: null };\n let type = connection.type;\n\n const { _events, _listener, _target } = connection.type ? {\n _events: {\n value: ['change', 'typechange']\n },\n _listener: {\n value: () => {\n const networkChanged = type !== this.type && this.isOnline;\n type = this.type;\n if (networkChanged) {\n onNetworkChanged();\n }\n }\n },\n _target: {\n value: connection\n }\n } : {\n _events: {\n value: ['online']\n },\n _listener: {\n value: onNetworkChanged\n },\n _target: {\n value: options.window\n }\n };\n\n Object.defineProperties(this, {\n isOnline: {\n enumerable: true,\n get() {\n return typeof nav.onLine === 'boolean'\n ? nav.onLine\n : true;\n }\n },\n type: {\n enumerable: true,\n get() {\n return connection.type || null;\n }\n },\n _listener,\n _events,\n _target\n });\n }\n\n /**\n * Start the {@link NetworkMonitor}.\n */\n start() {\n this._events.forEach(event => {\n this._target.addEventListener(event, this._listener);\n });\n }\n\n /**\n * Stop the {@link NetworkMonitor}.\n */\n stop() {\n this._events.forEach(event => {\n this._target.removeEventListener(event, this._listener);\n });\n }\n}\n\nmodule.exports = NetworkMonitor;\n", "'use strict';\n\n/**\n * A {@link Timeout} represents a resettable and clearable timeout.\n */\nclass Timeout {\n /**\n * Construct a {@link Timeout}.\n * @param {function} fn - Function to call\n * @param {number} delay - Delay in milliseconds\n * @param {boolean} [autoStart=true] - If true, then start the {@link Timeout}.\n */\n constructor(fn, delay, autoStart = true) {\n Object.defineProperties(this, {\n _delay: {\n value: delay,\n writable: true\n },\n _fn: {\n value: fn\n },\n _timeout: {\n value: null,\n writable: true\n }\n });\n\n if (autoStart) {\n this.start();\n }\n }\n\n /**\n * The {@link Timeout} delay in milliseconds.\n * @property {number}\n */\n get delay() {\n return this._delay;\n }\n\n /**\n * Whether the {@link Timeout} is set.\n * @property {boolean}\n */\n get isSet() {\n return !!this._timeout;\n }\n\n /**\n * Update the {@link Timeout} delay.\n * @param {number} delay\n * @returns {void}\n */\n setDelay(delay) {\n this._delay = delay;\n }\n\n /**\n * Start the {@link Timeout}, if not already started.\n * @returns {void}\n */\n start() {\n if (!this.isSet) {\n this._timeout = setTimeout(() => {\n const fn = this._fn;\n this.clear();\n fn();\n }, this._delay);\n }\n }\n\n /**\n * Clear the {@link Timeout}.\n * @returns {void}\n */\n clear() {\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n\n /**\n * Reset the {@link Timeout}.\n * @returns {void}\n */\n reset() {\n this.clear();\n this.start();\n }\n}\n\nmodule.exports = Timeout;\n", "module.exports = WebSocket;\n", "'use strict';\n\nconst StateMachine = require('./statemachine');\nconst { buildLogLevels, makeUUID } = require('./util');\nconst Log = require('./util/log');\nconst NetworkMonitor = require('./util/networkmonitor');\nconst Timeout = require('./util/timeout');\n\nlet nInstances = 0;\n\n/*\n TwilioConnection states\n -----------------------\n\n ------------------------------------------\n | |\n | v\n +---------+ +--------------+ +----------+\n | early | ----> | connecting | ----> | closed |\n +---------+ +--------------+ +----------+\n ^ | ^ | ^ ^\n | --------------------- | | | |\n | | --------------------- | | |\n | | | --------------------|------------------ |\n | v | | v |\n +----------+ +--------+ |\n | waiting | --------> | open | ---------------\n +----------+ +--------+\n */\n\nconst states = {\n closed: [],\n connecting: ['closed', 'open', 'waiting'],\n early: ['closed', 'connecting'],\n open: ['closed'],\n waiting: ['closed', 'connecting', 'early', 'open']\n};\n\nconst events = {\n closed: 'close',\n open: 'open',\n waiting: 'waiting'\n};\n\nconst TCMP_VERSION = 2;\n\nconst DEFAULT_MAX_CONSECUTIVE_MISSED_HEARTBEATS = 3;\nconst DEFAULT_MAX_CONSECUTIVE_FAILED_HELLOS = 3;\nconst DEFAULT_MAX_REQUESTED_HEARTBEAT_TIMEOUT = 5000;\nconst DEFAULT_OPEN_TIMEOUT = 15000;\nconst DEFAULT_WELCOME_TIMEOUT = 5000;\nconst OUTGOING_HEARTBEAT_OFFSET = 200;\n\nconst WS_CLOSE_NORMAL = 1000;\nconst WS_CLOSE_WELCOME_TIMEOUT = 3000;\nconst WS_CLOSE_HEARTBEATS_MISSED = 3001;\nconst WS_CLOSE_HELLO_FAILED = 3002;\nconst WS_CLOSE_SEND_FAILED = 3003;\nconst WS_CLOSE_NETWORK_CHANGED = 3004;\nconst WS_CLOSE_BUSY_WAIT = 3005;\nconst WS_CLOSE_SERVER_BUSY = 3006;\nconst WS_CLOSE_OPEN_TIMEOUT = 3007;\n\n// NOTE(joma): If you want to use close code 3008, please increment\n// the close code in test/integration/spec/docker/reconnection.js\n// line number 492.\n\nconst toplevel = globalThis;\nconst WebSocket = toplevel.WebSocket ? toplevel.WebSocket : require('ws');\n\nconst CloseReason = {\n BUSY: 'busy',\n FAILED: 'failed',\n LOCAL: 'local',\n REMOTE: 'remote',\n TIMEOUT: 'timeout'\n};\n\nconst wsCloseCodesToCloseReasons = new Map([\n [WS_CLOSE_WELCOME_TIMEOUT, CloseReason.TIMEOUT],\n [WS_CLOSE_HEARTBEATS_MISSED, CloseReason.TIMEOUT],\n [WS_CLOSE_HELLO_FAILED, CloseReason.FAILED],\n [WS_CLOSE_SEND_FAILED, CloseReason.FAILED],\n [WS_CLOSE_NETWORK_CHANGED, CloseReason.TIMEOUT],\n [WS_CLOSE_SERVER_BUSY, CloseReason.BUSY],\n [WS_CLOSE_OPEN_TIMEOUT, CloseReason.TIMEOUT]\n]);\n\n/**\n * A {@link TwilioConnection} represents a WebSocket connection\n * to a Twilio Connections Messaging Protocol (TCMP) server.\n * @fires TwilioConnection#close\n * @fires TwilioConnection#error\n * @fires TwilioConnection#message\n * @fires TwilioConnection#open\n * @fires TwilioConnection#waiting\n */\nclass TwilioConnection extends StateMachine {\n /**\n * Construct a {@link TwilioConnection}.\n * @param {string} serverUrl - TCMP server url\n * @param {TwilioConnectionOptions} options - {@link TwilioConnection} options\n */\n constructor(serverUrl, options) {\n super('early', states);\n\n options = Object.assign({\n helloBody: null,\n maxConsecutiveFailedHellos: DEFAULT_MAX_CONSECUTIVE_FAILED_HELLOS,\n maxConsecutiveMissedHeartbeats: DEFAULT_MAX_CONSECUTIVE_MISSED_HEARTBEATS,\n requestedHeartbeatTimeout: DEFAULT_MAX_REQUESTED_HEARTBEAT_TIMEOUT,\n openTimeout: DEFAULT_OPEN_TIMEOUT,\n welcomeTimeout: DEFAULT_WELCOME_TIMEOUT,\n Log,\n WebSocket\n }, options);\n\n const logLevels = buildLogLevels(options.logLevel);\n const log = new options.Log('default', this, logLevels, options.loggerName);\n\n const networkMonitor = options.networkMonitor ? new NetworkMonitor(() => {\n const { type } = networkMonitor;\n const reason = `Network changed${type ? ` to ${type}` : ''}`;\n log.debug(reason);\n this._close({ code: WS_CLOSE_NETWORK_CHANGED, reason });\n }) : null;\n\n Object.defineProperties(this, {\n _busyWaitTimeout: {\n value: null,\n writable: true\n },\n _consecutiveHeartbeatsMissed: {\n value: 0,\n writable: true\n },\n _cookie: {\n value: null,\n writable: true\n },\n _eventObserver: {\n value: options.eventObserver\n },\n _heartbeatTimeout: {\n value: null,\n writable: true\n },\n _hellosLeft: {\n value: options.maxConsecutiveFailedHellos,\n writable: true\n },\n _instanceId: {\n value: ++nInstances\n },\n _log: {\n value: log\n },\n _messageQueue: {\n value: []\n },\n _networkMonitor: {\n value: networkMonitor\n },\n _options: {\n value: options\n },\n _openTimeout: {\n value: null,\n writable: true\n },\n _sendHeartbeatTimeout: {\n value: null,\n writable: true\n },\n _serverUrl: {\n value: serverUrl\n },\n _welcomeTimeout: {\n value: null,\n writable: true\n },\n _ws: {\n value: null,\n writable: true\n }\n });\n\n const eventsToLevels = {\n connecting: 'info',\n early: 'info',\n open: 'info',\n waiting: 'warning',\n closed: 'info'\n };\n\n this.on('stateChanged', (state, ...args) => {\n if (state in events) {\n this.emit(events[state], ...args);\n }\n const event = { name: state, group: 'signaling', level: eventsToLevels[this.state] };\n if (state === 'closed') {\n const [reason] = args;\n event.payload = { reason };\n event.level = reason === CloseReason.LOCAL ? 'info' : 'error';\n }\n this._eventObserver.emit('event', event);\n });\n\n this._eventObserver.emit('event', { name: this.state, group: 'signaling', level: eventsToLevels[this.state] });\n this._connect();\n }\n\n toString() {\n return `[TwilioConnection #${this._instanceId}: ${this._ws.url}]`;\n }\n\n /**\n * Close the {@link TwilioConnection}.\n * @param {{code: number, reason: string}} event\n * @private\n */\n _close({ code, reason }) {\n if (this.state === 'closed') {\n return;\n }\n if (this._openTimeout) {\n this._openTimeout.clear();\n }\n if (this._welcomeTimeout) {\n this._welcomeTimeout.clear();\n }\n if (this._heartbeatTimeout) {\n this._heartbeatTimeout.clear();\n }\n if (this._sendHeartbeatTimeout) {\n this._sendHeartbeatTimeout.clear();\n }\n if (this._networkMonitor) {\n this._networkMonitor.stop();\n }\n if (this._busyWaitTimeout && code !== WS_CLOSE_BUSY_WAIT) {\n this._busyWaitTimeout.clear();\n }\n this._messageQueue.splice(0);\n const log = this._log;\n\n if (code === WS_CLOSE_NORMAL) {\n log.debug('Closed');\n this.transition('closed', null, [CloseReason.LOCAL]);\n } else {\n log.warn(`Closed: ${code} - ${reason}`);\n if (code !== WS_CLOSE_BUSY_WAIT) {\n this.transition('closed', null, [\n wsCloseCodesToCloseReasons.get(code) || CloseReason.REMOTE\n ]);\n }\n }\n const { readyState } = this._ws;\n const { WebSocket } = this._options;\n\n if (readyState !== WebSocket.CLOSING && readyState !== WebSocket.CLOSED) {\n this._ws.close(code, reason);\n }\n }\n\n /**\n * Connect to the TCMP server.\n * @private\n */\n _connect() {\n const log = this._log;\n if (this.state === 'waiting') {\n this.transition('early');\n } else if (this.state !== 'early') {\n log.warn(`Unexpected state \"${this.state}\" for connecting to the`\n + ' TCMP server.');\n return;\n }\n this._ws = new this._options.WebSocket(this._serverUrl);\n const ws = this._ws;\n log.debug('Created a new WebSocket:', ws);\n ws.addEventListener('close', event => this._close(event));\n\n const { openTimeout } = this._options;\n // Add a timeout for getting the onopen event on the WebSocket (15 sec). After that, attempt to reconnect only if this is not the first attempt.\n this._openTimeout = new Timeout(() => {\n const reason = `Failed to open in ${openTimeout} ms`;\n this._close({ code: WS_CLOSE_OPEN_TIMEOUT, reason });\n }, openTimeout);\n\n ws.addEventListener('open', () => {\n log.debug('WebSocket opened:', ws);\n this._openTimeout.clear();\n this._startHandshake();\n if (this._networkMonitor) {\n this._networkMonitor.start();\n }\n });\n\n ws.addEventListener('message', message => {\n log.debug(`Incoming: ${message.data}`);\n try {\n message = JSON.parse(message.data);\n } catch (error) {\n this.emit('error', error);\n return;\n }\n\n switch (message.type) {\n case 'bad':\n this._handleBad(message);\n break;\n case 'busy':\n this._handleBusy(message);\n break;\n case 'bye':\n // Do nothing.\n break;\n case 'msg':\n this._handleMessage(message);\n // NOTE(mpatwardhan): Each incoming message should be treated as an incoming\n // heartbeat intentionally falling through to 'heartbeat' case.\n // eslint-disable-next-line no-fallthrough\n case 'heartbeat':\n this._handleHeartbeat();\n break;\n case 'welcome':\n this._handleWelcome(message);\n break;\n default:\n this._log.debug(`Unknown message type: ${message.type}`);\n this.emit('error', new Error(`Unknown message type: ${message.type}`));\n break;\n }\n });\n }\n\n /**\n * Handle an incoming \"bad\" message.\n * @param {{reason: string}} message\n * @private\n */\n _handleBad({ reason }) {\n const log = this._log;\n if (!['connecting', 'open'].includes(this.state)) {\n log.warn(`Unexpected state \"${this.state}\" for handling a \"bad\" message`\n + ' from the TCMP server.');\n return;\n }\n if (this.state === 'connecting') {\n log.warn(`Closing: ${WS_CLOSE_HELLO_FAILED} - ${reason}`);\n this._close({ code: WS_CLOSE_HELLO_FAILED, reason });\n return;\n }\n log.debug(`Error: ${reason}`);\n this.emit('error', new Error(reason));\n }\n\n /**\n * Handle an incoming \"busy\" message.\n * @param {{cookie: ?string, keepAlive: boolean, retryAfter: number}} message\n * @private\n */\n _handleBusy({ cookie, keepAlive, retryAfter }) {\n const log = this._log;\n if (!['connecting', 'waiting'].includes(this.state)) {\n log.warn(`Unexpected state \"${this.state}\" for handling a \"busy\" message`\n + ' from the TCMP server.');\n return;\n }\n if (this._busyWaitTimeout) {\n this._busyWaitTimeout.clear();\n }\n if (this._welcomeTimeout) {\n this._welcomeTimeout.clear();\n }\n const reason = retryAfter < 0\n ? 'Received terminal \"busy\" message'\n : `Received \"busy\" message, retrying after ${retryAfter} ms`;\n\n if (retryAfter < 0) {\n log.warn(`Closing: ${WS_CLOSE_SERVER_BUSY} - ${reason}`);\n this._close({ code: WS_CLOSE_SERVER_BUSY, reason });\n return;\n }\n const { maxConsecutiveFailedHellos } = this._options;\n this._hellosLeft = maxConsecutiveFailedHellos;\n this._cookie = cookie || null;\n\n if (keepAlive) {\n log.warn(reason);\n this._busyWaitTimeout = new Timeout(() => this._startHandshake(), retryAfter);\n } else {\n log.warn(`Closing: ${WS_CLOSE_BUSY_WAIT} - ${reason}`);\n this._close({ code: WS_CLOSE_BUSY_WAIT, reason });\n this._busyWaitTimeout = new Timeout(() => this._connect(), retryAfter);\n }\n\n this.transition('waiting', null, [keepAlive, retryAfter]);\n }\n\n /**\n * Handle an incoming \"heartbeat\" message.\n * @private\n */\n _handleHeartbeat() {\n if (this.state !== 'open') {\n this._log.warn(`Unexpected state \"${this.state}\" for handling a \"heartbeat\"`\n + ' message from the TCMP server.');\n return;\n }\n this._heartbeatTimeout.reset();\n }\n\n /**\n * Handle a missed \"heartbeat\" message.\n * @private\n */\n _handleHeartbeatTimeout() {\n if (this.state !== 'open') {\n return;\n }\n const log = this._log;\n const { maxConsecutiveMissedHeartbeats } = this._options;\n\n log.debug(`Consecutive heartbeats missed: ${maxConsecutiveMissedHeartbeats}`);\n const reason = `Missed ${maxConsecutiveMissedHeartbeats} \"heartbeat\" messages`;\n log.warn(`Closing: ${WS_CLOSE_HEARTBEATS_MISSED} - ${reason}`);\n this._close({ code: WS_CLOSE_HEARTBEATS_MISSED, reason });\n }\n\n /**\n * Handle an incoming \"msg\" message.\n * @param {{body: object}} message\n * @private\n */\n _handleMessage({ body }) {\n if (this.state !== 'open') {\n this._log.warn(`Unexpected state \"${this.state}\" for handling a \"msg\" message`\n + ' from the TCMP server.');\n return;\n }\n this.emit('message', body);\n }\n\n /**\n * Handle an incoming \"welcome\" message.\n * @param {{ negotiatedTimeout: number }} message\n * @private\n */\n _handleWelcome({ negotiatedTimeout }) {\n const log = this._log;\n\n if (!['connecting', 'waiting'].includes(this.state)) {\n log.warn(`Unexpected state \"${this.state}\" for handling a \"welcome\"`\n + ' message from the TCMP server.');\n return;\n }\n\n if (this.state === 'waiting') {\n log.debug('Received \"welcome\" message, no need to retry connection.');\n this._busyWaitTimeout.clear();\n }\n\n const { maxConsecutiveMissedHeartbeats } = this._options;\n const heartbeatTimeout = negotiatedTimeout * maxConsecutiveMissedHeartbeats;\n const outgoingHeartbeatTimeout = negotiatedTimeout - OUTGOING_HEARTBEAT_OFFSET;\n\n this._welcomeTimeout.clear();\n this._heartbeatTimeout = new Timeout(() => this._handleHeartbeatTimeout(), heartbeatTimeout);\n this._messageQueue.splice(0).forEach(message => this._send(message));\n this._sendHeartbeatTimeout = new Timeout(() => this._sendHeartbeat(), outgoingHeartbeatTimeout);\n this.transition('open');\n }\n\n /**\n * Handle a missed \"welcome\" message.\n * @private\n */\n _handleWelcomeTimeout() {\n if (this.state !== 'connecting') {\n return;\n }\n const log = this._log;\n\n if (this._hellosLeft <= 0) {\n const reason = 'All handshake attempts failed';\n log.warn(`Closing: ${WS_CLOSE_WELCOME_TIMEOUT} - ${reason}`);\n this._close({ code: WS_CLOSE_WELCOME_TIMEOUT, reason });\n return;\n }\n\n const { maxConsecutiveFailedHellos } = this._options;\n log.warn(`Handshake attempt ${maxConsecutiveFailedHellos - this._hellosLeft} failed`);\n this._startHandshake();\n }\n\n /**\n * Send a message to the TCMP server.\n * @param {*} message\n * @private\n */\n _send(message) {\n const { readyState } = this._ws;\n const { WebSocket } = this._options;\n if (readyState === WebSocket.OPEN) {\n const data = JSON.stringify(message);\n this._log.debug(`Outgoing: ${data}`);\n try {\n this._ws.send(data);\n if (this._sendHeartbeatTimeout) {\n // Each outgoing message is to be treated as an outgoing heartbeat.\n this._sendHeartbeatTimeout.reset();\n }\n } catch (error) {\n const reason = 'Failed to send message';\n this._log.warn(`Closing: ${WS_CLOSE_SEND_FAILED} - ${reason}`);\n this._close({ code: WS_CLOSE_SEND_FAILED, reason });\n }\n }\n }\n\n /**\n * Send a \"heartbeat\" message.\n * @private\n */\n _sendHeartbeat() {\n if (this.state === 'closed') {\n return;\n }\n this._send({ type: 'heartbeat' });\n }\n\n /**\n * Send a \"hello\" message.\n * @private\n */\n _sendHello() {\n const { helloBody, requestedHeartbeatTimeout: timeout } = this._options;\n const hello = {\n id: makeUUID(),\n timeout,\n type: 'hello',\n version: TCMP_VERSION\n };\n if (this._cookie) {\n hello.cookie = this._cookie;\n }\n if (helloBody) {\n hello.body = helloBody;\n }\n this._send(hello);\n }\n\n /**\n * Send or enqueue a message.\n * @param {*} message\n * @private\n */\n _sendOrEnqueue(message) {\n if (this.state === 'closed') {\n return;\n }\n const sendOrEnqueue = this.state === 'open'\n ? message => this._send(message)\n : message => this._messageQueue.push(message);\n\n sendOrEnqueue(message);\n }\n\n /**\n * Start the TCMP handshake.\n * @private\n */\n _startHandshake() {\n if (['early', 'waiting'].includes(this.state)) {\n this.transition('connecting');\n }\n if (this.state !== 'connecting') {\n return;\n }\n this._hellosLeft--;\n this._sendHello();\n const { welcomeTimeout } = this._options;\n this._welcomeTimeout = new Timeout(() => this._handleWelcomeTimeout(), welcomeTimeout);\n }\n\n /**\n * Close the {@link TwilioConnection}.\n * @returns {void}\n */\n close() {\n if (this.state === 'closed') {\n return;\n }\n this._sendOrEnqueue({ type: 'bye' });\n this._close({ code: WS_CLOSE_NORMAL, reason: 'Normal' });\n }\n\n /**\n * Send a \"msg\" message.\n * @param {*} body\n * @returns {void}\n */\n sendMessage(body) {\n this._sendOrEnqueue({ body, type: 'msg' });\n }\n}\n\n/**\n * A unique string depicting the reason for the {@link TwilioConnection} being closed.\n * @enum {string}\n */\nTwilioConnection.CloseReason = CloseReason;\n\n/**\n * A {@link TwilioConnection} was closed.\n * @event TwilioConnection#close\n * @param {CloseReason} reason - The reason for the {@link TwilioConnection} being closed\n */\n\n/**\n * A {@link TwilioConnection} received an error from the TCMP server.\n * @event TwilioConnection#error\n * @param {Error} error - The TCMP server error\n */\n\n/**\n * A {@link TwilioConnection} received a message from the TCMP server.\n * @event TwilioConnection#message\n * @param {*} body - Message body\n */\n\n/**\n * A {@link TwilioConnection} completed a hello/welcome handshake with the TCMP server.\n * @event TwilioConnection#open\n */\n\n/**\n * A {@link TwilioConnection} received a \"busy\" message from the TCMP server.\n * @event TwilioConnection#waiting\n * @param {boolean} keepAlive - true if the WebSocket connection is retained\n * @param {number} retryAfter - delay in milliseconds after which a retry is attempted\n */\n\n/**\n * {@link TwilioConnection} options\n * @typedef {object} TwilioConnectionOptions\n * @property {EventObserver} [eventObserver] - Optional event observer\n * @property {*} [helloBody=null] - Optional body for \"hello\" message\n * @property {LogLevel} [logLevel=warn] - Log level of the {@link TwilioConnection}\n * @property {number} [maxConsecutiveFailedHellos=3] - Max. number of consecutive failed \"hello\"s\n * @property {number} [maxConsecutiveMissedHeartbeats=3] - Max. number of (effective) consecutive \"heartbeat\" messages that can be missed\n * @property {number} [requestedHeartbeatTimeout=5000] - \"heartbeat\" timeout (ms) requested by the {@link TwilioConnection}\n * @property {number} [welcomeTimeout=5000] - Time (ms) to wait for the \"welcome\" message after sending the \"hello\" message\n */\n\nmodule.exports = TwilioConnection;\n", "'use strict';\n\n/**\n * @extends Error\n * @property {number} code - Error code\n */\nclass TwilioError extends Error {\n /**\n * Creates a new {@link TwilioError}\n * @param {number} code - Error code\n * @param {string} [message] - Error message\n * @param {string} [fileName] - Name of the script file where error was generated\n * @param {number} [lineNumber] - Line number of the script file where error was generated\n */\n constructor(code) {\n const args = [].slice.call(arguments, 1);\n super(...args);\n Object.setPrototypeOf(this, TwilioError.prototype);\n\n const error = Error.apply(this, args);\n error.name = 'TwilioError';\n\n Object.defineProperty(this, 'code', {\n value: code,\n enumerable: true\n });\n\n Object.getOwnPropertyNames(error).forEach(function(prop) {\n Object.defineProperty(this, prop, {\n value: error[prop],\n enumerable: true\n });\n }, this);\n }\n\n /**\n * Returns human readable string describing the error.\n * @returns {string}\n */\n toString() {\n const message = this.message ? `: ${this.message}` : '';\n return `${this.name} ${this.code}${message}`;\n }\n}\n\nmodule.exports = TwilioError;\n", "// NOTE: Do not edit this file. This code is auto-generated. Contact the\n// Twilio SDK Team for more information.\n\n'use strict';\n\nconst TwilioError = require('./twilioerror');\nconst TwilioErrorByCode = {};\n\n/**\n * Create a {@link TwilioError} for a given code and message.\n * @private\n * @param {number} [code] - Error code\n * @param {string} [message] - Error message\n * @returns {TwilioError}\n */\nexports.createTwilioError = function createTwilioError(code, message) {\n code = typeof code === 'number' ? code : 0;\n message = typeof message === 'string' && message ? message : 'Unknown error';\n return TwilioErrorByCode[code] ? new TwilioErrorByCode[code]() : new TwilioError(code, message);\n};\n\n/**\n * @class AccessTokenInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room is invalid.\n * @extends TwilioError\n * @property {number} code - 20101\n * @property {string} message - 'Invalid Access Token'\n */\nclass AccessTokenInvalidError extends TwilioError {\n constructor() {\n super(20101, 'Invalid Access Token');\n Object.setPrototypeOf(this, AccessTokenInvalidError.prototype);\n }\n}\n\nexports.AccessTokenInvalidError = AccessTokenInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20101, { value: AccessTokenInvalidError });\n\n/**\n * @class AccessTokenHeaderInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room has an invalid header.\n * @extends TwilioError\n * @property {number} code - 20102\n * @property {string} message - 'Invalid Access Token header'\n */\nclass AccessTokenHeaderInvalidError extends TwilioError {\n constructor() {\n super(20102, 'Invalid Access Token header');\n Object.setPrototypeOf(this, AccessTokenHeaderInvalidError.prototype);\n }\n}\n\nexports.AccessTokenHeaderInvalidError = AccessTokenHeaderInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20102, { value: AccessTokenHeaderInvalidError });\n\n/**\n * @class AccessTokenIssuerInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room contains an invalid issuer or subject.\n * @extends TwilioError\n * @property {number} code - 20103\n * @property {string} message - 'Invalid Access Token issuer/subject'\n */\nclass AccessTokenIssuerInvalidError extends TwilioError {\n constructor() {\n super(20103, 'Invalid Access Token issuer/subject');\n Object.setPrototypeOf(this, AccessTokenIssuerInvalidError.prototype);\n }\n}\n\nexports.AccessTokenIssuerInvalidError = AccessTokenIssuerInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20103, { value: AccessTokenIssuerInvalidError });\n\n/**\n * @class AccessTokenExpiredError\n * @classdesc Raised whenever the AccessToken used for connecting, or reconnecting to a Room has expired.\n * @extends TwilioError\n * @property {number} code - 20104\n * @property {string} message - 'Access Token expired or expiration date invalid'\n */\nclass AccessTokenExpiredError extends TwilioError {\n constructor() {\n super(20104, 'Access Token expired or expiration date invalid');\n Object.setPrototypeOf(this, AccessTokenExpiredError.prototype);\n }\n}\n\nexports.AccessTokenExpiredError = AccessTokenExpiredError;\nObject.defineProperty(TwilioErrorByCode, 20104, { value: AccessTokenExpiredError });\n\n/**\n * @class AccessTokenNotYetValidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room is not yet valid.\n * @extends TwilioError\n * @property {number} code - 20105\n * @property {string} message - 'Access Token not yet valid'\n */\nclass AccessTokenNotYetValidError extends TwilioError {\n constructor() {\n super(20105, 'Access Token not yet valid');\n Object.setPrototypeOf(this, AccessTokenNotYetValidError.prototype);\n }\n}\n\nexports.AccessTokenNotYetValidError = AccessTokenNotYetValidError;\nObject.defineProperty(TwilioErrorByCode, 20105, { value: AccessTokenNotYetValidError });\n\n/**\n * @class AccessTokenGrantsInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room has invalid grants.\n * @extends TwilioError\n * @property {number} code - 20106\n * @property {string} message - 'Invalid Access Token grants'\n */\nclass AccessTokenGrantsInvalidError extends TwilioError {\n constructor() {\n super(20106, 'Invalid Access Token grants');\n Object.setPrototypeOf(this, AccessTokenGrantsInvalidError.prototype);\n }\n}\n\nexports.AccessTokenGrantsInvalidError = AccessTokenGrantsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20106, { value: AccessTokenGrantsInvalidError });\n\n/**\n * @class AccessTokenSignatureInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room has an invalid signature.\n * @extends TwilioError\n * @property {number} code - 20107\n * @property {string} message - 'Invalid Access Token signature'\n */\nclass AccessTokenSignatureInvalidError extends TwilioError {\n constructor() {\n super(20107, 'Invalid Access Token signature');\n Object.setPrototypeOf(this, AccessTokenSignatureInvalidError.prototype);\n }\n}\n\nexports.AccessTokenSignatureInvalidError = AccessTokenSignatureInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20107, { value: AccessTokenSignatureInvalidError });\n\n/**\n * @class SignalingConnectionError\n * @classdesc Raised whenever a signaling connection error occurs that is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53000\n * @property {string} message - 'Signaling connection error'\n */\nclass SignalingConnectionError extends TwilioError {\n constructor() {\n super(53000, 'Signaling connection error');\n Object.setPrototypeOf(this, SignalingConnectionError.prototype);\n }\n}\n\nexports.SignalingConnectionError = SignalingConnectionError;\nObject.defineProperty(TwilioErrorByCode, 53000, { value: SignalingConnectionError });\n\n/**\n * @class SignalingConnectionDisconnectedError\n * @classdesc Raised whenever the signaling connection is unexpectedly disconnected.\n * @extends TwilioError\n * @property {number} code - 53001\n * @property {string} message - 'Signaling connection disconnected'\n */\nclass SignalingConnectionDisconnectedError extends TwilioError {\n constructor() {\n super(53001, 'Signaling connection disconnected');\n Object.setPrototypeOf(this, SignalingConnectionDisconnectedError.prototype);\n }\n}\n\nexports.SignalingConnectionDisconnectedError = SignalingConnectionDisconnectedError;\nObject.defineProperty(TwilioErrorByCode, 53001, { value: SignalingConnectionDisconnectedError });\n\n/**\n * @class SignalingConnectionTimeoutError\n * @classdesc Raised when connection liveliness checks fail, or when the signaling session expires.\n * @extends TwilioError\n * @property {number} code - 53002\n * @property {string} message - 'Signaling connection timed out'\n */\nclass SignalingConnectionTimeoutError extends TwilioError {\n constructor() {\n super(53002, 'Signaling connection timed out');\n Object.setPrototypeOf(this, SignalingConnectionTimeoutError.prototype);\n }\n}\n\nexports.SignalingConnectionTimeoutError = SignalingConnectionTimeoutError;\nObject.defineProperty(TwilioErrorByCode, 53002, { value: SignalingConnectionTimeoutError });\n\n/**\n * @class SignalingIncomingMessageInvalidError\n * @classdesc Raised whenever the Client receives a message from the Server that the Client cannot handle.\n * @extends TwilioError\n * @property {number} code - 53003\n * @property {string} message - 'Client received an invalid signaling message'\n */\nclass SignalingIncomingMessageInvalidError extends TwilioError {\n constructor() {\n super(53003, 'Client received an invalid signaling message');\n Object.setPrototypeOf(this, SignalingIncomingMessageInvalidError.prototype);\n }\n}\n\nexports.SignalingIncomingMessageInvalidError = SignalingIncomingMessageInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53003, { value: SignalingIncomingMessageInvalidError });\n\n/**\n * @class SignalingOutgoingMessageInvalidError\n * @classdesc Raised whenever the Client sends a message to the Server that the Server cannot handle.\n * @extends TwilioError\n * @property {number} code - 53004\n * @property {string} message - 'Client sent an invalid signaling message'\n */\nclass SignalingOutgoingMessageInvalidError extends TwilioError {\n constructor() {\n super(53004, 'Client sent an invalid signaling message');\n Object.setPrototypeOf(this, SignalingOutgoingMessageInvalidError.prototype);\n }\n}\n\nexports.SignalingOutgoingMessageInvalidError = SignalingOutgoingMessageInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53004, { value: SignalingOutgoingMessageInvalidError });\n\n/**\n * @class SignalingServerBusyError\n * @classdesc Raised when the server is too busy to accept new clients.\n * @extends TwilioError\n * @property {number} code - 53006\n * @property {string} message - 'Video server is busy'\n */\nclass SignalingServerBusyError extends TwilioError {\n constructor() {\n super(53006, 'Video server is busy');\n Object.setPrototypeOf(this, SignalingServerBusyError.prototype);\n }\n}\n\nexports.SignalingServerBusyError = SignalingServerBusyError;\nObject.defineProperty(TwilioErrorByCode, 53006, { value: SignalingServerBusyError });\n\n/**\n * @class RoomNameInvalidError\n * @classdesc Raised whenever a Room name is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53100\n * @property {string} message - 'Room name is invalid'\n */\nclass RoomNameInvalidError extends TwilioError {\n constructor() {\n super(53100, 'Room name is invalid');\n Object.setPrototypeOf(this, RoomNameInvalidError.prototype);\n }\n}\n\nexports.RoomNameInvalidError = RoomNameInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53100, { value: RoomNameInvalidError });\n\n/**\n * @class RoomNameTooLongError\n * @classdesc Raised whenever a Room name is too long.\n * @extends TwilioError\n * @property {number} code - 53101\n * @property {string} message - 'Room name is too long'\n */\nclass RoomNameTooLongError extends TwilioError {\n constructor() {\n super(53101, 'Room name is too long');\n Object.setPrototypeOf(this, RoomNameTooLongError.prototype);\n }\n}\n\nexports.RoomNameTooLongError = RoomNameTooLongError;\nObject.defineProperty(TwilioErrorByCode, 53101, { value: RoomNameTooLongError });\n\n/**\n * @class RoomNameCharsInvalidError\n * @classdesc Raised whenever a Room name contains invalid characters.\n * @extends TwilioError\n * @property {number} code - 53102\n * @property {string} message - 'Room name contains invalid characters'\n */\nclass RoomNameCharsInvalidError extends TwilioError {\n constructor() {\n super(53102, 'Room name contains invalid characters');\n Object.setPrototypeOf(this, RoomNameCharsInvalidError.prototype);\n }\n}\n\nexports.RoomNameCharsInvalidError = RoomNameCharsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53102, { value: RoomNameCharsInvalidError });\n\n/**\n * @class RoomCreateFailedError\n * @classdesc Raised whenever the Server is unable to create a Room.\n * @extends TwilioError\n * @property {number} code - 53103\n * @property {string} message - 'Unable to create Room'\n */\nclass RoomCreateFailedError extends TwilioError {\n constructor() {\n super(53103, 'Unable to create Room');\n Object.setPrototypeOf(this, RoomCreateFailedError.prototype);\n }\n}\n\nexports.RoomCreateFailedError = RoomCreateFailedError;\nObject.defineProperty(TwilioErrorByCode, 53103, { value: RoomCreateFailedError });\n\n/**\n * @class RoomConnectFailedError\n * @classdesc Raised whenever a Client is unable to connect to a Room, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53104\n * @property {string} message - 'Unable to connect to Room'\n */\nclass RoomConnectFailedError extends TwilioError {\n constructor() {\n super(53104, 'Unable to connect to Room');\n Object.setPrototypeOf(this, RoomConnectFailedError.prototype);\n }\n}\n\nexports.RoomConnectFailedError = RoomConnectFailedError;\nObject.defineProperty(TwilioErrorByCode, 53104, { value: RoomConnectFailedError });\n\n/**\n * @class RoomMaxParticipantsExceededError\n * @classdesc Raised whenever a Client is unable to connect to a Room because the Room contains too many Participants.\n * @extends TwilioError\n * @property {number} code - 53105\n * @property {string} message - 'Room contains too many Participants'\n */\nclass RoomMaxParticipantsExceededError extends TwilioError {\n constructor() {\n super(53105, 'Room contains too many Participants');\n Object.setPrototypeOf(this, RoomMaxParticipantsExceededError.prototype);\n }\n}\n\nexports.RoomMaxParticipantsExceededError = RoomMaxParticipantsExceededError;\nObject.defineProperty(TwilioErrorByCode, 53105, { value: RoomMaxParticipantsExceededError });\n\n/**\n * @class RoomNotFoundError\n * @classdesc Raised whenever attempting operation on a non-existent Room.\n * @extends TwilioError\n * @property {number} code - 53106\n * @property {string} message - 'Room not found'\n */\nclass RoomNotFoundError extends TwilioError {\n constructor() {\n super(53106, 'Room not found');\n Object.setPrototypeOf(this, RoomNotFoundError.prototype);\n }\n}\n\nexports.RoomNotFoundError = RoomNotFoundError;\nObject.defineProperty(TwilioErrorByCode, 53106, { value: RoomNotFoundError });\n\n/**\n * @class RoomMaxParticipantsOutOfRangeError\n * @classdesc Raised in the REST API when MaxParticipants is set out of range.\n * @extends TwilioError\n * @property {number} code - 53107\n * @property {string} message - 'MaxParticipants is out of range'\n */\nclass RoomMaxParticipantsOutOfRangeError extends TwilioError {\n constructor() {\n super(53107, 'MaxParticipants is out of range');\n Object.setPrototypeOf(this, RoomMaxParticipantsOutOfRangeError.prototype);\n }\n}\n\nexports.RoomMaxParticipantsOutOfRangeError = RoomMaxParticipantsOutOfRangeError;\nObject.defineProperty(TwilioErrorByCode, 53107, { value: RoomMaxParticipantsOutOfRangeError });\n\n/**\n * @class RoomTypeInvalidError\n * @classdesc Raised in the REST API when the user attempts to create a Room with an invalid RoomType\n * @extends TwilioError\n * @property {number} code - 53108\n * @property {string} message - 'RoomType is not valid'\n */\nclass RoomTypeInvalidError extends TwilioError {\n constructor() {\n super(53108, 'RoomType is not valid');\n Object.setPrototypeOf(this, RoomTypeInvalidError.prototype);\n }\n}\n\nexports.RoomTypeInvalidError = RoomTypeInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53108, { value: RoomTypeInvalidError });\n\n/**\n * @class RoomTimeoutOutOfRangeError\n * @classdesc Raised in the REST API when Timeout is set out of range.\n * @extends TwilioError\n * @property {number} code - 53109\n * @property {string} message - 'Timeout is out of range'\n */\nclass RoomTimeoutOutOfRangeError extends TwilioError {\n constructor() {\n super(53109, 'Timeout is out of range');\n Object.setPrototypeOf(this, RoomTimeoutOutOfRangeError.prototype);\n }\n}\n\nexports.RoomTimeoutOutOfRangeError = RoomTimeoutOutOfRangeError;\nObject.defineProperty(TwilioErrorByCode, 53109, { value: RoomTimeoutOutOfRangeError });\n\n/**\n * @class RoomStatusCallbackMethodInvalidError\n * @classdesc Raised in the REST API when StatusCallbackMethod is set to an invalid value.\n * @extends TwilioError\n * @property {number} code - 53110\n * @property {string} message - 'StatusCallbackMethod is invalid'\n */\nclass RoomStatusCallbackMethodInvalidError extends TwilioError {\n constructor() {\n super(53110, 'StatusCallbackMethod is invalid');\n Object.setPrototypeOf(this, RoomStatusCallbackMethodInvalidError.prototype);\n }\n}\n\nexports.RoomStatusCallbackMethodInvalidError = RoomStatusCallbackMethodInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53110, { value: RoomStatusCallbackMethodInvalidError });\n\n/**\n * @class RoomStatusCallbackInvalidError\n * @classdesc Raised in the REST API when StatusCallback is not a valid URL or the url is too long.\n * @extends TwilioError\n * @property {number} code - 53111\n * @property {string} message - 'StatusCallback is invalid'\n */\nclass RoomStatusCallbackInvalidError extends TwilioError {\n constructor() {\n super(53111, 'StatusCallback is invalid');\n Object.setPrototypeOf(this, RoomStatusCallbackInvalidError.prototype);\n }\n}\n\nexports.RoomStatusCallbackInvalidError = RoomStatusCallbackInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53111, { value: RoomStatusCallbackInvalidError });\n\n/**\n * @class RoomStatusInvalidError\n * @classdesc Raised in the REST API when Status is not valid or the Room is not in-progress.\n * @extends TwilioError\n * @property {number} code - 53112\n * @property {string} message - 'Status is invalid'\n */\nclass RoomStatusInvalidError extends TwilioError {\n constructor() {\n super(53112, 'Status is invalid');\n Object.setPrototypeOf(this, RoomStatusInvalidError.prototype);\n }\n}\n\nexports.RoomStatusInvalidError = RoomStatusInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53112, { value: RoomStatusInvalidError });\n\n/**\n * @class RoomRoomExistsError\n * @classdesc Raised in the REST API when the Room creation fails because a Room exists with the same name.\n * @extends TwilioError\n * @property {number} code - 53113\n * @property {string} message - 'Room exists'\n */\nclass RoomRoomExistsError extends TwilioError {\n constructor() {\n super(53113, 'Room exists');\n Object.setPrototypeOf(this, RoomRoomExistsError.prototype);\n }\n}\n\nexports.RoomRoomExistsError = RoomRoomExistsError;\nObject.defineProperty(TwilioErrorByCode, 53113, { value: RoomRoomExistsError });\n\n/**\n * @class RoomInvalidParametersError\n * @classdesc Raised in the REST API when one or more Room creation parameter is incompatible with the Room type.\n * @extends TwilioError\n * @property {number} code - 53114\n * @property {string} message - 'Room creation parameter(s) incompatible with the Room type'\n */\nclass RoomInvalidParametersError extends TwilioError {\n constructor() {\n super(53114, 'Room creation parameter(s) incompatible with the Room type');\n Object.setPrototypeOf(this, RoomInvalidParametersError.prototype);\n }\n}\n\nexports.RoomInvalidParametersError = RoomInvalidParametersError;\nObject.defineProperty(TwilioErrorByCode, 53114, { value: RoomInvalidParametersError });\n\n/**\n * @class RoomMediaRegionInvalidError\n * @classdesc Raised in the REST API when MediaRegion is set to an invalid value.\n * @extends TwilioError\n * @property {number} code - 53115\n * @property {string} message - 'MediaRegion is invalid'\n */\nclass RoomMediaRegionInvalidError extends TwilioError {\n constructor() {\n super(53115, 'MediaRegion is invalid');\n Object.setPrototypeOf(this, RoomMediaRegionInvalidError.prototype);\n }\n}\n\nexports.RoomMediaRegionInvalidError = RoomMediaRegionInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53115, { value: RoomMediaRegionInvalidError });\n\n/**\n * @class RoomMediaRegionUnavailableError\n * @classdesc Raised in the REST API when MediaRegion is set to a valid value but no media servers are available.\n * @extends TwilioError\n * @property {number} code - 53116\n * @property {string} message - 'There are no media servers available in the MediaRegion'\n */\nclass RoomMediaRegionUnavailableError extends TwilioError {\n constructor() {\n super(53116, 'There are no media servers available in the MediaRegion');\n Object.setPrototypeOf(this, RoomMediaRegionUnavailableError.prototype);\n }\n}\n\nexports.RoomMediaRegionUnavailableError = RoomMediaRegionUnavailableError;\nObject.defineProperty(TwilioErrorByCode, 53116, { value: RoomMediaRegionUnavailableError });\n\n/**\n * @class RoomSubscriptionOperationNotSupportedError\n * @classdesc Raised whenever the subscription operation requested is not supported for the Room type.\n * @extends TwilioError\n * @property {number} code - 53117\n * @property {string} message - 'The subscription operation requested is not supported for the Room type'\n */\nclass RoomSubscriptionOperationNotSupportedError extends TwilioError {\n constructor() {\n super(53117, 'The subscription operation requested is not supported for the Room type');\n Object.setPrototypeOf(this, RoomSubscriptionOperationNotSupportedError.prototype);\n }\n}\n\nexports.RoomSubscriptionOperationNotSupportedError = RoomSubscriptionOperationNotSupportedError;\nObject.defineProperty(TwilioErrorByCode, 53117, { value: RoomSubscriptionOperationNotSupportedError });\n\n/**\n * @class RoomCompletedError\n * @classdesc Raised whenever a Room is completed via the REST API.\n * @extends TwilioError\n * @property {number} code - 53118\n * @property {string} message - 'Room completed'\n */\nclass RoomCompletedError extends TwilioError {\n constructor() {\n super(53118, 'Room completed');\n Object.setPrototypeOf(this, RoomCompletedError.prototype);\n }\n}\n\nexports.RoomCompletedError = RoomCompletedError;\nObject.defineProperty(TwilioErrorByCode, 53118, { value: RoomCompletedError });\n\n/**\n * @class RoomAudioOnlyFlagNotSupportedError\n * @classdesc Raised whenever a participant tries to set the AudioOnly flag for a Room type other than Group Rooms.\n * @extends TwilioError\n * @property {number} code - 53124\n * @property {string} message - 'The AudioOnly flag is not supported for the Room type'\n */\nclass RoomAudioOnlyFlagNotSupportedError extends TwilioError {\n constructor() {\n super(53124, 'The AudioOnly flag is not supported for the Room type');\n Object.setPrototypeOf(this, RoomAudioOnlyFlagNotSupportedError.prototype);\n }\n}\n\nexports.RoomAudioOnlyFlagNotSupportedError = RoomAudioOnlyFlagNotSupportedError;\nObject.defineProperty(TwilioErrorByCode, 53124, { value: RoomAudioOnlyFlagNotSupportedError });\n\n/**\n * @class RoomTrackKindNotSupportedError\n * @classdesc Raised whenever a participant tries to publish a track or connects with a track that is not supported by the group room.\n * @extends TwilioError\n * @property {number} code - 53125\n * @property {string} message - 'The track kind is not supported by the Room'\n */\nclass RoomTrackKindNotSupportedError extends TwilioError {\n constructor() {\n super(53125, 'The track kind is not supported by the Room');\n Object.setPrototypeOf(this, RoomTrackKindNotSupportedError.prototype);\n }\n}\n\nexports.RoomTrackKindNotSupportedError = RoomTrackKindNotSupportedError;\nObject.defineProperty(TwilioErrorByCode, 53125, { value: RoomTrackKindNotSupportedError });\n\n/**\n * @class ParticipantIdentityInvalidError\n * @classdesc Raised whenever a Participant identity is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53200\n * @property {string} message - 'Participant identity is invalid'\n */\nclass ParticipantIdentityInvalidError extends TwilioError {\n constructor() {\n super(53200, 'Participant identity is invalid');\n Object.setPrototypeOf(this, ParticipantIdentityInvalidError.prototype);\n }\n}\n\nexports.ParticipantIdentityInvalidError = ParticipantIdentityInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53200, { value: ParticipantIdentityInvalidError });\n\n/**\n * @class ParticipantIdentityTooLongError\n * @classdesc Raised whenever a Participant identity is too long.\n * @extends TwilioError\n * @property {number} code - 53201\n * @property {string} message - 'Participant identity is too long'\n */\nclass ParticipantIdentityTooLongError extends TwilioError {\n constructor() {\n super(53201, 'Participant identity is too long');\n Object.setPrototypeOf(this, ParticipantIdentityTooLongError.prototype);\n }\n}\n\nexports.ParticipantIdentityTooLongError = ParticipantIdentityTooLongError;\nObject.defineProperty(TwilioErrorByCode, 53201, { value: ParticipantIdentityTooLongError });\n\n/**\n * @class ParticipantIdentityCharsInvalidError\n * @classdesc Raised whenever a Participant identity contains invalid characters.\n * @extends TwilioError\n * @property {number} code - 53202\n * @property {string} message - 'Participant identity contains invalid characters'\n */\nclass ParticipantIdentityCharsInvalidError extends TwilioError {\n constructor() {\n super(53202, 'Participant identity contains invalid characters');\n Object.setPrototypeOf(this, ParticipantIdentityCharsInvalidError.prototype);\n }\n}\n\nexports.ParticipantIdentityCharsInvalidError = ParticipantIdentityCharsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53202, { value: ParticipantIdentityCharsInvalidError });\n\n/**\n * @class ParticipantMaxTracksExceededError\n * @classdesc Raised whenever a Participant tries to publish a Track and the maximum number of published tracks allowed in the Room at the same time has been reached\n * @extends TwilioError\n * @property {number} code - 53203\n * @property {string} message - 'The maximum number of published tracks allowed in the Room at the same time has been reached'\n */\nclass ParticipantMaxTracksExceededError extends TwilioError {\n constructor() {\n super(53203, 'The maximum number of published tracks allowed in the Room at the same time has been reached');\n Object.setPrototypeOf(this, ParticipantMaxTracksExceededError.prototype);\n }\n}\n\nexports.ParticipantMaxTracksExceededError = ParticipantMaxTracksExceededError;\nObject.defineProperty(TwilioErrorByCode, 53203, { value: ParticipantMaxTracksExceededError });\n\n/**\n * @class ParticipantNotFoundError\n * @classdesc Raised whenever attempting an operation on a non-existent Participant.\n * @extends TwilioError\n * @property {number} code - 53204\n * @property {string} message - 'Participant not found'\n */\nclass ParticipantNotFoundError extends TwilioError {\n constructor() {\n super(53204, 'Participant not found');\n Object.setPrototypeOf(this, ParticipantNotFoundError.prototype);\n }\n}\n\nexports.ParticipantNotFoundError = ParticipantNotFoundError;\nObject.defineProperty(TwilioErrorByCode, 53204, { value: ParticipantNotFoundError });\n\n/**\n * @class ParticipantDuplicateIdentityError\n * @classdesc Raised by the server to the existing Participant when a new Participant joins a Room with the same identity as the existing Participant.\n * @extends TwilioError\n * @property {number} code - 53205\n * @property {string} message - 'Participant disconnected because of duplicate identity'\n */\nclass ParticipantDuplicateIdentityError extends TwilioError {\n constructor() {\n super(53205, 'Participant disconnected because of duplicate identity');\n Object.setPrototypeOf(this, ParticipantDuplicateIdentityError.prototype);\n }\n}\n\nexports.ParticipantDuplicateIdentityError = ParticipantDuplicateIdentityError;\nObject.defineProperty(TwilioErrorByCode, 53205, { value: ParticipantDuplicateIdentityError });\n\n/**\n * @class TrackInvalidError\n * @classdesc Raised whenever a Track is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53300\n * @property {string} message - 'Track is invalid'\n */\nclass TrackInvalidError extends TwilioError {\n constructor() {\n super(53300, 'Track is invalid');\n Object.setPrototypeOf(this, TrackInvalidError.prototype);\n }\n}\n\nexports.TrackInvalidError = TrackInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53300, { value: TrackInvalidError });\n\n/**\n * @class TrackNameInvalidError\n * @classdesc Raised whenever a Track name is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53301\n * @property {string} message - 'Track name is invalid'\n */\nclass TrackNameInvalidError extends TwilioError {\n constructor() {\n super(53301, 'Track name is invalid');\n Object.setPrototypeOf(this, TrackNameInvalidError.prototype);\n }\n}\n\nexports.TrackNameInvalidError = TrackNameInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53301, { value: TrackNameInvalidError });\n\n/**\n * @class TrackNameTooLongError\n * @classdesc Raised whenever a Track name is too long.\n * @extends TwilioError\n * @property {number} code - 53302\n * @property {string} message - 'Track name is too long'\n */\nclass TrackNameTooLongError extends TwilioError {\n constructor() {\n super(53302, 'Track name is too long');\n Object.setPrototypeOf(this, TrackNameTooLongError.prototype);\n }\n}\n\nexports.TrackNameTooLongError = TrackNameTooLongError;\nObject.defineProperty(TwilioErrorByCode, 53302, { value: TrackNameTooLongError });\n\n/**\n * @class TrackNameCharsInvalidError\n * @classdesc Raised whenever a Track name contains invalid characters.\n * @extends TwilioError\n * @property {number} code - 53303\n * @property {string} message - 'Track name contains invalid characters'\n */\nclass TrackNameCharsInvalidError extends TwilioError {\n constructor() {\n super(53303, 'Track name contains invalid characters');\n Object.setPrototypeOf(this, TrackNameCharsInvalidError.prototype);\n }\n}\n\nexports.TrackNameCharsInvalidError = TrackNameCharsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53303, { value: TrackNameCharsInvalidError });\n\n/**\n * @class TrackNameIsDuplicatedError\n * @classdesc Raised whenever a Participant is currently publishing a Track with the same name.\n * @extends TwilioError\n * @property {number} code - 53304\n * @property {string} message - 'Track name is duplicated'\n */\nclass TrackNameIsDuplicatedError extends TwilioError {\n constructor() {\n super(53304, 'Track name is duplicated');\n Object.setPrototypeOf(this, TrackNameIsDuplicatedError.prototype);\n }\n}\n\nexports.TrackNameIsDuplicatedError = TrackNameIsDuplicatedError;\nObject.defineProperty(TwilioErrorByCode, 53304, { value: TrackNameIsDuplicatedError });\n\n/**\n * @class TrackServerTrackCapacityReachedError\n * @classdesc The server does not have enough resources available to create a new Track.\n * @extends TwilioError\n * @property {number} code - 53305\n * @property {string} message - 'The server has reached capacity and cannot fulfill this request'\n */\nclass TrackServerTrackCapacityReachedError extends TwilioError {\n constructor() {\n super(53305, 'The server has reached capacity and cannot fulfill this request');\n Object.setPrototypeOf(this, TrackServerTrackCapacityReachedError.prototype);\n }\n}\n\nexports.TrackServerTrackCapacityReachedError = TrackServerTrackCapacityReachedError;\nObject.defineProperty(TwilioErrorByCode, 53305, { value: TrackServerTrackCapacityReachedError });\n\n/**\n * @class MediaClientLocalDescFailedError\n * @classdesc Raised whenever a Client is unable to create or apply a local media description.\n * @extends TwilioError\n * @property {number} code - 53400\n * @property {string} message - 'Client is unable to create or apply a local media description'\n */\nclass MediaClientLocalDescFailedError extends TwilioError {\n constructor() {\n super(53400, 'Client is unable to create or apply a local media description');\n Object.setPrototypeOf(this, MediaClientLocalDescFailedError.prototype);\n }\n}\n\nexports.MediaClientLocalDescFailedError = MediaClientLocalDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53400, { value: MediaClientLocalDescFailedError });\n\n/**\n * @class MediaServerLocalDescFailedError\n * @classdesc Raised whenever the Server is unable to create or apply a local media description.\n * @extends TwilioError\n * @property {number} code - 53401\n * @property {string} message - 'Server is unable to create or apply a local media description'\n */\nclass MediaServerLocalDescFailedError extends TwilioError {\n constructor() {\n super(53401, 'Server is unable to create or apply a local media description');\n Object.setPrototypeOf(this, MediaServerLocalDescFailedError.prototype);\n }\n}\n\nexports.MediaServerLocalDescFailedError = MediaServerLocalDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53401, { value: MediaServerLocalDescFailedError });\n\n/**\n * @class MediaClientRemoteDescFailedError\n * @classdesc Raised whenever the Client receives a remote media description but is unable to apply it.\n * @extends TwilioError\n * @property {number} code - 53402\n * @property {string} message - 'Client is unable to apply a remote media description'\n */\nclass MediaClientRemoteDescFailedError extends TwilioError {\n constructor() {\n super(53402, 'Client is unable to apply a remote media description');\n Object.setPrototypeOf(this, MediaClientRemoteDescFailedError.prototype);\n }\n}\n\nexports.MediaClientRemoteDescFailedError = MediaClientRemoteDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53402, { value: MediaClientRemoteDescFailedError });\n\n/**\n * @class MediaServerRemoteDescFailedError\n * @classdesc Raised whenever the Server receives a remote media description but is unable to apply it.\n * @extends TwilioError\n * @property {number} code - 53403\n * @property {string} message - 'Server is unable to apply a remote media description'\n */\nclass MediaServerRemoteDescFailedError extends TwilioError {\n constructor() {\n super(53403, 'Server is unable to apply a remote media description');\n Object.setPrototypeOf(this, MediaServerRemoteDescFailedError.prototype);\n }\n}\n\nexports.MediaServerRemoteDescFailedError = MediaServerRemoteDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53403, { value: MediaServerRemoteDescFailedError });\n\n/**\n * @class MediaNoSupportedCodecError\n * @classdesc Raised whenever the intersection of codecs supported by the Client and the Server (or, in peer-to-peer, the Client and another Participant) is empty.\n * @extends TwilioError\n * @property {number} code - 53404\n * @property {string} message - 'No supported codec'\n */\nclass MediaNoSupportedCodecError extends TwilioError {\n constructor() {\n super(53404, 'No supported codec');\n Object.setPrototypeOf(this, MediaNoSupportedCodecError.prototype);\n }\n}\n\nexports.MediaNoSupportedCodecError = MediaNoSupportedCodecError;\nObject.defineProperty(TwilioErrorByCode, 53404, { value: MediaNoSupportedCodecError });\n\n/**\n * @class MediaConnectionError\n * @classdesc Raised by the Client or Server whenever a media connection fails or raised by the Client whenever it detects that media has stopped flowing.\n * @extends TwilioError\n * @property {number} code - 53405\n * @property {string} message - 'Media connection failed or Media activity ceased'\n */\nclass MediaConnectionError extends TwilioError {\n constructor() {\n super(53405, 'Media connection failed or Media activity ceased');\n Object.setPrototypeOf(this, MediaConnectionError.prototype);\n }\n}\n\nexports.MediaConnectionError = MediaConnectionError;\nObject.defineProperty(TwilioErrorByCode, 53405, { value: MediaConnectionError });\n\n/**\n * @class MediaDTLSTransportFailedError\n * @classdesc There was a problem while negotiating with the remote DTLS peer. Therefore the Participant will not be able to publish or subscribe to Tracks.\n * @extends TwilioError\n * @property {number} code - 53407\n * @property {string} message - 'Media connection failed due to DTLS handshake failure'\n */\nclass MediaDTLSTransportFailedError extends TwilioError {\n constructor() {\n super(53407, 'Media connection failed due to DTLS handshake failure');\n Object.setPrototypeOf(this, MediaDTLSTransportFailedError.prototype);\n }\n}\n\nexports.MediaDTLSTransportFailedError = MediaDTLSTransportFailedError;\nObject.defineProperty(TwilioErrorByCode, 53407, { value: MediaDTLSTransportFailedError });\n\n/**\n * @class ConfigurationAcquireFailedError\n * @classdesc Raised whenever the Client is unable to acquire configuration information from the Server.\n * @extends TwilioError\n * @property {number} code - 53500\n * @property {string} message - 'Unable to acquire configuration'\n */\nclass ConfigurationAcquireFailedError extends TwilioError {\n constructor() {\n super(53500, 'Unable to acquire configuration');\n Object.setPrototypeOf(this, ConfigurationAcquireFailedError.prototype);\n }\n}\n\nexports.ConfigurationAcquireFailedError = ConfigurationAcquireFailedError;\nObject.defineProperty(TwilioErrorByCode, 53500, { value: ConfigurationAcquireFailedError });\n\n/**\n * @class ConfigurationAcquireTurnFailedError\n * @classdesc Raised whenever the Server is unable to return TURN credentials to the Client\n * @extends TwilioError\n * @property {number} code - 53501\n * @property {string} message - 'Unable to acquire TURN credentials'\n */\nclass ConfigurationAcquireTurnFailedError extends TwilioError {\n constructor() {\n super(53501, 'Unable to acquire TURN credentials');\n Object.setPrototypeOf(this, ConfigurationAcquireTurnFailedError.prototype);\n }\n}\n\nexports.ConfigurationAcquireTurnFailedError = ConfigurationAcquireTurnFailedError;\nObject.defineProperty(TwilioErrorByCode, 53501, { value: ConfigurationAcquireTurnFailedError });\n", "/* eslint-disable camelcase */\nconst TwilioConnection = require('../twilioconnection.js');\nconst { ICE_VERSION } = require('../util/constants');\nconst { createTwilioError, SignalingConnectionError } = require('../util/twilio-video-errors');\n\nimport { RTCIceServer, RTCStats } from './rtctypes';\nimport { EventEmitter } from 'events';\n\n\nexport { RTCStats, RTCIceServer };\nexport function getTurnCredentials(token: string, wsServer: string): Promise {\n return new Promise((resolve, reject) => {\n const eventObserver = new EventEmitter();\n const connectionOptions = {\n networkMonitor: null,\n eventObserver,\n helloBody: {\n edge: 'roaming', // roaming here means use same edge as signaling.\n preflight: true,\n token: token,\n type: 'ice',\n version: ICE_VERSION\n },\n };\n\n const twilioConnection = new TwilioConnection(wsServer, connectionOptions);\n let done = false;\n twilioConnection.once('close', () => {\n if (!done) {\n done = true;\n reject(new SignalingConnectionError());\n }\n });\n\n twilioConnection.on('message', (messageData: {\n code: number;\n message: string;\n ice_servers: RTCIceServer[];\n type: string;\n }) => {\n const { code, message, ice_servers, type } = messageData;\n if ((type === 'iced' || type === 'error') && !done) {\n done = true;\n if (type === 'iced') {\n resolve(ice_servers);\n } else {\n reject(createTwilioError(code, message));\n }\n twilioConnection.close();\n }\n });\n });\n}\n\n", "\nimport type { Stats } from '../../tsdef/PreflightTypes';\n\n/**\n * Computes min, max, average for given array.\n * @param {Array} values\n * @returns {{min: number, max: number: average: number}|null}\n */\nexport function makeStat(values?: number[]|null) : Stats|null {\n if (values && values.length) {\n const min = Math.min(...values);\n const max = Math.max(...values);\n const average = values.reduce((total, value) => total + value, 0) / values.length;\n return { min, max, average };\n }\n return null;\n}\n", "interface MediaStreamAudioDestinationNode extends AudioNode {\n stream: MediaStream;\n}\n\nexport function syntheticAudio(): MediaStreamTrack {\n // NOTE(mpatwardhan): We have to delay require-ing AudioContextFactory, because\n // it exports a default instance whose constructor calls Object.assign.\n const audioContextFactory = require('../webaudio/audiocontext');\n const holder = {};\n const audioContext = audioContextFactory.getOrCreate(holder);\n const oscillator = audioContext.createOscillator();\n const dst = oscillator.connect(audioContext.createMediaStreamDestination()) as MediaStreamAudioDestinationNode;\n oscillator.start();\n const track = dst.stream.getAudioTracks()[0];\n const originalStop = track.stop;\n track.stop = () => {\n originalStop.call(track);\n audioContextFactory.release(holder);\n };\n return track;\n}\n", "interface CanvasElement extends HTMLCanvasElement {\n captureStream(frameRate?: number): MediaStream;\n}\n\nexport function syntheticVideo({ width = 640, height = 480 } = {}): MediaStreamTrack {\n const canvas = Object.assign(\n document.createElement('canvas'), { width, height }\n ) as CanvasElement;\n\n let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;\n ctx.fillStyle = 'green';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n let stopped = false;\n requestAnimationFrame(function animate() {\n if (!stopped) {\n // draw random rect/circle.\n const r = Math.round(Math.random() * 255);\n const g = Math.round(Math.random() * 255);\n const b = Math.round(Math.random() * 255);\n const a = Math.round(Math.random() * 255);\n ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;\n ctx.fillRect(Math.random() * width, Math.random() * height, 50, 50);\n requestAnimationFrame(animate);\n }\n });\n const stream = canvas.captureStream(30);\n const track = stream.getTracks()[0];\n const originalStop = track.stop;\n track.stop = () => {\n stopped = true;\n originalStop.call(track);\n };\n\n return track;\n}\n\n", "'use strict';\n\n/**\n * Calculates the moving average delta for the given pair ofsamples. A sample (S)\n * consists of a numerator (Sn) and a denominator (Sd).The moving average delta is\n * calculated as follows:\n *\n * MovingAvgDelta = (Sn[1] - Sn[0]) / (Sd[1] - Sd[0])\n */\nclass MovingAverageDelta {\n /**\n * Constructor.\n */\n constructor() {\n Object.defineProperties(this, {\n _samples: {\n value: [\n { denominator: 0, numerator: 0 },\n { denominator: 0, numerator: 0 }\n ],\n }\n });\n }\n\n /**\n * Get the moving average delta.\n * @returns {number}\n */\n get() {\n const { _samples: samples } = this;\n const denominatorDelta = (samples[1].denominator - samples[0].denominator) || Infinity;\n const numeratorDelta = samples[1].numerator - samples[0].numerator;\n return numeratorDelta / denominatorDelta;\n }\n\n /**\n * Put a sample and get rid of the older sample to maintain sample size of 2.\n * @param numerator\n * @param denominator\n */\n putSample(numerator, denominator) {\n const { _samples: samples } = this;\n samples.shift();\n samples.push({ denominator, numerator });\n }\n}\n\nmodule.exports = MovingAverageDelta;\n", "/* eslint-disable no-console */\n'use strict';\n\nconst { EventEmitter } = require('events');\n\nconst VALID_GROUPS = [\n 'signaling',\n 'room',\n 'media',\n 'quality',\n 'video-processor',\n 'preflight'\n];\n\nconst VALID_LEVELS = [\n 'debug',\n 'error',\n 'info',\n 'warning'\n];\n\n/**\n * EventObserver listens to SDK events and re-emits them on the\n * @link EventListener} with some additional information.\n * @extends EventEmitter\n * @emits EventObserver#event\n */\nclass EventObserver extends EventEmitter {\n /**\n * Constructor.\n * @param {InsightsPublisher} publisher\n * @param {number} connectTimestamp\n * @param {Log} log\n * @param {EventListener} [eventListener]\n */\n constructor(publisher, connectTimestamp, log, eventListener = null) {\n super();\n this.on('event', ({ name, group, level, payload }) => {\n if (typeof name !== 'string') {\n log.error('Unexpected name: ', name);\n throw new Error('Unexpected name: ', name);\n }\n\n if (!VALID_GROUPS.includes(group)) {\n log.error('Unexpected group: ', group);\n throw new Error('Unexpected group: ', group);\n }\n\n if (!VALID_LEVELS.includes(level)) {\n log.error('Unexpected level: ', level);\n throw new Error('Unexpected level: ', level);\n }\n\n const timestamp = Date.now();\n const elapsedTime = timestamp - connectTimestamp;\n\n const publisherPayload = Object.assign({ elapsedTime, level }, payload ? payload : {});\n publisher.publish(group, name, publisherPayload);\n\n const event = Object.assign({\n elapsedTime,\n group,\n level,\n name,\n timestamp\n }, payload ? { payload } : {});\n\n const logLevel = {\n debug: 'debug',\n error: 'error',\n info: 'info',\n warning: 'warn',\n }[level];\n log[logLevel]('event', event);\n\n if (eventListener && group === 'signaling') {\n eventListener.emit('event', event);\n }\n });\n }\n}\n\n/**\n * An SDK event.\n * @event EventObserver#event\n * @param {{name: string, payload: *}} event\n */\n\nmodule.exports = EventObserver;\n", "/* eslint-disable camelcase */\n'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\nconst { getUserAgent } = require('..');\n\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst RECONNECT_INTERVAL_MS = 50;\nconst WS_CLOSE_NORMAL = 1000;\n\nconst toplevel = globalThis;\nconst WebSocket = toplevel.WebSocket ? toplevel.WebSocket : require('ws');\nconst { hardwareDevicePublisheriPad, hardwareDevicePublisheriPhone } = require('../constants');\nconst util = require('../../util');\nconst browserdetection = require('../browserdetection');\n\n/**\n * Publish events to the Insights gateway.\n * @extends EventEmitter\n * @emits InsightsPublisher#connected\n * @emits InsightsPublisher#disconnected\n * @emits InsightsPublisher#reconnecting\n */\nclass InsightsPublisher extends EventEmitter {\n /**\n * @param {string} token - Insights gateway token\n * @param {string} sdkName - Name of the SDK using the {@link InsightsPublisher}\n * @param {string} sdkVersion - Version of the SDK using the {@link InsightsPublisher}\n * @param {string} environment - One of 'dev', 'stage' or 'prod'\n * @param {string} realm - Region identifier\n * @param {InsightsPublisherOptions} options - Override default behavior\n */\n constructor(token, sdkName, sdkVersion, environment, realm, options) {\n super();\n\n options = Object.assign({\n gateway: `${createGateway(environment, realm)}/v1/VideoEvents`,\n maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS,\n reconnectIntervalMs: RECONNECT_INTERVAL_MS,\n userAgent: getUserAgent(),\n WebSocket,\n }, options);\n\n Object.defineProperties(this, {\n _connectTimestamp: {\n value: 0,\n writable: true\n },\n _eventQueue: {\n value: []\n },\n _readyToConnect: {\n value: util.defer()\n },\n _reconnectAttemptsLeft: {\n value: options.maxReconnectAttempts,\n writable: true\n },\n _ws: {\n value: null,\n writable: true\n },\n _WebSocket: {\n value: options.WebSocket\n }\n });\n\n this._readyToConnect.promise.then(({ roomSid, participantSid }) => {\n const self = this;\n this.on('disconnected', function maybeReconnect(error) {\n self._session = null;\n if (error && self._reconnectAttemptsLeft > 0) {\n self.emit('reconnecting');\n reconnect(self, token, sdkName, sdkVersion, roomSid, participantSid, options);\n return;\n }\n self.removeListener('disconnected', maybeReconnect);\n });\n connect(this, token, sdkName, sdkVersion, roomSid, participantSid, options);\n }).catch(() => {\n // ignore failures to connect\n });\n }\n\n /**\n * Start connecting to the Insights gateway.\n * @param {string} roomSid\n * @param {string} participantSid\n * @returns {void}\n */\n connect(roomSid, participantSid) {\n this._readyToConnect.resolve({ roomSid, participantSid });\n }\n\n /**\n * Publish an event to the Insights gateway.\n * @private\n * @param {*} event\n */\n _publish(event) {\n event.session = this._session;\n this._ws.send(JSON.stringify(event));\n }\n\n /**\n * Disconnect from the Insights gateway.\n * @returns {boolean} true if called when connecting/open, false if not\n */\n disconnect() {\n if (this._ws === null\n || this._ws.readyState === this._WebSocket.CLOSING\n || this._ws.readyState === this._WebSocket.CLOSED) {\n return false;\n }\n\n try {\n this._ws.close();\n } catch (error) {\n // Do nothing.\n }\n this.emit('disconnected');\n\n return true;\n }\n\n /**\n * Publish (or queue, if not connected) an event to the Insights gateway.\n * @param {string} groupName - Event group name\n * @param {string} eventName - Event name\n * @param {object} payload - Event payload\n * @returns {boolean} true if queued or published, false if disconnect() called\n */\n publish(groupName, eventName, payload) {\n if (this._ws !== null\n && (this._ws.readyState === this._WebSocket.CLOSING\n || this._ws.readyState === this._WebSocket.CLOSED)) {\n return false;\n }\n\n const publishOrEnqueue = typeof this._session === 'string'\n ? this._publish.bind(this)\n : this._eventQueue.push.bind(this._eventQueue);\n\n publishOrEnqueue({\n group: groupName,\n name: eventName,\n payload,\n timestamp: Date.now(),\n type: 'event',\n version: 1\n });\n\n return true;\n }\n}\n\n/**\n * Start connecting to the Insights gateway.\n * @private\n * @param {InsightsPublisher} publisher\n * @param {string} name\n * @param {string} token\n * @param {string} sdkName\n * @param {string} sdkVersion\n * @param {string} roomSid\n * @param {string} participantSid\n * @param {InsightsPublisherOptions} options\n */\nfunction connect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options) {\n publisher._connectTimestamp = Date.now();\n publisher._reconnectAttemptsLeft--;\n publisher._ws = new options.WebSocket(options.gateway);\n const ws = publisher._ws;\n\n ws.addEventListener('close', event => {\n if (event.code === WS_CLOSE_NORMAL) {\n publisher.emit('disconnected');\n return;\n }\n publisher.emit('disconnected', new Error(`WebSocket Error ${event.code}: ${event.reason}`));\n });\n\n ws.addEventListener('message', message => {\n handleConnectResponse(publisher, JSON.parse(message.data), options);\n });\n\n ws.addEventListener('open', () => {\n const connectRequest = {\n type: 'connect',\n token,\n version: 1\n };\n\n connectRequest.publisher = {\n name: sdkName,\n sdkVersion,\n userAgent: options.userAgent,\n participantSid: participantSid,\n roomSid: roomSid,\n };\n\n if (browserdetection.isIpad()) {\n connectRequest.publisher = { ...connectRequest.publisher, ...hardwareDevicePublisheriPad };\n } else if (browserdetection.isIphone()) {\n connectRequest.publisher = { ...connectRequest.publisher, ...hardwareDevicePublisheriPhone };\n }\n\n ws.send(JSON.stringify(connectRequest));\n });\n}\n\n/**\n * Create the Insights Websocket gateway URL.\n * @param {string} environment\n * @param {string} realm\n * @returns {string}\n */\nfunction createGateway(environment, realm) {\n return environment === 'prod' ? `wss://sdkgw.${realm}.twilio.com`\n : `wss://sdkgw.${environment}-${realm}.twilio.com`;\n}\n\n/**\n * Handle connect response from the Insights gateway.\n * @param {InsightsPublisher} publisher\n * @param {*} response\n * @param {InsightsPublisherOptions} options\n */\nfunction handleConnectResponse(publisher, response, options) {\n switch (response.type) {\n case 'connected':\n publisher._session = response.session;\n publisher._reconnectAttemptsLeft = options.maxReconnectAttempts;\n publisher._eventQueue.splice(0).forEach(publisher._publish, publisher);\n publisher.emit('connected');\n break;\n case 'error':\n publisher._ws.close();\n publisher.emit('disconnected', new Error(response.message));\n break;\n }\n}\n\n/**\n * Start re-connecting to the Insights gateway with an appropriate delay based\n * on InsightsPublisherOptions#reconnectIntervalMs.\n * @private\n * @param {InsightsPublisher} publisher\n * @param {string} token\n * @param {string} sdkName\n * @param {string} sdkVersion\n * @param {string} roomSid\n * @param {string} participantSid\n * @param {InsightsPublisherOptions} options\n */\nfunction reconnect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options) {\n const connectInterval = Date.now() - publisher._connectTimestamp;\n const timeToWait = options.reconnectIntervalMs - connectInterval;\n\n if (timeToWait > 0) {\n setTimeout(() => {\n connect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options);\n }, timeToWait);\n return;\n }\n\n connect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options);\n}\n\n/**\n * The {@link InsightsPublisher} is connected to the gateway.\n * @event InsightsPublisher#connected\n */\n\n/**\n * The {@link InsightsPublisher} is disconnected from the gateway.\n * @event InsightsPublisher#disconnected\n * @param {Error} [error] - Optional error if disconnected unintentionally\n */\n\n/**\n * The {@link InsightsPublisher} is re-connecting to the gateway.\n * @event InsightsPublisher#reconnecting\n */\n\n/**\n * {@link InsightsPublisher} options.\n * @typedef {object} InsightsPublisherOptions\n * @property {string} [gateway=sdkgw.{environment}-{realm}.twilio.com] - Insights WebSocket gateway url\n * @property {number} [maxReconnectAttempts=5] - Max re-connect attempts\n * @property {number} [reconnectIntervalMs=50] - Re-connect interval in ms\n */\n\nmodule.exports = InsightsPublisher;\n", "import { DEFAULT_ENVIRONMENT, DEFAULT_LOGGER_NAME, DEFAULT_LOG_LEVEL, DEFAULT_REALM, SDK_NAME, SDK_VERSION } from '../util/constants';\nimport { PreflightOptions, PreflightTestReport, ProgressEvent, RTCIceCandidateStats, SelectedIceCandidatePairStats, Stats } from '../../tsdef/PreflightTypes';\nimport { StatsReport } from '../../tsdef/types';\nimport { Timer } from './timer';\nimport { TwilioError } from '../../tsdef/TwilioError';\nimport { calculateMOS } from './mos';\nimport { getCombinedConnectionStats } from './getCombinedConnectionStats';\nimport { getTurnCredentials } from './getturncredentials';\nimport { makeStat } from './makestat';\nimport { syntheticAudio } from './syntheticaudio';\nimport { syntheticVideo } from './syntheticvideo';\nimport { waitForSometime } from '../util/index';\n\nconst { WS_SERVER } = require('../util/constants');\nconst Log = require('../util/log');\nconst EventEmitter = require('../eventemitter');\nconst MovingAverageDelta = require('../util/movingaveragedelta');\nconst EventObserver = require('../util/eventobserver');\nconst InsightsPublisher = require('../util/insightspublisher');\nconst { createSID, sessionSID } = require('../util/sid');\nconst {\n SignalingConnectionTimeoutError,\n MediaConnectionError\n} = require('../util/twilio-video-errors');\n\nconst SECOND = 1000;\nconst DEFAULT_TEST_DURATION = 10 * SECOND;\n\n/**\n * progress values that are sent by {@link PreflightTest#event:progress}\n * @enum {string}\n */\nconst PreflightProgress = {\n /**\n * {@link PreflightTest} has successfully generated synthetic tracks\n */\n mediaAcquired: 'mediaAcquired',\n\n /**\n * {@link PreflightTest} has successfully connected to twilio server and obtained turn credentials\n */\n connected: 'connected',\n\n /**\n * SubscriberParticipant successfully subscribed to media tracks.\n */\n mediaSubscribed: 'mediaSubscribed',\n\n /**\n * Media flow was detected.\n */\n mediaStarted: 'mediaStarted',\n\n /**\n * Established DTLS connection. This is measured from RTCDtlsTransport `connecting` to `connected` state.\n * On Safari, Support for measuring this is missing, this event will be not be emitted on Safari.\n */\n dtlsConnected: 'dtlsConnected',\n\n /**\n * Established a PeerConnection, This is measured from PeerConnection `connecting` to `connected` state.\n * On Firefox, Support for measuring this is missing, this event will be not be emitted on Firefox.\n */\n peerConnectionConnected: 'peerConnectionConnected',\n\n /**\n * Established ICE connection. This is measured from ICE connection `checking` to `connected` state.\n */\n iceConnected: 'iceConnected'\n};\n\ndeclare interface PreflightStats {\n jitter: number[],\n rtt: number[],\n outgoingBitrate: number[],\n incomingBitrate: number[],\n packetLoss: number[], // fraction of packets lost.\n mos: number[],\n selectedIceCandidatePairStats: SelectedIceCandidatePairStats | null,\n iceCandidateStats: RTCIceCandidateStats[],\n}\n\ndeclare interface PreflightTestReportInternal extends PreflightTestReport {\n error?: string,\n mos?: Stats|null\n}\n\ndeclare interface PreflightOptionsInternal extends PreflightOptions {\n environment?: string;\n wsServer?: string;\n}\n\nfunction notEmpty(value: TValue | null | undefined): value is TValue {\n return value !== null && typeof value !== 'undefined';\n}\n\nlet nInstances = 0;\n\n/**\n * A {@link PreflightTest} monitors progress of an ongoing preflight test.\n *

\n * Instance of {@link PreflightTest} is returned by calling {@link module:twilio-video.runPreflight}\n * @extends EventEmitter\n * @emits PreflightTest#completed\n * @emits PreflightTest#failed\n * @emits PreflightTest#progress\n */\nexport class PreflightTest extends EventEmitter {\n\n private _testTiming = new Timer();\n private _dtlsTiming = new Timer();\n private _iceTiming = new Timer();\n private _peerConnectionTiming = new Timer();\n private _mediaTiming = new Timer();\n private _connectTiming = new Timer();\n private _sentBytesMovingAverage = new MovingAverageDelta();\n private _packetLossMovingAverage = new MovingAverageDelta();\n private _progressEvents: ProgressEvent[] = [];\n private _receivedBytesMovingAverage = new MovingAverageDelta();\n private _log: typeof Log;\n private _testDuration: number;\n private _instanceId: number;\n\n /**\n * Constructs {@link PreflightTest}.\n * @param {string} token\n * @param {?PreflightOptions} [options]\n */\n constructor(token: string, options: PreflightOptions) {\n super();\n const internalOptions = options as PreflightOptionsInternal;\n const { environment = 'prod', region = 'gll', duration = DEFAULT_TEST_DURATION } = internalOptions;\n // eslint-disable-next-line new-cap\n const wsServer = internalOptions.wsServer || WS_SERVER(environment, region);\n\n this._log = new Log('default', this, DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME);\n this._testDuration = duration;\n this._instanceId = nInstances++;\n this._testTiming.start();\n this._runPreflightTest(token, environment, wsServer);\n }\n\n toString(): string {\n return `[Preflight #${this._instanceId}]`;\n }\n\n /**\n * stops ongoing tests and emits error\n */\n stop():void {\n this._stopped = true;\n }\n\n private _generatePreflightReport(collectedStats?: PreflightStats) : PreflightTestReportInternal {\n this._testTiming.stop();\n return {\n testTiming: this._testTiming.getTimeMeasurement(),\n networkTiming: {\n dtls: this._dtlsTiming.getTimeMeasurement(),\n ice: this._iceTiming.getTimeMeasurement(),\n peerConnection: this._peerConnectionTiming.getTimeMeasurement(),\n connect: this._connectTiming.getTimeMeasurement(),\n media: this._mediaTiming.getTimeMeasurement()\n },\n stats: {\n jitter: makeStat(collectedStats?.jitter),\n rtt: makeStat(collectedStats?.rtt),\n packetLoss: makeStat(collectedStats?.packetLoss),\n },\n selectedIceCandidatePairStats: collectedStats ? collectedStats.selectedIceCandidatePairStats : null,\n iceCandidateStats: collectedStats ? collectedStats.iceCandidateStats : [],\n progressEvents: this._progressEvents,\n // NOTE(mpatwardhan): internal properties.\n mos: makeStat(collectedStats?.mos),\n };\n }\n\n private async _executePreflightStep(stepName: string, step: () => T|Promise, timeoutError?: TwilioError|Error) : Promise {\n this._log.debug('Executing step: ', stepName);\n const MAX_STEP_DURATION = this._testDuration + 10 * SECOND;\n if (this._stopped) {\n throw new Error('stopped');\n }\n\n const stepPromise = Promise.resolve().then(step);\n let timer: number | null = null;\n const timeoutPromise = new Promise((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(timeoutError || new Error(`${stepName} timeout.`));\n }, MAX_STEP_DURATION) as unknown as number;\n });\n try {\n const result = await Promise.race([timeoutPromise, stepPromise]);\n return result as T;\n } finally {\n if (timer !== null) {\n clearTimeout(timer);\n }\n }\n }\n\n private _collectNetworkTimings(pc: RTCPeerConnection): Promise {\n return new Promise(resolve => {\n let dtlsTransport: RTCDtlsTransport;\n\n pc.addEventListener('iceconnectionstatechange', () => {\n if (pc.iceConnectionState === 'checking') {\n this._iceTiming.start();\n }\n if (pc.iceConnectionState === 'connected') {\n this._iceTiming.stop();\n this._updateProgress(PreflightProgress.iceConnected);\n if (!dtlsTransport || dtlsTransport && dtlsTransport.state === 'connected') {\n resolve();\n }\n }\n });\n\n // firefox does not support connectionstatechange.\n pc.addEventListener('connectionstatechange', () => {\n if (pc.connectionState === 'connecting') {\n this._peerConnectionTiming.start();\n }\n if (pc.connectionState === 'connected') {\n this._peerConnectionTiming.stop();\n this._updateProgress(PreflightProgress.peerConnectionConnected);\n }\n });\n\n // Safari does not expose sender.transport.\n let senders = pc.getSenders();\n let transport = senders.map(sender => sender.transport).find(notEmpty);\n if (typeof transport !== 'undefined') {\n dtlsTransport = transport as RTCDtlsTransport;\n dtlsTransport.addEventListener('statechange', () => {\n if (dtlsTransport.state === 'connecting') {\n this._dtlsTiming.start();\n }\n if (dtlsTransport.state === 'connected') {\n this._dtlsTiming.stop();\n this._updateProgress(PreflightProgress.dtlsConnected);\n if (pc.iceConnectionState === 'connected') {\n resolve();\n }\n }\n });\n }\n });\n }\n\n private _setupInsights({ token, environment = DEFAULT_ENVIRONMENT, realm = DEFAULT_REALM } : {\n token: string,\n environment?: string,\n realm?: string\n }) {\n const eventPublisherOptions = {};\n const eventPublisher = new InsightsPublisher(\n token,\n SDK_NAME,\n SDK_VERSION,\n environment,\n realm,\n eventPublisherOptions);\n\n // event publisher requires room sid/participant sid. supply fake ones.\n eventPublisher.connect('PREFLIGHT_ROOM_SID', 'PREFLIGHT_PARTICIPANT');\n const eventObserver = new EventObserver(eventPublisher, Date.now(), this._log);\n\n // eslint-disable-next-line no-undefined\n const undefinedValue = undefined;\n return {\n reportToInsights: ({ report }: { report: PreflightTestReportInternal }) => {\n const jitterStats = report.stats.jitter || undefinedValue;\n const rttStats = report.stats.rtt || undefinedValue;\n const packetLossStats = report.stats.packetLoss || undefinedValue;\n const mosStats = report.mos || undefinedValue;\n\n // stringify important info from ice candidates.\n const candidateTypeToProtocols = new Map();\n report.iceCandidateStats.forEach(candidateStats => {\n if (candidateStats.candidateType && candidateStats.protocol) {\n let protocols = candidateTypeToProtocols.get(candidateStats.candidateType) || [];\n if (protocols.indexOf(candidateStats.protocol) < 0) {\n protocols.push(candidateStats.protocol);\n }\n candidateTypeToProtocols.set(candidateStats.candidateType, protocols);\n }\n });\n const iceCandidateStats = JSON.stringify(Object.fromEntries(candidateTypeToProtocols));\n\n const insightsReport = {\n name: 'report',\n group: 'preflight',\n level: report.error ? 'error' : 'info',\n payload: {\n sessionSID,\n preflightSID: createSID('PF'),\n progressEvents: JSON.stringify(report.progressEvents),\n testTiming: report.testTiming,\n dtlsTiming: report.networkTiming.dtls,\n iceTiming: report.networkTiming.ice,\n peerConnectionTiming: report.networkTiming.peerConnection,\n connectTiming: report.networkTiming.connect,\n mediaTiming: report.networkTiming.media,\n selectedLocalCandidate: report.selectedIceCandidatePairStats?.localCandidate,\n selectedRemoteCandidate: report.selectedIceCandidatePairStats?.remoteCandidate,\n iceCandidateStats,\n jitterStats,\n rttStats,\n packetLossStats,\n mosStats,\n error: report.error\n }\n };\n eventObserver.emit('event', insightsReport);\n setTimeout(() => eventPublisher.disconnect(), 2000);\n }\n };\n }\n\n private async _runPreflightTest(token: string, environment: string, wsServer: string) {\n let localTracks: MediaStreamTrack[] = [];\n let pcs: RTCPeerConnection[] = [];\n const { reportToInsights } = this._setupInsights({ token, environment });\n try {\n let elements = [];\n localTracks = await this._executePreflightStep('Acquire media', () => [syntheticAudio(), syntheticVideo({ width: 640, height: 480 })]);\n\n this._updateProgress(PreflightProgress.mediaAcquired);\n this.emit('debug', { localTracks });\n\n this._connectTiming.start();\n let iceServers = await this._executePreflightStep('Get turn credentials', () => getTurnCredentials(token, wsServer), new SignalingConnectionTimeoutError());\n\n this._connectTiming.stop();\n this._updateProgress(PreflightProgress.connected);\n\n const senderPC: RTCPeerConnection = new RTCPeerConnection({ iceServers, iceTransportPolicy: 'relay', bundlePolicy: 'max-bundle' });\n const receiverPC: RTCPeerConnection = new RTCPeerConnection({ iceServers, bundlePolicy: 'max-bundle' });\n pcs.push(senderPC);\n pcs.push(receiverPC);\n\n this._mediaTiming.start();\n const remoteTracks = await this._executePreflightStep('Setup Peer Connections', async () => {\n senderPC.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => event.candidate && receiverPC.addIceCandidate(event.candidate));\n receiverPC.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => event.candidate && senderPC.addIceCandidate(event.candidate));\n\n localTracks.forEach(track => senderPC.addTrack(track));\n\n const remoteTracksPromise: Promise = new Promise(resolve => {\n let remoteTracks: MediaStreamTrack[] = [];\n receiverPC.addEventListener('track', event => {\n remoteTracks.push(event.track);\n if (remoteTracks.length === localTracks.length) {\n resolve(remoteTracks);\n }\n });\n });\n\n const offer = await senderPC.createOffer();\n const updatedOffer = offer;\n await senderPC.setLocalDescription(updatedOffer);\n await receiverPC.setRemoteDescription(updatedOffer);\n\n const answer = await receiverPC.createAnswer();\n await receiverPC.setLocalDescription(answer);\n await senderPC.setRemoteDescription(answer);\n await this._collectNetworkTimings(senderPC);\n\n return remoteTracksPromise;\n }, new MediaConnectionError());\n this.emit('debug', { remoteTracks });\n remoteTracks.forEach(track => {\n track.addEventListener('ended', () => this._log.warn(track.kind + ':ended'));\n track.addEventListener('mute', () => this._log.warn(track.kind + ':muted'));\n track.addEventListener('unmute', () => this._log.warn(track.kind + ':unmuted'));\n });\n this._updateProgress(PreflightProgress.mediaSubscribed);\n\n await this._executePreflightStep('Wait for tracks to start', () => {\n return new Promise(resolve => {\n const element = document.createElement('video');\n element.autoplay = true;\n element.playsInline = true;\n element.muted = true;\n element.srcObject = new MediaStream(remoteTracks);\n elements.push(element);\n this.emit('debugElement', element);\n element.oncanplay = resolve;\n });\n }, new MediaConnectionError());\n this._mediaTiming.stop();\n this._updateProgress(PreflightProgress.mediaStarted);\n\n const collectedStats = await this._executePreflightStep('Collect stats for duration',\n () => this._collectRTCStatsForDuration(this._testDuration, initCollectedStats(), senderPC, receiverPC));\n\n const report = await this._executePreflightStep('Generate report', () => this._generatePreflightReport(collectedStats));\n reportToInsights({ report });\n this.emit('completed', report);\n\n } catch (error) {\n const preflightReport = this._generatePreflightReport();\n reportToInsights({ report: { ...preflightReport, error: error?.toString() } });\n this.emit('failed', error, preflightReport);\n } finally {\n pcs.forEach(pc => pc.close());\n localTracks.forEach(track => track.stop());\n }\n }\n\n private async _collectRTCStats(collectedStats: PreflightStats, senderPC: RTCPeerConnection, receiverPC: RTCPeerConnection) {\n const combinedStats = await getCombinedConnectionStats({ publisher: senderPC, subscriber: receiverPC });\n const { timestamp, bytesSent, bytesReceived, packets, packetsLost, roundTripTime, jitter, selectedIceCandidatePairStats, iceCandidateStats } = combinedStats;\n const hasLastData = collectedStats.jitter.length > 0;\n collectedStats.jitter.push(jitter);\n collectedStats.rtt.push(roundTripTime);\n\n this._sentBytesMovingAverage.putSample(bytesSent, timestamp);\n this._receivedBytesMovingAverage.putSample(bytesReceived, timestamp);\n this._packetLossMovingAverage.putSample(packetsLost, packets);\n if (hasLastData) {\n // convert BytesMovingAverage which is in bytes/millisecond to bits/second\n collectedStats.outgoingBitrate.push(this._sentBytesMovingAverage.get() * 1000 * 8);\n collectedStats.incomingBitrate.push(this._receivedBytesMovingAverage.get() * 1000 * 8);\n const fractionPacketLost = this._packetLossMovingAverage.get();\n const percentPacketsLost = Math.min(100, fractionPacketLost * 100);\n\n collectedStats.packetLoss.push(percentPacketsLost);\n\n const score = calculateMOS(roundTripTime, jitter, fractionPacketLost);\n collectedStats.mos.push(score);\n }\n\n if (!collectedStats.selectedIceCandidatePairStats) {\n collectedStats.selectedIceCandidatePairStats = selectedIceCandidatePairStats;\n }\n\n if (collectedStats.iceCandidateStats.length === 0) {\n collectedStats.iceCandidateStats = iceCandidateStats;\n }\n }\n\n private async _collectRTCStatsForDuration(duration: number, collectedStats: PreflightStats, senderPC: RTCPeerConnection, receiverPC: RTCPeerConnection) : Promise {\n const startTime = Date.now();\n const STAT_INTERVAL = Math.min(1000, duration);\n\n await waitForSometime(STAT_INTERVAL);\n\n await this._collectRTCStats(collectedStats, senderPC, receiverPC);\n\n const remainingDuration = duration - (Date.now() - startTime);\n\n if (remainingDuration > 0) {\n collectedStats = await this._collectRTCStatsForDuration(remainingDuration, collectedStats, senderPC, receiverPC);\n }\n return collectedStats;\n }\n\n private _updateProgress(name: string): void {\n const duration = Date.now() - this._testTiming.getTimeMeasurement().start;\n this._progressEvents.push({ duration, name });\n this.emit('progress', name);\n }\n}\n\n\nexport interface InternalStatsReport extends StatsReport {\n activeIceCandidatePair: {\n timestamp: number;\n bytesSent: number;\n bytesReceived: number;\n currentRoundTripTime?: number;\n localCandidate: RTCIceCandidateStats;\n remoteCandidate: RTCIceCandidateStats;\n }\n}\n\nfunction initCollectedStats() : PreflightStats {\n return {\n mos: [],\n jitter: [],\n rtt: [],\n outgoingBitrate: [],\n incomingBitrate: [],\n packetLoss: [],\n selectedIceCandidatePairStats: null,\n iceCandidateStats: [],\n };\n}\n\n/**\n * Represents network timing measurements captured during preflight test\n * @typedef {object} NetworkTiming\n * @property {TimeMeasurement} [connect] - Time to establish signaling connection and acquire turn credentials\n * @property {TimeMeasurement} [media] - Time to start media. This is measured from calling connect to remote media getting started.\n * @property {TimeMeasurement} [dtls] - Time to establish dtls connection. This is measured from RTCDtlsTransport `connecting` to `connected` state. (Not available on Safari)\n * @property {TimeMeasurement} [ice] - Time to establish ice connectivity. This is measured from ICE connection `checking` to `connected` state.\n * @property {TimeMeasurement} [peerConnection] - Time to establish peer connectivity. This is measured from PeerConnection `connecting` to `connected` state. (Not available on Firefox)\n */\n\n/**\n * Represents stats for a numerical metric.\n * @typedef {object} Stats\n * @property {number} [average] - Average value observed.\n * @property {number} [max] - Max value observed.\n * @property {number} [min] - Min value observed.\n */\n\n/**\n * Represents stats for a numerical metric.\n * @typedef {object} SelectedIceCandidatePairStats\n * @property {RTCIceCandidateStats} [localCandidate] - Selected local ice candidate\n * @property {RTCIceCandidateStats} [remoteCandidate] - Selected local ice candidate\n */\n\n/**\n * Represents RTC related stats that were observed during preflight test\n * @typedef {object} PreflightReportStats\n * @property {Stats} [jitter] - Packet delay variation in seconds\n * @property {Stats} [rtt] - Round trip time, to the server back to the client in milliseconds.\n * @property {Stats} [packetLoss] - Packet loss as a percent of total packets sent.\n*/\n\n/**\n * A {@link PreflightProgress} event with timing information.\n * @typedef {object} ProgressEvent\n * @property {number} [duration] - The duration of the event, measured from the start of the test.\n * @property {string} [name] - The {@link PreflightProgress} event name.\n */\n\n/**\n * Represents report generated by {@link PreflightTest}.\n * @typedef {object} PreflightTestReport\n * @property {TimeMeasurement} [testTiming] - Time measurements of test run time.\n * @property {NetworkTiming} [networkTiming] - Network related time measurements.\n * @property {PreflightReportStats} [stats] - RTC related stats captured during the test.\n * @property {Array} [iceCandidateStats] - List of gathered ice candidates.\n * @property {SelectedIceCandidatePairStats} selectedIceCandidatePairStats - Stats for the ice candidates that were used for the connection.\n * @property {Array} [progressEvents] - {@link ProgressEvent} events detected during the test.\n * Use this information to determine which steps were completed and which ones were not.\n */\n\n/**\n * You may pass these options to {@link module:twilio-video.testPreflight} in order to override the\n * default behavior.\n * @typedef {object} PreflightOptions\n * @property {string} [region='gll'] - Preferred signaling region; By default, you will be connected to the\n * nearest signaling server determined by latency based routing. Setting a value other\n * than gll bypasses routing and guarantees that signaling traffic will be\n * terminated in the region that you prefer. Please refer to this table\n * for the list of supported signaling regions.\n * @property {number} [duration=10000] - number of milliseconds to run test for.\n * once connected test will run for this duration before generating the stats report.\n */\n\n/**\n * Preflight test has completed successfully.\n * @param {PreflightTestReport} report - Results of the test.\n * @event PreflightTest#completed\n */\n\n/**\n * Preflight test has encountered a failure and is now stopped.\n * @param {TwilioError|Error} error - A TwilioError or a DOMException.\n * Possible TwilioErrors include Signaling and Media related errors which can be found\n * here.\n * @param {PreflightTestReport} report - Partial results gathered during the test. Use this information to help determine the cause of failure.\n * @event PreflightTest#failed\n */\n\n/**\n * Emitted to indicate progress of the test\n * @param {PreflightProgress} progress - Indicates the status completed.\n * @event PreflightTest#progress\n */\n\n/**\n * @method\n * @name runPreflight\n * @description Run a preflight test. This method will start a test to check the quality of network connection.\n * @memberof module:twilio-video\n * @param {string} token - The Access Token string\n * @param {PreflightOptions} options - Options for the test\n * @returns {PreflightTest} preflightTest - An instance to be used to monitor progress of the test.\n * @example\n * var { runPreflight } = require('twilio-video');\n * var preflight = runPreflight(token, preflightOptions);\n * preflightTest.on('progress', progress => {\n * console.log('preflight progress:', progress);\n * });\n *\n * preflightTest.on('failed', (error, report) => {\n * console.error('preflight error:', error, report);\n * });\n *\n * preflightTest.on('completed', report => {\n * console.log('preflight completed:', report));\n * });\n*/\nexport function runPreflight(token: string, options: PreflightOptions = {}): PreflightTest {\n const preflight = new PreflightTest(token, options);\n return preflight;\n}\n\n", "'use strict';\n\n/**\n * A Promise that can be canceled with {@link CancelablePromise#cancel}.\n * @extends Promise\n*/\nclass CancelablePromise {\n /**\n * Construct a new {@link CancelablePromise}.\n * @param {CancelablePromise.OnCreate} onCreate\n * @param {CancelablePromise.OnCancel} onCancel\n *//**\n * A function to be called on {@link CancelablePromise} creation\n * @typedef {function} CancelablePromise.OnCreate\n * @param {function(*)} resolve\n * @param {function(*)} reject\n * @param {function(): boolean} isCanceled\n *//**\n * A function to be called when {@link CancelablePromise#cancel} is called\n * @typedef {function} CancelablePromise.OnCancel\n */\n constructor(onCreate, onCancel) {\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _isCancelable: {\n writable: true,\n value: true\n },\n _isCanceled: {\n writable: true,\n value: false\n },\n _onCancel: {\n value: onCancel\n }\n });\n\n Object.defineProperty(this, '_promise', {\n value: new Promise((resolve, reject) => {\n onCreate(value => {\n this._isCancelable = false;\n resolve(value);\n }, reason => {\n this._isCancelable = false;\n reject(reason);\n }, () => this._isCanceled);\n })\n });\n }\n\n /**\n * Create a synchronously-rejected {@link CancelablePromise}.\n * @param {*} reason\n * @returns {Promise<*>}\n */\n static reject(reason) {\n return new CancelablePromise(function rejected(resolve, reject) {\n reject(reason);\n }, function onCancel() {\n // Do nothing.\n });\n }\n\n /**\n * Create a synchronously-resolved {@link CancelablePromise}.\n * @param {*|Promise<*>|Thenable<*>} result\n * @returns {CancelablePromise<*>}\n */\n static resolve(result) {\n return new CancelablePromise(function resolved(resolve) {\n resolve(result);\n }, function onCancel() {\n // Do nothing.\n });\n }\n\n /**\n * Attempt to cancel the {@link CancelablePromise}.\n * @returns {this}\n */\n cancel() {\n if (this._isCancelable) {\n this._isCanceled = true;\n this._onCancel();\n }\n return this;\n }\n\n /**\n * @param {function} onRejected\n * @returns {CancelablePromise}\n */\n catch() {\n const args = [].slice.call(arguments);\n const promise = this._promise;\n return new CancelablePromise(function onCreate(resolve, reject) {\n promise.catch(...args).then(resolve, reject);\n }, this._onCancel);\n }\n\n /**\n * @param {?function} onResolved\n * @param {function} [onRejected]\n * @returns {CancelablePromise}\n */\n then() {\n const args = [].slice.call(arguments);\n const promise = this._promise;\n return new CancelablePromise(function onCreate(resolve, reject) {\n promise.then(...args).then(resolve, reject);\n }, this._onCancel);\n }\n\n /**\n * @param {?function} onFinally\n * @returns {CancelablePromise}\n */\n finally() {\n const args = [].slice.call(arguments);\n const promise = this._promise;\n return new CancelablePromise(function onCreate(resolve, reject) {\n promise.finally(...args).then(resolve, reject);\n }, this._onCancel);\n }\n}\n\nmodule.exports = CancelablePromise;\n", "'use strict';\n\nconst CancelablePromise = require('./util/cancelablepromise');\n\n/**\n * Create a {@link CancelablePromise}.\n * @param {function(function(Array): CancelablePromise):\n * Promise>} getLocalTracks\n * @param {function(Array): LocalParticipant} createLocalParticipant\n * @param {function(Array): CancelablePromise} createRoomSignaling\n * @param {function(LocalParticipant, RoomSignaling): Room} createRoom\n * @returns CancelablePromise\n */\nfunction createCancelableRoomPromise(getLocalTracks, createLocalParticipant, createRoomSignaling, createRoom) {\n let cancelableRoomSignalingPromise;\n const cancellationError = new Error('Canceled');\n\n return new CancelablePromise(function onCreate(resolve, reject, isCanceled) {\n let localParticipant;\n getLocalTracks(function getLocalTracksSucceeded(localTracks) {\n if (isCanceled()) {\n return CancelablePromise.reject(cancellationError);\n }\n localParticipant = createLocalParticipant(localTracks);\n return createRoomSignaling(localParticipant).then(function createRoomSignalingSucceeded(getCancelableRoomSignalingPromise) {\n if (isCanceled()) {\n throw cancellationError;\n }\n cancelableRoomSignalingPromise = getCancelableRoomSignalingPromise();\n return cancelableRoomSignalingPromise;\n });\n }).then(function roomSignalingConnected(roomSignaling) {\n if (isCanceled()) {\n roomSignaling.disconnect();\n throw cancellationError;\n }\n resolve(createRoom(localParticipant, roomSignaling));\n }).catch(function onError(error) {\n reject(error);\n });\n }, function onCancel() {\n if (cancelableRoomSignalingPromise) {\n cancelableRoomSignalingPromise.cancel();\n }\n });\n}\n\nmodule.exports = createCancelableRoomPromise;\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\n/**\n * {@link EncodingParametersImpl} represents an object which notifies its\n * listeners of any changes in the values of its properties.\n * @extends EventEmitter\n * @implements EncodingParameters\n * @emits EncodingParametersImpl#changed\n * @property {?number} maxAudioBitrate\n * @property {?number} maxVideoBitrate\n */\nclass EncodingParametersImpl extends EventEmitter {\n /**\n * Construct an {@link EncodingParametersImpl}.\n * @param {EncodingParamters} encodingParameters - Initial {@link EncodingParameters}\n * @param {Boolean} adaptiveSimulcast - true if adaptive simulcast was enabled by connect options.\n */\n constructor(encodingParameters, adaptiveSimulcast) {\n super();\n\n encodingParameters = Object.assign({\n maxAudioBitrate: null,\n maxVideoBitrate: null\n }, encodingParameters);\n\n Object.defineProperties(this, {\n maxAudioBitrate: {\n value: encodingParameters.maxAudioBitrate,\n writable: true\n },\n maxVideoBitrate: {\n value: encodingParameters.maxVideoBitrate,\n writable: true\n },\n adaptiveSimulcast: {\n value: adaptiveSimulcast\n }\n });\n }\n\n /**\n * Returns the bitrate values in an {@link EncodingParameters}.\n * @returns {EncodingParameters}\n */\n toJSON() {\n return {\n maxAudioBitrate: this.maxAudioBitrate,\n maxVideoBitrate: this.maxVideoBitrate\n };\n }\n\n /**\n * Update the bitrate values with those in the given {@link EncodingParameters}.\n * @param {EncodingParameters} encodingParameters - The new {@link EncodingParameters}\n * @fires EncodingParametersImpl#changed\n */\n update(encodingParameters) {\n encodingParameters = Object.assign({\n maxAudioBitrate: this.maxAudioBitrate,\n maxVideoBitrate: this.maxVideoBitrate\n }, encodingParameters);\n\n const shouldEmitChanged = [\n 'maxAudioBitrate',\n 'maxVideoBitrate'\n ].reduce((shouldEmitChanged, maxKindBitrate) => {\n if (this[maxKindBitrate] !== encodingParameters[maxKindBitrate]) {\n this[maxKindBitrate] = encodingParameters[maxKindBitrate];\n shouldEmitChanged = true;\n }\n return shouldEmitChanged;\n }, false);\n\n if (shouldEmitChanged) {\n this.emit('changed');\n }\n }\n}\n\n/**\n * At least one of the {@link EncodingParametersImpl}'s bitrate values changed.\n * @event EncodingParametersImpl#changed\n */\n\nmodule.exports = EncodingParametersImpl;\n", "'use strict';\n\nconst { isNonArrayObject } = require('./');\nconst { typeErrors: E, clientTrackSwitchOffControl, videoContentPreferencesMode, subscriptionMode, trackPriority, trackSwitchOffMode } = require('./constants');\n\n/**\n * Validate the {@link BandwidthProfileOptions} object.\n * @param {BandwidthProfileOptions} bandwidthProfile\n * @returns {?Error} - null if valid, Error if not.\n */\nfunction validateBandwidthProfile(bandwidthProfile) {\n let error = validateObject(bandwidthProfile, 'options.bandwidthProfile');\n if (!bandwidthProfile || error) {\n return error;\n }\n error = validateObject(bandwidthProfile.video, 'options.bandwidthProfile.video', [\n { prop: 'contentPreferencesMode', values: Object.values(videoContentPreferencesMode) },\n { prop: 'dominantSpeakerPriority', values: Object.values(trackPriority) },\n { prop: 'maxSubscriptionBitrate', type: 'number' },\n { prop: 'maxTracks', type: 'number' },\n { prop: 'mode', values: Object.values(subscriptionMode) },\n { prop: 'clientTrackSwitchOffControl', values: Object.values(clientTrackSwitchOffControl) },\n { prop: 'trackSwitchOffMode', values: Object.values(trackSwitchOffMode) }\n ]);\n\n if (error) {\n return error;\n }\n\n if (bandwidthProfile.video) {\n\n // maxTracks is replaced by clientTrackSwitchOffControl.\n // throw an error if both are specified.\n if ('maxTracks' in bandwidthProfile.video && 'clientTrackSwitchOffControl' in bandwidthProfile.video) {\n return new TypeError('options.bandwidthProfile.video.maxTracks is deprecated. Use options.bandwidthProfile.video.clientTrackSwitchOffControl instead.');\n }\n\n // renderDimensions is replaced by contentPreferencesMode.\n // throw an error if both are specified.\n if ('renderDimensions' in bandwidthProfile.video && 'contentPreferencesMode' in bandwidthProfile.video) {\n return new TypeError('options.bandwidthProfile.video.renderDimensions is deprecated. Use options.bandwidthProfile.video.contentPreferencesMode instead.');\n }\n\n return validateRenderDimensions(bandwidthProfile.video.renderDimensions);\n }\n\n return null;\n}\n\n/**\n * Throw if the given track is not a {@link LocalAudioTrack}, a\n * {@link LocalVideoTrack} or a MediaStreamTrack.\n * @param {*} track\n * @param {object} options\n */\nfunction validateLocalTrack(track, options) {\n if (!(track instanceof options.LocalAudioTrack\n || track instanceof options.LocalDataTrack\n || track instanceof options.LocalVideoTrack\n || track instanceof options.MediaStreamTrack)) {\n /* eslint new-cap:0 */\n throw E.INVALID_TYPE('track', 'LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n }\n}\n\n/**\n * Validate an object. An object is valid if it is undefined or a non-null, non-array\n * object whose properties satisfy the specified data-type or value-range requirements.\n * @param {object} object - the object to be validated\n * @param {string} name - the object name to be used to build the error message, if invalid\n * @param {Array} [propChecks] - optional data-type or value-range requirements\n * for the object's properties\n * @returns {?Error} - null if object is valid, Error if not\n */\nfunction validateObject(object, name, propChecks = []) {\n // NOTE(mmalavalli): We determine that an undefined object is valid because this\n // means the parent object does not contain this object as a property, which is\n // a valid scenario.\n if (typeof object === 'undefined') {\n return null;\n }\n // NOTE(mmalavalli): We determine that if the object is null, or an Array, or\n // any other non-object type, then it is invalid.\n if (object === null || !isNonArrayObject(object)) {\n return E.INVALID_TYPE(name, 'object');\n }\n // NOTE(mmalavalli): We determine that the object is invalid if at least one of\n // its properties does not satisfy its data-type or value-range requirement.\n return propChecks.reduce((error, { prop, type, values }) => {\n if (error || !(prop in object)) {\n return error;\n }\n const value = object[prop];\n if (type && typeof value !== type) {\n return E.INVALID_TYPE(`${name}.${prop}`, type);\n }\n if (type === 'number' && isNaN(value)) {\n return E.INVALID_TYPE(`${name}.${prop}`, type);\n }\n if (Array.isArray(values) && !values.includes(value)) {\n return E.INVALID_VALUE(`${name}.${prop}`, values);\n }\n return error;\n }, null);\n}\n\n/**\n * Validates the renderDimensions field to be \"auto\" or {@link VideoRenderDimensions} object.\n * @param {string|VideoRenderDimensions} renderDimensions\n * @returns {?Error} - null if valid, Error if not.\n */\nfunction validateRenderDimensions(renderDimensions) {\n const name = 'options.bandwidthProfile.video.renderDimensions';\n let error = validateObject(renderDimensions, name);\n return renderDimensions ? error || Object.values(trackPriority).reduce((error, prop) => {\n return error || validateObject(renderDimensions[prop], `${name}.${prop}`, [\n { prop: 'height', type: 'number' },\n { prop: 'width', type: 'number' }\n ]);\n }, null) : error;\n}\n\nexports.validateBandwidthProfile = validateBandwidthProfile;\nexports.validateLocalTrack = validateLocalTrack;\nexports.validateObject = validateObject;\n", "'use strict';\n\nconst EventEmitter = require('../../eventemitter');\nconst { buildLogLevels, valueToJSON } = require('../../util');\nconst { DEFAULT_LOG_LEVEL } = require('../../util/constants');\nconst Log = require('../../util/log');\nlet nInstances = 0;\n\n/**\n * A {@link TrackPublication} represents a {@link Track} that\n * has been published to a {@link Room}.\n * @property {string} trackName - the published {@link Track}'s name\n * @property {Track.SID} trackSid - SID assigned to the published {@link Track}\n * @emits TrackPublication#trackDisabled\n * @emits TrackPublication#trackEnabled\n */\nclass TrackPublication extends EventEmitter {\n /**\n * Construct a {@link TrackPublication}.\n * @param {string} trackName - the published {@link Track}'s name\n * @param {Track.SID} trackSid - SID assigned to the {@link Track}\n * @param {TrackPublicationOptions} options - {@link TrackPublication} options\n */\n constructor(trackName, trackSid, options) {\n super();\n\n options = Object.assign({\n logLevel: DEFAULT_LOG_LEVEL\n }, options);\n\n const logLevels = buildLogLevels(options.logLevel);\n\n Object.defineProperties(this, {\n _instanceId: {\n value: nInstances++\n },\n _log: {\n value: options.log ? options.log.createLog('default', this) : new Log('default', this, logLevels, options.loggerName)\n },\n trackName: {\n enumerable: true,\n value: trackName\n },\n trackSid: {\n enumerable: true,\n value: trackSid\n }\n });\n }\n\n toJSON() {\n return valueToJSON(this);\n }\n\n toString() {\n return `[TrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * The published {@link Track} was disabled.\n * @event TrackPublication#trackDisabled\n */\n\n/**\n * The published {@link Track} was enabled.\n * @event TrackPublication#trackEnabled\n */\n\n/**\n * A {@link LocalAudioTrackPublication} or a {@link RemoteAudioTrackPublication}.\n * @typedef {LocalAudioTrackPublication|RemoteAudioTrackPublication} AudioTrackPublication\n */\n\n/**\n * A {@link LocalDataTrackPublication} or a {@link RemoteDataTrackPublication}.\n * @typedef {LocalDataTrackPublication|RemoteDataTrackPublication} DataTrackPublication\n */\n\n/**\n * A {@link LocalVideoTrackPublication} or a {@link RemoteVideoTrackPublication}.\n * @typedef {LocalVideoTrackPublication|RemoteVideoTrackPublication} VideoTrackPublication\n */\n\n/**\n * {@link TrackPublication} options\n * @typedef {object} TrackPublicationOptions\n * @property {LogLevel|LogLevels} logLevel - Log level for 'media' modules\n */\n\nmodule.exports = TrackPublication;\n", "/* eslint new-cap:0 */\n'use strict';\n\nconst TrackPublication = require('./trackpublication');\nconst { typeErrors: E, trackPriority } = require('../../util/constants');\n\n/**\n * A {@link LocalTrackPublication} is a {@link LocalTrack} that has been\n * published to a {@link Room}.\n * @extends TrackPublication\n * @property {boolean} isTrackEnabled - whether the published {@link LocalTrack}\n * is enabled\n * @property {Track.Kind} kind - kind of the published {@link LocalTrack}\n * @property {Track.Priority} priority - the publish priority of the {@link LocalTrack}\n * @property {LocalTrack} track - the {@link LocalTrack}\n * @emits LocalTrackPublication#warning\n * @emits LocalTrackPublication#warningsCleared\n */\nclass LocalTrackPublication extends TrackPublication {\n /**\n * Construct a {@link LocalTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalTrack} track - The {@link LocalTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication}\n * options\n */\n constructor(signaling, track, unpublish, options) {\n super(track.name, signaling.sid, options);\n\n Object.defineProperties(this, {\n _reemitSignalingEvent: {\n value: (...args) => this.emit(\n args && args.length ? 'warning' : 'warningsCleared',\n ...args\n )\n },\n _reemitTrackEvent: {\n value: () => this.emit(this.isTrackEnabled\n ? 'trackEnabled'\n : 'trackDisabled')\n },\n _signaling: {\n value: signaling\n },\n _unpublish: {\n value: unpublish\n },\n isTrackEnabled: {\n enumerable: true,\n get() {\n return this.track.kind === 'data' ? true : this.track.isEnabled;\n }\n },\n kind: {\n enumerable: true,\n value: track.kind\n },\n priority: {\n enumerable: true,\n get() {\n return signaling.updatedPriority;\n }\n },\n track: {\n enumerable: true,\n value: track\n }\n });\n\n ['disabled', 'enabled'].forEach(name =>\n track.on(name, this._reemitTrackEvent));\n\n ['warning', 'warningsCleared'].forEach(name =>\n signaling.on(name, this._reemitSignalingEvent));\n }\n\n toString() {\n return `[LocalTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n\n /**\n * Update the {@link Track.Priority} of the published {@link LocalTrack}.\n * @param {Track.Priority} priority - the new {@link Track.priority}\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n const priorityValues = Object.values(trackPriority);\n if (!priorityValues.includes(priority)) {\n throw E.INVALID_VALUE('priority', priorityValues);\n }\n this._signaling.setPriority(priority);\n return this;\n }\n\n /**\n * Unpublish a {@link LocalTrackPublication}. This means that the media\n * from this {@link LocalTrackPublication} is no longer available to the\n * {@link Room}'s {@link RemoteParticipant}s.\n * @returns {this}\n */\n unpublish() {\n ['disabled', 'enabled'].forEach(name =>\n this.track.removeListener(name, this._reemitTrackEvent));\n\n ['warning', 'warningsCleared'].forEach(name =>\n this._signaling.removeListener(name, this._reemitSignalingEvent));\n\n this._unpublish(this);\n return this;\n }\n}\n\n/**\n * The published {@link LocalTrack} encountered a warning.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @event LocalTrackPublication#warning\n * @param {string} name - The warning that was raised.\n */\n\n/**\n * The published {@link LocalTrack} cleared all warnings.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @event LocalTrackPublication#warningsCleared\n */\n\nmodule.exports = LocalTrackPublication;\n", "'use strict';\n\nconst LocalTrackPublication = require('./localtrackpublication');\n\n/**\n * A {@link LocalAudioTrackPublication} is a {@link LocalAudioTrack} that has\n * been published to a {@link Room}.\n * @extends LocalTrackPublication\n * @property {Track.Kind} kind - \"audio\"\n * @property {LocalAudioTrack} track - the {@link LocalAudioTrack}\n */\nclass LocalAudioTrackPublication extends LocalTrackPublication {\n /**\n * Construct a {@link LocalAudioTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalAudioTrack} track - the {@link LocalAudioTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication} options\n */\n constructor(signaling, track, unpublish, options) {\n super(signaling, track, unpublish, options);\n }\n\n toString() {\n return `[LocalAudioTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\nmodule.exports = LocalAudioTrackPublication;\n", "'use strict';\n\nconst LocalTrackPublication = require('./localtrackpublication');\n\n/**\n * A {@link LocalDataTrackPublication} is a {@link LocalDataTrack} that has been\n * published to a {@link Room}.\n * @extends LocalTrackPublication\n * @property {Track.Kind} kind - \"data\"\n * @property {LocalDataTrack} track - the {@link LocalDataTrack}\n */\nclass LocalDataTrackPublication extends LocalTrackPublication {\n /**\n * Construct a {@link LocalDataTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalDataTrack} track - the {@link LocalDataTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication} options\n */\n constructor(signaling, track, unpublish, options) {\n super(signaling, track, unpublish, options);\n }\n\n toString() {\n return `[LocalDataTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\nmodule.exports = LocalDataTrackPublication;\n", "'use strict';\n\nconst LocalTrackPublication = require('./localtrackpublication');\n\n/**\n * A {@link LocalVideoTrackPublication} is a {@link LocalVideoTrack} that has\n * been published to a {@link Room}.\n * @extends LocalTrackPublication\n * @property {Track.Kind} kind - \"video\"\n * @property {LocalVideoTrack} track - the {@link LocalVideoTrack}\n */\nclass LocalVideoTrackPublication extends LocalTrackPublication {\n /**\n * Construct a {@link LocalVideoTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalVideoTrack} track - the {@link LocalVideoTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication} options\n */\n constructor(signaling, track, unpublish, options) {\n super(signaling, track, unpublish, options);\n }\n\n toString() {\n return `[LocalVideoTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\nmodule.exports = LocalVideoTrackPublication;\n", "'use strict';\n\nconst { typeErrors: E, trackPriority } = require('../../util/constants');\nconst { isIOS } = require('../../util/browserdetection');\nconst documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');\n\nfunction mixinRemoteMediaTrack(AudioOrVideoTrack) {\n /**\n * A {@link RemoteMediaTrack} represents a {@link MediaTrack} published to a\n * {@link Room} by a {@link RemoteParticipant}.\n * @property {boolean} isEnabled - Whether the {@link RemoteMediaTrack} is enabled\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteMediaTrack} is switched off\n * @property {Track.SID} sid - The SID assigned to the {@link RemoteMediaTrack}\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteMediaTrack}\n * @emits RemoteMediaTrack#disabled\n * @emits RemoteMediaTrack#enabled\n * @emits RemoteMediaTrack#switchedOff\n * @emits RemoteMediaTrack#switchedOn\n */\n return class RemoteMediaTrack extends AudioOrVideoTrack {\n /**\n * Construct a {@link RemoteMediaTrack}.\n * @param {Track.SID} sid\n * @param {MediaTrackReceiver} mediaTrackReceiver\n * @param {boolean} isEnabled\n @param {boolean} isSwitchedOff\n * @param {function(?Track.Priority): void} setPriority - Set or clear the subscribe\n * {@link Track.Priority} of the {@link RemoteMediaTrack}\n * @param {function(ClientRenderHint): void} setRenderHint - Set render hints.\n * @param {{log: Log, name: ?string}} options\n */\n constructor(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options) {\n options = Object.assign({\n // NOTE(mpatwardhan): WebKit bug: 212780 sometimes causes the audio/video elements to stay paused when safari\n // regains foreground. To workaround it, when safari gains foreground - we will play any elements that were\n // playing before safari lost foreground.\n workaroundWebKitBug212780: isIOS()\n && typeof document === 'object'\n && typeof document.addEventListener === 'function'\n && typeof document.visibilityState === 'string'\n }, options);\n\n super(mediaTrackReceiver, options);\n\n Object.defineProperties(this, {\n _isEnabled: {\n value: isEnabled,\n writable: true\n },\n _isSwitchedOff: {\n value: isSwitchedOff,\n writable: true\n },\n _priority: {\n value: null,\n writable: true\n },\n _setPriority: {\n value: setPriority\n },\n _setRenderHint: {\n value: renderHint => {\n this._log.debug('updating render hint:', renderHint);\n setRenderHint(renderHint);\n }\n },\n isEnabled: {\n enumerable: true,\n get() {\n return this._isEnabled;\n }\n },\n isSwitchedOff: {\n enumerable: true,\n get() {\n return this._isSwitchedOff;\n }\n },\n priority: {\n enumerable: true,\n get() {\n return this._priority;\n }\n },\n sid: {\n enumerable: true,\n value: sid\n },\n _workaroundWebKitBug212780: {\n value: options.workaroundWebKitBug212780\n },\n _workaroundWebKitBug212780Cleanup: {\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Update the subscribe {@link Track.Priority} of the {@link RemoteMediaTrack}.\n * @param {?Track.Priority} priority - the new subscribe {@link Track.Priority};\n * If null, then the subscribe {@link Track.Priority} is cleared, which\n * means the {@link Track.Priority} set by the publisher is now the effective priority.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n const priorityValues = [null, ...Object.values(trackPriority)];\n if (!priorityValues.includes(priority)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_VALUE('priority', priorityValues);\n }\n if (this._priority !== priority) {\n this._priority = priority;\n this._setPriority(priority);\n }\n return this;\n }\n\n /**\n * @private\n * @param {boolean} isEnabled\n */\n _setEnabled(isEnabled) {\n if (this._isEnabled !== isEnabled) {\n this._isEnabled = isEnabled;\n this.emit(this._isEnabled ? 'enabled' : 'disabled', this);\n }\n }\n\n /**\n * @private\n * @param {boolean} isSwitchedOff\n */\n _setSwitchedOff(isSwitchedOff) {\n if (this._isSwitchedOff !== isSwitchedOff) {\n this._isSwitchedOff = isSwitchedOff;\n this.emit(isSwitchedOff ? 'switchedOff' : 'switchedOn', this);\n }\n }\n\n attach(el) {\n const result = super.attach(el);\n if (this.mediaStreamTrack.enabled !== true) {\n // NOTE(mpatwardhan): we disable mediaStreamTrack when there\n // are no attachments to it (see notes below). Now that there\n // are attachments re-enable the track.\n this.mediaStreamTrack.enabled = true;\n if (this.processedTrack) {\n this.processedTrack.enabled = true;\n }\n\n // NOTE(csantos): since remote tracks disables/enables the mediaStreamTrack,\n // captureFrames stops along with it. We need to start it again after re-enabling.\n // See attach/detach methods in this class and in VideoTrack class.\n if (this.processor) {\n this._captureFrames();\n }\n }\n if (this._workaroundWebKitBug212780) {\n this._workaroundWebKitBug212780Cleanup = this._workaroundWebKitBug212780Cleanup\n || playIfPausedWhileInBackground(this);\n }\n\n return result;\n }\n\n detach(el) {\n const result = super.detach(el);\n if (this._attachments.size === 0) {\n // NOTE(mpatwardhan): chrome continues playing webrtc audio\n // track even after audio element is removed from the DOM.\n // https://bugs.chromium.org/p/chromium/issues/detail?id=749928\n // to workaround: here disable the track when\n // there are no elements attached to it.\n this.mediaStreamTrack.enabled = false;\n if (this.processedTrack) {\n this.processedTrack.enabled = false;\n }\n\n if (this._workaroundWebKitBug212780Cleanup) {\n // unhook visibility change\n this._workaroundWebKitBug212780Cleanup();\n this._workaroundWebKitBug212780Cleanup = null;\n }\n }\n return result;\n }\n };\n}\n\nfunction playIfPausedWhileInBackground(remoteMediaTrack) {\n const { _log: log, kind } = remoteMediaTrack;\n\n function onVisibilityChanged(isVisible) {\n if (!isVisible) {\n return;\n }\n remoteMediaTrack._attachments.forEach(el => {\n const shim = remoteMediaTrack._elShims.get(el);\n const isInadvertentlyPaused = el.paused && shim && !shim.pausedIntentionally();\n if (isInadvertentlyPaused) {\n log.info(`Playing inadvertently paused <${kind}> element`);\n log.debug('Element:', el);\n log.debug('RemoteMediaTrack:', remoteMediaTrack);\n el.play().then(() => {\n log.info(`Successfully played inadvertently paused <${kind}> element`);\n log.debug('Element:', el);\n log.debug('RemoteMediaTrack:', remoteMediaTrack);\n }).catch(err => {\n log.warn(`Error while playing inadvertently paused <${kind}> element:`, { err, el, remoteMediaTrack });\n });\n }\n });\n }\n\n // NOTE(mpatwardhan): listen for document visibility callback on phase 2.\n // this ensures that any LocalMediaTrack's restart (which listen on phase 1) gets executed\n // first. This order is important because we `play` tracks in the callback, and\n // play can fail on safari if audio is not being captured.\n documentVisibilityMonitor.onVisibilityChange(2, onVisibilityChanged);\n return () => {\n documentVisibilityMonitor.offVisibilityChange(2, onVisibilityChanged);\n };\n}\n\n/**\n * A {@link RemoteMediaTrack} was disabled.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * disabled\n * @event RemoteMediaTrack#disabled\n */\n\n/**\n * A {@link RemoteMediaTrack} was enabled.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * enabled\n * @event RemoteMediaTrack#enabled\n */\n\n/**\n * A {@link RemoteMediaTrack} was switched off.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * switched off\n * @event RemoteMediaTrack#switchedOff\n */\n\n/**\n * A {@link RemoteMediaTrack} was switched on.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * switched on\n * @event RemoteMediaTrack#switchedOn\n */\n\n/**\n * A {@link ClientRenderHint} object specifies track dimensions and /enabled disable state.\n * This state will be used by the server(SFU) to determine bandwidth allocation for the track,\n * and turn it on or off as needed.\n * @typedef {object} ClientRenderHint\n * @property {boolean} [enabled] - track is enabled or disabled. defaults to disabled.\n * @property {VideoTrack.Dimensions} [renderDimensions] - Optional parameter to specify the desired\n * render dimensions of {@link RemoteVideoTrack}s. This property must be specified if enabled=true\n */\n\nmodule.exports = mixinRemoteMediaTrack;\n", "'use strict';\n\nconst AudioTrack = require('./audiotrack');\nconst mixinRemoteMediaTrack = require('./remotemediatrack');\n\nconst RemoteMediaAudioTrack = mixinRemoteMediaTrack(AudioTrack);\n\n/**\n * A {@link RemoteAudioTrack} represents an {@link AudioTrack} published to a\n * {@link Room} by a {@link RemoteParticipant}.\n * @extends AudioTrack\n * @property {boolean} isEnabled - Whether the {@link RemoteAudioTrack} is enabled\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteAudioTrack} is switched off\n * @property {Track.SID} sid - The {@link RemoteAudioTrack}'s SID\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteAudioTrack}\n * @emits RemoteAudioTrack#disabled\n * @emits RemoteAudioTrack#enabled\n * @emits RemoteAudioTrack#started\n * @emits RemoteAudioTrack#switchedOff\n * @emits RemoteAudioTrack#switchedOn\n */\nclass RemoteAudioTrack extends RemoteMediaAudioTrack {\n /**\n * Construct a {@link RemoteAudioTrack}.\n * @param {Track.SID} sid - The {@link RemoteAudioTrack}'s SID\n * @param {MediaTrackReceiver} mediaTrackReceiver - An audio MediaStreamTrack container\n * @param {boolean} isEnabled - Whether the {@link RemoteAudioTrack} is enabled\n * @param {boolean} isSwitchedOff - Whether the {@link RemoteAudioTrack} is switched off\n * @param {function(?Track.Priority): void} setPriority - Set or clear the subscribe\n * {@link Track.Priority} of the {@link RemoteAudioTrack}\n * @param {function(ClientRenderHint): void} setRenderHint - Set render hints.\n * @param {{log: Log}} options - The {@link RemoteTrack} options\n */\n constructor(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options) {\n super(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options);\n }\n\n toString() {\n return `[RemoteAudioTrack #${this._instanceId}: ${this.sid}]`;\n }\n\n /**\n * @private\n */\n _start() {\n super._start();\n if (this._dummyEl) {\n // NOTE(mpatwardhan): To fix VIDEO-6336, clear dummy element after the\n // RemoteAudioTrack has started.\n this._dummyEl.srcObject = null;\n this._dummyEl = null;\n }\n }\n\n /**\n * Update the subscribe {@link Track.Priority} of the {@link RemoteAudioTrack}.\n * @param {?Track.Priority} priority - the new subscribe {@link Track.Priority};\n * Currently setPriority has no effect on audio tracks.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n return super.setPriority(priority);\n }\n}\n\n/**\n * The {@link RemoteAudioTrack} was disabled, i.e. \"muted\".\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * disabled\n * @event RemoteAudioTrack#disabled\n */\n\n/**\n * The {@link RemoteAudioTrack} was enabled, i.e. \"unmuted\".\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * enabled\n * @event RemoteAudioTrack#enabled\n */\n\n/**\n * The {@link RemoteAudioTrack} started. This means there is enough audio data\n * to begin playback.\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that started\n * @event RemoteAudioTrack#started\n */\n\n/**\n * A {@link RemoteAudioTrack} was switched off.\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * switched off\n * @event RemoteAudioTrack#switchedOff\n */\n\n/**\n * A {@link RemoteAudioTrack} was switched on.\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * switched on\n * @event RemoteAudioTrack#switchedOn\n */\n\nmodule.exports = RemoteAudioTrack;\n", "'use strict';\n\nconst TrackPublication = require('./trackpublication');\n\n/**\n * A {@link RemoteTrackPublication} represents a {@link RemoteTrack} that has\n * been published to a {@link Room}.\n * @extends TrackPublication\n * @property {boolean} isSubscribed - whether the published {@link RemoteTrack}\n * is subscribed to\n * @property {boolean} isTrackEnabled - whether the published\n * {@link RemoteTrack} is enabled\n * @property {Track.Kind} kind - kind of the published {@link RemoteTrack}\n * @property {Track.Priority} publishPriority - the {@link Track.Priority} of the published\n * {@link RemoteTrack} set by the {@link RemoteParticipant}\n * @property {?RemoteTrack} track - Unless you have subscribed to the\n * {@link RemoteTrack}, this property is null\n * @emits RemoteTrackPublication#publishPriorityChanged\n * @emits RemoteTrackPublication#subscribed\n * @emits RemoteTrackPublication#subscriptionFailed\n * @emits RemoteTrackPublication#trackDisabled\n * @emits RemoteTrackPublication#trackEnabled\n * @emits RemoteTrackPublication#trackSwitchedOff\n * @emits RemoteTrackPublication#trackSwitchedOn\n * @emits RemoteTrackPublication#unsubscribed\n *\n */\nclass RemoteTrackPublication extends TrackPublication {\n /**\n * Construct a {@link RemoteTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling.name, signaling.sid, options);\n\n Object.defineProperties(this, {\n _signaling: {\n value: signaling\n },\n _track: {\n value: null,\n writable: true\n },\n isSubscribed: {\n enumerable: true,\n get() {\n return !!this._track;\n }\n },\n isTrackEnabled: {\n enumerable: true,\n get() {\n return signaling.isEnabled;\n }\n },\n kind: {\n enumerable: true,\n value: signaling.kind\n },\n publishPriority: {\n enumerable: true,\n get() {\n return signaling.priority;\n }\n },\n track: {\n enumerable: true,\n get() {\n return this._track;\n }\n }\n });\n\n // remember original state, and fire events only on change.\n let {\n error,\n isEnabled,\n isSwitchedOff,\n priority\n } = signaling;\n\n signaling.on('updated', () => {\n if (error !== signaling.error) {\n error = signaling.error;\n this.emit('subscriptionFailed', signaling.error);\n return;\n }\n if (isEnabled !== signaling.isEnabled) {\n isEnabled = signaling.isEnabled;\n if (this.track) {\n this.track._setEnabled(signaling.isEnabled);\n }\n this.emit(signaling.isEnabled ? 'trackEnabled' : 'trackDisabled');\n }\n if (isSwitchedOff !== signaling.isSwitchedOff) {\n this._log.debug(`${this.trackSid}: ${isSwitchedOff ? 'OFF' : 'ON'} => ${signaling.isSwitchedOff ? 'OFF' : 'ON'}`);\n isSwitchedOff = signaling.isSwitchedOff;\n if (this.track) {\n this.track._setSwitchedOff(signaling.isSwitchedOff);\n this.emit(isSwitchedOff ? 'trackSwitchedOff' : 'trackSwitchedOn', this.track);\n } else if (isSwitchedOff) {\n this._log.warn('Track was not subscribed when switched Off.');\n }\n }\n if (priority !== signaling.priority) {\n priority = signaling.priority;\n this.emit('publishPriorityChanged', priority);\n }\n });\n }\n\n toString() {\n return `[RemoteTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n\n /**\n * @private\n * @param {RemoteTrack} track\n */\n _subscribed(track) {\n if (!this._track && track) {\n this._track = track;\n this.emit('subscribed', track);\n }\n }\n\n /**\n * @private\n */\n _unsubscribe() {\n if (this._track) {\n const track = this._track;\n this._track = null;\n this.emit('unsubscribed', track);\n }\n }\n}\n\n/**\n * The {@link RemoteTrack}'s publish {@link Track.Priority} was changed by the\n * {@link RemoteParticipant}.\n * @param {Track.Priority} priority - the {@link RemoteTrack}'s new publish\n * {@link Track.Priority}; RemoteTrackPublication#publishPriority is also\n * updated accordingly\n * @event RemoteTrackPublication#publishPriorityChanged\n */\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteTrack}.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was subscribed to\n * @event RemoteTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteTrack} could not be\n * subscribed to\n * @event RemoteTrackPublication#subscriptionFailed\n */\n\n/**\n * The {@link RemoteTrack} was disabled.\n * @event RemoteTrackPublication#trackDisabled\n */\n\n/**\n * The {@link RemoteTrack} was enabled.\n * @event RemoteTrackPublication#trackEnabled\n */\n\n/**\n * The {@link RemoteTrack} was switched off.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was switched off\n * @event RemoteTrackPublication#trackSwitchedOff\n */\n\n/**\n * The {@link RemoteTrack} was switched on.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was switched on\n * @event RemoteTrackPublication#trackSwitchedOn\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteTrack}.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was unsubscribed from\n * @event RemoteTrackPublication#unsubscribed\n */\n\n/**\n * {@link RemoteTrackPublication} options\n * @typedef {object} RemoteTrackPublicationOptions\n * @property {LogLevel|LogLevels} logLevel - Log level for 'media' modules\n */\n\nmodule.exports = RemoteTrackPublication;\n", "'use strict';\n\nconst RemoteTrackPublication = require('./remotetrackpublication');\n\n/**\n * A {@link RemoteAudioTrackPublication} represents a {@link RemoteAudioTrack}\n * that has been published to a {@link Room}.\n * @property {Track.Kind} kind - \"audio\"\n * @property {?RemoteAudioTrack} track - unless you have subscribed to the\n * {@link RemoteAudioTrack}, this property is null\n * @emits RemoteAudioTrackPublication#subscribed\n * @emits RemoteAudioTrackPublication#subscriptionFailed\n * @emits RemoteAudioTrackPublication#trackDisabled\n * @emits RemoteAudioTrackPublication#trackEnabled\n * @emits RemoteAudioTrackPublication#unsubscribed\n */\nclass RemoteAudioTrackPublication extends RemoteTrackPublication {\n /**\n * Construct a {@link RemoteAudioTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling, options);\n }\n\n toString() {\n return `[RemoteAudioTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteAudioTrack}.\n * @param {RemoteAudioTrack} track - the {@link RemoteAudioTrack} that was subscribed to\n * @event RemoteAudioTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteAudioTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteAudioTrack} could not be\n * subscribed to\n * @event RemoteAudioTrackPublication#subscriptionFailed\n */\n\n/**\n * The {@link RemoteAudioTrack} was disabled.\n * @event RemoteAudioTrackPublication#trackDisabled\n */\n\n/**\n * The {@link RemoteAudioTrack} was enabled.\n * @event RemoteAudioTrackPublication#trackEnabled\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteAudioTrack}.\n * @param {RemoteAudioTrack} track - the {@link RemoteAudioTrack} that was unsubscribed from\n * @event RemoteAudioTrackPublication#unsubscribed\n */\n\nmodule.exports = RemoteAudioTrackPublication;\n", "'use strict';\n\nconst Track = require('./');\nconst { typeErrors: E, trackPriority } = require('../../util/constants');\n\n/**\n * A {@link RemoteDataTrack} represents data published to a {@link Room} by a\n * {@link RemoteParticipant}.\n * @extends Track\n * @property {boolean} isEnabled - true\n * @property {boolean} isSubscribed - Whether the {@link RemoteDataTrack} is\n * subscribed to\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteDataTrack} is\n * switched off\n * @property {Track.Kind} kind - \"data\"\n * @property {?number} maxPacketLifeTime - If non-null, this represents a time\n * limit (in milliseconds) during which data will be transmitted or\n * retransmitted if not acknowledged on the underlying RTCDataChannel.\n * @property {?number} maxRetransmits - If non-null, this represents the number\n * of times the data will be retransmitted if not successfully received on the\n * underlying RTCDataChannel.\n * @property {boolean} ordered - true if data on the {@link RemoteDataTrack} can\n * be received out-of-order.\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteDataTrack}\n * @property {boolean} reliable - This is true if both\n * maxPacketLifeTime and maxRetransmits are set to\n * null. In other words, if this is true, there is no bound on packet lifetime\n * or the number of retransmits that will be attempted, ensuring \"reliable\"\n * transmission.\n * @property {Track.SID} sid - The SID assigned to the {@link RemoteDataTrack}\n * @emits RemoteDataTrack#message\n * @emits RemoteDataTrack#switchedOff\n * @emits RemoteDataTrack#switchedOn\n */\nclass RemoteDataTrack extends Track {\n /**\n * Construct a {@link RemoteDataTrack} from a {@link DataTrackReceiver}.\n * @param {Track.SID} sid\n * @param {DataTrackReceiver} dataTrackReceiver\n * @param {{log: Log, name: ?string}} options\n */\n constructor(sid, dataTrackReceiver, options) {\n super(dataTrackReceiver.id, 'data', options);\n\n Object.defineProperties(this, {\n _isSwitchedOff: {\n value: false,\n writable: true\n },\n _priority: {\n value: null,\n writable: true\n },\n isEnabled: {\n enumerable: true,\n value: true\n },\n isSwitchedOff: {\n enumerable: true,\n get() {\n return this._isSwitchedOff;\n }\n },\n maxPacketLifeTime: {\n enumerable: true,\n value: dataTrackReceiver.maxPacketLifeTime\n },\n maxRetransmits: {\n enumerable: true,\n value: dataTrackReceiver.maxRetransmits\n },\n ordered: {\n enumerable: true,\n value: dataTrackReceiver.ordered\n },\n priority: {\n enumerable: true,\n get() {\n return this._priority;\n }\n },\n reliable: {\n enumerable: true,\n value: dataTrackReceiver.maxPacketLifeTime === null\n && dataTrackReceiver.maxRetransmits === null\n },\n sid: {\n enumerable: true,\n value: sid\n }\n });\n\n dataTrackReceiver.on('message', data => {\n this.emit('message', data, this);\n });\n }\n\n /**\n * Update the subscriber {@link Track.Priority} of the {@link RemoteDataTrack}.\n * @param {?Track.Priority} priority - the new {@link Track.priority};\n * Currently setPriority has no effect on data tracks.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n const priorityValues = [null, ...Object.values(trackPriority)];\n if (!priorityValues.includes(priority)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_VALUE('priority', priorityValues);\n }\n\n // Note: priority has no real effect on the data tracks.\n this._priority = priority;\n return this;\n }\n\n /**\n * @private\n */\n _setEnabled() {\n // Do nothing.\n }\n\n /**\n * @private\n * @param {boolean} isSwitchedOff\n */\n _setSwitchedOff(isSwitchedOff) {\n if (this._isSwitchedOff !== isSwitchedOff) {\n this._isSwitchedOff = isSwitchedOff;\n this.emit(isSwitchedOff ? 'switchedOff' : 'switchedOn', this);\n }\n }\n}\n\n/**\n * A message was received over the {@link RemoteDataTrack}.\n * @event RemoteDataTrack#message\n * @param {string|ArrayBuffer} data\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} that received\n * the message\n */\n\n/**\n * A {@link RemoteDataTrack} was switched off.\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} that was\n * switched off\n * @event RemoteDataTrack#switchedOff\n */\n\n/**\n * A {@link RemoteDataTrack} was switched on.\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} that was\n * switched on\n * @event RemoteDataTrack#switchedOn\n */\n\nmodule.exports = RemoteDataTrack;\n", "'use strict';\n\nconst RemoteTrackPublication = require('./remotetrackpublication');\n\n/**\n * A {@link RemoteDataTrackPublication} represents a {@link RemoteDataTrack}\n * that has been published to a {@link Room}.\n * @property {Track.Kind} kind - \"data\"\n * @property {?RemoteDataTrack} track - unless you have subscribed to the\n * {@link RemoteDataTrack}, this property is null\n * @emits RemoteDataTrackPublication#subscribed\n * @emits RemoteDataTrackPublication#subscriptionFailed\n * @emits RemoteDataTrackPublication#unsubscribed\n */\nclass RemoteDataTrackPublication extends RemoteTrackPublication {\n /**\n * Construct a {@link RemoteDataTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling, options);\n }\n\n toString() {\n return `[RemoteDataTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteDataTrack}.\n * @param {RemoteDataTrack} track - the {@link RemoteDataTrack} that was subscribed to\n * @event RemoteDataTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteDataTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteDataTrack} could not be\n * subscribed to\n * @event RemoteDataTrackPublication#subscriptionFailed\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteDataTrack}.\n * @param {RemoteDataTrack} track - the {@link RemoteDataTrack} that was unsubscribed from\n * @event RemoteDataTrackPublication#unsubscribed\n */\n\nmodule.exports = RemoteDataTrackPublication;\n", "/* eslint-disable no-console */\n'use strict';\n\nclass NullObserver {\n constructor(callback) {\n Object.defineProperties(this, {\n _callback: {\n value: callback\n }\n });\n }\n\n observe() {\n }\n\n unobserve() {\n }\n\n makeVisible(videoEl) {\n const visibleEntry = this._makeFakeEntry(videoEl, true);\n this._callback([visibleEntry]);\n }\n\n makeInvisible(videoEl) {\n const invisibleEntry = this._makeFakeEntry(videoEl, false);\n this._callback([invisibleEntry]);\n }\n\n _makeFakeEntry(videoElement, isIntersecting) {\n return { target: videoElement, isIntersecting };\n }\n}\n\nclass NullIntersectionObserver extends NullObserver { }\n\nclass NullResizeObserver extends NullObserver {\n resize(videoEl) {\n const entry = this._makeFakeEntry(videoEl, true);\n this._callback([entry]);\n }\n}\n\n\nmodule.exports = { NullIntersectionObserver, NullResizeObserver, NullObserver };\n", "'use strict';\n\nconst mixinRemoteMediaTrack = require('./remotemediatrack');\nconst VideoTrack = require('./videotrack');\nconst documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');\nconst { NullObserver } = require('../../util/nullobserver.js');\nconst Timeout = require('../../util/timeout');\n\nconst RemoteMediaVideoTrack = mixinRemoteMediaTrack(VideoTrack);\nconst TRACK_TURN_OF_DELAY_MS = 50;\n\n/**\n * A {@link RemoteVideoTrack} represents a {@link VideoTrack} published to a\n * {@link Room} by a {@link RemoteParticipant}.\n * @extends VideoTrack\n * @property {boolean} isEnabled - Whether the {@link RemoteVideoTrack} is enabled\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteVideoTrack} is switched off\n * @property {Track.SID} sid - The {@link RemoteVideoTrack}'s SID\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteVideoTrack}\n * @emits RemoteVideoTrack#dimensionsChanged\n * @emits RemoteVideoTrack#disabled\n * @emits RemoteVideoTrack#enabled\n * @emits RemoteVideoTrack#started\n * @emits RemoteVideoTrack#switchedOff\n * @emits RemoteVideoTrack#switchedOn\n */\nclass RemoteVideoTrack extends RemoteMediaVideoTrack {\n /**\n * Construct a {@link RemoteVideoTrack}.\n * @param {Track.SID} sid - The {@link RemoteVideoTrack}'s SID\n * @param {MediaTrackReceiver} mediaTrackReceiver - A video MediaStreamTrack container\n * @param {boolean} isEnabled - whether the {@link RemoteVideoTrack} is enabled\n * @param {boolean} isSwitchedOff - Whether the {@link RemoteVideoTrack} is switched off\n * @param {function(?Track.Priority): void} setPriority - Set or clear the subscribe\n * {@link Track.Priority} of the {@link RemoteVideoTrack}\n * @param {function(ClientRenderHint): void} setRenderHint - Set render hints.\n * @param {{log: Log}} options - The {@link RemoteTrack} options\n */\n constructor(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options) {\n options = Object.assign({\n clientTrackSwitchOffControl: 'auto',\n contentPreferencesMode: 'auto',\n enableDocumentVisibilityTurnOff: true,\n }, options);\n\n options = Object.assign({\n IntersectionObserver: typeof IntersectionObserver === 'undefined' || options.clientTrackSwitchOffControl !== 'auto' ? NullObserver : IntersectionObserver,\n ResizeObserver: typeof ResizeObserver === 'undefined' || options.contentPreferencesMode !== 'auto' ? NullObserver : ResizeObserver,\n }, options);\n\n super(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options);\n\n Object.defineProperties(this, {\n _enableDocumentVisibilityTurnOff: {\n value: options.enableDocumentVisibilityTurnOff === true && options.clientTrackSwitchOffControl === 'auto',\n },\n _documentVisibilityTurnOffCleanup: {\n value: null,\n writable: true\n },\n _clientTrackSwitchOffControl: {\n value: options.clientTrackSwitchOffControl,\n },\n _contentPreferencesMode: {\n value: options.contentPreferencesMode,\n },\n _invisibleElements: {\n value: new WeakSet(),\n },\n _elToPipCallbacks: {\n value: new WeakMap(),\n },\n _elToPipWindows: {\n value: new WeakMap(),\n },\n _turnOffTimer: {\n value: new Timeout(() => {\n this._setRenderHint({ enabled: false });\n }, TRACK_TURN_OF_DELAY_MS, false),\n },\n _resizeObserver: {\n value: new options.ResizeObserver(entries => {\n // NOTE(mpatwardhan): we ignore elements in _invisibleElements\n // to ensure that ResizeObserver does not end-up turning off a track when a fresh Video element is\n // attached and IntersectionObserver has not had its callback executed yet.\n const visibleElementResized = entries.find(entry => !this._invisibleElements.has(entry.target));\n if (visibleElementResized) {\n maybeUpdateDimensionHint(this);\n }\n })\n },\n _intersectionObserver: {\n value: new options.IntersectionObserver(entries => {\n let shouldSetRenderHint = false;\n entries.forEach(entry => {\n const wasVisible = !this._invisibleElements.has(entry.target);\n if (wasVisible !== entry.isIntersecting) {\n if (entry.isIntersecting) {\n this._log.debug('intersectionObserver detected: Off => On');\n this._invisibleElements.delete(entry.target);\n } else {\n this._log.debug('intersectionObserver detected: On => Off');\n this._invisibleElements.add(entry.target);\n }\n shouldSetRenderHint = true;\n }\n });\n if (shouldSetRenderHint) {\n maybeUpdateEnabledHint(this);\n\n // when visibility of an element changes that may cause the \"biggest\" element to change,\n // update dimensions as well. since dimensions are cached and de-duped at signaling layer,\n // its okay if they got resent.\n maybeUpdateDimensionHint(this);\n }\n }, { threshold: 0.25 })\n },\n });\n }\n\n /**\n * @private\n */\n _start(dummyEl) {\n const result = super._start.call(this, dummyEl);\n // NOTE(mpatwardhan): after emitting started, update turn off track if not visible.\n maybeUpdateEnabledHint(this);\n return result;\n }\n\n /**\n * Request to switch on a {@link RemoteVideoTrack}, This method is applicable only for the group rooms and only when connected with\n * clientTrackSwitchOffControl in video bandwidth profile options set to 'manual'\n * @returns {this}\n */\n switchOn() {\n if (this._clientTrackSwitchOffControl !== 'manual') {\n throw new Error('Invalid state. You can call switchOn only when bandwidthProfile.video.clientTrackSwitchOffControl is set to \"manual\"');\n }\n this._setRenderHint({ enabled: true });\n return this;\n }\n\n /**\n * Request to switch off a {@link RemoteVideoTrack}, This method is applicable only for the group rooms and only when connected with\n * clientTrackSwitchOffControl in video bandwidth profile options set to 'manual'\n * @returns {this}\n */\n switchOff() {\n if (this._clientTrackSwitchOffControl !== 'manual') {\n throw new Error('Invalid state. You can call switchOff only when bandwidthProfile.video.clientTrackSwitchOffControl is set to \"manual\"');\n }\n this._setRenderHint({ enabled: false });\n return this;\n }\n\n /**\n * Set the {@link RemoteVideoTrack}'s content preferences. This method is applicable only for the group rooms and only when connected with\n * videoContentPreferencesMode in video bandwidth profile options set to 'manual'\n * @param {VideoContentPreferences} contentPreferences - requested preferences.\n * @returns {this}\n */\n setContentPreferences(contentPreferences) {\n if (this._contentPreferencesMode !== 'manual') {\n throw new Error('Invalid state. You can call switchOn only when bandwidthProfile.video.contentPreferencesMode is set to \"manual\"');\n }\n\n if (contentPreferences.renderDimensions) {\n this._setRenderHint({ renderDimensions: contentPreferences.renderDimensions });\n }\n return this;\n }\n\n _unObservePip(el) {\n const pipCallbacks = this._elToPipCallbacks.get(el);\n if (pipCallbacks) {\n el.removeEventListener('enterpictureinpicture', pipCallbacks.onEnterPip);\n el.removeEventListener('leavepictureinpicture', pipCallbacks.onLeavePip);\n this._elToPipCallbacks.delete(el);\n }\n }\n\n _observePip(el) {\n const pipCallbacks = this._elToPipCallbacks.get(el);\n if (!pipCallbacks) {\n const onEnterPip = event => this._onEnterPip(event, el);\n const onLeavePip = event => this._onLeavePip(event, el);\n const onResizePip = event => this._onResizePip(event, el);\n\n el.addEventListener('enterpictureinpicture', onEnterPip);\n el.addEventListener('leavepictureinpicture', onLeavePip);\n this._elToPipCallbacks.set(el, { onEnterPip, onLeavePip, onResizePip });\n }\n }\n\n _onEnterPip(event, videoEl) {\n this._log.debug('onEnterPip');\n const pipWindow = event.pictureInPictureWindow;\n this._elToPipWindows.set(videoEl, pipWindow);\n const { onResizePip } = this._elToPipCallbacks.get(videoEl);\n pipWindow.addEventListener('resize', onResizePip);\n maybeUpdateEnabledHint(this);\n }\n\n _onLeavePip(event, videoEl) {\n this._log.debug('onLeavePip');\n this._elToPipWindows.delete(videoEl);\n const { onResizePip } = this._elToPipCallbacks.get(videoEl);\n const pipWindow = event.pictureInPictureWindow;\n pipWindow.removeEventListener('resize', onResizePip);\n maybeUpdateEnabledHint(this);\n }\n\n _onResizePip() {\n maybeUpdateDimensionHint(this);\n }\n\n attach(el) {\n const result = super.attach(el);\n\n if (this._clientTrackSwitchOffControl === 'auto') {\n // start off the element as invisible. will mark it\n // visible (and update render hints) once intersection observer calls back.\n this._invisibleElements.add(result);\n }\n\n this._intersectionObserver.observe(result);\n this._resizeObserver.observe(result);\n\n if (this._enableDocumentVisibilityTurnOff) {\n this._documentVisibilityTurnOffCleanup = this._documentVisibilityTurnOffCleanup || setupDocumentVisibilityTurnOff(this);\n }\n\n this._observePip(result);\n return result;\n }\n\n detach(el) {\n const result = super.detach(el);\n const elements = Array.isArray(result) ? result : [result];\n elements.forEach(element => {\n this._intersectionObserver.unobserve(element);\n this._resizeObserver.unobserve(element);\n this._invisibleElements.delete(element);\n this._unObservePip(element);\n });\n\n if (this._attachments.size === 0) {\n if (this._documentVisibilityTurnOffCleanup) {\n this._documentVisibilityTurnOffCleanup();\n this._documentVisibilityTurnOffCleanup = null;\n }\n }\n\n maybeUpdateEnabledHint(this);\n maybeUpdateDimensionHint(this);\n return result;\n }\n\n /**\n * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.\n * When a Participant un-publishes and re-publishes a VideoTrack, a new RemoteVideoTrack is created and\n * any VideoProcessors attached to the previous RemoteVideoTrack would have to be re-added again.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.\n * @param {AddProcessorOptions} [options] - {@link AddProcessorOptions} to provide.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const grayscaleProcessor = new GrayScaleProcessor(100);\n *\n * Array.from(room.participants.values()).forEach(participant => {\n * const remoteVideoTrack = Array.from(participant.videoTracks.values())[0].track;\n * remoteVideoTrack.addProcessor(grayscaleProcessor);\n * });\n */\n addProcessor() {\n return super.addProcessor.apply(this, arguments);\n }\n\n /**\n * Remove the previously added {@link VideoProcessor} using `addProcessor` API.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to remove.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const grayscaleProcessor = new GrayScaleProcessor(100);\n *\n * Array.from(room.participants.values()).forEach(participant => {\n * const remoteVideoTrack = Array.from(participant.videoTracks.values())[0].track;\n * remoteVideoTrack.addProcessor(grayscaleProcessor);\n * });\n *\n * document.getElementById('remove-button').onclick = () => {\n * Array.from(room.participants.values()).forEach(participant => {\n * const remoteVideoTrack = Array.from(participant.videoTracks.values())[0].track;\n * remoteVideoTrack.removeProcessor(grayscaleProcessor);\n * });\n * }\n */\n removeProcessor() {\n return super.removeProcessor.apply(this, arguments);\n }\n\n toString() {\n return `[RemoteVideoTrack #${this._instanceId}: ${this.sid}]`;\n }\n\n /**\n * Update the subscribe {@link Track.Priority} of the {@link RemoteVideoTrack}.\n * @param {?Track.Priority} priority - the new subscribe {@link Track.Priority};\n * If null, then the subscribe {@link Track.Priority} is cleared, which\n * means the {@link Track.Priority} set by the publisher is now the effective priority.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n return super.setPriority(priority);\n }\n}\n\nfunction setupDocumentVisibilityTurnOff(removeVideoTrack) {\n function onVisibilityChanged() {\n maybeUpdateEnabledHint(removeVideoTrack);\n }\n\n documentVisibilityMonitor.onVisibilityChange(1, onVisibilityChanged);\n return () => {\n documentVisibilityMonitor.offVisibilityChange(1, onVisibilityChanged);\n };\n}\n\nfunction maybeUpdateEnabledHint(remoteVideoTrack) {\n if (remoteVideoTrack._clientTrackSwitchOffControl !== 'auto') {\n return;\n }\n\n const visibleElements = remoteVideoTrack._getAllAttachedElements().filter(el => !remoteVideoTrack._invisibleElements.has(el));\n const pipWindows = remoteVideoTrack._getAllAttachedElements().filter(el => remoteVideoTrack._elToPipWindows.has(el));\n\n // even when document is invisible we may have track playing in pip window.\n const enabled = pipWindows.length > 0 || (document.visibilityState === 'visible' && visibleElements.length > 0);\n\n if (enabled === true) {\n remoteVideoTrack._turnOffTimer.clear();\n remoteVideoTrack._setRenderHint({ enabled: true });\n } else if (!remoteVideoTrack._turnOffTimer.isSet) {\n // set the track to be turned off after some delay.\n remoteVideoTrack._turnOffTimer.start();\n }\n}\n\nfunction maybeUpdateDimensionHint(remoteVideoTrack) {\n if (remoteVideoTrack._contentPreferencesMode !== 'auto') {\n return;\n }\n\n const visibleElements = remoteVideoTrack._getAllAttachedElements().filter(el => !remoteVideoTrack._invisibleElements.has(el));\n const pipElements = remoteVideoTrack._getAllAttachedElements().map(el => {\n const pipWindow = remoteVideoTrack._elToPipWindows.get(el);\n return pipWindow ? { clientHeight: pipWindow.height, clientWidth: pipWindow.width } : { clientHeight: 0, clientWidth: 0 };\n });\n const totalElements = visibleElements.concat(pipElements);\n if (totalElements.length > 0) {\n const [{ clientHeight, clientWidth }] = totalElements.sort((el1, el2) =>\n el2.clientHeight + el2.clientWidth - el1.clientHeight - el1.clientWidth - 1);\n const renderDimensions = { height: clientHeight, width: clientWidth };\n remoteVideoTrack._setRenderHint({ renderDimensions });\n }\n}\n\n/**\n * @typedef {object} VideoContentPreferences\n * @property {VideoTrack.Dimensions} [renderDimensions] - Render Dimensions to request for the {@link RemoteVideoTrack}.\n */\n\n/**\n * The {@link RemoteVideoTrack}'s dimensions changed.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} whose\n * dimensions changed\n * @event RemoteVideoTrack#dimensionsChanged\n */\n\n/**\n * The {@link RemoteVideoTrack} was disabled, i.e. \"paused\".\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * disabled\n * @event RemoteVideoTrack#disabled\n */\n\n/**\n * The {@link RemoteVideoTrack} was enabled, i.e. \"resumed\".\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * enabled\n * @event RemoteVideoTrack#enabled\n */\n\n/**\n * The {@link RemoteVideoTrack} started. This means there is enough video data\n * to begin playback.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that started\n * @event RemoteVideoTrack#started\n */\n\n/**\n * A {@link RemoteVideoTrack} was switched off.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * switched off\n * @event RemoteVideoTrack#switchedOff\n */\n\n/**\n * A {@link RemoteVideoTrack} was switched on.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * switched on\n * @event RemoteVideoTrack#switchedOn\n */\n\nmodule.exports = RemoteVideoTrack;\n", "'use strict';\n\nconst RemoteTrackPublication = require('./remotetrackpublication');\n\n/**\n * A {@link RemoteVideoTrackPublication} represents a {@link RemoteVideoTrack}\n * that has been published to a {@link Room}.\n * @property {Track.Kind} kind - \"video\"\n * @property {?RemoteVideoTrack} track - unless you have subscribed to the\n * {@link RemoteVideoTrack}, this property is null\n * @emits RemoteVideoTrackPublication#subscribed\n * @emits RemoteVideoTrackPublication#subscriptionFailed\n * @emits RemoteVideoTrackPublication#trackDisabled\n * @emits RemoteVideoTrackPublication#trackEnabled\n * @emits RemoteVideoTrackPublication#unsubscribed\n */\nclass RemoteVideoTrackPublication extends RemoteTrackPublication {\n /**\n * Construct a {@link RemoteVideoTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling, options);\n }\n\n toString() {\n return `[RemoteVideoTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteVideoTrack}.\n * @param {RemoteVideoTrack} track - the {@link RemoteVideoTrack} that was subscribed to\n * @event RemoteVideoTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteVideoTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteVideoTrack} could not be\n * subscribed to\n * @event RemoteVideoTrackPublication#subscriptionFailed\n */\n\n/**\n * The {@link RemoteVideoTrack} was disabled.\n * @event RemoteVideoTrackPublication#trackDisabled\n */\n\n/**\n * The {@link RemoteVideoTrack} was enabled.\n * @event RemoteVideoTrackPublication#trackEnabled\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteVideoTrack}.\n * @param {RemoteVideoTrack} track - the {@link RemoteVideoTrack} that was unsubscribed from\n * @event RemoteVideoTrackPublication#unsubscribed\n */\n\nmodule.exports = RemoteVideoTrackPublication;\n", "'use strict';\n\nconst EventEmitter = require('./eventemitter');\nconst RemoteAudioTrack = require('./media/track/remoteaudiotrack');\nconst RemoteAudioTrackPublication = require('./media/track/remoteaudiotrackpublication');\nconst RemoteDataTrack = require('./media/track/remotedatatrack');\nconst RemoteDataTrackPublication = require('./media/track/remotedatatrackpublication');\nconst RemoteVideoTrack = require('./media/track/remotevideotrack');\nconst RemoteVideoTrackPublication = require('./media/track/remotevideotrackpublication');\nconst util = require('./util');\n\nlet nInstances = 0;\n\n/**\n * {@link NetworkQualityLevel} is a value from 0\u20135, inclusive, representing the\n * quality of a network connection.\n * @typedef {number} NetworkQualityLevel\n */\n\n/**\n * @extends EventEmitter\n * @property {Map} audioTracks -\n * The {@link Participant}'s {@link AudioTrackPublication}s\n * @property {Map} dataTracks -\n * The {@link Participant}'s {@link DataTrackPublication}s.\n * @property {Participant.Identity} identity - The identity of the {@link Participant}\n * @property {?NetworkQualityLevel} networkQualityLevel - The\n * {@link Participant}'s current {@link NetworkQualityLevel}, if any\n * @property {?NetworkQualityStats} networkQualityStats - The\n * {@link Participant}'s current {@link NetworkQualityStats}, if any\n * @property {Participant.SID} sid - The {@link Participant}'s SID\n * @property {string} state - \"connected\", \"disconnected\" or \"reconnecting\"\n * @property {Map} tracks -\n * The {@link Participant}'s {@link TrackPublication}s\n * @property {Map} videoTracks -\n * The {@link Participant}'s {@link VideoTrackPublication}s\n * @emits Participant#disconnected\n * @emits Participant#networkQualityLevelChanged\n * @emits Participant#reconnected\n * @emits Participant#reconnecting\n * @emits Participant#trackDimensionsChanged\n * @emits Participant#trackStarted\n */\nclass Participant extends EventEmitter {\n /**\n * Construct a {@link Participant}.\n * @param {ParticipantSignaling} signaling\n * @param {object} [options]\n */\n constructor(signaling, options) {\n super();\n\n options = Object.assign({\n RemoteAudioTrack,\n RemoteAudioTrackPublication,\n RemoteDataTrack,\n RemoteDataTrackPublication,\n RemoteVideoTrack,\n RemoteVideoTrackPublication,\n tracks: []\n }, options);\n\n const indexed = indexTracksById(options.tracks);\n const log = options.log.createLog('default', this);\n const audioTracks = new Map(indexed.audioTracks);\n const dataTracks = new Map(indexed.dataTracks);\n const tracks = new Map(indexed.tracks);\n const videoTracks = new Map(indexed.videoTracks);\n\n Object.defineProperties(this, {\n _RemoteAudioTrack: {\n value: options.RemoteAudioTrack\n },\n _RemoteAudioTrackPublication: {\n value: options.RemoteAudioTrackPublication\n },\n _RemoteDataTrack: {\n value: options.RemoteDataTrack\n },\n _RemoteDataTrackPublication: {\n value: options.RemoteDataTrackPublication\n },\n _RemoteVideoTrack: {\n value: options.RemoteVideoTrack\n },\n _RemoteVideoTrackPublication: {\n value: options.RemoteVideoTrackPublication\n },\n _audioTracks: {\n value: audioTracks\n },\n _dataTracks: {\n value: dataTracks\n },\n _instanceId: {\n value: ++nInstances\n },\n _clientTrackSwitchOffControl: {\n value: options.clientTrackSwitchOffControl,\n },\n _contentPreferencesMode: {\n value: options.contentPreferencesMode,\n },\n _log: {\n value: log\n },\n _signaling: {\n value: signaling\n },\n _tracks: {\n value: tracks\n },\n _trackEventReemitters: {\n value: new Map()\n },\n _trackPublicationEventReemitters: {\n value: new Map()\n },\n _trackSignalingUpdatedEventCallbacks: {\n value: new Map()\n },\n _videoTracks: {\n value: videoTracks\n },\n audioTracks: {\n enumerable: true,\n value: new Map()\n },\n dataTracks: {\n enumerable: true,\n value: new Map()\n },\n identity: {\n enumerable: true,\n get() {\n return signaling.identity;\n }\n },\n networkQualityLevel: {\n enumerable: true,\n get() {\n return signaling.networkQualityLevel;\n }\n },\n networkQualityStats: {\n enumerable: true,\n get() {\n return signaling.networkQualityStats;\n }\n },\n sid: {\n enumerable: true,\n get() {\n return signaling.sid;\n }\n },\n state: {\n enumerable: true,\n get() {\n return signaling.state;\n }\n },\n tracks: {\n enumerable: true,\n value: new Map()\n },\n videoTracks: {\n enumerable: true,\n value: new Map()\n }\n });\n\n this._tracks.forEach(reemitTrackEvents.bind(null, this));\n signaling.on('networkQualityLevelChanged', () =>\n this.emit('networkQualityLevelChanged', this.networkQualityLevel,\n this.networkQualityStats &&\n (this.networkQualityStats.audio || this.networkQualityStats.video)\n ? this.networkQualityStats\n : null));\n reemitSignalingStateChangedEvents(this, signaling);\n log.info(`Created a new Participant${this.identity ? `: ${this.identity}` : ''}`);\n }\n\n /**\n * Get the {@link RemoteTrack} events to re-emit.\n * @private\n * @returns {Array>} events\n */\n _getTrackEvents() {\n return [\n ['dimensionsChanged', 'trackDimensionsChanged'],\n ['message', 'trackMessage'],\n ['started', 'trackStarted']\n ];\n }\n\n /**\n * @private\n */\n _getTrackPublicationEvents() {\n return [];\n }\n\n toString() {\n return `[Participant #${this._instanceId}: ${this.sid}]`;\n }\n\n /**\n * @private\n * @param {RemoteTrack} track\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _addTrack(track, id) {\n const log = this._log;\n if (this._tracks.has(id)) {\n return null;\n }\n this._tracks.set(id, track);\n\n const tracksByKind = {\n audio: this._audioTracks,\n video: this._videoTracks,\n data: this._dataTracks\n }[track.kind];\n tracksByKind.set(id, track);\n reemitTrackEvents(this, track, id);\n\n log.info(`Added a new ${util.trackClass(track)}:`, id);\n log.debug(`${util.trackClass(track)}:`, track);\n\n return track;\n }\n\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _addTrackPublication(publication) {\n const log = this._log;\n if (this.tracks.has(publication.trackSid)) {\n return null;\n }\n this.tracks.set(publication.trackSid, publication);\n\n const trackPublicationsByKind = {\n audio: this.audioTracks,\n data: this.dataTracks,\n video: this.videoTracks\n }[publication.kind];\n trackPublicationsByKind.set(publication.trackSid, publication);\n reemitTrackPublicationEvents(this, publication);\n\n log.info(`Added a new ${util.trackPublicationClass(publication)}:`, publication.trackSid);\n log.debug(`${util.trackPublicationClass(publication)}:`, publication);\n return publication;\n }\n\n /**\n * @private\n */\n _handleTrackSignalingEvents() {\n const { _log: log, _clientTrackSwitchOffControl: clientTrackSwitchOffControl, _contentPreferencesMode: contentPreferencesMode } = this;\n const self = this;\n\n if (this.state === 'disconnected') {\n return;\n }\n\n const RemoteAudioTrack = this._RemoteAudioTrack;\n const RemoteAudioTrackPublication = this._RemoteAudioTrackPublication;\n const RemoteVideoTrack = this._RemoteVideoTrack;\n const RemoteVideoTrackPublication = this._RemoteVideoTrackPublication;\n const RemoteDataTrack = this._RemoteDataTrack;\n const RemoteDataTrackPublication = this._RemoteDataTrackPublication;\n const participantSignaling = this._signaling;\n\n function trackSignalingAdded(signaling) {\n const RemoteTrackPublication = {\n audio: RemoteAudioTrackPublication,\n data: RemoteDataTrackPublication,\n video: RemoteVideoTrackPublication\n }[signaling.kind];\n\n const publication = new RemoteTrackPublication(signaling, { log });\n self._addTrackPublication(publication);\n\n let isSubscribed = signaling.isSubscribed;\n if (isSubscribed) {\n trackSignalingSubscribed(signaling);\n }\n\n self._trackSignalingUpdatedEventCallbacks.set(signaling.sid, () => {\n if (isSubscribed !== signaling.isSubscribed) {\n isSubscribed = signaling.isSubscribed;\n if (isSubscribed) {\n trackSignalingSubscribed(signaling);\n return;\n }\n trackSignalingUnsubscribed(signaling);\n }\n });\n signaling.on('updated', self._trackSignalingUpdatedEventCallbacks.get(signaling.sid));\n }\n\n function trackSignalingRemoved(signaling) {\n if (signaling.isSubscribed) {\n signaling.setTrackTransceiver(null);\n }\n const updated = self._trackSignalingUpdatedEventCallbacks.get(signaling.sid);\n if (updated) {\n signaling.removeListener('updated', updated);\n self._trackSignalingUpdatedEventCallbacks.delete(signaling.sid);\n }\n const publication = self.tracks.get(signaling.sid);\n if (publication) {\n self._removeTrackPublication(publication);\n }\n }\n\n function trackSignalingSubscribed(signaling) {\n const { isEnabled, name, kind, sid, trackTransceiver, isSwitchedOff } = signaling;\n const RemoteTrack = {\n audio: RemoteAudioTrack,\n video: RemoteVideoTrack,\n data: RemoteDataTrack\n }[kind];\n\n const publication = self.tracks.get(sid);\n\n // NOTE(mroberts): It should never be the case that the TrackSignaling and\n // MediaStreamTrack or DataTrackReceiver kinds disagree; however, just in\n // case, we handle it here.\n if (!RemoteTrack || kind !== trackTransceiver.kind) {\n return;\n }\n\n const options = { log, name, clientTrackSwitchOffControl, contentPreferencesMode };\n const setPriority = newPriority => participantSignaling.updateSubscriberTrackPriority(sid, newPriority);\n const setRenderHint = renderHint => {\n if (signaling.isSubscribed) {\n participantSignaling.updateTrackRenderHint(sid, renderHint);\n }\n };\n const track = kind === 'data'\n ? new RemoteTrack(sid, trackTransceiver, options)\n : new RemoteTrack(sid, trackTransceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options);\n\n self._addTrack(track, publication, trackTransceiver.id);\n }\n\n function trackSignalingUnsubscribed(signaling) {\n const [id, track] = Array.from(self._tracks.entries()).find(([, track]) => track.sid === signaling.sid);\n const publication = self.tracks.get(signaling.sid);\n if (track) {\n self._removeTrack(track, publication, id);\n }\n }\n\n participantSignaling.on('trackAdded', trackSignalingAdded);\n participantSignaling.on('trackRemoved', trackSignalingRemoved);\n\n participantSignaling.tracks.forEach(trackSignalingAdded);\n\n participantSignaling.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n log.debug('Removing event listeners');\n participantSignaling.removeListener('stateChanged', stateChanged);\n participantSignaling.removeListener('trackAdded', trackSignalingAdded);\n participantSignaling.removeListener('trackRemoved', trackSignalingRemoved);\n } else if (state === 'connected') {\n // NOTE(mmalavalli): Any transition to \"connected\" here is a result of\n // successful signaling reconnection, and not a first-time establishment\n // of the signaling connection.\n log.info('reconnected');\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => self.emit('reconnected'), 0);\n\n }\n });\n }\n\n /**\n * @private\n * @param {RemoteTrack} track\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _removeTrack(track, id) {\n if (!this._tracks.has(id)) {\n return null;\n }\n this._tracks.delete(id);\n\n const tracksByKind = {\n audio: this._audioTracks,\n video: this._videoTracks,\n data: this._dataTracks\n }[track.kind];\n tracksByKind.delete(id);\n\n const reemitters = this._trackEventReemitters.get(id) || new Map();\n reemitters.forEach((reemitter, event) => {\n track.removeListener(event, reemitter);\n });\n\n const log = this._log;\n log.info(`Removed a ${util.trackClass(track)}:`, id);\n log.debug(`${util.trackClass(track)}:`, track);\n return track;\n }\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _removeTrackPublication(publication) {\n publication = this.tracks.get(publication.trackSid);\n if (!publication) {\n return null;\n }\n this.tracks.delete(publication.trackSid);\n\n const trackPublicationsByKind = {\n audio: this.audioTracks,\n data: this.dataTracks,\n video: this.videoTracks\n }[publication.kind];\n trackPublicationsByKind.delete(publication.trackSid);\n\n const reemitters = this._trackPublicationEventReemitters.get(publication.trackSid) || new Map();\n reemitters.forEach((reemitter, event) => {\n publication.removeListener(event, reemitter);\n });\n\n const log = this._log;\n log.info(`Removed a ${util.trackPublicationClass(publication)}:`, publication.trackSid);\n log.debug(`${util.trackPublicationClass(publication)}:`, publication);\n return publication;\n }\n\n toJSON() {\n return util.valueToJSON(this);\n }\n}\n\n/**\n * A {@link Participant.SID} is a 34-character string starting with \"PA\"\n * that uniquely identifies a {@link Participant}.\n * @type string\n * @typedef Participant.SID\n */\n\n/**\n * A {@link Participant.Identity} is a string that identifies a\n * {@link Participant}. You can think of it like a name.\n * @typedef {string} Participant.Identity\n */\n\n/**\n * The {@link Participant} has disconnected.\n * @param {Participant} participant - The {@link Participant} that disconnected.\n * @event Participant#disconnected\n */\n\n/**\n * The {@link Participant}'s {@link NetworkQualityLevel} changed.\n * @param {NetworkQualityLevel} networkQualityLevel - The new\n * {@link NetworkQualityLevel}\n * @param {?NetworkQualityStats} networkQualityStats - The {@link NetworkQualityStats}\n * based on which {@link NetworkQualityLevel} is calculated, if any\n * @event Participant#networkQualityLevelChanged\n */\n\n/**\n * The {@link Participant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @event Participant#reconnected\n */\n\n/**\n * The {@link Participant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @event Participant#reconnecting\n */\n\n/**\n * One of the {@link Participant}'s {@link VideoTrack}'s dimensions changed.\n * @param {VideoTrack} track - The {@link VideoTrack} whose dimensions changed\n * @event Participant#trackDimensionsChanged\n */\n\n/**\n * One of the {@link Participant}'s {@link Track}s started.\n * @param {Track} track - The {@link Track} that started\n * @event Participant#trackStarted\n */\n\n/**\n * Indexed {@link Track}s by {@link Track.ID}.\n * @typedef {object} IndexedTracks\n * @property {Array<{0: Track.ID, 1: AudioTrack}>} audioTracks - Indexed\n * {@link AudioTrack}s\n * @property {Array<{0: Track.ID, 1: DataTrack}>} dataTracks - Indexed\n * {@link DataTrack}s\n * @property {Array<{0: Track.ID, 1: Track}>} tracks - Indexed {@link Track}s\n * @property {Array<{0: Track.ID, 1: VideoTrack}>} videoTracks - Indexed\n * {@link VideoTrack}s\n * @private\n */\n\n/**\n * Index tracks by {@link Track.ID}.\n * @param {Array} tracks\n * @returns {IndexedTracks}\n * @private\n */\nfunction indexTracksById(tracks) {\n const indexedTracks = tracks.map(track => [track.id, track]);\n const indexedAudioTracks = indexedTracks.filter(keyValue => keyValue[1].kind === 'audio');\n const indexedVideoTracks = indexedTracks.filter(keyValue => keyValue[1].kind === 'video');\n const indexedDataTracks = indexedTracks.filter(keyValue => keyValue[1].kind === 'data');\n\n return {\n audioTracks: indexedAudioTracks,\n dataTracks: indexedDataTracks,\n tracks: indexedTracks,\n videoTracks: indexedVideoTracks\n };\n}\n\n/**\n * Re-emit {@link ParticipantSignaling} 'stateChanged' events.\n * @param {Participant} participant\n * @param {ParticipantSignaling} signaling\n * @private\n */\nfunction reemitSignalingStateChangedEvents(participant, signaling) {\n const log = participant._log;\n\n if (participant.state === 'disconnected') {\n return;\n }\n\n // Reemit state transition events from the ParticipantSignaling.\n signaling.on('stateChanged', function stateChanged(state) {\n log.debug('Transitioned to state:', state);\n participant.emit(state, participant);\n if (state === 'disconnected') {\n log.debug('Removing Track event reemitters');\n signaling.removeListener('stateChanged', stateChanged);\n\n participant._tracks.forEach(track => {\n const reemitters = participant._trackEventReemitters.get(track.id);\n if (track && reemitters) {\n reemitters.forEach((reemitter, event) => {\n track.removeListener(event, reemitter);\n });\n }\n });\n\n // eslint-disable-next-line no-warning-comments\n // TODO(joma): Removing this introduced unit test failures in the RemoteParticipant.\n // Investigate further before removing.\n signaling.tracks.forEach(trackSignaling => {\n const track = participant._tracks.get(trackSignaling.id);\n const reemitters = participant._trackEventReemitters.get(trackSignaling.id);\n if (track && reemitters) {\n reemitters.forEach((reemitter, event) => {\n track.removeListener(event, reemitter);\n });\n }\n });\n\n participant._trackEventReemitters.clear();\n\n participant.tracks.forEach(publication => {\n participant._trackPublicationEventReemitters.get(publication.trackSid)\n .forEach((reemitter, event) => {\n publication.removeListener(event, reemitter);\n });\n });\n participant._trackPublicationEventReemitters.clear();\n }\n });\n}\n\n/**\n * Re-emit {@link Track} events.\n * @param {Participant} participant\n * @param {Track} track\n * @param {Track.ID} id\n * @private\n */\nfunction reemitTrackEvents(participant, track, id) {\n const trackEventReemitters = new Map();\n\n if (participant.state === 'disconnected') {\n return;\n }\n\n participant._getTrackEvents().forEach(eventPair => {\n const trackEvent = eventPair[0];\n const participantEvent = eventPair[1];\n\n trackEventReemitters.set(trackEvent, function() {\n const args = [participantEvent].concat([].slice.call(arguments));\n return participant.emit(...args);\n });\n\n track.on(trackEvent, trackEventReemitters.get(trackEvent));\n });\n\n participant._trackEventReemitters.set(id, trackEventReemitters);\n}\n\n/**\n * Re-emit {@link TrackPublication} events.\n * @private\n * @param {Participant} participant\n * @param {TrackPublication} publication\n */\nfunction reemitTrackPublicationEvents(participant, publication) {\n const publicationEventReemitters = new Map();\n\n if (participant.state === 'disconnected') {\n return;\n }\n\n participant._getTrackPublicationEvents().forEach(([publicationEvent, participantEvent]) => {\n publicationEventReemitters.set(publicationEvent, (...args) => {\n participant.emit(participantEvent, ...args, publication);\n });\n publication.on(publicationEvent, publicationEventReemitters.get(publicationEvent));\n });\n\n participant._trackPublicationEventReemitters.set(publication.trackSid, publicationEventReemitters);\n}\n\nmodule.exports = Participant;\n", "'use strict';\n\nconst { MediaStreamTrack } = require('./webrtc');\nconst { asLocalTrack, asLocalTrackPublication, trackClass } = require('./util');\nconst { typeErrors: E, trackPriority } = require('./util/constants');\nconst { validateLocalTrack } = require('./util/validate');\n\nconst {\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack\n} = require('./media/track/es5');\n\nconst LocalAudioTrackPublication = require('./media/track/localaudiotrackpublication');\nconst LocalDataTrackPublication = require('./media/track/localdatatrackpublication');\nconst LocalVideoTrackPublication = require('./media/track/localvideotrackpublication');\nconst Participant = require('./participant');\n\n/**\n * A {@link LocalParticipant} represents the local {@link Participant} in a\n * {@link Room}.\n * @extends Participant\n * @property {Map} audioTracks -\n * The {@link LocalParticipant}'s {@link LocalAudioTrackPublication}s\n * @property {Map} dataTracks -\n * The {@link LocalParticipant}'s {@link LocalDataTrackPublication}s\n * @property {Map} tracks -\n * The {@link LocalParticipant}'s {@link LocalTrackPublication}s\n * @property {Map} videoTracks -\n * The {@link LocalParticipant}'s {@link LocalVideoTrackPublication}s\n * @property {string} signalingRegion - The geographical region of the\n * signaling edge the {@link LocalParticipant} is connected to.\n *\n * @emits RemoteParticipant#reconnected\n * @emits RemoteParticipant#reconnecting\n * @emits LocalParticipant#trackDimensionsChanged\n * @emits LocalParticipant#trackDisabled\n * @emits LocalParticipant#trackEnabled\n * @emits LocalParticipant#trackPublicationFailed\n * @emits LocalParticipant#trackPublished\n * @emits LocalParticipant#trackStarted\n * @emits LocalParticipant#trackStopped\n * @emits LocalParticipant#trackWarning\n * @emits LocalParticipant#trackWarningsCleared\n */\nclass LocalParticipant extends Participant {\n /**\n * Construct a {@link LocalParticipant}.\n * @param {ParticipantSignaling} signaling\n * @param {Array} localTracks\n * @param {Object} options\n */\n constructor(signaling, localTracks, options) {\n options = Object.assign({\n LocalAudioTrack,\n LocalVideoTrack,\n LocalDataTrack,\n MediaStreamTrack,\n LocalAudioTrackPublication,\n LocalVideoTrackPublication,\n LocalDataTrackPublication,\n shouldStopLocalTracks: false,\n tracks: localTracks\n }, options);\n\n const tracksToStop = options.shouldStopLocalTracks\n ? new Set(localTracks.filter(localTrack => localTrack.kind !== 'data'))\n : new Set();\n\n super(signaling, options);\n\n Object.defineProperties(this, {\n _eventObserver: {\n value: options.eventObserver\n },\n _LocalAudioTrack: {\n value: options.LocalAudioTrack\n },\n _LocalDataTrack: {\n value: options.LocalDataTrack\n },\n _LocalVideoTrack: {\n value: options.LocalVideoTrack\n },\n _MediaStreamTrack: {\n value: options.MediaStreamTrack\n },\n _LocalAudioTrackPublication: {\n value: options.LocalAudioTrackPublication\n },\n _LocalDataTrackPublication: {\n value: options.LocalDataTrackPublication\n },\n _LocalVideoTrackPublication: {\n value: options.LocalVideoTrackPublication\n },\n _tracksToStop: {\n value: tracksToStop\n },\n signalingRegion: {\n enumerable: true,\n get() {\n return signaling.signalingRegion;\n }\n }\n });\n\n this._handleTrackSignalingEvents();\n }\n\n /**\n * @private\n * @param {LocalTrack} track\n * @param {Track.ID} id\n * @param {Track.Priority} priority\n * @returns {?LocalTrack}\n */\n _addTrack(track, id, priority) {\n const addedTrack = super._addTrack(track, id);\n if (addedTrack && this.state !== 'disconnected') {\n this._addLocalTrack(track, priority);\n }\n return addedTrack;\n }\n\n /**\n * @private\n * @param {LocalTrack} track\n * @param {Track.Priority} priority\n * @returns {void}\n */\n _addLocalTrack(track, priority) {\n // check if track has noise cancellation enabled.\n const vendor = track.noiseCancellation?.vendor;\n this._signaling.addTrack(track._trackSender, track.name, priority, vendor);\n this._log.info(`Added a new ${trackClass(track, true)}:`, track.id);\n this._log.debug(`${trackClass(track, true)}:`, track);\n }\n\n /**\n * @private\n * @param {LocalTrack} track\n * @param {Track.ID} id\n * @returns {?LocalTrack}\n */\n _removeTrack(track, id) {\n const removedTrack = super._removeTrack(track, id);\n if (removedTrack && this.state !== 'disconnected') {\n this._signaling.removeTrack(track._trackSender);\n this._log.info(`Removed a ${trackClass(track, true)}:`, track.id);\n this._log.debug(`${trackClass(track, true)}:`, track);\n }\n return removedTrack;\n }\n\n /**\n * Get the {@link LocalTrack} events to re-emit.\n * @private\n * @returns {Array>} events\n */\n _getTrackEvents() {\n return super._getTrackEvents.call(this).concat([\n ['disabled', 'trackDisabled'],\n ['enabled', 'trackEnabled'],\n ['stopped', 'trackStopped']\n ]);\n }\n\n toString() {\n return `[LocalParticipant #${this._instanceId}${this.sid ? `: ${this.sid}` : ''}]`;\n }\n\n /**\n * @private\n */\n _handleTrackSignalingEvents() {\n const log = this._log;\n\n if (this.state === 'disconnected') {\n return;\n }\n\n const localTrackDisabled = localTrack => {\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (trackSignaling) {\n trackSignaling.disable();\n log.debug(`Disabled the ${trackClass(localTrack, true)}:`, localTrack.id);\n }\n };\n\n const localTrackEnabled = localTrack => {\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (trackSignaling) {\n trackSignaling.enable();\n log.debug(`Enabled the ${trackClass(localTrack, true)}:`, localTrack.id);\n }\n };\n\n const localTrackStopped = localTrack => {\n // NOTE(mroberts): We shouldn't need to check for `stop`, since DataTracks\n // do not emit \"stopped\".\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (trackSignaling) {\n trackSignaling.stop();\n }\n return trackSignaling;\n };\n\n const stateChanged = state => {\n log.debug('Transitioned to state:', state);\n if (state === 'disconnected') {\n log.debug('Removing LocalTrack event listeners');\n this._signaling.removeListener('stateChanged', stateChanged);\n this.removeListener('trackDisabled', localTrackDisabled);\n this.removeListener('trackEnabled', localTrackEnabled);\n this.removeListener('trackStopped', localTrackStopped);\n\n // NOTE(mmalavalli): Remove the stale MediaTrackSender clones so that we\n // do not call replaceTrack() on their RTCRtpSenders.\n this._tracks.forEach(track => {\n const trackSignaling = localTrackStopped(track);\n if (trackSignaling) {\n track._trackSender.removeClone(trackSignaling._trackTransceiver);\n }\n });\n\n log.info(`LocalParticipant disconnected. Stopping ${this._tracksToStop.size} automatically-acquired LocalTracks`);\n this._tracksToStop.forEach(track => {\n track.stop();\n });\n } else if (state === 'connected') {\n // NOTE(mmalavalli): Any transition to \"connected\" here is a result of\n // successful signaling reconnection, and not a first-time establishment\n // of the signaling connection.\n log.info('reconnected');\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => this.emit('reconnected'), 0);\n }\n };\n\n this.on('trackDisabled', localTrackDisabled);\n this.on('trackEnabled', localTrackEnabled);\n this.on('trackStopped', localTrackStopped);\n\n this._signaling.on('stateChanged', stateChanged);\n\n this._tracks.forEach(track => {\n this._addLocalTrack(track, trackPriority.PRIORITY_STANDARD);\n this._getOrCreateLocalTrackPublication(track).catch(error => {\n // Just log a warning for now.\n log.warn(`Failed to get or create LocalTrackPublication for ${track}:`, error);\n });\n });\n }\n\n /**\n * @private\n * @param {LocalTrack} localTrack\n * @returns {Promise}\n */\n _getOrCreateLocalTrackPublication(localTrack) {\n let localTrackPublication = getTrackPublication(this.tracks, localTrack);\n if (localTrackPublication) {\n return Promise.resolve(localTrackPublication);\n }\n\n const log = this._log;\n const self = this;\n\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (!trackSignaling) {\n return Promise.reject(new Error(`Unexpected error: The ${localTrack} cannot be published`));\n }\n\n return new Promise((resolve, reject) => {\n function updated() {\n const error = trackSignaling.error;\n if (error) {\n trackSignaling.removeListener('updated', updated);\n log.warn(`Failed to publish the ${trackClass(localTrack, true)}: ${error.message}`);\n self._removeTrack(localTrack, localTrack.id);\n setTimeout(() => {\n self.emit('trackPublicationFailed', error, localTrack);\n });\n reject(error);\n return;\n }\n\n if (!self._tracks.has(localTrack.id)) {\n trackSignaling.removeListener('updated', updated);\n reject(new Error(`The ${localTrack} was unpublished`));\n return;\n }\n\n const sid = trackSignaling.sid;\n if (!sid) {\n return;\n }\n\n trackSignaling.removeListener('updated', updated);\n\n const options = {\n log,\n LocalAudioTrackPublication: self._LocalAudioTrackPublication,\n LocalDataTrackPublication: self._LocalDataTrackPublication,\n LocalVideoTrackPublication: self._LocalVideoTrackPublication\n };\n\n localTrackPublication = getTrackPublication(self.tracks, localTrack);\n\n const warningHandler = twilioWarningName =>\n self.emit('trackWarning', twilioWarningName, localTrackPublication);\n\n const warningsClearedHandler = () =>\n self.emit('trackWarningsCleared', localTrackPublication);\n\n const unpublish = publication => {\n localTrackPublication.removeListener('trackWarning', warningHandler);\n localTrackPublication.removeListener('trackWarningsCleared', warningsClearedHandler);\n self.unpublishTrack(publication.track);\n };\n\n if (!localTrackPublication) {\n localTrackPublication = asLocalTrackPublication(localTrack, trackSignaling, unpublish, options);\n self._addTrackPublication(localTrackPublication);\n }\n\n localTrackPublication.on('warning', warningHandler);\n localTrackPublication.on('warningsCleared', warningsClearedHandler);\n\n const { state } = self._signaling;\n if (state === 'connected' || state === 'connecting') {\n if (localTrack._processorEventObserver) {\n localTrack._processorEventObserver.on('event', event => {\n self._eventObserver.emit('event', {\n name: event.name,\n payload: event.data,\n group: 'video-processor',\n level: 'info'\n });\n });\n }\n\n // NOTE(csantos): For tracks created before joining a room or already joined but about to publish it\n if (localTrack.processedTrack) {\n localTrack._captureFrames();\n localTrack._setSenderMediaStreamTrack(true);\n }\n }\n if (state === 'connected') {\n setTimeout(() => {\n self.emit('trackPublished', localTrackPublication);\n });\n }\n resolve(localTrackPublication);\n }\n\n trackSignaling.on('updated', updated);\n });\n }\n\n /**\n * Publishes a {@link LocalTrack} to the {@link Room}.\n * @param {LocalTrack} localTrack - The {@link LocalTrack} to publish\n * @param {LocalTrackPublishOptions} [options] - The {@link LocalTrackPublishOptions}\n * for publishing the {@link LocalTrack}\n * @returns {Promise} - Resolves with the corresponding\n * {@link LocalTrackPublication} if successful; In a Large Group Room (Maximum\n * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError}\n * if either the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack}\n * is part of a set of {@link LocalTrack}s which along with the published Tracks exceeds 16.\n * @throws {TypeError}\n * @throws {RangeError}\n * @example\n * var Video = require('twilio-video');\n *\n * Video.connect(token, {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * return Video.createLocalVideoTrack({\n * name: 'camera'\n * }).then(function(localVideoTrack) {\n * return room.localParticipant.publishTrack(localVideoTrack, {\n * priority: 'high'\n * });\n * });\n * }).then(function(publication) {\n * console.log('The LocalTrack \"' + publication.trackName\n * + '\" was successfully published with priority \"'\n * * publication.priority + '\"');\n * });\n *//**\n * Publishes a MediaStreamTrack to the {@link Room}.\n * @param {MediaStreamTrack} mediaStreamTrack - The MediaStreamTrack\n * to publish; if a corresponding {@link LocalAudioTrack} or\n * {@link LocalVideoTrack} has not yet been published, this method will\n * construct one\n * @param {MediaStreamTrackPublishOptions} [options] - The options for publishing\n * the MediaStreamTrack\n * @returns {Promise} - Resolves with the corresponding\n * {@link LocalTrackPublication} if successful; In a Large Group Room (Maximum\n * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError}\n * if the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack}\n * is part of a set of {@link LocalTrack}s which along with the published Tracks exceeds 16.\n * @throws {TypeError}\n * @throws {RangeError}\n * @example\n * var Video = require('twilio-video');\n *\n * Video.connect(token, {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * return navigator.mediaDevices.getUserMedia({\n * video: true\n * }).then(function(mediaStream) {\n * var mediaStreamTrack = mediaStream.getTracks()[0];\n * return room.localParticipant.publishTrack(mediaStreamTrack, {\n * name: 'camera',\n * priority: 'high'\n * });\n * });\n * }).then(function(publication) {\n * console.log('The LocalTrack \"' + publication.trackName\n * + '\" was successfully published with priority \"'\n * * publication.priority + '\"');\n * });\n */\n publishTrack(localTrackOrMediaStreamTrack, options) {\n const trackPublication = getTrackPublication(this.tracks, localTrackOrMediaStreamTrack);\n if (trackPublication) {\n return Promise.resolve(trackPublication);\n }\n\n options = Object.assign({\n log: this._log,\n priority: trackPriority.PRIORITY_STANDARD,\n LocalAudioTrack: this._LocalAudioTrack,\n LocalDataTrack: this._LocalDataTrack,\n LocalVideoTrack: this._LocalVideoTrack,\n MediaStreamTrack: this._MediaStreamTrack\n }, options);\n\n let localTrack;\n try {\n localTrack = asLocalTrack(localTrackOrMediaStreamTrack, options);\n } catch (error) {\n return Promise.reject(error);\n }\n\n const noiseCancellation = localTrack.noiseCancellation;\n const allowedAudioProcessors = this._signaling.audioProcessors;\n if (noiseCancellation && !allowedAudioProcessors.includes(noiseCancellation.vendor)) {\n this._log.warn(`${noiseCancellation.vendor} is not supported in this room. disabling it permanently`);\n noiseCancellation.disablePermanently();\n }\n\n const priorityValues = Object.values(trackPriority);\n if (!priorityValues.includes(options.priority)) {\n // eslint-disable-next-line new-cap\n return Promise.reject(E.INVALID_VALUE('LocalTrackPublishOptions.priority', priorityValues));\n }\n\n let addedLocalTrack = this._addTrack(localTrack, localTrack.id, options.priority)\n || this._tracks.get(localTrack.id);\n\n return this._getOrCreateLocalTrackPublication(addedLocalTrack);\n }\n\n /**\n * Publishes multiple {@link LocalTrack}s to the {@link Room}.\n * @param {Array} tracks - The {@link LocalTrack}s\n * to publish; for any MediaStreamTracks provided, if a corresponding\n * {@link LocalAudioTrack} or {@link LocalVideoTrack} has not yet been\n * published, this method will construct one\n * @returns {Promise>} - The resulting\n * {@link LocalTrackPublication}s if successful; In a Large Group Room (Maximum\n * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError}\n * if the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack}s\n * along with the published Tracks exceeds 16.\n * @throws {TypeError}\n */\n publishTracks(tracks) {\n if (!Array.isArray(tracks)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('tracks',\n 'Array of LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n }\n return Promise.all(tracks.map(this.publishTrack, this));\n }\n\n setBandwidthProfile() {\n this._log.warn('setBandwidthProfile is not implemented yet and may be available in future versions of twilio-video.js');\n }\n\n /**\n * Sets the {@link NetworkQualityVerbosity} for the {@link LocalParticipant} and\n * {@link RemoteParticipant}s. It does nothing if Network Quality is not enabled\n * while calling {@link connect}.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration - The new\n * {@link NetworkQualityConfiguration}; If either or both of the local and\n * remote {@link NetworkQualityVerbosity} values are absent, then the corresponding\n * existing values are retained\n * @returns {this}\n * @example\n * // Update verbosity levels for both LocalParticipant and RemoteParticipants\n * localParticipant.setNetworkQualityConfiguration({\n * local: 1,\n * remote: 2\n * });\n * @example\n * // Update verbosity level for only the LocalParticipant\n * localParticipant.setNetworkQualityConfiguration({\n * local: 1\n * });\n * @example\n * // Update verbosity level for only the RemoteParticipants\n * localParticipant.setNetworkQualityConfiguration({\n * remote: 2\n * });\n */\n setNetworkQualityConfiguration(networkQualityConfiguration) {\n if (typeof networkQualityConfiguration !== 'object'\n || networkQualityConfiguration === null) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('networkQualityConfiguration', 'NetworkQualityConfiguration');\n }\n ['local', 'remote'].forEach(prop => {\n if (prop in networkQualityConfiguration && (typeof networkQualityConfiguration[prop] !== 'number' || isNaN(networkQualityConfiguration[prop]))) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE(`networkQualityConfiguration.${prop}`, 'number');\n }\n });\n this._signaling.setNetworkQualityConfiguration(networkQualityConfiguration);\n return this;\n }\n\n /**\n * Set the {@link LocalParticipant}'s {@link EncodingParameters}.\n * @param {?EncodingParameters} [encodingParameters] - The new\n * {@link EncodingParameters}; If null, then the bitrate limits are removed;\n * If not specified, then the existing bitrate limits are preserved\n * @returns {this}\n * @throws {TypeError}\n */\n setParameters(encodingParameters) {\n if (typeof encodingParameters !== 'undefined'\n && typeof encodingParameters !== 'object') {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('encodingParameters',\n 'EncodingParameters, null or undefined');\n }\n\n if (encodingParameters) {\n if (this._signaling.getParameters().adaptiveSimulcast && encodingParameters.maxVideoBitrate) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('encodingParameters', 'encodingParameters.maxVideoBitrate is not compatible with \"preferredVideoCodecs=auto\"');\n }\n\n ['maxAudioBitrate', 'maxVideoBitrate'].forEach(prop => {\n if (typeof encodingParameters[prop] !== 'undefined'\n && typeof encodingParameters[prop] !== 'number'\n && encodingParameters[prop] !== null) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE(`encodingParameters.${prop}`, 'number, null or undefined');\n }\n });\n } else if (encodingParameters === null) {\n encodingParameters = { maxAudioBitrate: null, maxVideoBitrate: null };\n }\n\n this._signaling.setParameters(encodingParameters);\n return this;\n }\n\n /**\n * Stops publishing a {@link LocalTrack} to the {@link Room}.\n * @param {LocalTrack|MediaStreamTrack} track - The {@link LocalTrack}\n * to stop publishing; if a MediaStreamTrack is provided, this method\n * looks up the corresponding {@link LocalAudioTrack} or\n * {@link LocalVideoTrack} to stop publishing\n * @returns {?LocalTrackPublication} - The corresponding\n * {@link LocalTrackPublication} if the {@link LocalTrack} was previously\n * published, null otherwise\n * @throws {TypeError}\n */\n unpublishTrack(track) {\n validateLocalTrack(track, {\n LocalAudioTrack: this._LocalAudioTrack,\n LocalDataTrack: this._LocalDataTrack,\n LocalVideoTrack: this._LocalVideoTrack,\n MediaStreamTrack: this._MediaStreamTrack\n });\n\n let localTrack = this._tracks.get(track.id);\n if (!localTrack) {\n return null;\n }\n\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n trackSignaling.publishFailed(new Error(`The ${localTrack} was unpublished`));\n\n localTrack = this._removeTrack(localTrack, localTrack.id);\n if (!localTrack) {\n return null;\n }\n\n const localTrackPublication = getTrackPublication(this.tracks, localTrack);\n if (localTrackPublication) {\n this._removeTrackPublication(localTrackPublication);\n }\n return localTrackPublication;\n }\n\n /**\n * Stops publishing multiple {@link LocalTrack}s to the {@link Room}.\n * @param {Array} tracks - The {@link LocalTrack}s\n * to stop publishing; for any MediaStreamTracks provided, this method looks\n * up the corresponding {@link LocalAudioTrack} or {@link LocalVideoTrack} to\n * stop publishing\n * @returns {Array} - The corresponding\n * {@link LocalTrackPublication}s that were successfully unpublished\n * @throws {TypeError}\n */\n unpublishTracks(tracks) {\n if (!Array.isArray(tracks)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('tracks',\n 'Array of LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n }\n\n return tracks.reduce((unpublishedTracks, track) => {\n const unpublishedTrack = this.unpublishTrack(track);\n return unpublishedTrack ? unpublishedTracks.concat(unpublishedTrack) : unpublishedTracks;\n }, []);\n }\n}\n\n/**\n * The {@link LocalParticipant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @event LocalParticipant#reconnected\n */\n\n/**\n * The {@link LocalParticipant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @event LocalParticipant#reconnecting\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalVideoTrack}'s dimensions changed.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} whose dimensions changed\n * @event LocalParticipant#trackDimensionsChanged\n */\n\n/**\n * A {@link LocalTrack} was disabled by the {@link LocalParticipant}.\n * @param {LocalTrack} track - The {@link LocalTrack} that was disabled\n * @event LocalParticipant#trackDisabled\n */\n\n/**\n * A {@link LocalTrack} was enabled by the {@link LocalParticipant}.\n * @param {LocalTrack} track - The {@link LocalTrack} that was enabled\n * @event LocalParticipant#trackEnabled\n */\n\n/**\n * A {@link LocalTrack} failed to publish. Check the error message for more\n * information. In a Large Group Room (Maximum Participants greater than 50),\n * this event is raised with a {@link ParticipantMaxTracksExceededError} either\n * when attempting to publish the {@link LocalTrack} will exceed the Maximum Published\n * Tracks limit of 16, or the {@link LocalTrack} is part of a set of {@link LocalTrack}s\n * which along with the published Tracks exceeds 16.\n * @param {TwilioError} error - A {@link TwilioError} explaining why publication\n * failed\n * @param {LocalTrack} localTrack - The {@link LocalTrack} that failed to\n * publish\n * @event LocalParticipant#trackPublicationFailed\n */\n\n/**\n * A {@link LocalTrack} that was added using {@link LocalParticipant#publishTrack} was successfully published. This event\n * is not raised for {@link LocalTrack}s added in {@link ConnectOptions}.tracks or auto-created within\n * {@link connect}.\n * @param {LocalTrackPublication} publication - The resulting\n * {@link LocalTrackPublication} for the published {@link LocalTrack}\n * @event LocalParticipant#trackPublished\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrack}s started.\n * @param {LocalTrack} track - The {@link LocalTrack} that started\n * @event LocalParticipant#trackStarted\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrack}s stopped, either\n * because {@link LocalTrack#stop} was called or because the underlying\n * MediaStreamTrack ended).\n * @param {LocalTrack} track - The {@link LocalTrack} that stopped\n * @event LocalParticipant#trackStopped\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s encountered a warning.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {string} name - The warning that was raised.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that encountered the warning.\n * @event LocalParticipant#trackWarning\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s cleared all warnings.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that cleared all warnings.\n * @event LocalParticipant#trackWarningsCleared\n */\n\n/**\n * Outgoing media encoding parameters.\n * @typedef {object} EncodingParameters\n * @property {?number} [maxAudioBitrate] - Max outgoing audio bitrate (bps);\n * If not specified, retains the existing bitrate limit; A null or a\n * 0 value removes any previously set bitrate limit; This value is set\n * as a hint for variable bitrate codecs, but will not take effect for fixed bitrate\n * codecs; Based on our tests, Chrome, Firefox and Safari support a bitrate range of\n * 12000 bps to 256000 bps for Opus codec; This parameter has no effect on iSAC, PCMU\n * and PCMA codecs\n * @property {?number} [maxVideoBitrate] - Max outgoing video bitrate (bps);\n * If not specified, retains the existing bitrate limit; A null or\n * a 0 value removes any previously set bitrate limit; This value is\n * set as a hint for variable bitrate codecs, but will not take effect for fixed\n * bitrate codecs; Based on our tests, Chrome, Firefox and Safari all seem to support\n * an average bitrate range of 20000 bps (20 kbps) to 8000000 bps (8 mbps) for a\n * 720p VideoTrack.\n * Note: this limit is not applied for screen share tracks published on Chrome.\n */\n\n/**\n * Options for publishing a {@link LocalTrack}.\n * @typedef {object} LocalTrackPublishOptions\n * @property {Track.Priority} [priority='standard'] - The priority with which the {@link LocalTrack}\n * is to be published; In Group or Small Group Rooms, the appropriate bandwidth is\n * allocated to the {@link LocalTrack} based on its {@link Track.Priority}; It has no\n * effect in Peer-to-Peer Rooms; It defaults to \"standard\" when not provided\n */\n\n/**\n * Options for publishing a {@link MediaStreamTrack}.\n * @typedef {LocalTrackOptions} MediaStreamTrackPublishOptions\n * @property {Track.Priority} [priority='standard'] - The priority with which the {@link LocalTrack}\n * is to be published; In Group or Small Group Rooms, the appropriate bandwidth is\n * allocated to the {@link LocalTrack} based on its {@link Track.Priority}; It has no\n * effect in Peer-to-Peer Rooms; It defaults to \"standard\" when not provided\n */\n\n/**\n * @private\n * @param {Map} trackPublications\n * @param {LocalTrack|MediaStreamTrack} track\n * @returns {?LocalTrackPublication} trackPublication\n */\nfunction getTrackPublication(trackPublications, track) {\n return Array.from(trackPublications.values()).find(trackPublication => trackPublication.track === track\n || trackPublication.track.mediaStreamTrack === track) || null;\n}\n\nmodule.exports = LocalParticipant;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): This should be described as implementing some\n// InsightsPublisher interface.\n'use strict';\n\n/**\n * Null Insights publisher.\n */\nclass InsightsPublisher {\n constructor() {\n Object.defineProperties(this, {\n _connected: {\n writable: true,\n value: true\n }\n });\n }\n\n /**\n * Connect\n * @returns {void}\n */\n connect() {\n }\n\n /**\n * Disconnect.\n * @returns {boolean}\n */\n disconnect() {\n if (this._connected) {\n this._connected = false;\n return true;\n }\n return false;\n }\n\n /**\n * Publish.\n * @returns {boolean}\n */\n publish() {\n return this._connected;\n }\n}\n\nmodule.exports = InsightsPublisher;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\nconst {\n DEFAULT_NQ_LEVEL_LOCAL,\n DEFAULT_NQ_LEVEL_REMOTE,\n MAX_NQ_LEVEL\n} = require('./util/constants');\n\nconst { inRange } = require('./util');\n\n/**\n * {@link NetworkQualityConfigurationImpl} represents an object which notifies its\n * listeners of any changes in the values of its properties.\n * @extends EventEmitter\n * @implements NetworkQualityConfiguration\n * @property {?NetworkQualityVerbosity} local - Verbosity level for {@link LocalParticipant}\n * @property {?NetworkQualityVerbosity} remote - Verbosity level for {@link RemoteParticipant}s\n */\nclass NetworkQualityConfigurationImpl extends EventEmitter {\n /**\n * Construct an {@link NetworkQualityConfigurationImpl}.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration - Initial {@link NetworkQualityConfiguration}\n */\n constructor(networkQualityConfiguration) {\n super();\n\n networkQualityConfiguration = Object.assign({\n local: DEFAULT_NQ_LEVEL_LOCAL,\n remote: DEFAULT_NQ_LEVEL_REMOTE\n }, networkQualityConfiguration);\n\n Object.defineProperties(this, {\n local: {\n value: inRange(networkQualityConfiguration.local, DEFAULT_NQ_LEVEL_LOCAL, MAX_NQ_LEVEL)\n ? networkQualityConfiguration.local\n : DEFAULT_NQ_LEVEL_LOCAL,\n writable: true\n },\n remote: {\n value: inRange(networkQualityConfiguration.remote, DEFAULT_NQ_LEVEL_REMOTE, MAX_NQ_LEVEL)\n ? networkQualityConfiguration.remote\n : DEFAULT_NQ_LEVEL_REMOTE,\n writable: true\n }\n });\n }\n\n /**\n * Update the verbosity levels for network quality information for\n * {@link LocalParticipant} and {@link RemoteParticipant} with those\n * in the given {@link NetworkQualityConfiguration}.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration - The new {@link NetworkQualityConfiguration}\n */\n update(networkQualityConfiguration) {\n networkQualityConfiguration = Object.assign({\n local: this.local,\n remote: this.remote\n }, networkQualityConfiguration);\n\n [\n ['local', DEFAULT_NQ_LEVEL_LOCAL, 3],\n ['remote', DEFAULT_NQ_LEVEL_REMOTE, 3]\n ].forEach(([localOrRemote, min, max]) => {\n this[localOrRemote] = typeof networkQualityConfiguration[localOrRemote] === 'number'\n && inRange(networkQualityConfiguration[localOrRemote], min, max)\n ? networkQualityConfiguration[localOrRemote]\n : min;\n });\n }\n}\n\nmodule.exports = NetworkQualityConfigurationImpl;\n", "'use strict';\n\nconst Participant = require('./participant');\n\n/**\n * A {@link RemoteParticipant} represents a remote {@link Participant} in a\n * {@link Room}.\n * @extends Participant\n * @property {Map} audioTracks -\n * The {@link Participant}'s {@link RemoteAudioTrackPublication}s\n * @property {Map} dataTracks -\n * The {@link Participant}'s {@link RemoteDataTrackPublication}s\n * @property {Map} tracks -\n * The {@link Participant}'s {@link RemoteTrackPublication}s\n * @property {Map} videoTracks -\n * The {@link Participant}'s {@link RemoteVideoTrackPublication}s\n * @emits RemoteParticipant#reconnected\n * @emits RemoteParticipant#reconnecting\n * @emits RemoteParticipant#trackDimensionsChanged\n * @emits RemoteParticipant#trackDisabled\n * @emits RemoteParticipant#trackEnabled\n * @emits RemoteParticipant#trackMessage\n * @emits RemoteParticipant#trackPublished\n * @emits RemoteParticipant#trackPublishPriorityChanged\n * @emits RemoteParticipant#trackStarted\n * @emits RemoteParticipant#trackSubscribed\n * @emits RemoteParticipant#trackSubscriptionFailed\n * @emits RemoteParticipant#trackSwitchedOff\n * @emits RemoteParticipant#trackSwitchedOn\n * @emits RemoteParticipant#trackUnpublished\n * @emits RemoteParticipant#trackUnsubscribed\n */\nclass RemoteParticipant extends Participant {\n /**\n * Construct a {@link RemoteParticipant}.\n * @param {ParticipantSignaling} signaling\n * @param {object} [options]\n */\n constructor(signaling, options) {\n super(signaling, options);\n this._handleTrackSignalingEvents();\n this.once('disconnected', this._unsubscribeTracks.bind(this));\n }\n\n toString() {\n return `[RemoteParticipant #${this._instanceId}${this.sid ? `: ${this.sid}` : ''}]`;\n }\n\n /**\n * @private\n * @param {RemoteTrack} remoteTrack\n * @param {RemoteTrackPublication} publication\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _addTrack(remoteTrack, publication, id) {\n if (!super._addTrack(remoteTrack, id)) {\n return null;\n }\n publication._subscribed(remoteTrack);\n this.emit('trackSubscribed', remoteTrack, publication);\n return remoteTrack;\n }\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _addTrackPublication(publication) {\n const addedPublication = super._addTrackPublication(publication);\n if (!addedPublication) {\n return null;\n }\n this.emit('trackPublished', addedPublication);\n return addedPublication;\n }\n /**\n * @private\n */\n _getTrackPublicationEvents() {\n return [\n ...super._getTrackPublicationEvents(),\n ['subscriptionFailed', 'trackSubscriptionFailed'],\n ['trackDisabled', 'trackDisabled'],\n ['trackEnabled', 'trackEnabled'],\n ['publishPriorityChanged', 'trackPublishPriorityChanged'],\n ['trackSwitchedOff', 'trackSwitchedOff'],\n ['trackSwitchedOn', 'trackSwitchedOn']\n ];\n }\n\n /**\n * @private\n */\n _unsubscribeTracks() {\n this.tracks.forEach(publication => {\n if (publication.isSubscribed) {\n const track = publication.track;\n publication._unsubscribe();\n this.emit('trackUnsubscribed', track, publication);\n }\n });\n }\n\n /**\n * @private\n * @param {RemoteTrack} remoteTrack\n * @param {RemoteTrackPublication} publication\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _removeTrack(remoteTrack, publication, id) {\n const unsubscribedTrack = this._tracks.get(id);\n if (!unsubscribedTrack) {\n return null;\n }\n\n super._removeTrack(unsubscribedTrack, id);\n publication._unsubscribe();\n this.emit('trackUnsubscribed', unsubscribedTrack, publication);\n return unsubscribedTrack;\n }\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _removeTrackPublication(publication) {\n this._signaling.clearTrackHint(publication.trackSid);\n const removedPublication = super._removeTrackPublication(publication);\n if (!removedPublication) {\n return null;\n }\n this.emit('trackUnpublished', removedPublication);\n return removedPublication;\n }\n}\n\n/**\n * The {@link RemoteParticipant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @event RemoteParticipant#reconnected\n */\n\n/**\n * The {@link RemoteParticipant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @event RemoteParticipant#reconnecting\n */\n\n/**\n * One of the {@link RemoteParticipant}'s {@link RemoteVideoTrack}'s dimensions changed.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} whose dimensions changed\n * @event RemoteParticipant#trackDimensionsChanged\n */\n\n/**\n * A {@link RemoteTrack} was disabled by the {@link RemoteParticipant}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} associated with the disabled {@link RemoteTrack}\n * @event RemoteParticipant#trackDisabled\n */\n\n/**\n * A {@link RemoteTrack} was enabled by the {@link RemoteParticipant}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} associated with the enabled {@link RemoteTrack}\n * @event RemoteParticipant#trackEnabled\n */\n\n/**\n * A message was received over one of the {@link RemoteParticipant}'s\n * {@link RemoteDataTrack}s.\n * @event RemoteParticipant#trackMessage\n * @param {string|ArrayBuffer} data\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} over which the\n * message was received\n */\n\n/**\n * A {@link RemoteTrack} was published by the {@link RemoteParticipant} after\n * connecting to the {@link Room}. This event is not emitted for\n * {@link RemoteTrack}s that were published while the {@link RemoteParticipant}\n * was connecting to the {@link Room}.\n * @event RemoteParticipant#trackPublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the published {@link RemoteTrack}\n * @example\n * function trackPublished(publication) {\n * console.log(`Track ${publication.trackSid} was published`);\n * }\n *\n * room.on('participantConnected', participant => {\n * // Handle RemoteTracks published while connecting to the Room.\n * participant.trackPublications.forEach(trackPublished);\n *\n * // Handle RemoteTracks published after connecting to the Room.\n * participant.on('trackPublished', trackPublished);\n * });\n */\n\n/**\n * One of the {@link RemoteParticipant}'s {@link RemoteTrack}s started.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that started\n * @event RemoteParticipant#trackStarted\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was subscribed to.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was subscribed to\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @event RemoteParticipant#trackSubscribed\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} could not be subscribed to.\n * @param {TwilioError} error - The reason the {@link RemoteTrack} could not be\n * subscribed to\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that could not\n * be subscribed to\n * @event RemoteParticipant#trackSubscriptionFailed\n */\n\n/**\n * The {@link RemoteTrackPublication}'s publish {@link Track.Priority} was changed by the\n * {@link RemoteParticipant}.\n * @param {Track.Priority} priority - the {@link RemoteTrack}'s new publish\n * {@link Track.Priority};\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that changed priority\n * @event RemoteParticipant#trackPublishPriorityChanged\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was subscribed to.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched off\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was switched off\n * @event RemoteParticipant#trackSwitchedOff\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was switched on.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched on.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was switched on\n * @event RemoteParticipant#trackSwitchedOn\n */\n\n/**\n * A {@link RemoteTrack} was unpublished by the {@link RemoteParticipant}.\n * @event RemoteParticipant#trackUnpublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the unpublished {@link RemoteTrack}\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was unsubscribed from.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was unsubscribed from\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was unsubscribed from\n * @event RemoteParticipant#trackUnsubscribed\n */\n\nmodule.exports = RemoteParticipant;\n", "'use strict';\n\n/**\n * Statistics for a {@link Track}.\n * @property {Track.ID} trackId - The {@link Track} ID\n * @property {Track.SID} trackSid - The {@link Track}'s SID when published in\n * in a {@link Room}\n * @property {number} timestamp - A Unix timestamp in milliseconds indicating\n * when the {@link TrackStats} were gathered\n * @property {string} ssrc - The {@link Track}'s SSRC when transmitted over the\n * RTCPeerConnection\n * @property {?number} packetsLost - The number of packets lost\n * @property {?string} codec - The name of the codec used to encode the\n * {@link Track}'s media\n */\nclass TrackStats {\n /**\n * @param {string} trackId - {@link Track} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n if (typeof trackId !== 'string') {\n throw new Error('Track id must be a string');\n }\n\n Object.defineProperties(this, {\n trackId: {\n value: trackId,\n enumerable: true\n },\n trackSid: {\n value: statsReport.trackSid,\n enumerable: true\n },\n timestamp: {\n value: statsReport.timestamp,\n enumerable: true\n },\n ssrc: {\n value: statsReport.ssrc,\n enumerable: true\n },\n packetsLost: {\n value: typeof statsReport.packetsLost === 'number'\n ? statsReport.packetsLost\n : null,\n enumerable: true\n },\n codec: {\n value: typeof statsReport.codecName === 'string'\n ? statsReport.codecName\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = TrackStats;\n", "'use strict';\n\nconst TrackStats = require('./trackstats');\n\n/**\n * Statistics for a {@link LocalTrack}.\n * @extends TrackStats\n * @property {?number} bytesSent - Number of bytes sent\n * @property {?number} packetsSent - Number of packets sent\n * @property {?number} roundTripTime - Round trip time in milliseconds\n */\nclass LocalTrackStats extends TrackStats {\n /**\n * @param {string} trackId - {@link LocalTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n * @param {boolean} prepareForInsights\n */\n constructor(trackId, statsReport, prepareForInsights) {\n super(trackId, statsReport);\n\n Object.defineProperties(this, {\n bytesSent: {\n value: typeof statsReport.bytesSent === 'number'\n ? statsReport.bytesSent\n : prepareForInsights ? 0 : null,\n enumerable: true\n },\n packetsSent: {\n value: typeof statsReport.packetsSent === 'number'\n ? statsReport.packetsSent\n : prepareForInsights ? 0 : null,\n enumerable: true\n },\n roundTripTime: {\n value: typeof statsReport.roundTripTime === 'number'\n ? statsReport.roundTripTime\n : prepareForInsights ? 0 : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = LocalTrackStats;\n", "'use strict';\n\nconst LocalTrackStats = require('./localtrackstats');\n\n/**\n * Statistics for a {@link LocalAudioTrack}.\n * @extends LocalTrackStats\n * @property {?AudioLevel} audioLevel - Input {@link AudioLevel}\n * @property {?number} jitter - Audio jitter in milliseconds\n */\nclass LocalAudioTrackStats extends LocalTrackStats {\n /**\n * @param {string} trackId - {@link LocalAudioTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n * @param {boolean} prepareForInsights\n */\n constructor(trackId, statsReport, prepareForInsights) {\n super(trackId, statsReport, prepareForInsights);\n\n Object.defineProperties(this, {\n audioLevel: {\n value: typeof statsReport.audioInputLevel === 'number'\n ? statsReport.audioInputLevel\n : null,\n enumerable: true\n },\n jitter: {\n value: typeof statsReport.jitter === 'number'\n ? statsReport.jitter\n : null,\n enumerable: true\n }\n });\n }\n}\n\n/**\n * The maximum absolute amplitude of a set of audio samples in the\n * range of 0 to 32767 inclusive.\n * @typedef {number} AudioLevel\n */\n\nmodule.exports = LocalAudioTrackStats;\n", "'use strict';\n\nconst LocalTrackStats = require('./localtrackstats');\n\n/**\n * Statistics for a {@link LocalVideoTrack}.\n * @extends LocalTrackStats\n * @property {?VideoTrack#Dimensions} captureDimensions - Video capture resolution\n * @property {?VideoTrack#Dimensions} dimensions - Video encoding resolution\n * @property {?number} captureFrameRate - Video capture frame rate\n * @property {?number} frameRate - Video encoding frame rate\n */\nclass LocalVideoTrackStats extends LocalTrackStats {\n /**\n * @param {string} trackId - {@link LocalVideoTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n * @param {boolean} prepareForInsights\n */\n constructor(trackId, statsReport, prepareForInsights) {\n super(trackId, statsReport, prepareForInsights);\n\n let captureDimensions = null;\n if (typeof statsReport.frameWidthInput === 'number' &&\n typeof statsReport.frameHeightInput === 'number') {\n captureDimensions = {};\n\n Object.defineProperties(captureDimensions, {\n width: {\n value: statsReport.frameWidthInput,\n enumerable: true\n },\n height: {\n value: statsReport.frameHeightInput,\n enumerable: true\n }\n });\n }\n\n let dimensions = null;\n if (typeof statsReport.frameWidthSent === 'number' &&\n typeof statsReport.frameHeightSent === 'number') {\n dimensions = {};\n\n Object.defineProperties(dimensions, {\n width: {\n value: statsReport.frameWidthSent,\n enumerable: true\n },\n height: {\n value: statsReport.frameHeightSent,\n enumerable: true\n }\n });\n }\n\n Object.defineProperties(this, {\n captureDimensions: {\n value: captureDimensions,\n enumerable: true\n },\n dimensions: {\n value: dimensions,\n enumerable: true\n },\n captureFrameRate: {\n value: typeof statsReport.frameRateInput === 'number'\n ? statsReport.frameRateInput\n : null,\n enumerable: true\n },\n frameRate: {\n value: typeof statsReport.frameRateSent === 'number'\n ? statsReport.frameRateSent\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = LocalVideoTrackStats;\n", "'use strict';\n\nconst TrackStats = require('./trackstats');\n\n/**\n * Statistics for a remote {@link Track}.\n * @extends TrackStats\n * @property {?number} bytesReceived - Number of bytes received\n * @property {?number} packetsReceived - Number of packets received\n */\nclass RemoteTrackStats extends TrackStats {\n /*\n * @param {string} trackId - {@link Track} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n super(trackId, statsReport);\n\n Object.defineProperties(this, {\n bytesReceived: {\n value: typeof statsReport.bytesReceived === 'number'\n ? statsReport.bytesReceived\n : null,\n enumerable: true\n },\n packetsReceived: {\n value: typeof statsReport.packetsReceived === 'number'\n ? statsReport.packetsReceived\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = RemoteTrackStats;\n", "'use strict';\n\nconst RemoteTrackStats = require('./remotetrackstats');\n\n/**\n * Statistics for an {@link AudioTrack}.\n * @extends RemoteTrackStats\n * @property {?AudioLevel} audioLevel - Output {@link AudioLevel}\n * @property {?number} jitter - Audio jitter in milliseconds\n */\nclass RemoteAudioTrackStats extends RemoteTrackStats {\n /**\n * @param {string} trackId - {@link AudioTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n super(trackId, statsReport);\n\n Object.defineProperties(this, {\n audioLevel: {\n value: typeof statsReport.audioOutputLevel === 'number'\n ? statsReport.audioOutputLevel\n : null,\n enumerable: true\n },\n jitter: {\n value: typeof statsReport.jitter === 'number'\n ? statsReport.jitter\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = RemoteAudioTrackStats;\n", "'use strict';\n\nconst RemoteTrackStats = require('./remotetrackstats');\n\n/**\n * Statistics for a {@link VideoTrack}.\n * @extends RemoteTrackStats\n * @property {?VideoTrack#Dimensions} dimensions - Received video resolution\n * @property {?number} frameRate - Received video frame rate\n */\nclass RemoteVideoTrackStats extends RemoteTrackStats {\n /**\n * @param {string} trackId - {@link VideoTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n super(trackId, statsReport);\n\n let dimensions = null;\n if (typeof statsReport.frameWidthReceived === 'number' &&\n typeof statsReport.frameHeightReceived === 'number') {\n dimensions = {};\n\n Object.defineProperties(dimensions, {\n width: {\n value: statsReport.frameWidthReceived,\n enumerable: true\n },\n height: {\n value: statsReport.frameHeightReceived,\n enumerable: true\n }\n });\n }\n\n Object.defineProperties(this, {\n dimensions: {\n value: dimensions,\n enumerable: true\n },\n frameRate: {\n value: typeof statsReport.frameRateReceived === 'number'\n ? statsReport.frameRateReceived\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = RemoteVideoTrackStats;\n", "'use strict';\n\nconst LocalAudioTrackStats = require('./localaudiotrackstats');\nconst LocalVideoTrackStats = require('./localvideotrackstats');\nconst RemoteAudioTrackStats = require('./remoteaudiotrackstats');\nconst RemoteVideoTrackStats = require('./remotevideotrackstats');\n\n/**\n * Statistics report for an RTCPeerConnection.\n * @property {string} peerConnectionId - ID of the RTCPeerConnection\n * @property {Array} localAudioTrackStats - List of {@link LocalAudioTrackStats}\n * @property {Array} localVideoTrackStats - List of {@link LocalVideoTrackStats}\n * @property {Array} remoteAudioTrackStats - List of {@link RemoteAudioTrackStats}\n * @property {Array} remoteVideoTrackStats - List of {@link RemoteVideoTrackStats}\n */\nclass StatsReport {\n /**\n * @param {string} peerConnectionId - RTCPeerConnection ID\n * @param {StandardizedStatsResponse} statsResponse\n * @param {boolean} prepareForInsights - if report is being prepared to send to insights.\n */\n constructor(peerConnectionId, statsResponse, prepareForInsights) {\n if (typeof peerConnectionId !== 'string') {\n throw new Error('RTCPeerConnection id must be a string');\n }\n\n Object.defineProperties(this, {\n peerConnectionId: {\n value: peerConnectionId,\n enumerable: true\n },\n localAudioTrackStats: {\n value: statsResponse.localAudioTrackStats.map(report => new LocalAudioTrackStats(report.trackId, report, prepareForInsights)),\n enumerable: true\n },\n localVideoTrackStats: {\n value: statsResponse.localVideoTrackStats.map(report => new LocalVideoTrackStats(report.trackId, report, prepareForInsights)),\n enumerable: true\n },\n remoteAudioTrackStats: {\n value: statsResponse.remoteAudioTrackStats.map(report => new RemoteAudioTrackStats(report.trackId, report)),\n enumerable: true\n },\n remoteVideoTrackStats: {\n value: statsResponse.remoteVideoTrackStats.map(report => new RemoteVideoTrackStats(report.trackId, report)),\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = StatsReport;\n", "'use strict';\n\nconst EventEmitter = require('./eventemitter');\nconst RemoteParticipant = require('./remoteparticipant');\nconst StatsReport = require('./stats/statsreport');\nconst { flatMap, valueToJSON } = require('./util');\n\nlet nInstances = 0;\n\n/**\n * A {@link Room} represents communication between you and one or more\n * {@link RemoteParticipant}s sharing {@link AudioTrack}s and\n * {@link VideoTrack}s.\n *

\n * You can connect to a {@link Room} by calling {@link module:twilio-video.connect}.\n * @extends EventEmitter\n * @property {?RemoteParticipant} dominantSpeaker - The Dominant Speaker in the\n * {@link Room}, if any\n * @property {boolean} isRecording - Whether or not the {@link Room} is being\n * recorded\n * @property {LocalParticipant} localParticipant - Your {@link LocalParticipant}\n * in the {@link Room}\n * @property {string} mediaRegion - String indicating geographical region\n * where media is processed for the {@link Room}.\n * @property {string} name - The {@link Room}'s name\n * @property {Map} participants -\n * The {@link RemoteParticipant}s participating in this {@link Room}\n * @property {Room.SID} sid - The {@link Room}'s SID\n * @property {string} state - \"connected\", \"reconnecting\", or \"disconnected\"\n * @throws {SignalingConnectionDisconnectedError}\n * @emits Room#disconnected\n * @emits Room#participantConnected\n * @emits Room#participantDisconnected\n * @emits Room#participantReconnected\n * @emits Room#participantReconnecting\n * @emits Room#reconnected\n * @emits Room#reconnecting\n * @emits Room#recordingStarted\n * @emits Room#recordingStopped\n * @emits Room#trackDimensionsChanged\n * @emits Room#trackDisabled\n * @emits Room#trackEnabled\n * @emits Room#trackMessage\n * @emits Room#trackPublished\n * @emits Room#trackPublishPriorityChanged\n * @emits Room#trackStarted\n * @emits Room#trackSubscribed\n * @emits Room#trackSwitchedOff\n * @emits Room#trackSwitchedOn\n * @emits Room#trackUnpublished\n * @emits Room#trackUnsubscribed\n * @emits Room#trackWarning\n * @emits Room#trackWarningsCleared\n */\nclass Room extends EventEmitter {\n /**\n * Construct a {@link Room}.\n * @param {RoomSignaling} signaling\n * @param {?object} [options={}]\n */\n constructor(localParticipant, signaling, options) {\n super();\n\n const log = options.log.createLog('default', this);\n const participants = new Map();\n\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _log: {\n value: log\n },\n _clientTrackSwitchOffControl: {\n value: options.clientTrackSwitchOffControl || 'disabled'\n },\n _contentPreferencesMode: {\n value: options.contentPreferencesMode || 'disabled'\n },\n _instanceId: {\n value: ++nInstances\n },\n _options: {\n value: options\n },\n _participants: {\n value: participants\n },\n _signaling: {\n value: signaling\n },\n dominantSpeaker: {\n enumerable: true,\n get() {\n return this.participants.get(signaling.dominantSpeakerSid) || null;\n }\n },\n isRecording: {\n enumerable: true,\n get() {\n return signaling.recording.isEnabled || false;\n }\n },\n localParticipant: {\n enumerable: true,\n value: localParticipant\n },\n name: {\n enumerable: true,\n value: signaling.name\n },\n participants: {\n enumerable: true,\n value: participants\n },\n sid: {\n enumerable: true,\n value: signaling.sid\n },\n state: {\n enumerable: true,\n get() {\n return signaling.state;\n }\n },\n mediaRegion: {\n enumerable: true,\n value: signaling.mediaRegion\n }\n });\n\n handleLocalParticipantEvents(this, localParticipant);\n handleRecordingEvents(this, signaling.recording);\n handleSignalingEvents(this, signaling);\n verifyNoiseCancellation(this);\n\n log.info('Created a new Room:', this.name);\n log.debug('Initial RemoteParticipants:', Array.from(this._participants.values()));\n }\n\n toString() {\n return `[Room #${this._instanceId}: ${this.sid}]`;\n }\n\n\n /**\n * Disconnect from the {@link Room}.\n * @returns {this}\n */\n disconnect() {\n this._log.info('Disconnecting');\n this._signaling.disconnect();\n return this;\n }\n\n /**\n * Get the {@link Room}'s media statistics. This is not supported in Safari 12.0 or below\n * due to this bug : https://bugs.webkit.org/show_bug.cgi?id=192601\n *\n * @returns {Promise.>}\n */\n getStats() {\n return this._signaling.getStats().then(responses =>\n Array.from(responses).map(([id, response]) =>\n new StatsReport(id, Object.assign({}, response, {\n localAudioTrackStats: rewriteLocalTrackIds(this, response.localAudioTrackStats),\n localVideoTrackStats: rewriteLocalTrackIds(this, response.localVideoTrackStats)\n }))\n )\n );\n }\n\n /**\n * Restart the muted local media {@link Track}s and play inadvertently paused HTMLMediaElements\n * that are attached to local and remote media {@link Track}s. This method is useful mainly on\n * mobile browsers (Safari and Chrome on iOS), where there is a possibility that the muted local\n * media {@link Track}s are never unmuted and inadvertently paused HTMLMediaElements are never\n * played again, especially after handling an incoming phone call.\n * @returns {this}\n */\n refreshInactiveMedia() {\n const { tracks: localTrackPublications } = this.localParticipant;\n\n const localMediaTracks = Array.from(localTrackPublications.values())\n .filter(({ track: { kind } }) => kind !== 'data')\n .map(({ track }) => track);\n\n const remoteMediaTracks = flatMap(this.participants, participants => Array.from(participants.tracks.values()))\n .filter(({ track }) => track && track.kind !== 'data')\n .map(({ track }) => track);\n\n const mediaTracks = localMediaTracks.concat(remoteMediaTracks);\n\n const unmuteEvent = new Event('unmute');\n localMediaTracks.forEach(({ isMuted, mediaStreamTrack }) => {\n if (isMuted) {\n mediaStreamTrack.dispatchEvent(unmuteEvent);\n }\n });\n\n const pauseEvent = new Event('pause');\n mediaTracks.forEach(({ _attachments: attachments, _elShims: elShims }) => attachments.forEach(el => {\n const shim = elShims.get(el);\n const isInadvertentlyPaused = el.paused && shim && !shim.pausedIntentionally();\n if (isInadvertentlyPaused) {\n el.dispatchEvent(pauseEvent);\n }\n }));\n\n return this;\n }\n\n toJSON() {\n return valueToJSON(this);\n }\n}\n\nfunction verifyNoiseCancellation(room) {\n const allowedAudioProcessors = room.localParticipant._signaling.audioProcessors;\n room.localParticipant.audioTracks.forEach(({ track }) => {\n const noiseCancellation = track.noiseCancellation;\n if (noiseCancellation && !allowedAudioProcessors.includes(noiseCancellation.vendor)) {\n room._log.warn(`${noiseCancellation.vendor} is not supported in this room. disabling it permanently`);\n noiseCancellation.disablePermanently();\n }\n });\n}\n\nfunction rewriteLocalTrackIds(room, trackStats) {\n const localParticipantSignaling = room.localParticipant._signaling;\n return trackStats.reduce((trackStats, trackStat) => {\n const publication = localParticipantSignaling.tracks.get(trackStat.trackId);\n const trackSender = localParticipantSignaling.getSender(publication);\n return trackSender\n ? [Object.assign({}, trackStat, { trackId: trackSender.id })].concat(trackStats)\n : trackStats;\n }, []);\n}\n\n/**\n * A {@link Room.SID} is a 34-character string starting with \"RM\"\n * that uniquely identifies a {@link Room}.\n * @type string\n * @typedef Room.SID\n */\n\n/**\n * The Dominant Speaker in the {@link Room} changed. Either the Dominant Speaker\n * is a new {@link RemoteParticipant} or the Dominant Speaker has been reset and\n * is now null.\n * @param {?RemoteParticipant} dominantSpeaker - The Dominant Speaker in the\n * {@link Room}, if any\n * @event Room#dominantSpeakerChanged\n */\n\n/**\n * Your {@link LocalParticipant} was disconnected from the {@link Room} and all\n * other {@link RemoteParticipant}s.\n * @param {Room} room - The {@link Room} your\n * {@link LocalParticipant} was disconnected from\n * @param {?TwilioError} error - Present when the {@link LocalParticipant} got\n * disconnected from the {@link Room} unexpectedly\n * @event Room#disconnected\n * @example\n * myRoom.on('disconnected', function(room, error) {\n * if (error) {\n * console.log('Unexpectedly disconnected:', error);\n * }\n * myRoom.localParticipant.tracks.forEach(function(track) {\n * track.stop();\n * track.detach();\n * });\n * });\n */\n\n/**\n * A {@link RemoteParticipant} joined the {@link Room}. In Large Group Rooms (Maximum\n * Participants greater than 50), this event is raised only when a {@link RemoteParticipant}\n * publishes at least one {@link LocalTrack}.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who joined\n * @event Room#participantConnected\n * @example\n * myRoom.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' joined the Room');\n * });\n */\n\n/**\n * A {@link RemoteParticipant} left the {@link Room}. In Large Group Rooms (Maximum\n * Participants greater than 50), this event is raised only when a {@link RemoteParticipant}\n * unpublishes all its {@link LocalTrack}s.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who left\n * @event Room#participantDisconnected\n * @example\n * myRoom.on('participantDisconnected', function(participant) {\n * console.log(participant.identity + ' left the Room');\n * participant.tracks.forEach(function(track) {\n * track.detach().forEach(function(mediaElement) {\n * mediaElement.remove();\n * });\n * });\n * });\n */\n\n/**\n * A {@link RemoteParticipant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} that has reconnected.\n * @event Room#participantReconnected\n * @example\n * myRoom.on('participantReconnected', participant => {\n * console.log(participant.identity + ' reconnected to the Room');\n * });\n */\n\n/**\n * A {@link RemoteParticipant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} that is reconnecting.\n * @event Room#participantReconnecting\n * @example\n * myRoom.on('participantReconnecting', participant => {\n * console.log(participant.identity + ' is reconnecting to the Room');\n * });\n */\n\n/**\n * Your application successfully reconnected to the {@link Room}. When this\n * event is emitted, the {@link Room} is in state \"connected\".\n * @event Room#reconnected\n * @example\n * myRoom.on('reconnected', () => {\n * console.log('Reconnected!');\n * });\n */\n\n/**\n * Your application is reconnecting to the {@link Room}. This happens when there\n * is a disruption in your signaling connection and/or your media connection. When\n * this event is emitted, the {@link Room} is in state \"reconnecting\". If reconnecting\n * succeeds, the {@link Room} will emit a \"reconnected\" event.\n * @param {MediaConnectionError|SignalingConnectionDisconnectedError} error - A\n * {@link MediaConnectionError} if your application is reconnecting due to a\n * disruption in your media connection, or a {@link SignalingConnectionDisconnectedError}\n * if your application is reconnecting due to a disruption in your signaling connection\n * @event Room#reconnecting\n * @example\n * myRoom.on('reconnecting', error => {\n * if (error.code === 53001) {\n * console.log('Reconnecting your signaling connection!', error.message);\n * } else if (error.code === 53405) {\n * console.log('Reconnecting your media connection!', error.message);\n * }\n * });\n */\n\n/**\n * The {@link Room} is now being recorded\n * @event Room#recordingStarted\n */\n\n/**\n * The {@link Room} is no longer being recorded\n * @event Room#recordingStopped\n */\n\n/**\n * One of the {@link RemoteParticipant}'s {@link VideoTrack}'s dimensions changed.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} whose dimensions changed\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteVideoTrack}'s dimensions changed\n * @event Room#trackDimensionsChanged\n */\n\n/**\n * A {@link RemoteTrack} was disabled by a {@link RemoteParticipant} in the {@link Room}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} that represents disabled {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * disabled the {@link RemoteTrack}\n * @event Room#trackDisabled\n */\n\n/**\n * A {@link RemoteTrack} was enabled by a {@link RemoteParticipant} in the {@link Room}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} that represents enabled {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * enabled the {@link RemoteTrack}\n * @event Room#trackEnabled\n */\n\n/**\n * A message was received over one of the {@link RemoteParticipant}'s\n * {@link RemoteDataTrack}'s.\n * @param {string|ArrayBuffer} data\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} over which the\n * message was received\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteDataTrack} received the message\n * @event Room#trackMessage\n */\n\n/**\n * A {@link RemoteTrack} was published by a {@link RemoteParticipant} after\n * connecting to the {@link Room}. This event is not emitted for\n * {@link RemoteTrack}s that were published while the {@link RemoteParticipant}\n * was connecting to the {@link Room}.\n * @event Room#trackPublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the published {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * published the {@link RemoteTrack}\n * @example\n * function trackPublished(publication, participant) {\n * console.log(`RemoteParticipant ${participant.sid} published Track ${publication.trackSid}`);\n * }\n *\n * // Handle RemoteTracks published after connecting to the Room.\n * room.on('trackPublished', trackPublished);\n *\n * room.on('participantConnected', participant => {\n * // Handle RemoteTracks published while connecting to the Room.\n * participant.trackPublications.forEach(publication => trackPublished(publication, participant));\n * });\n */\n\n/**\n * One of a {@link RemoteParticipant}'s {@link RemoteTrack}s in the {@link Room} started.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that started\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} started\n * @event Room#trackStarted\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was subscribed to.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was subscribed\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was subscribed\n * @event Room#trackSubscribed\n * @example\n * room.on('trackSubscribed', function(track, publication, participant) {\n * var participantView = document.getElementById('participant-view-' + participant.identity);\n * participantView.appendChild(track.attach());\n * });\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was switched off.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched off\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was switched off\n * @event Room#trackSwitchedOff\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was switched on.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched on\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was switched on\n * @event Room#trackSwitchedOn\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} could not be subscribed to.\n * @param {TwilioError} error - The reason the {@link RemoteTrack} could not be\n * subscribed to\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that could not\n * be subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} could not be subscribed to\n * @event Room#trackSubscriptionFailed\n */\n\n/**\n * The {@link RemoteTrack}'s publish {@link Track.Priority} was changed by the\n * {@link RemoteParticipant}.\n * @param {Track.Priority} priority - the {@link RemoteTrack}'s new publish\n * {@link Track.Priority};\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that changed priority\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} changed priority\n * @event Room#trackPublishPriorityChanged\n */\n\n/**\n * A {@link RemoteTrack} was unpublished by a {@link RemoteParticipant} to the {@link Room}.\n * @event Room#trackUnpublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the unpublished {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * unpublished the {@link RemoteTrack}\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was unsubscribed from.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was unsubscribed\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was unsubscribed from\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was unsubscribed\n * @event Room#trackUnsubscribed\n * @example\n * room.on('trackUnsubscribed', function(track, publication, participant) {\n * track.detach().forEach(function(mediaElement) {\n * mediaElement.remove();\n * });\n * });\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s in the {@link Room} encountered a warning.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {string} name - The warning that was raised.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that encountered the warning.\n * @param {LocalParticipant} participant - The {@link LocalParticipant}\n * @event Room#trackWarning\n * @example\n * room.on('trackWarning', (name, publication, participant) => {\n * if (name === 'recording-media-lost') {\n * log(`LocalTrack ${publication.track.name} is not recording media.`,\n * name, publication, participant);\n *\n * // Wait a reasonable amount of time to clear the warning.\n * const timer = setTimeout(() => {\n * // If the warning is not cleared, you can manually\n * // reconnect to the room, or show a dialog to the user\n * }, 5000);\n *\n * room.once('trackWarningsCleared', (publication, participant) => {\n * log('LocalTrack warnings have cleared!',\n * publication, participant);\n * clearTimeout(timer);\n * });\n * }\n});\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s in the {@link Room} cleared all warnings.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that cleared all warnings.\n * @param {LocalParticipant} participant - The {@link LocalParticipant}\n * @event Room#trackWarningsCleared\n */\n\nfunction connectParticipant(room, participantSignaling) {\n const { _log: log, _clientTrackSwitchOffControl: clientTrackSwitchOffControl, _contentPreferencesMode: contentPreferencesMode } = room;\n const participant = new RemoteParticipant(participantSignaling, { log, clientTrackSwitchOffControl, contentPreferencesMode });\n\n log.info('A new RemoteParticipant connected:', participant);\n room._participants.set(participant.sid, participant);\n room.emit('participantConnected', participant);\n\n // Reemit Track and RemoteParticipant events.\n const eventListeners = [\n ['reconnected', 'participantReconnected'],\n ['reconnecting', 'participantReconnecting'],\n 'trackDimensionsChanged',\n 'trackDisabled',\n 'trackEnabled',\n 'trackMessage',\n 'trackPublished',\n 'trackPublishPriorityChanged',\n 'trackStarted',\n 'trackSubscribed',\n 'trackSubscriptionFailed',\n 'trackSwitchedOff',\n 'trackSwitchedOn',\n 'trackUnpublished',\n 'trackUnsubscribed'\n ].map(eventOrPair => {\n const [event, participantEvent] = Array.isArray(eventOrPair)\n ? eventOrPair\n : [eventOrPair, eventOrPair];\n\n function reemit() {\n const args = [].slice.call(arguments);\n args.unshift(participantEvent);\n args.push(participant);\n room.emit(...args);\n }\n participant.on(event, reemit);\n return [event, reemit];\n });\n\n participant.once('disconnected', function participantDisconnected() {\n const dominantSpeaker = room.dominantSpeaker;\n log.info('RemoteParticipant disconnected:', participant);\n room._participants.delete(participant.sid);\n eventListeners.forEach(args => {\n participant.removeListener(args[0], args[1]);\n });\n room.emit('participantDisconnected', participant);\n if (participant === dominantSpeaker) {\n room.emit('dominantSpeakerChanged', room.dominantSpeaker);\n }\n });\n}\n\nfunction handleLocalParticipantEvents(room, localParticipant) {\n const events = ['trackWarning', 'trackWarningsCleared'].map(event => ({\n eventName: event,\n handler: (...args) => room.emit(event, ...[...args, localParticipant]),\n }));\n\n events.forEach(({ eventName, handler }) =>\n localParticipant.on(eventName, handler));\n\n room.once('disconnected', () =>\n events.forEach(({ eventName, handler }) =>\n localParticipant.removeListener(eventName, handler)));\n}\n\nfunction handleRecordingEvents(room, recording) {\n recording.on('updated', function updated() {\n const started = recording.isEnabled;\n room._log.info(`Recording ${started ? 'started' : 'stopped'}`);\n room.emit(`recording${started ? 'Started' : 'Stopped'}`);\n });\n}\n\nfunction handleSignalingEvents(room, signaling) {\n const log = room._log;\n\n // Reemit RemoteParticipant events from the RoomSignaling.\n log.debug('Creating a new RemoteParticipant for each ParticipantSignaling '\n + 'in the RoomSignaling');\n signaling.participants.forEach(connectParticipant.bind(null, room));\n log.debug('Setting up RemoteParticipant creation for all subsequent '\n + 'ParticipantSignalings that connect to the RoomSignaling');\n signaling.on('participantConnected', connectParticipant.bind(null, room));\n\n signaling.on('dominantSpeakerChanged', () => room.emit('dominantSpeakerChanged', room.dominantSpeaker));\n\n // Reemit state transition events from the RoomSignaling.\n signaling.on('stateChanged', function stateChanged(state, error) {\n log.info('Transitioned to state:', state);\n switch (state) {\n case 'disconnected':\n room.participants.forEach(participant => {\n participant._unsubscribeTracks();\n });\n room.emit(state, room, error);\n room.localParticipant.tracks.forEach(publication => {\n publication.unpublish();\n });\n signaling.removeListener('stateChanged', stateChanged);\n break;\n case 'reconnecting':\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => room.emit('reconnecting', error), 0);\n\n break;\n default:\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => room.emit('reconnected'), 0);\n }\n });\n}\n\nmodule.exports = Room;\n", "\n/**\n * Expose `Backoff`.\n */\n\nclass Backoff {\n /**\n * Construct a {@link Backoff}.\n * @param {object} options\n * @property {number} min - Initial timeout in milliseconds [100]\n * @property {number} max - Max timeout [10000]\n * @property {boolean} jitter - Apply jitter [0]\n * @property {number} factor - Multiplication factor for Backoff operation [2]\n */\n constructor(options) {\n Object.defineProperties(this, {\n _min: {\n value: options.min || 100\n },\n _max: {\n value: options.max || 10000\n },\n _jitter: {\n value: options.jitter > 0 && options.jitter <= 1 ? options.jitter : 0\n },\n _factor: {\n value: options.factor || 2\n },\n _attempts: {\n value: 0,\n writable: true\n },\n _duration: {\n enumerable: false,\n get() {\n let ms = this._min * Math.pow(this._factor, this._attempts);\n if (this._jitter) {\n const rand = Math.random();\n const deviation = Math.floor(rand * this._jitter * ms);\n ms = (Math.floor(rand * 10) & 1) === 0 ? ms - deviation : ms + deviation;\n }\n return Math.min(ms, this._max) | 0;\n }\n },\n _timeoutID: {\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Start the backoff operation.\n * @param {function} fn - Function to call\n * @return {void}\n * @api public\n */\n backoff(fn) {\n let duration = this._duration;\n if (this._timeoutID) {\n clearTimeout(this._timeoutID);\n this._timeoutID = null;\n }\n this._timeoutID = setTimeout(() => {\n this._attempts++;\n fn();\n }, duration);\n }\n\n /**\n * Reset the number of attempts and clear the timer.\n *\n * @return {void}\n * @api public\n */\n reset() {\n this._attempts = 0;\n if (this._timeoutID) {\n clearTimeout(this._timeoutID);\n this._timeoutID = null;\n }\n }\n}\n\nmodule.exports = Backoff;\n", "'use strict';\n\nconst { difference, flatMap } = require('../');\n\n/**\n * Create a random {@link SSRC}.\n * @returns {SSRC}\n */\nfunction createSSRC() {\n const ssrcMax = 0xffffffff;\n return String(Math.floor(Math.random() * ssrcMax));\n}\n\n/**\n * @property {string} cName\n * @property {boolean} isSimulcastEnabled\n * @property {Map} rtxPairs\n * @property {Set} primarySSRCs\n * @property {string} streamId\n * @property {Track.ID} trackId\n */\nclass TrackAttributes {\n /**\n * Construct a {@link MediaStreamTrack} attribute store.\n * @param {Track.ID} trackId - The MediaStreamTrack ID\n * @param {MediaStreamID} streamId - The MediaStream ID\n * @param {string} cName - The MediaStream cname\n */\n constructor(trackId, streamId, cName) {\n Object.defineProperties(this, {\n cName: {\n enumerable: true,\n value: cName\n },\n isSimulcastEnabled: {\n enumerable: true,\n value: false,\n writable: true\n },\n primarySSRCs: {\n enumerable: true,\n value: new Set()\n },\n rtxPairs: {\n enumerable: true,\n value: new Map()\n },\n streamId: {\n enumerable: true,\n value: streamId\n },\n trackId: {\n enumerable: true,\n value: trackId\n }\n });\n }\n\n /**\n * Add {@link SimSSRC}s to the {@link TrackAttributes}.\n * @returns {void}\n */\n addSimulcastSSRCs() {\n if (this.isSimulcastEnabled) {\n return;\n }\n const simulcastSSRCs = [createSSRC(), createSSRC()];\n simulcastSSRCs.forEach(function(ssrc) {\n this.primarySSRCs.add(ssrc);\n }, this);\n\n if (this.rtxPairs.size) {\n simulcastSSRCs.forEach(function(ssrc) {\n this.rtxPairs.set(createSSRC(), ssrc);\n }, this);\n }\n }\n\n /**\n * Add the given {@link PrimarySSRC} or {@link RtxSSRC} to the {@link TrackAttributes}\n * and update the \"isSimulcastEnabled\" flag if it is also a {@link SimSSRC}.\n * @param {SSRC} ssrc - The {@link SSRC} to be added\n * @param {?PrimarySSRC} primarySSRC - The {@link PrimarySSRC}; if the given\n * {@link SSRC} itself is the {@link PrimarySSRC}, then this is set to null\n * @param {boolean} isSimSSRC - true if the given {@link SSRC} is a\n * {@link SimSSRC}; false otherwise\n * @returns {void}\n */\n addSSRC(ssrc, primarySSRC, isSimSSRC) {\n if (primarySSRC) {\n this.rtxPairs.set(ssrc, primarySSRC);\n } else {\n this.primarySSRCs.add(ssrc);\n }\n this.isSimulcastEnabled = this.isSimulcastEnabled || isSimSSRC;\n }\n\n /**\n * Construct the SDP lines for the {@link TrackAttributes}.\n * @param {boolean} [excludeRtx=false]\n * @returns {Array} Array of SDP lines\n */\n toSdpLines(excludeRtx) {\n const rtxPairs = excludeRtx\n ? []\n : Array.from(this.rtxPairs.entries()).map(rtxPair => rtxPair.reverse());\n\n const simSSRCs = Array.from(this.primarySSRCs.values());\n const ssrcs = rtxPairs.length ? flatMap(rtxPairs) : simSSRCs;\n\n const attrLines = flatMap(ssrcs, ssrc => [\n `a=ssrc:${ssrc} cname:${this.cName}`,\n `a=ssrc:${ssrc} msid:${this.streamId} ${this.trackId}`\n ]);\n const rtxPairLines = rtxPairs.map(rtxPair => `a=ssrc-group:FID ${rtxPair.join(' ')}`);\n const simGroupLines = [\n `a=ssrc-group:SIM ${simSSRCs.join(' ')}`\n ];\n\n return rtxPairLines.concat(attrLines).concat(simGroupLines);\n }\n}\n\n/**\n * Get the matches for a given RegEx pattern.\n * @param {string} section - SDP media section\n * @param {string} pattern - RegEx pattern\n * @returns {Array>} - Array of pattern matches\n */\nfunction getMatches(section, pattern) {\n const matches = section.match(new RegExp(pattern, 'gm')) || [];\n return matches.map(match => {\n const matches = match.match(new RegExp(pattern)) || [];\n return matches.slice(1);\n });\n}\n\n/**\n * Get the {@link SimSSRC}s that belong to a simulcast group.\n * @param {string} section - SDP media section\n * @returns {Set} Set of simulcast {@link SSRC}s\n */\nfunction getSimulcastSSRCs(section) {\n const simGroupPattern = '^a=ssrc-group:SIM ([0-9]+) ([0-9]+) ([0-9]+)$';\n return new Set(flatMap(getMatches(section, simGroupPattern)));\n}\n\n/**\n * Get the value of the given attribute for an SSRC.\n * @param {string} section - SDP media section\n * @param {SSRC} ssrc - {@link SSRC} whose attribute's value is to be determinded\n * @param {string} attribute - {@link SSRC} attribute name\n * @param {string} - {@link SSRC} attribute value\n */\nfunction getSSRCAttribute(section, ssrc, attribute) {\n const pattern = `a=ssrc:${ssrc} ${attribute}:(.+)`;\n return section.match(new RegExp(pattern))[1];\n}\n\n/**\n * Create a Map of {@link PrimarySSRC}s and their {@link RtxSSRC}s.\n * @param {string} section - SDP media section\n * @returns {Map} - Map of {@link RtxSSRC}s and their\n * corresponding {@link PrimarySSRC}s\n */\nfunction getSSRCRtxPairs(section) {\n const rtxPairPattern = '^a=ssrc-group:FID ([0-9]+) ([0-9]+)$';\n return new Map(getMatches(section, rtxPairPattern).map(pair => pair.reverse()));\n}\n\n/**\n * Create SSRC attribute tuples.\n * @param {string} section\n * @returns {Array<[SSRC, MediaStreamID, Track.ID]>}\n */\nfunction createSSRCAttributeTuples(section) {\n const [streamId, trackId] = flatMap(getMatches(section, '^a=msid:(.+) (.+)$'));\n const ssrcs = flatMap(getMatches(section, '^a=ssrc:(.+) cname:.+$'));\n return ssrcs.map(ssrc => [ssrc, streamId, trackId]);\n}\n\n/**\n * Create a Map of MediaStreamTrack IDs and their {@link TrackAttributes}.\n * @param {string} section - SDP media section\n * @returns {Map}\n */\nfunction createTrackIdsToAttributes(section) {\n const simSSRCs = getSimulcastSSRCs(section);\n const rtxPairs = getSSRCRtxPairs(section);\n const ssrcAttrTuples = createSSRCAttributeTuples(section);\n\n return ssrcAttrTuples.reduce((trackIdsToSSRCs, tuple) => {\n const ssrc = tuple[0];\n const streamId = tuple[1];\n const trackId = tuple[2];\n\n const trackAttributes = trackIdsToSSRCs.get(trackId) || new TrackAttributes(\n trackId,\n streamId,\n getSSRCAttribute(section, ssrc, 'cname'));\n\n const primarySSRC = rtxPairs.get(ssrc) || null;\n trackAttributes.addSSRC(ssrc, primarySSRC, simSSRCs.has(ssrc));\n return trackIdsToSSRCs.set(trackId, trackAttributes);\n }, new Map());\n}\n\n/**\n * Apply simulcast settings to the given SDP media section.\n * @param {string} section - SDP media section\n * @param {Map} trackIdsToAttributes - Existing\n * map which will be updated for new MediaStreamTrack IDs\n * @returns {string} - The transformed SDP media section\n */\nfunction setSimulcastInMediaSection(section, trackIdsToAttributes) {\n const newTrackIdsToAttributes = createTrackIdsToAttributes(section);\n const newTrackIds = Array.from(newTrackIdsToAttributes.keys());\n let trackIds = Array.from(trackIdsToAttributes.keys());\n const trackIdsToAdd = difference(newTrackIds, trackIds);\n const trackIdsToIgnore = difference(trackIds, newTrackIds);\n\n // Update \"trackIdsToAttributes\" with TrackAttributes for new\n // MediaStreamTrack IDs.\n const trackAttributesToAdd = flatMap(trackIdsToAdd, trackId => newTrackIdsToAttributes.get(trackId));\n trackAttributesToAdd.forEach(trackAttributes => {\n trackAttributes.addSimulcastSSRCs();\n trackIdsToAttributes.set(trackAttributes.trackId, trackAttributes);\n });\n\n // Get the SDP lines of the relevant MediaStreamTrack IDs from\n // \"trackIdsToAttributes\".\n trackIds = Array.from(trackIdsToAttributes.keys());\n const relevantTrackIds = difference(trackIds, trackIdsToIgnore);\n const relevantTrackAttributes = flatMap(relevantTrackIds, trackId => trackIdsToAttributes.get(trackId));\n const excludeRtx = !section.match(/a=rtpmap:[0-9]+ rtx/);\n const relevantSdpLines = flatMap(relevantTrackAttributes, trackAttributes => trackAttributes.toSdpLines(excludeRtx));\n\n // Add the simulcast SSRC SDP lines to the media section. The Set ensures\n // that the duplicates of the SSRC SDP lines that are in both \"section\" and\n // \"relevantSdpLines\" are removed.\n const sectionLines = flatMap(new Set(section.split('\\r\\n').concat(relevantSdpLines)));\n\n const xGoogleFlagConference = 'a=x-google-flag:conference';\n if (!section.match(xGoogleFlagConference)) {\n sectionLines.push(xGoogleFlagConference);\n }\n\n return sectionLines.join('\\r\\n');\n}\n\n/**\n * String representing a MediaStream ID.\n * @typedef {string} MediaStreamID\n */\n\n/**\n * String representing the SSRC of a MediaStreamTrack.\n * @typedef {string} SSRC\n */\n\n/**\n * Primary SSRC.\n * @typedef {SSRC} PrimarySSRC\n */\n\n/**\n * Retransmission SSRC.\n * @typedef {SSRC} RtxSSRC\n */\n\n/**\n * Simulcast SSRC.\n * @typedef {SSRC} SimSSRC\n */\n\nmodule.exports = setSimulcastInMediaSection;\n", "'use strict';\n\nconst { difference, flatMap } = require('../');\nconst setSimulcastInMediaSection = require('./simulcast');\n\nconst ptToFixedBitrateAudioCodecName = {\n 0: 'PCMU',\n 8: 'PCMA'\n};\n\n/**\n * A payload type\n * @typedef {number} PT\n */\n\n/**\n * An {@link AudioCodec} or {@link VideoCodec}\n * @typedef {AudioCodec|VideoCodec} Codec\n */\n\n/**\n * Create a Codec Map for the given m= section.\n * @param {string} section - The given m= section\n * @returns {Map>}\n */\nfunction createCodecMapForMediaSection(section) {\n return Array.from(createPtToCodecName(section)).reduce((codecMap, pair) => {\n const pt = pair[0];\n const codecName = pair[1];\n const pts = codecMap.get(codecName) || [];\n return codecMap.set(codecName, pts.concat(pt));\n }, new Map());\n}\n\n/**\n * Create a Map of MIDs to m= sections for the given SDP.\n * @param {string} sdp\n * @returns {Map}\n */\nfunction createMidToMediaSectionMap(sdp) {\n return getMediaSections(sdp).reduce((midsToMediaSections, mediaSection) => {\n const mid = getMidForMediaSection(mediaSection);\n return mid ? midsToMediaSections.set(mid, mediaSection) : midsToMediaSections;\n }, new Map());\n}\n\n/**\n * Create a Map from PTs to codec names for the given m= section.\n * @param {string} mediaSection - The given m= section.\n * @returns {Map} ptToCodecName\n */\nfunction createPtToCodecName(mediaSection) {\n return getPayloadTypesInMediaSection(mediaSection).reduce((ptToCodecName, pt) => {\n const rtpmapPattern = new RegExp(`a=rtpmap:${pt} ([^/]+)`);\n const matches = mediaSection.match(rtpmapPattern);\n const codecName = matches\n ? matches[1].toLowerCase()\n : ptToFixedBitrateAudioCodecName[pt]\n ? ptToFixedBitrateAudioCodecName[pt].toLowerCase()\n : '';\n return ptToCodecName.set(pt, codecName);\n }, new Map());\n}\n\n/**\n * Get the associated fmtp attributes for the given Payload Type in an m= section.\n * @param {PT} pt\n * @param {string} mediaSection\n * @returns {?object}\n */\nfunction getFmtpAttributesForPt(pt, mediaSection) {\n // In \"a=fmtp: =[;=]*\", the regex matches the codec\n // profile parameters expressed as name/value pairs separated by \";\".\n const fmtpRegex = new RegExp(`^a=fmtp:${pt} (.+)$`, 'm');\n const matches = mediaSection.match(fmtpRegex);\n return matches && matches[1].split(';').reduce((attrs, nvPair) => {\n const [name, value] = nvPair.split('=');\n attrs[name] = isNaN(value) ? value : parseInt(value, 10);\n return attrs;\n }, {});\n}\n\n/**\n * Get the MID for the given m= section.\n * @param {string} mediaSection\n * @return {?string}\n */\nfunction getMidForMediaSection(mediaSection) {\n // In \"a=mid:\", the regex matches .\n const midMatches = mediaSection.match(/^a=mid:(.+)$/m);\n return midMatches && midMatches[1];\n}\n\n/**\n * Get the m= sections of a particular kind and direction from an sdp.\n * @param {string} sdp - SDP string\n * @param {string} [kind] - Pattern for matching kind\n * @param {string} [direction] - Pattern for matching direction\n * @returns {Array} mediaSections\n */\nfunction getMediaSections(sdp, kind, direction) {\n return sdp.replace(/\\r\\n\\r\\n$/, '\\r\\n').split('\\r\\nm=').slice(1).map(mediaSection => `m=${mediaSection}`).filter(mediaSection => {\n const kindPattern = new RegExp(`m=${kind || '.*'}`, 'gm');\n const directionPattern = new RegExp(`a=${direction || '.*'}`, 'gm');\n return kindPattern.test(mediaSection) && directionPattern.test(mediaSection);\n });\n}\n\n/**\n * Get the Codec Payload Types present in the first line of the given m= section\n * @param {string} section - The m= section\n * @returns {Array} Payload Types\n */\nfunction getPayloadTypesInMediaSection(section) {\n const mLine = section.split('\\r\\n')[0];\n\n // In \"m= ... \",\n // the regex matches and the Payload Types.\n const matches = mLine.match(/([0-9]+)/g);\n\n // This should not happen, but in case there are no Payload Types in\n // the m= line, return an empty array.\n if (!matches) {\n return [];\n }\n\n // Since only the Payload Types are needed, we discard the .\n return matches.slice(1).map(match => parseInt(match, 10));\n}\n\n/**\n * Create the reordered Codec Payload Types based on the preferred Codec Names.\n * @param {Map>} codecMap - Codec Map\n * @param {Array} preferredCodecs - Preferred Codecs\n * @returns {Array} Reordered Payload Types\n */\nfunction getReorderedPayloadTypes(codecMap, preferredCodecs) {\n preferredCodecs = preferredCodecs.map(({ codec }) => codec.toLowerCase());\n const preferredPayloadTypes = flatMap(preferredCodecs, codecName => codecMap.get(codecName) || []);\n const remainingCodecs = difference(Array.from(codecMap.keys()), preferredCodecs);\n const remainingPayloadTypes = flatMap(remainingCodecs, codecName => codecMap.get(codecName));\n return preferredPayloadTypes.concat(remainingPayloadTypes);\n}\n\n/**\n * Set the given Codec Payload Types in the first line of the given m= section.\n * @param {Array} payloadTypes - Payload Types\n * @param {string} section - Given m= section\n * @returns {string} - Updated m= section\n */\nfunction setPayloadTypesInMediaSection(payloadTypes, section) {\n const lines = section.split('\\r\\n');\n let mLine = lines[0];\n const otherLines = lines.slice(1);\n mLine = mLine.replace(/([0-9]+\\s?)+$/, payloadTypes.join(' '));\n return [mLine].concat(otherLines).join('\\r\\n');\n}\n\n/**\n * Return a new SDP string with the re-ordered codec preferences.\n * @param {string} sdp\n * @param {Array} preferredAudioCodecs - If empty, the existing order\n * of audio codecs is preserved\n * @param {Array} preferredVideoCodecs - If empty, the\n * existing order of video codecs is preserved\n * @returns {string} Updated SDP string\n */\nfunction setCodecPreferences(sdp, preferredAudioCodecs, preferredVideoCodecs) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(section => {\n // Codec preferences should not be applied to m=application sections.\n if (!/^m=(audio|video)/.test(section)) {\n return section;\n }\n const kind = section.match(/^m=(audio|video)/)[1];\n const codecMap = createCodecMapForMediaSection(section);\n const preferredCodecs = kind === 'audio' ? preferredAudioCodecs : preferredVideoCodecs;\n const payloadTypes = getReorderedPayloadTypes(codecMap, preferredCodecs);\n const newSection = setPayloadTypesInMediaSection(payloadTypes, section);\n\n const pcmaPayloadTypes = codecMap.get('pcma') || [];\n const pcmuPayloadTypes = codecMap.get('pcmu') || [];\n const fixedBitratePayloadTypes = kind === 'audio'\n ? new Set(pcmaPayloadTypes.concat(pcmuPayloadTypes))\n : new Set();\n\n return fixedBitratePayloadTypes.has(payloadTypes[0])\n ? newSection.replace(/\\r\\nb=(AS|TIAS):([0-9]+)/g, '')\n : newSection;\n })).join('\\r\\n');\n}\n\n/**\n * Return a new SDP string with simulcast settings.\n * @param {string} sdp\n * @param {Map} trackIdsToAttributes\n * @returns {string} Updated SDP string\n */\nfunction setSimulcast(sdp, trackIdsToAttributes) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(section => {\n section = section.replace(/\\r\\n$/, '');\n if (!/^m=video/.test(section)) {\n return section;\n }\n const codecMap = createCodecMapForMediaSection(section);\n const payloadTypes = getPayloadTypesInMediaSection(section);\n const vp8PayloadTypes = new Set(codecMap.get('vp8') || []);\n\n const hasVP8PayloadType = payloadTypes.some(payloadType => vp8PayloadTypes.has(payloadType));\n return hasVP8PayloadType\n ? setSimulcastInMediaSection(section, trackIdsToAttributes)\n : section;\n })).concat('').join('\\r\\n');\n}\n\n/**\n * Get the matching Payload Types in an m= section for a particular peer codec.\n * @param {Codec} peerCodec\n * @param {PT} peerPt\n * @param {Map} codecsToPts\n * @param {string} section\n * @param {string} peerSection\n * @returns {Array}\n */\nfunction getMatchingPayloadTypes(peerCodec, peerPt, codecsToPts, section, peerSection) {\n // If there is at most one local Payload Type that matches the remote codec, retain it.\n const matchingPts = codecsToPts.get(peerCodec) || [];\n if (matchingPts.length <= 1) {\n return matchingPts;\n }\n\n // If there are no fmtp attributes for the codec in the peer m= section, then we\n // cannot get a match in the m= section. In that case, retain all matching Payload\n // Types.\n const peerFmtpAttrs = getFmtpAttributesForPt(peerPt, peerSection);\n if (!peerFmtpAttrs) {\n return matchingPts;\n }\n\n // Among the matched local Payload Types, find the one that matches the remote\n // fmtp attributes.\n const matchingPt = matchingPts.find(pt => {\n const fmtpAttrs = getFmtpAttributesForPt(pt, section);\n return fmtpAttrs && Object.keys(peerFmtpAttrs).every(attr => {\n return peerFmtpAttrs[attr] === fmtpAttrs[attr];\n });\n });\n\n // If none of the matched Payload Types also have matching fmtp attributes,\n // then retain all of them, otherwise retain only the Payload Type that\n // matches the peer fmtp attributes.\n return typeof matchingPt === 'number' ? [matchingPt] : matchingPts;\n}\n\n/**\n * Filter codecs in an m= section based on its peer m= section from the other peer.\n * @param {string} section\n * @param {Map} peerMidsToMediaSections\n * @param {Array} codecsToRemove\n * @returns {string}\n */\nfunction filterCodecsInMediaSection(section, peerMidsToMediaSections, codecsToRemove) {\n // Do nothing if the m= section represents neither audio nor video.\n if (!/^m=(audio|video)/.test(section)) {\n return section;\n }\n\n // Do nothing if the m= section does not have an equivalent remote m= section.\n const mid = getMidForMediaSection(section);\n const peerSection = mid && peerMidsToMediaSections.get(mid);\n if (!peerSection) {\n return section;\n }\n\n // Construct a Map of the peer Payload Types to their codec names.\n const peerPtToCodecs = createPtToCodecName(peerSection);\n // Construct a Map of the codec names to their Payload Types.\n const codecsToPts = createCodecMapForMediaSection(section);\n // Maintain a list of non-rtx Payload Types to retain.\n let pts = flatMap(Array.from(peerPtToCodecs), ([peerPt, peerCodec]) =>\n peerCodec !== 'rtx' && !codecsToRemove.includes(peerCodec)\n ? getMatchingPayloadTypes(\n peerCodec,\n peerPt,\n codecsToPts,\n section,\n peerSection)\n : []);\n\n // For each Payload Type that will be retained, retain their corresponding rtx\n // Payload Type if present.\n const rtxPts = codecsToPts.get('rtx') || [];\n // In \"a=fmtp: apt=\", extract the codec PT associated with rtxPt.\n pts = pts.concat(rtxPts.filter(rtxPt => {\n const fmtpAttrs = getFmtpAttributesForPt(rtxPt, section);\n return fmtpAttrs && pts.includes(fmtpAttrs.apt);\n }));\n\n // Filter out the below mentioned attribute lines in the m= section that do not\n // belong to one of the Payload Types that are to be retained.\n // 1. \"a=rtpmap: \"\n // 2. \"a=rtcp-fb: [ ]*\"\n // 3. \"a=fmtp: =[;=]*\"\n const lines = section.split('\\r\\n').filter(line => {\n const ptMatches = line.match(/^a=(rtpmap|fmtp|rtcp-fb):(.+) .+$/);\n const pt = ptMatches && ptMatches[2];\n return !ptMatches || (pt && pts.includes(parseInt(pt, 10)));\n });\n\n // Filter the list of Payload Types in the first line of the m= section.\n const orderedPts = getPayloadTypesInMediaSection(section).filter(pt => pts.includes(pt));\n return setPayloadTypesInMediaSection(orderedPts, lines.join('\\r\\n'));\n}\n\n/**\n * Filter local codecs based on the remote SDP.\n * @param {string} localSdp\n * @param {string} remoteSdp\n * @returns {string} - Updated local SDP\n */\nfunction filterLocalCodecs(localSdp, remoteSdp) {\n const localMediaSections = getMediaSections(localSdp);\n const localSession = localSdp.split('\\r\\nm=')[0];\n const remoteMidsToMediaSections = createMidToMediaSectionMap(remoteSdp);\n return [localSession].concat(localMediaSections.map(localSection => {\n return filterCodecsInMediaSection(localSection, remoteMidsToMediaSections, []);\n })).join('\\r\\n');\n}\n\n/**\n * Return a new SDP string after reverting simulcast for non vp8 sections in remote sdp.\n * @param localSdp - simulcast enabled local sdp\n * @param localSdpWithoutSimulcast - local sdp before simulcast was set\n * @param remoteSdp - remote sdp\n * @param revertForAll - when true simulcast will be reverted for all codecs. when false it will be reverted\n * only for non-vp8 codecs.\n * @return {string} Updated SDP string\n */\nfunction revertSimulcast(localSdp, localSdpWithoutSimulcast, remoteSdp, revertForAll = false) {\n const remoteMidToMediaSections = createMidToMediaSectionMap(remoteSdp);\n const localMidToMediaSectionsWithoutSimulcast = createMidToMediaSectionMap(localSdpWithoutSimulcast);\n const mediaSections = getMediaSections(localSdp);\n const session = localSdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(section => {\n section = section.replace(/\\r\\n$/, '');\n if (!/^m=video/.test(section)) {\n return section;\n }\n const midMatches = section.match(/^a=mid:(.+)$/m);\n const mid = midMatches && midMatches[1];\n if (!mid) {\n return section;\n }\n\n const remoteSection = remoteMidToMediaSections.get(mid);\n const remotePtToCodecs = createPtToCodecName(remoteSection);\n const remotePayloadTypes = getPayloadTypesInMediaSection(remoteSection);\n\n const isVP8ThePreferredCodec = remotePayloadTypes.length && remotePtToCodecs.get(remotePayloadTypes[0]) === 'vp8';\n const shouldRevertSimulcast = revertForAll || !isVP8ThePreferredCodec;\n return shouldRevertSimulcast ? localMidToMediaSectionsWithoutSimulcast.get(mid).replace(/\\r\\n$/, '') : section;\n })).concat('').join('\\r\\n');\n}\n\n/**\n * Add or rewrite MSIDs for new m= sections in the given SDP with their corresponding\n * local MediaStreamTrack IDs. These can be different when previously removed MediaStreamTracks\n * are added back (or Track IDs may not be present in the SDPs at all once browsers implement\n * the latest WebRTC spec).\n * @param {string} sdp\n * @param {Map} activeMidsToTrackIds\n * @param {Map>} trackIdsByKind\n * @returns {string}\n */\nfunction addOrRewriteNewTrackIds(sdp, activeMidsToTrackIds, trackIdsByKind) {\n // NOTE(mmalavalli): The m= sections for the new MediaStreamTracks are usually\n // present after the m= sections for the existing MediaStreamTracks, in order\n // of addition.\n const newMidsToTrackIds = Array.from(trackIdsByKind).reduce((midsToTrackIds, [kind, trackIds]) => {\n const mediaSections = getMediaSections(sdp, kind, 'send(only|recv)');\n const newMids = mediaSections.map(getMidForMediaSection).filter(mid => !activeMidsToTrackIds.has(mid));\n newMids.forEach((mid, i) => midsToTrackIds.set(mid, trackIds[i]));\n return midsToTrackIds;\n }, new Map());\n return addOrRewriteTrackIds(sdp, newMidsToTrackIds);\n}\n\n/**\n * Add or rewrite MSIDs in the given SDP with their corresponding local MediaStreamTrack IDs.\n * These IDs need not be the same (or Track IDs may not be present in the SDPs at all once\n * browsers implement the latest WebRTC spec).\n * @param {string} sdp\n * @param {Map} midsToTrackIds\n * @returns {string}\n */\nfunction addOrRewriteTrackIds(sdp, midsToTrackIds) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(mediaSection => {\n // Do nothing if the m= section represents neither audio nor video.\n if (!/^m=(audio|video)/.test(mediaSection)) {\n return mediaSection;\n }\n // This shouldn't happen, but in case there is no MID for the m= section, do nothing.\n const mid = getMidForMediaSection(mediaSection);\n if (!mid) {\n return mediaSection;\n }\n // In case there is no Track ID for the given MID in the map, do nothing.\n const trackId = midsToTrackIds.get(mid);\n if (!trackId) {\n return mediaSection;\n }\n // This shouldn't happen, but in case there is no a=msid: line, do nothing.\n const attributes = (mediaSection.match(/^a=msid:(.+)$/m) || [])[1];\n if (!attributes) {\n return mediaSection;\n }\n // If the a=msid: line contains the \"appdata\" field, then replace it with the Track ID,\n // otherwise append the Track ID.\n const [msid, trackIdToRewrite] = attributes.split(' ');\n const msidRegex = new RegExp(`msid:${msid}${trackIdToRewrite ? ` ${trackIdToRewrite}` : ''}$`, 'gm');\n return mediaSection.replace(msidRegex, `msid:${msid} ${trackId}`);\n })).join('\\r\\n');\n}\n\n/**\n * Removes specified ssrc attributes from given sdp.\n * @param {string} sdp\n * @param {Array} ssrcAttributesToRemove\n * @returns {string}\n */\nfunction removeSSRCAttributes(sdp, ssrcAttributesToRemove) {\n return sdp.split('\\r\\n').filter(line =>\n !ssrcAttributesToRemove.find(srcAttribute => new RegExp('a=ssrc:.*' + srcAttribute + ':', 'g').test(line))\n ).join('\\r\\n');\n}\n\n/**\n * Disable RTX in a given sdp.\n * @param {string} sdp\n * @returns {string} sdp without RTX\n */\nfunction disableRtx(sdp) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(mediaSection => {\n // Do nothing if the m= section does not represent a video track.\n if (!/^m=video/.test(mediaSection)) {\n return mediaSection;\n }\n\n // Create a map of codecs to payload types.\n const codecsToPts = createCodecMapForMediaSection(mediaSection);\n // Get the RTX payload types.\n const rtxPts = codecsToPts.get('rtx');\n\n // Do nothing if there are no RTX payload types.\n if (!rtxPts) {\n return mediaSection;\n }\n\n // Remove the RTX payload types.\n const pts = new Set(getPayloadTypesInMediaSection(mediaSection));\n rtxPts.forEach(rtxPt => pts.delete(rtxPt));\n\n // Get the RTX SSRC.\n const rtxSSRCMatches = mediaSection.match(/a=ssrc-group:FID [0-9]+ ([0-9]+)/);\n const rtxSSRC = rtxSSRCMatches && rtxSSRCMatches[1];\n\n // Remove the following lines associated with the RTX payload types:\n // 1. \"a=fmtp: apt=\"\n // 2. \"a=rtpmap: rtx/...\"\n // 3. \"a=ssrc: cname:...\"\n // 4. \"a=ssrc-group:FID \"\n const filterRegexes = [\n /^a=fmtp:.+ apt=.+$/,\n /^a=rtpmap:.+ rtx\\/.+$/,\n /^a=ssrc-group:.+$/\n ].concat(rtxSSRC\n ? [new RegExp(`^a=ssrc:${rtxSSRC} .+$`)]\n : []);\n\n mediaSection = mediaSection.split('\\r\\n')\n .filter(line => filterRegexes.every(regex => !regex.test(line)))\n .join('\\r\\n');\n\n // Reconstruct the m= section without the RTX payload types.\n return setPayloadTypesInMediaSection(Array.from(pts), mediaSection);\n })).join('\\r\\n');\n}\n\n/**\n * Generate an a=fmtp: line from the given payload type and attributes.\n * @param {PT} pt\n * @param {*} fmtpAttrs\n * @returns {string}\n */\nfunction generateFmtpLineFromPtAndAttributes(pt, fmtpAttrs) {\n const serializedFmtpAttrs = Object.entries(fmtpAttrs).map(([name, value]) => {\n return `${name}=${value}`;\n }).join(';');\n return `a=fmtp:${pt} ${serializedFmtpAttrs}`;\n}\n\n/**\n * Enable DTX for opus in the m= sections for the given MIDs.`\n * @param {string} sdp\n * @param {Array} [mids] - If not specified, enables opus DTX for all\n * audio m= lines.\n * @returns {string}\n */\nfunction enableDtxForOpus(sdp, mids) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n\n mids = mids || mediaSections\n .filter(section => /^m=audio/.test(section))\n .map(getMidForMediaSection);\n\n return [session].concat(mediaSections.map(section => {\n // Do nothing if the m= section is not audio.\n if (!/^m=audio/.test(section)) {\n return section;\n }\n\n // Build a map codecs to payload types.\n const codecsToPts = createCodecMapForMediaSection(section);\n\n // Do nothing if a payload type for opus does not exist.\n const opusPt = codecsToPts.get('opus');\n if (!opusPt) {\n return section;\n }\n\n // If no fmtp attributes are found for opus, do nothing.\n const opusFmtpAttrs = getFmtpAttributesForPt(opusPt, section);\n if (!opusFmtpAttrs) {\n return section;\n }\n\n // Add usedtx=1 to the a=fmtp: line for opus.\n const origOpusFmtpLine = generateFmtpLineFromPtAndAttributes(opusPt, opusFmtpAttrs);\n const origOpusFmtpRegex = new RegExp(origOpusFmtpLine);\n\n // If the m= section's MID is in the list of MIDs, then enable dtx. Otherwise disable it.\n const mid = getMidForMediaSection(section);\n if (mids.includes(mid)) {\n opusFmtpAttrs.usedtx = 1;\n } else {\n delete opusFmtpAttrs.usedtx;\n }\n\n const opusFmtpLineWithDtx = generateFmtpLineFromPtAndAttributes(opusPt, opusFmtpAttrs);\n return section.replace(origOpusFmtpRegex, opusFmtpLineWithDtx);\n })).join('\\r\\n');\n}\n\nexports.addOrRewriteNewTrackIds = addOrRewriteNewTrackIds;\nexports.addOrRewriteTrackIds = addOrRewriteTrackIds;\nexports.createCodecMapForMediaSection = createCodecMapForMediaSection;\nexports.createPtToCodecName = createPtToCodecName;\nexports.disableRtx = disableRtx;\nexports.enableDtxForOpus = enableDtxForOpus;\nexports.filterLocalCodecs = filterLocalCodecs;\nexports.getMediaSections = getMediaSections;\nexports.removeSSRCAttributes = removeSSRCAttributes;\nexports.revertSimulcast = revertSimulcast;\nexports.setCodecPreferences = setCodecPreferences;\nexports.setSimulcast = setSimulcast;\n", "'use strict';\n\nclass Filter {\n constructor(options) {\n options = Object.assign({\n getKey: function defaultGetKey(a) { return a; },\n getValue: function defaultGetValue(a) { return a; },\n isLessThanOrEqualTo: function defaultIsLessThanOrEqualTo(a, b) { return a <= b; }\n }, options);\n Object.defineProperties(this, {\n _getKey: {\n value: options.getKey\n },\n _getValue: {\n value: options.getValue\n },\n _isLessThanOrEqualTo: {\n value: options.isLessThanOrEqualTo\n },\n _map: {\n value: new Map()\n }\n });\n }\n\n toMap() {\n return new Map(this._map);\n }\n\n updateAndFilter(entries) {\n return entries.filter(this.update, this);\n }\n\n update(entry) {\n const key = this._getKey(entry);\n const value = this._getValue(entry);\n if (this._map.has(key) &&\n this._isLessThanOrEqualTo(value, this._map.get(key))) {\n return false;\n }\n this._map.set(key, value);\n return true;\n }\n}\n\nmodule.exports = Filter;\n", "'use strict';\n\nconst Filter = require('../../util/filter');\n\n/**\n * An {@link IceBox} stores trickled ICE candidates. Candidates added to the\n * {@link IceBox} via {@link IceBox#update} are compared against previously\n * trickled candidates and only new candidates will be returned (assuming they\n * match the current ICE username fragment set by {@link IceBox#setUfrag}).\n * @property {?string} ufrag\n */\nclass IceBox {\n /**\n * Construct an {@link IceBox}.\n */\n constructor() {\n Object.defineProperties(this, {\n _filter: {\n value: new Filter({\n getKey: function getKey(iceState) {\n return iceState.ufrag;\n },\n isLessThanOrEqualTo: function isLessThanOrEqualTo(a, b) {\n return a.revision <= b.revision;\n }\n })\n },\n _ufrag: {\n writable: true,\n value: null\n },\n ufrag: {\n enumerable: true,\n get() {\n return this._ufrag;\n }\n }\n });\n }\n\n /**\n * Set the ICE username fragment on the {@link IceBox}. This method returns any\n * ICE candidates associated with the username fragment.\n * @param {string} ufrag\n * @returns {Array}\n */\n setUfrag(ufrag) {\n this._ufrag = ufrag;\n const ice = this._filter.toMap().get(ufrag);\n return ice ? ice.candidates : [];\n }\n\n /**\n * Update the {@link IceBox}. This method returns any new ICE candidates\n * associated with the current username fragment.\n * @param {object} iceState\n * @returns {Array}\n */\n update(iceState) {\n // NOTE(mroberts): The Server sometimes does not set the candidates property.\n iceState.candidates = iceState.candidates || [];\n const oldIceState = this._filter.toMap().get(iceState.ufrag);\n const oldCandidates = oldIceState ? oldIceState.candidates : [];\n return this._filter.update(iceState) && this._ufrag === iceState.ufrag\n ? iceState.candidates.slice(oldCandidates.length)\n : [];\n }\n}\n\nmodule.exports = IceBox;\n", "'use strict';\n\nconst { ICE_ACTIVITY_CHECK_PERIOD_MS, ICE_INACTIVITY_THRESHOLD_MS } = require('../../util/constants');\n\n/**\n * Monitors a {@link RTCPeerConnection}'s stats and notifies\n * caller when inactivity is detected.\n */\nclass IceConnectionMonitor {\n /**\n * Construct an {@link IceConnectionMonitor}.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options]\n */\n constructor(peerConnection, options) {\n options = Object.assign({\n activityCheckPeriodMs: ICE_ACTIVITY_CHECK_PERIOD_MS,\n inactivityThresholdMs: ICE_INACTIVITY_THRESHOLD_MS,\n }, options);\n\n Object.defineProperties(this, {\n _activityCheckPeriodMs: {\n value: options.activityCheckPeriodMs\n },\n _inactivityThresholdMs: {\n value: options.inactivityThresholdMs\n },\n _lastActivity: {\n value: null,\n writable: true\n },\n _peerConnection: {\n value: peerConnection\n },\n _timer: {\n value: null,\n writable: true,\n },\n _onIceConnectionStateChanged: {\n value: null,\n writable: true\n }\n });\n }\n\n _getActivePairStat(stats) {\n const statsArray = Array.from(stats.values());\n const activePairStats = statsArray.find(stat => stat.type === 'candidate-pair' && stat.nominated);\n // NOTE(mpatwardhan): sometimes (JSDK-2667) after getting disconnected while switching network\n // we may not find active pair. Treat this as 0 bytesReceived so that we count it towards inactivity.\n return activePairStats || {\n bytesReceived: 0,\n timestamp: Math.round((new Date()).getTime())\n };\n }\n\n /**\n * Get ICE connection stats, and extract received and send bytes.\n * @returns Promise\n */\n _getIceConnectionStats() {\n return this._peerConnection.getStats().then(stats => this._getActivePairStat(stats)).catch(() => {\n return null;\n });\n }\n\n /**\n * schedules/un-schedules inactivity callback.\n */\n _scheduleInactivityCallback(callback) {\n if (callback && this._onIceConnectionStateChanged === null) {\n // schedule callback\n this._onIceConnectionStateChanged = () => {\n if (this._peerConnection.iceConnectionState === 'disconnected') {\n // eslint-disable-next-line callback-return\n callback();\n }\n };\n this._peerConnection.addEventListener('iceconnectionstatechange', this._onIceConnectionStateChanged);\n } else if (!callback && this._onIceConnectionStateChanged) {\n // unschedule callback\n this._peerConnection.removeEventListener('iceconnectionstatechange', this._onIceConnectionStateChanged);\n this._onIceConnectionStateChanged = null;\n }\n }\n\n /**\n * Start monitoring the ICE connection.\n * Monitors bytes received on active ice connection pair,\n * invokes onIceConnectionInactive when inactivity is detected.\n * @param {function} onIceConnectionInactive\n */\n start(onIceConnectionInactive) {\n this.stop();\n\n this._timer = setInterval(() => {\n this._getIceConnectionStats().then(iceStats => {\n if (!iceStats) {\n return;\n }\n\n // NOTE(mpatwardhan): We look at bytesReceived on active candidate pair as an indication of active ice connection.\n // As per spec (https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-bytesreceived) this value\n // includes RTCP traffic and is +ve even when there are no tracks subscribed to.\n if (!this._lastActivity || this._lastActivity.bytesReceived !== iceStats.bytesReceived) {\n this._lastActivity = iceStats;\n // detected activity, cancel scheduled callback if any.\n this._scheduleInactivityCallback(null);\n }\n\n if (iceStats.timestamp - this._lastActivity.timestamp >= this._inactivityThresholdMs) {\n // detected inactivity.\n if (this._peerConnection.iceConnectionState === 'disconnected') {\n onIceConnectionInactive();\n } else if (this._onIceConnectionStateChanged === null) {\n this._scheduleInactivityCallback(onIceConnectionInactive);\n }\n }\n });\n }, this._activityCheckPeriodMs);\n }\n\n /**\n * Stop monitoring the ICE connection state.\n * @returns {void}\n */\n stop() {\n this._scheduleInactivityCallback(null);\n if (this._timer !== null) {\n clearInterval(this._timer);\n this._timer = null;\n this._lastActivity = null;\n }\n }\n}\n\nmodule.exports = IceConnectionMonitor;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\n/**\n * @classdesc A {@link DataTransport} implements {@link MediaSignalingTransport}\n * in terms of an RTCDataChannel.\n * @extends EventEmitter\n * @implements MediaSignalingTransport\n * @emits DataTransport#message\n */\nclass DataTransport extends EventEmitter {\n /**\n * Construct a {@link DataTransport}.\n * @param {RTCDataChannel} dataChannel\n */\n constructor(dataChannel) {\n super();\n\n Object.defineProperties(this, {\n _dataChannel: {\n value: dataChannel\n },\n _messageQueue: {\n value: []\n }\n });\n\n dataChannel.addEventListener('open', () => {\n this._messageQueue.splice(0).forEach(message => this._publish(message));\n });\n\n dataChannel.addEventListener('message', ({ data }) => {\n try {\n const message = JSON.parse(data);\n this.emit('message', message);\n } catch (error) {\n // Do nothing.\n }\n });\n\n this.publish({ type: 'ready' });\n }\n\n /**\n * @param message\n * @private\n */\n _publish(message) {\n const data = JSON.stringify(message);\n try {\n this._dataChannel.send(data);\n } catch (error) {\n // Do nothing.\n }\n }\n\n /**\n * Publish a message. Returns true if calling the method resulted in\n * publishing (or eventually publishing) the update.\n * @param {object} message\n * @returns {boolean}\n */\n publish(message) {\n const dataChannel = this._dataChannel;\n if (dataChannel.readyState === 'closing' || dataChannel.readyState === 'closed') {\n return false;\n }\n if (dataChannel.readyState === 'connecting') {\n this._messageQueue.push(message);\n return true;\n }\n this._publish(message);\n return true;\n }\n}\n\n/**\n * The {@link DataTransport} received a message.\n * @event DataTransport#message\n * @param {object} message\n */\n\nmodule.exports = DataTransport;\n", "'use strict';\n\nconst DataTrackTransceiver = require('./transceiver');\nconst DataTransport = require('./transport');\n\n/**\n * A {@link DataTrackReceiver} represents a {@link DataTrackTransceiver} over\n * which data can be received. Internally, it users a single RTCDataChannel to\n * receive data.\n * @extends DataTrackTransceiver\n * @emits DataTrackReceiver#message\n * @emits DataTrackReceiver#close\n */\nclass DataTrackReceiver extends DataTrackTransceiver {\n /**\n * Construct an {@link DataTrackReceiver}.\n * @param {RTCDataChannel} dataChannel\n */\n constructor(dataChannel) {\n super(\n dataChannel.label,\n dataChannel.maxPacketLifeTime,\n dataChannel.maxRetransmits,\n dataChannel.ordered\n );\n\n Object.defineProperties(this, {\n _dataChannel: {\n value: dataChannel\n }\n });\n\n // NOTE(mmalavalli): In Firefox, the default value for \"binaryType\" is \"blob\".\n // So, we set it to \"arraybuffer\" to ensure that it is consistent with Chrome\n // and Safari.\n dataChannel.binaryType = 'arraybuffer';\n\n dataChannel.addEventListener('message', event => {\n this.emit('message', event.data);\n });\n\n dataChannel.addEventListener('close', () => {\n this.emit('close');\n });\n }\n\n stop() {\n this._dataChannel.close();\n super.stop();\n }\n\n /**\n * Create a {@link DataTransport} from the {@link DataTrackReceiver}.\n * @returns {DataTransport}\n */\n toDataTransport() {\n return new DataTransport(this._dataChannel);\n }\n}\n\n/**\n * @event DataTrackReceiver#message\n * @param {string|ArrayBuffer} data\n */\n\n/**\n * @event DataTrackReceiver#close\n */\n\nmodule.exports = DataTrackReceiver;\n", "'use strict';\n\nconst MediaTrackTransceiver = require('./transceiver');\n\n/**\n * A {@link MediaTrackReceiver} represents a remote MediaStreamTrack.\n * @extends MediaTrackTransceiver\n */\nclass MediaTrackReceiver extends MediaTrackTransceiver {\n /**\n * Construct a {@link MediaTrackReceiver}.\n * @param {Track.ID} id - The MediaStreamTrack ID signaled through RSP/SDP\n * @param {MediaStreamTrack} mediaStreamTrack - The remote MediaStreamTrack\n */\n constructor(id, mediaStreamTrack) {\n super(id, mediaStreamTrack);\n }\n}\n\nmodule.exports = MediaTrackReceiver;\n", "'use strict';\n\nconst { getMediaSections } = require('./');\n\n/**\n * An {@link TrackMatcher} matches an RTCTrackEvent with a MediaStreamTrack\n * ID based on the MID of the underlying RTCRtpTransceiver.\n */\nclass TrackMatcher {\n /**\n * Construct an {@link TrackMatcher}.\n */\n constructor() {\n Object.defineProperties(this, {\n _midsToTrackIds: {\n value: new Map(),\n writable: true\n }\n });\n }\n\n /**\n * Match a given MediaStreamTrack with its ID.\n * @param {RTCTrackEvent} event\n * @returns {?Track.ID}\n */\n match(event) {\n return this._midsToTrackIds.get(event.transceiver.mid) || null;\n }\n\n /**\n * Update the {@link TrackMatcher} with a new SDP.\n * @param {string} sdp\n */\n update(sdp) {\n const sections = getMediaSections(sdp, '(audio|video)');\n this._midsToTrackIds = sections.reduce((midsToTrackIds, section) => {\n const midMatches = section.match(/^a=mid:(.+)$/m) || [];\n const trackIdMatches = section.match(/^a=msid:.+ (.+)$/m) || [];\n const mid = midMatches[1];\n const trackId = trackIdMatches[1];\n return mid && trackId ? midsToTrackIds.set(mid, trackId) : midsToTrackIds;\n }, this._midsToTrackIds);\n }\n}\n\nmodule.exports = TrackMatcher;\n", "'use strict';\n\nconst { RTCSessionDescription } = require('../../webrtc');\n\nconst { createPtToCodecName, getMediaSections } = require('./');\n\n/**\n * An RTX payload type\n * @typedef {PT} RtxPT\n */\n\n/**\n * A non-RTX payload type\n * @typedef {PT} NonRtxPT\n */\n\n/**\n * A Set with at least one element\n * @typedef {Set} NonEmptySet\n */\n\n/**\n * Apply the workaround for Issue 8329 to an RTCSessionDescriptionInit.\n * @param {RTCSessionDescriptionInit} description\n * @returns {RTCSessionDescription} newDescription\n */\nfunction workaround(description) {\n const descriptionInit = { type: description.type };\n if (description.type !== 'rollback') {\n descriptionInit.sdp = sdpWorkaround(description.sdp);\n }\n return new RTCSessionDescription(descriptionInit);\n}\n\n/**\n * @param {string} sdp\n * @returns {string} newSdp\n */\nfunction sdpWorkaround(sdp) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session]\n .concat(mediaSections.map(mediaSectionWorkaround))\n .join('\\r\\n');\n}\n\n/**\n * @param {string} mediaSection\n * @returns {string} newMediaSection\n */\nfunction mediaSectionWorkaround(mediaSection) {\n const ptToCodecName = createPtToCodecName(mediaSection);\n mediaSection = deleteDuplicateRtxPts(mediaSection, ptToCodecName);\n const codecNameToPts = createCodecNameToPts(ptToCodecName);\n const rtxPts = codecNameToPts.get('rtx') || new Set();\n\n const invalidRtxPts = new Set();\n const rtxPtToAssociatedPt = createRtxPtToAssociatedPt(\n mediaSection, ptToCodecName, rtxPts, invalidRtxPts);\n const associatedPtToRtxPt = createAssociatedPtToRtxPt(\n rtxPtToAssociatedPt, invalidRtxPts);\n\n const unassociatedRtxPts = Array.from(invalidRtxPts);\n\n // NOTE(mroberts): We normalize to lowercase.\n const knownCodecNames = ['h264', 'vp8', 'vp9'];\n const unassociatedPts = knownCodecNames.reduce((unassociatedPts, codecName) => {\n const pts = codecNameToPts.get(codecName) || new Set();\n return Array.from(pts).reduce((unassociatedPts, pt) => associatedPtToRtxPt.has(pt)\n ? unassociatedPts\n : unassociatedPts.add(pt), unassociatedPts);\n }, new Set());\n\n unassociatedPts.forEach(pt => {\n if (unassociatedRtxPts.length) {\n const rtxPt = unassociatedRtxPts.shift();\n mediaSection = deleteFmtpAttributesForRtxPt(mediaSection, rtxPt);\n mediaSection = addFmtpAttributeForRtxPt(mediaSection, rtxPt, pt);\n }\n });\n\n unassociatedRtxPts.forEach(rtxPt => {\n mediaSection = deleteFmtpAttributesForRtxPt(mediaSection, rtxPt);\n mediaSection = deleteRtpmapAttributesForRtxPt(mediaSection, rtxPt);\n });\n\n return mediaSection;\n}\n\n/**\n * @param {string} mediaSection\n * @param {Map} ptToCodecName\n * @returns {string} newMediaSection\n */\nfunction deleteDuplicateRtxPts(mediaSection, ptToCodecName) {\n // NOTE(syerrapragada): In some cases Chrome produces an offer/answer\n // with duplicate \"rtx\" payload mapping in media section. When applied,\n // Chrome rejects the SDP. We workaround this by deleting duplicate\n // \"rtx\" mappings found in SDP.\n return Array.from(ptToCodecName.keys()).reduce((section, pt) => {\n const rtpmapRegex = new RegExp(`^a=rtpmap:${pt} rtx.+$`, 'gm');\n return (section.match(rtpmapRegex) || []).slice(ptToCodecName.get(pt) === 'rtx' ? 1 : 0).reduce((section, rtpmap) => {\n const rtpmapRegex = new RegExp(`\\r\\n${rtpmap}`);\n const fmtpmapRegex = new RegExp(`\\r\\na=fmtp:${pt} apt=[0-9]+`);\n return section.replace(rtpmapRegex, '').replace(fmtpmapRegex, '');\n }, section);\n }, mediaSection);\n}\n\n/**\n * @param {Map} ptToCodecName\n * @returns {Map>} codecNameToPts\n */\nfunction createCodecNameToPts(ptToCodecName) {\n const codecNameToPts = new Map();\n ptToCodecName.forEach((codecName, pt) => {\n const pts = codecNameToPts.get(codecName) || new Set();\n return codecNameToPts.set(codecName, pts.add(pt));\n });\n return codecNameToPts;\n}\n\n/**\n * @param {string} mediaSection\n * @param {Map} ptToCodecName\n * @param {Set} rtxPts\n * @param {Set} invalidRtxPts\n * @returns {Map} rtxPtToAssociatedPt\n */\nfunction createRtxPtToAssociatedPt(mediaSection, ptToCodecName, rtxPts, invalidRtxPts) {\n return Array.from(rtxPts).reduce((rtxPtToAssociatedPt, rtxPt) => {\n const fmtpPattern = new RegExp(`a=fmtp:${rtxPt} apt=(\\\\d+)`);\n const matches = mediaSection.match(fmtpPattern);\n if (!matches) {\n invalidRtxPts.add(rtxPt);\n return rtxPtToAssociatedPt;\n }\n\n const pt = Number.parseInt(matches[1]);\n if (!ptToCodecName.has(pt)) {\n // This is Issue 8329.\n invalidRtxPts.add(rtxPt);\n return rtxPtToAssociatedPt;\n }\n\n const codecName = ptToCodecName.get(pt);\n if (codecName === 'rtx') {\n // Strange\n invalidRtxPts.add(rtxPt);\n return rtxPtToAssociatedPt;\n }\n\n return rtxPtToAssociatedPt.set(rtxPt, pt);\n }, new Map());\n}\n\n/**\n * @param {string} mediaSection\n * @param {Map} rtxPtToAssociatedPt\n * @param {Set} invalidRtxPts\n * @returns {Map} associatedPtToRtxPt\n */\nfunction createAssociatedPtToRtxPt(rtxPtToAssociatedPt, invalidRtxPts) {\n // First, we construct a Map>.\n const associatedPtToRtxPts = Array.from(rtxPtToAssociatedPt).reduce((associatedPtToRtxPts, pair) => {\n const rtxPt = pair[0];\n const pt = pair[1];\n const rtxPts = associatedPtToRtxPts.get(pt) || new Set();\n return associatedPtToRtxPts.set(pt, rtxPts.add(rtxPt));\n }, new Map());\n\n // Then, we filter down to a Map. Any RtxPTs that map to the\n // same NonRtxPT are removed and added to invalidRtxPts.\n return Array.from(associatedPtToRtxPts).reduce((associatedPtToRtxPt, pair) => {\n const pt = pair[0];\n const rtxPts = Array.from(pair[1]);\n if (rtxPts.length > 1) {\n rtxPts.forEach(rtxPt => {\n invalidRtxPts.add(rtxPt);\n });\n return associatedPtToRtxPt;\n }\n return associatedPtToRtxPt.set(pt, rtxPts[0]);\n }, new Map());\n}\n\n/**\n * @param {string} mediaSection\n * @param {RtxPT} rtxPt\n * @returns {string} newMediaSection\n */\nfunction deleteFmtpAttributesForRtxPt(mediaSection, rtxPt) {\n const pattern = new RegExp(`a=fmtp:${rtxPt}.*\\r\\n`, 'gm');\n return mediaSection.replace(pattern, '');\n}\n\n/**\n * @param {string} mediaSection\n * @param {RtxPT} rtxPt\n * @returns {string} newMediaSection\n */\nfunction deleteRtpmapAttributesForRtxPt(mediaSection, rtxPt) {\n const pattern = new RegExp(`a=rtpmap:${rtxPt}.*\\r\\n`, 'gm');\n return mediaSection.replace(pattern, '');\n}\n\n/**\n * @param {string} mediaSection\n * @param {RtxPT} rtxPt\n * @param {NonRtxPT} pt\n * @returns {string} newMediaSection\n */\nfunction addFmtpAttributeForRtxPt(mediaSection, rtxPt, pt) {\n return mediaSection.endsWith('\\r\\n')\n ? `${mediaSection}a=fmtp:${rtxPt} apt=${pt}\\r\\n`\n : `${mediaSection}\\r\\na=fmtp:${rtxPt} apt=${pt}`;\n}\n\nmodule.exports = workaround;\n", "'use strict';\n\nconst DefaultBackoff = require('../../util/backoff');\nconst {\n RTCIceCandidate: DefaultRTCIceCandidate,\n RTCPeerConnection: DefaultRTCPeerConnection,\n RTCSessionDescription: DefaultRTCSessionDescription,\n getStats: getStatistics\n} = require('../../webrtc');\n\nconst util = require('../../webrtc/util');\n\nconst {\n DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n DEFAULT_LOG_LEVEL,\n DEFAULT_SESSION_TIMEOUT_SEC,\n iceRestartBackoffConfig\n} = require('../../util/constants');\n\nconst {\n addOrRewriteNewTrackIds,\n addOrRewriteTrackIds,\n createCodecMapForMediaSection,\n disableRtx,\n enableDtxForOpus,\n filterLocalCodecs,\n getMediaSections,\n removeSSRCAttributes,\n revertSimulcast,\n setCodecPreferences,\n setSimulcast\n} = require('../../util/sdp');\n\nconst DefaultTimeout = require('../../util/timeout');\n\nconst {\n MediaClientLocalDescFailedError,\n MediaClientRemoteDescFailedError\n} = require('../../util/twilio-video-errors');\n\nconst {\n buildLogLevels,\n getPlatform,\n isChromeScreenShareTrack,\n oncePerTick,\n defer\n} = require('../../util');\n\nconst IceBox = require('./icebox');\nconst DefaultIceConnectionMonitor = require('./iceconnectionmonitor.js');\nconst DataTrackReceiver = require('../../data/receiver');\nconst MediaTrackReceiver = require('../../media/track/receiver');\nconst StateMachine = require('../../statemachine');\nconst Log = require('../../util/log');\nconst TrackMatcher = require('../../util/sdp/trackmatcher');\nconst workaroundIssue8329 = require('../../util/sdp/issue8329');\n\nconst guess = util.guessBrowser();\nconst platform = getPlatform();\nconst isAndroid = /android/.test(platform);\nconst isChrome = guess === 'chrome';\nconst isFirefox = guess === 'firefox';\nconst isSafari = guess === 'safari';\n\nlet nInstances = 0;\n\n/*\nPeerConnectionV2 States\n-----------------------\n\n +------+ +--------+\n | | | |\n | open |--->| closed |\n | | | |\n +------+ +--------+\n | ^ ^\n | | |\n | | |\n v | |\n +----------+ |\n | | |\n | updating |------+\n | |\n +----------+\n\n*/\n\nconst states = {\n open: [\n 'closed',\n 'updating'\n ],\n updating: [\n 'closed',\n 'open'\n ],\n closed: []\n};\n\n/**\n * @extends StateMachine\n * @property {id}\n * @emits PeerConnectionV2#connectionStateChanged\n * @emits PeerConnectionV2#iceConnectionStateChanged\n * @emits PeerConnectionV2#candidates\n * @emits PeerConnectionV2#description\n */\nclass PeerConnectionV2 extends StateMachine {\n /**\n * Construct a {@link PeerConnectionV2}.\n * @param {string} id\n * @param {EncodingParametersImpl} encodingParameters\n * @param {PreferredCodecs} preferredCodecs\n * @param {object} [options]\n */\n constructor(id, encodingParameters, preferredCodecs, options) {\n super('open', states);\n options = Object.assign({\n enableDscp: false,\n dummyAudioMediaStreamTrack: null,\n isChromeScreenShareTrack,\n iceServers: [],\n logLevel: DEFAULT_LOG_LEVEL,\n offerOptions: {},\n revertSimulcast,\n sessionTimeout: DEFAULT_SESSION_TIMEOUT_SEC * 1000,\n setCodecPreferences,\n setSimulcast,\n Backoff: DefaultBackoff,\n IceConnectionMonitor: DefaultIceConnectionMonitor,\n RTCIceCandidate: DefaultRTCIceCandidate,\n RTCPeerConnection: DefaultRTCPeerConnection,\n RTCSessionDescription: DefaultRTCSessionDescription,\n Timeout: DefaultTimeout\n }, options);\n\n const configuration = getConfiguration(options);\n const logLevels = buildLogLevels(options.logLevel);\n const RTCPeerConnection = options.RTCPeerConnection;\n\n if (options.enableDscp === true) {\n options.chromeSpecificConstraints = options.chromeSpecificConstraints || {};\n options.chromeSpecificConstraints.optional = options.chromeSpecificConstraints.optional || [];\n options.chromeSpecificConstraints.optional.push({ googDscp: true });\n }\n\n const log = options.log ? options.log.createLog('webrtc', this) : new Log('webrtc', this, logLevels, options.loggerName);\n const peerConnection = new RTCPeerConnection(configuration, options.chromeSpecificConstraints);\n\n if (options.dummyAudioMediaStreamTrack) {\n peerConnection.addTrack(options.dummyAudioMediaStreamTrack);\n }\n\n Object.defineProperties(this, {\n _appliedTrackIdsToAttributes: {\n value: new Map(),\n writable: true\n },\n _dataChannels: {\n value: new Map()\n },\n _dataTrackReceivers: {\n value: new Set()\n },\n _descriptionRevision: {\n writable: true,\n value: 0\n },\n _didGenerateLocalCandidates: {\n writable: true,\n value: false\n },\n _enableDscp: {\n value: options.enableDscp\n },\n _encodingParameters: {\n value: encodingParameters\n },\n _isChromeScreenShareTrack: {\n value: options.isChromeScreenShareTrack,\n },\n _iceGatheringFailed: {\n value: false,\n writable: true\n },\n _iceGatheringTimeout: {\n value: new options.Timeout(\n () => this._handleIceGatheringTimeout(),\n DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n false)\n },\n _iceRestartBackoff: {\n // eslint-disable-next-line new-cap\n value: new options.Backoff(iceRestartBackoffConfig)\n },\n _instanceId: {\n value: ++nInstances\n },\n _isIceConnectionInactive: {\n writable: true,\n value: false\n },\n _isIceLite: {\n writable: true,\n value: false\n },\n _isIceRestartBackoffInProgress: {\n writable: true,\n value: false\n },\n _isRestartingIce: {\n writable: true,\n value: false\n },\n _lastIceConnectionState: {\n writable: true,\n value: null\n },\n _lastStableDescriptionRevision: {\n writable: true,\n value: 0\n },\n _localCandidates: {\n writable: true,\n value: []\n },\n _localCodecs: {\n value: new Set()\n },\n _localCandidatesRevision: {\n writable: true,\n value: 1\n },\n _localDescriptionWithoutSimulcast: {\n writable: true,\n value: null\n },\n _localDescription: {\n writable: true,\n value: null\n },\n _localUfrag: {\n writable: true,\n value: null\n },\n _log: {\n value: log\n },\n _eventObserver: {\n value: options.eventObserver\n },\n _remoteCodecMaps: {\n value: new Map()\n },\n _rtpSenders: {\n value: new Map()\n },\n _rtpNewSenders: {\n value: new Set()\n },\n _iceConnectionMonitor: {\n value: new options.IceConnectionMonitor(peerConnection)\n },\n _mediaTrackReceivers: {\n value: new Set()\n },\n _needsAnswer: {\n writable: true,\n value: false\n },\n _negotiationRole: {\n writable: true,\n value: null\n },\n _offerOptions: {\n writable: true,\n value: options.offerOptions\n },\n _onEncodingParametersChanged: {\n value: oncePerTick(() => {\n if (!this._needsAnswer) {\n updateEncodingParameters(this);\n }\n })\n },\n _peerConnection: {\n value: peerConnection\n },\n _preferredAudioCodecs: {\n value: preferredCodecs.audio\n },\n _preferredVideoCodecs: {\n value: preferredCodecs.video\n },\n _shouldApplyDtx: {\n value: preferredCodecs.audio.every(({ codec }) => codec !== 'opus')\n || preferredCodecs.audio.some(({ codec, dtx }) => codec === 'opus' && dtx)\n },\n _queuedDescription: {\n writable: true,\n value: null\n },\n _iceReconnectTimeout: {\n value: new options.Timeout(() => {\n log.debug('ICE reconnect timed out');\n this.close();\n }, options.sessionTimeout, false)\n },\n _recycledTransceivers: {\n value: {\n audio: [],\n video: []\n }\n },\n _replaceTrackPromises: {\n value: new Map()\n },\n _remoteCandidates: {\n writable: true,\n value: new IceBox()\n },\n _setCodecPreferences: {\n // NOTE(mmalavalli): Re-ordering payload types in order to make sure a non-H264\n // preferred codec is selected does not work on Android Firefox due to this behavior:\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1683258. So, we work around this by\n // not applying any non-H264 preferred video codec.\n value: isFirefox && isAndroid && preferredCodecs.video[0] && preferredCodecs.video[0].codec.toLowerCase() !== 'h264'\n ? sdp => sdp\n : options.setCodecPreferences\n },\n _setSimulcast: {\n value: options.setSimulcast\n },\n _revertSimulcast: {\n value: options.revertSimulcast\n },\n _RTCIceCandidate: {\n value: options.RTCIceCandidate\n },\n _RTCPeerConnection: {\n value: options.RTCPeerConnection\n },\n _RTCSessionDescription: {\n value: options.RTCSessionDescription\n },\n _shouldOffer: {\n writable: true,\n value: false\n },\n _shouldRestartIce: {\n writable: true,\n value: false\n },\n _trackIdsToAttributes: {\n value: new Map(),\n writable: true\n },\n _trackMatcher: {\n writable: true,\n value: null\n },\n _mediaTrackSenderToPublisherHints: {\n value: new Map()\n },\n id: {\n enumerable: true,\n value: id\n }\n });\n\n encodingParameters.on('changed', this._onEncodingParametersChanged);\n\n peerConnection.addEventListener('connectionstatechange', this._handleConnectionStateChange.bind(this));\n peerConnection.addEventListener('datachannel', this._handleDataChannelEvent.bind(this));\n peerConnection.addEventListener('icecandidate', this._handleIceCandidateEvent.bind(this));\n peerConnection.addEventListener('iceconnectionstatechange', this._handleIceConnectionStateChange.bind(this));\n peerConnection.addEventListener('icegatheringstatechange', this._handleIceGatheringStateChange.bind(this));\n peerConnection.addEventListener('signalingstatechange', this._handleSignalingStateChange.bind(this));\n peerConnection.addEventListener('track', this._handleTrackEvent.bind(this));\n\n const self = this;\n this.on('stateChanged', function stateChanged(state) {\n if (state !== 'closed') {\n return;\n }\n self.removeListener('stateChanged', stateChanged);\n self._dataChannels.forEach((dataChannel, dataTrackSender) => {\n self.removeDataTrackSender(dataTrackSender);\n });\n });\n }\n\n toString() {\n return `[PeerConnectionV2 #${this._instanceId}: ${this.id}]`;\n }\n\n setEffectiveAdaptiveSimulcast(effectiveAdaptiveSimulcast) {\n this._log.debug('Setting setEffectiveAdaptiveSimulcast: ', effectiveAdaptiveSimulcast);\n // clear adaptive simulcast from codec preferences if it was set.\n this._preferredVideoCodecs.forEach(cs => {\n if ('adaptiveSimulcast' in cs) {\n cs.adaptiveSimulcast = effectiveAdaptiveSimulcast;\n }\n });\n }\n\n get _shouldApplySimulcast() {\n if (!isChrome && !isSafari) {\n return false;\n }\n\n // adaptiveSimulcast is set to false after connected message is received if other party does not support it.\n const simulcast = this._preferredVideoCodecs.some(cs => {\n return cs.codec.toLowerCase() === 'vp8' && cs.simulcast && cs.adaptiveSimulcast !== false;\n });\n\n return simulcast;\n }\n\n /**\n * The {@link PeerConnectionV2}'s underlying RTCPeerConnection's RTCPeerConnectionState\n * if supported by the browser, its RTCIceConnectionState otherwise.\n * @property {RTCPeerConnectionState}\n */\n get connectionState() {\n return this.iceConnectionState === 'failed'\n ? 'failed' : (this._peerConnection.connectionState || this.iceConnectionState);\n }\n\n /**\n * The {@link PeerConnectionV2}'s underlying RTCPeerConnection's\n * RTCIceConnectionState.\n * @property {RTCIceConnectionState}\n */\n get iceConnectionState() {\n return ((this._isIceConnectionInactive && this._peerConnection.iceConnectionState === 'disconnected') || this._iceGatheringFailed)\n ? 'failed' : this._peerConnection.iceConnectionState;\n }\n\n /**\n * Whether the {@link PeerConnectionV2} has negotiated or is in the process\n * of negotiating the application m= section.\n * @returns {boolean}\n */\n get isApplicationSectionNegotiated() {\n if (this._peerConnection.signalingState !== 'closed') {\n // accessing .localDescription in 'closed' state causes it throw exceptions.\n return this._peerConnection.localDescription\n ? getMediaSections(this._peerConnection.localDescription.sdp, 'application').length > 0\n : false;\n }\n return true;\n }\n\n /**\n * Whether adaptive simulcast is enabled.\n * @returns {boolean}\n */\n get _isAdaptiveSimulcastEnabled() {\n const adaptiveSimulcastEntry = this._preferredVideoCodecs.find(cs => 'adaptiveSimulcast' in cs);\n return adaptiveSimulcastEntry && adaptiveSimulcastEntry.adaptiveSimulcast === true;\n }\n\n /**\n * @param {MediaStreamTrack} track\n * @param {Array} encodings\n * @param {boolean} trackReplaced\n * @returns {boolean} true if encodings were updated.\n */\n _maybeUpdateEncodings(track, encodings, trackReplaced = false) {\n if (track.kind !== 'video' || track.readyState === 'ended') {\n return false;\n }\n // NOTE(mmalavalli): There is no guarantee that CanvasCaptureMediaStreamTracks will always have \"width\" and \"height\"\n // in their settings. So, we don't update the encodings if they are not present.\n // Chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1367082\n const { height, width } = track.getSettings();\n if (typeof height !== 'number' || typeof width !== 'number') {\n return false;\n }\n // Note(mpatwardhan): always configure encodings for safari.\n // for chrome only when adaptive simulcast enabled.\n const browser = util.guessBrowser();\n if (browser === 'safari' || (browser === 'chrome' && this._isAdaptiveSimulcastEnabled)) {\n this._updateEncodings(track, encodings, trackReplaced);\n return true;\n }\n\n return false;\n }\n\n /**\n * Configures with default encodings depending on track type and resolution.\n * Default configuration sets some encodings to disabled, and for others set scaleResolutionDownBy\n * values. When trackReplaced is set to true, it will clear 'active' for any encodings that\n * needs to be enabled.\n * @param {MediaStreamTrack} track\n * @param {Array} encodings\n * @param {boolean} trackReplaced\n */\n _updateEncodings(track, encodings, trackReplaced) {\n if (this._isChromeScreenShareTrack(track)) {\n const screenShareActiveLayerConfig = [\n { scaleResolutionDownBy: 1 },\n { scaleResolutionDownBy: 1 }\n ];\n encodings.forEach((encoding, i) => {\n const activeLayerConfig = screenShareActiveLayerConfig[i];\n if (activeLayerConfig) {\n encoding.scaleResolutionDownBy = activeLayerConfig.scaleResolutionDownBy;\n if (trackReplaced) {\n delete encoding.active;\n }\n } else {\n encoding.active = false;\n delete encoding.scaleResolutionDownBy;\n }\n });\n } else {\n const { width, height } = track.getSettings();\n // NOTE(mpatwardhan): for non-screen share tracks\n // enable layers depending on track resolutions\n const pixelsToMaxActiveLayers = [\n { pixels: 960 * 540, maxActiveLayers: 3 },\n { pixels: 480 * 270, maxActiveLayers: 2 },\n { pixels: 0, maxActiveLayers: 1 }\n ];\n\n const trackPixels = width * height;\n const activeLayersInfo = pixelsToMaxActiveLayers.find(layer => trackPixels >= layer.pixels);\n const activeLayers = Math.min(encodings.length, activeLayersInfo.maxActiveLayers);\n encodings.forEach((encoding, i) => {\n const enabled = i < activeLayers;\n if (enabled) {\n encoding.scaleResolutionDownBy = 1 << (activeLayers - i - 1);\n if (trackReplaced) {\n encoding.active = true;\n }\n } else {\n encoding.active = false;\n delete encoding.scaleResolutionDownBy;\n }\n });\n }\n this._log.debug('_updateEncodings:', encodings.map(({ active, scaleResolutionDownBy }, i) => `[${i}: ${active}, ${scaleResolutionDownBy || 0}]`).join(', '));\n }\n\n /**\n * Add an ICE candidate to the {@link PeerConnectionV2}.\n * @private\n * @param {object} candidate\n * @returns {Promise}\n */\n _addIceCandidate(candidate) {\n return Promise.resolve().then(() => {\n candidate = new this._RTCIceCandidate(candidate);\n return this._peerConnection.addIceCandidate(candidate);\n }).catch(error => {\n // NOTE(mmalavalli): Firefox 68+ now generates an RTCIceCandidate with an\n // empty candidate string to signal end-of-candidates, followed by a null\n // candidate. As of now, Chrome and Safari reject this RTCIceCandidate. Since\n // this does not affect the media connection between Firefox 68+ and Chrome/Safari\n // in Peer-to-Peer Rooms, we suppress the Error and log a warning message.\n //\n // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=978582\n //\n this._log.warn(`Failed to add RTCIceCandidate ${candidate ? `\"${candidate.candidate}\"` : 'null'}: `\n + error.message);\n });\n }\n\n /**\n * Add ICE candidates to the {@link PeerConnectionV2}.\n * @private\n * @param {Array} candidates\n * @returns {Promise}\n */\n _addIceCandidates(candidates) {\n return Promise.all(candidates.map(this._addIceCandidate, this)).then(() => {});\n }\n\n /**\n * Add a new RTCRtpTransceiver or update an existing RTCRtpTransceiver for the\n * given MediaStreamTrack.\n * @private\n * @param {MediaStreamTrack} track\n * @returns {RTCRtpTransceiver}\n */\n _addOrUpdateTransceiver(track) {\n const transceiver = takeRecycledTransceiver(this, track.kind);\n if (transceiver && transceiver.sender) {\n const oldTrackId = transceiver.sender.track ? transceiver.sender.track.id : null;\n if (oldTrackId) {\n this._log.warn(`Reusing transceiver: ${transceiver.mid}] ${oldTrackId} => ${track.id}`);\n }\n // NOTE(mpatwardhan):remember this transceiver while we replace track.\n // we recycle transceivers that are not in use after 'negotiationCompleted', but we want to prevent\n // this one from getting recycled while replaceTrack is pending.\n this._replaceTrackPromises.set(transceiver, transceiver.sender.replaceTrack(track).then(() => {\n transceiver.direction = 'sendrecv';\n }, () => {\n // Do nothing.\n }).finally(() => {\n this._replaceTrackPromises.delete(transceiver);\n }));\n return transceiver;\n }\n return this._peerConnection.addTransceiver(track);\n }\n\n /**\n * Check the {@link IceBox}.\n * @private\n * @param {RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _checkIceBox(description) {\n const ufrag = getUfrag(description);\n if (!ufrag) {\n return Promise.resolve();\n }\n const candidates = this._remoteCandidates.setUfrag(ufrag);\n return this._addIceCandidates(candidates);\n }\n\n /**\n * Create an answer and set it on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescriptionInit} offer\n * @returns {Promise}\n */\n _answer(offer) {\n return Promise.resolve().then(() => {\n if (!this._negotiationRole) {\n this._negotiationRole = 'answerer';\n }\n return this._setRemoteDescription(offer);\n }).catch(() => {\n throw new MediaClientRemoteDescFailedError();\n }).then(() => {\n return this._peerConnection.createAnswer();\n }).then(answer => {\n if (isFirefox) {\n // NOTE(mmalavalli): We work around Chromium bug 1106157 by disabling\n // RTX in Firefox 79+. For more details about the bug, please go here:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1106157\n answer = new this._RTCSessionDescription({\n sdp: disableRtx(answer.sdp),\n type: answer.type\n });\n } else {\n answer = workaroundIssue8329(answer);\n }\n\n // NOTE(mpatwardhan): Upcoming chrome versions are going to remove ssrc attributes\n // mslabel and label. See this bug https://bugs.chromium.org/p/webrtc/issues/detail?id=7110\n // and PSA: https://groups.google.com/forum/#!searchin/discuss-webrtc/PSA%7Csort:date/discuss-webrtc/jcZO-Wj0Wus/k2XvPCvoAwAJ\n // We are not referencing those attributes, but this changes goes ahead and removes them to see if it works.\n // this also helps reduce bytes on wires\n let updatedSdp = removeSSRCAttributes(answer.sdp, ['mslabel', 'label']);\n\n if (this._shouldApplySimulcast) {\n let sdpWithoutSimulcast = updatedSdp;\n updatedSdp = this._setSimulcast(sdpWithoutSimulcast, this._trackIdsToAttributes);\n // NOTE(syerrapragada): VMS does not support H264 simulcast. So,\n // unset simulcast for sections in local offer where corresponding\n // sections in answer doesn't have vp8 as preferred codec and reapply offer.\n updatedSdp = this._revertSimulcast(updatedSdp, sdpWithoutSimulcast, offer.sdp);\n }\n\n // NOTE(mmalavalli): Work around Chromium bug 1074421.\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1074421\n updatedSdp = updatedSdp.replace(/42e015/g, '42e01f');\n\n return this._setLocalDescription({\n type: answer.type,\n sdp: updatedSdp\n });\n }).then(() => {\n return this._checkIceBox(offer);\n }).then(() => {\n return this._queuedDescription\n && this._updateDescription(this._queuedDescription);\n }).then(() => {\n this._queuedDescription = null;\n return this._maybeReoffer(this._peerConnection.localDescription);\n }).catch(error => {\n const errorToThrow = error instanceof MediaClientRemoteDescFailedError ? error : new MediaClientLocalDescFailedError();\n this._publishMediaWarning({\n message: 'Failed to _answer',\n code: errorToThrow.code,\n error\n });\n throw errorToThrow;\n });\n }\n\n /**\n * Close the underlying RTCPeerConnection. Returns false if the\n * RTCPeerConnection was already closed.\n * @private\n * @returns {boolean}\n */\n _close() {\n this._iceConnectionMonitor.stop();\n if (this._peerConnection.signalingState !== 'closed') {\n this._peerConnection.close();\n this.preempt('closed');\n this._encodingParameters.removeListener('changed', this._onEncodingParametersChanged);\n return true;\n }\n return false;\n }\n\n /**\n * Handle a \"connectionstatechange\" event.\n * @private\n * @returns {void}\n */\n _handleConnectionStateChange() {\n this.emit('connectionStateChanged');\n }\n\n /**\n * Handle a \"datachannel\" event.\n * @private\n * @param {RTCDataChannelEvent} event\n * @returns {void}\n */\n _handleDataChannelEvent(event) {\n const dataChannel = event.channel;\n const dataTrackReceiver = new DataTrackReceiver(dataChannel);\n this._dataTrackReceivers.add(dataTrackReceiver);\n\n dataChannel.addEventListener('close', () => {\n this._dataTrackReceivers.delete(dataTrackReceiver);\n });\n\n this.emit('trackAdded', dataTrackReceiver);\n }\n\n /**\n * Handle a glare scenario on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescriptionInit} offer\n * @returns {Promise}\n */\n _handleGlare(offer) {\n this._log.debug('Glare detected; rolling back');\n if (this._isRestartingIce) {\n this._log.debug('An ICE restart was in progress; we\\'ll need to restart ICE again after rolling back');\n this._isRestartingIce = false;\n this._shouldRestartIce = true;\n }\n return Promise.resolve().then(() => {\n this._trackIdsToAttributes = new Map(this._appliedTrackIdsToAttributes);\n return this._setLocalDescription({ type: 'rollback' });\n }).then(() => {\n this._needsAnswer = false;\n return this._answer(offer);\n }).then(didReoffer => {\n return didReoffer ? Promise.resolve() : this._offer();\n });\n }\n\n _publishMediaWarning({ message, code, error, sdp }) {\n this._eventObserver.emit('event', { level: 'warning', name: 'error', group: 'media', payload: {\n message,\n code,\n context: JSON.stringify({ error: error.message, sdp })\n } });\n }\n\n /**\n * Handle an ICE candidate event.\n * @private\n * @param {Event} event\n * @returns {void}\n */\n _handleIceCandidateEvent(event) {\n if (event.candidate) {\n this._log.debug('Clearing ICE gathering timeout');\n this._didGenerateLocalCandidates = true;\n this._iceGatheringTimeout.clear();\n this._localCandidates.push(event.candidate);\n }\n const peerConnectionState = {\n ice: {\n candidates: this._isIceLite ? [] : this._localCandidates.slice(),\n ufrag: this._localUfrag\n },\n id: this.id\n };\n if (!event.candidate) {\n peerConnectionState.ice.complete = true;\n }\n if (!(this._isIceLite && event.candidate)) {\n peerConnectionState.ice.revision = this._localCandidatesRevision++;\n this.emit('candidates', peerConnectionState);\n }\n }\n\n /**\n * Handle an ICE connection state change event.\n * @private\n * @returns {void}\n */\n _handleIceConnectionStateChange() {\n const { iceConnectionState } = this._peerConnection;\n const isIceConnectedOrComplete = ['connected', 'completed'].includes(iceConnectionState);\n const log = this._log;\n\n log.debug(`ICE connection state is \"${iceConnectionState}\"`);\n if (isIceConnectedOrComplete) {\n this._iceReconnectTimeout.clear();\n this._iceRestartBackoff.reset();\n }\n\n if (this._lastIceConnectionState !== 'failed' && iceConnectionState === 'failed' && !this._shouldRestartIce && !this._isRestartingIce) {\n // Case 1: Transition to \"failed\".\n log.warn('ICE failed');\n this._initiateIceRestartBackoff();\n } else if (['disconnected', 'failed'].includes(this._lastIceConnectionState) && isIceConnectedOrComplete) {\n // Case 2: Transition from \"disconnected\" or \"failed\".\n log.debug('ICE reconnected');\n }\n\n // start monitor media when connected, and continue to monitor while state is complete-disconnected-connected.\n if (iceConnectionState === 'connected') {\n this._isIceConnectionInactive = false;\n this._iceConnectionMonitor.start(() => {\n // note: iceConnection monitor waits for iceConnectionState=disconnected for\n // detecting inactivity. Its possible that it may know about disconnected before _handleIceConnectionStateChange\n this._iceConnectionMonitor.stop();\n if (!this._shouldRestartIce && !this._isRestartingIce) {\n log.warn('ICE Connection Monitor detected inactivity');\n this._isIceConnectionInactive = true;\n this._initiateIceRestartBackoff();\n this.emit('iceConnectionStateChanged');\n this.emit('connectionStateChanged');\n }\n });\n } else if (!['disconnected', 'completed'].includes(iceConnectionState)) { // don't stop monitoring for disconnected or completed.\n this._iceConnectionMonitor.stop();\n this._isIceConnectionInactive = false;\n }\n\n this._lastIceConnectionState = iceConnectionState;\n this.emit('iceConnectionStateChanged');\n }\n\n /**\n * Handle ICE gathering timeout.\n * @private\n * @returns {void}\n */\n _handleIceGatheringTimeout() {\n this._log.warn('ICE failed to gather any local candidates');\n this._iceGatheringFailed = true;\n this._initiateIceRestartBackoff();\n this.emit('iceConnectionStateChanged');\n this.emit('connectionStateChanged');\n }\n\n /**\n * Handle an ICE gathering state change event.\n * @private\n * @returns {void}\n */\n _handleIceGatheringStateChange() {\n const { iceGatheringState } = this._peerConnection;\n const log = this._log;\n log.debug(`ICE gathering state is \"${iceGatheringState}\"`);\n\n // NOTE(mmalavalli): Start the ICE gathering timeout only if the RTCPeerConnection\n // has started gathering candidates for the first time since the initial offer/answer\n // or an offer/answer with ICE restart.\n const { delay, isSet } = this._iceGatheringTimeout;\n if (iceGatheringState === 'gathering' && !this._didGenerateLocalCandidates && !isSet) {\n log.debug(`Starting ICE gathering timeout: ${delay}`);\n this._iceGatheringFailed = false;\n this._iceGatheringTimeout.start();\n }\n }\n\n /**\n * Handle a signaling state change event.\n * @private\n * @returns {void}\n */\n _handleSignalingStateChange() {\n if (this._peerConnection.signalingState === 'stable') {\n this._appliedTrackIdsToAttributes = new Map(this._trackIdsToAttributes);\n }\n }\n\n /**\n * Handle a track event.\n * @private\n * @param {RTCTrackEvent} event\n * @returns {void}\n */\n _handleTrackEvent(event) {\n const sdp = this._peerConnection.remoteDescription\n ? this._peerConnection.remoteDescription.sdp\n : null;\n\n this._trackMatcher = this._trackMatcher || new TrackMatcher();\n this._trackMatcher.update(sdp);\n\n const mediaStreamTrack = event.track;\n const signaledTrackId = this._trackMatcher.match(event) || mediaStreamTrack.id;\n const mediaTrackReceiver = new MediaTrackReceiver(signaledTrackId, mediaStreamTrack);\n\n // NOTE(mmalavalli): \"ended\" is not fired on the remote MediaStreamTrack when\n // the remote peer removes a track. So, when this MediaStreamTrack is re-used\n // for a different track due to the remote peer calling RTCRtpSender.replaceTrack(),\n // we delete the previous MediaTrackReceiver that owned this MediaStreamTrack\n // before adding the new MediaTrackReceiver.\n this._mediaTrackReceivers.forEach(trackReceiver => {\n if (trackReceiver.track.id === mediaTrackReceiver.track.id) {\n this._mediaTrackReceivers.delete(trackReceiver);\n }\n });\n\n this._mediaTrackReceivers.add(mediaTrackReceiver);\n mediaStreamTrack.addEventListener('ended', () => this._mediaTrackReceivers.delete(mediaTrackReceiver));\n this.emit('trackAdded', mediaTrackReceiver);\n }\n\n /**\n * Initiate ICE Restart.\n * @private\n * @returns {void}\n */\n _initiateIceRestart() {\n if (this._peerConnection.signalingState === 'closed') {\n return;\n }\n const log = this._log;\n log.warn('Attempting to restart ICE');\n this._didGenerateLocalCandidates = false;\n this._isIceRestartBackoffInProgress = false;\n this._shouldRestartIce = true;\n\n const { delay, isSet } = this._iceReconnectTimeout;\n if (!isSet) {\n log.debug(`Starting ICE reconnect timeout: ${delay}`);\n this._iceReconnectTimeout.start();\n }\n this.offer().catch(ex => {\n log.error(`offer failed in _initiateIceRestart with: ${ex.message}`);\n });\n }\n\n /**\n * Schedule an ICE Restart.\n * @private\n * @returns {void}\n */\n _initiateIceRestartBackoff() {\n if (this._peerConnection.signalingState === 'closed' || this._isIceRestartBackoffInProgress) {\n return;\n }\n this._log.warn('An ICE restart has been scheduled');\n this._isIceRestartBackoffInProgress = true;\n this._iceRestartBackoff.backoff(() => this._initiateIceRestart());\n }\n\n /**\n * Conditionally re-offer.\n * @private\n * @param {?RTCSessionDescriptionInit} localDescription\n * @returns {Promise}\n */\n _maybeReoffer(localDescription) {\n let shouldReoffer = this._shouldOffer;\n\n if (localDescription && localDescription.sdp) {\n // NOTE(mmalavalli): If the local RTCSessionDescription has fewer audio and/or\n // video send* m= lines than the corresponding RTCRtpSenders with non-null\n // MediaStreamTracks, it means that the newly added RTCRtpSenders require\n // renegotiation.\n const senders = this._peerConnection.getSenders().filter(sender => sender.track);\n shouldReoffer = ['audio', 'video'].reduce((shouldOffer, kind) => {\n const mediaSections = getMediaSections(localDescription.sdp, kind, '(sendrecv|sendonly)');\n const sendersOfKind = senders.filter(isSenderOfKind.bind(null, kind));\n return shouldOffer || (mediaSections.length < sendersOfKind.length);\n }, shouldReoffer);\n\n // NOTE(mroberts): We also need to re-offer if we have a DataTrack to share\n // but no m= application section.\n const hasDataTrack = this._dataChannels.size > 0;\n const hasApplicationMediaSection = getMediaSections(localDescription.sdp, 'application').length > 0;\n const needsApplicationMediaSection = hasDataTrack && !hasApplicationMediaSection;\n shouldReoffer = shouldReoffer || needsApplicationMediaSection;\n }\n\n const promise = shouldReoffer ? this._offer() : Promise.resolve();\n return promise.then(() => shouldReoffer);\n }\n\n /**\n * Create an offer and set it on the {@link PeerConnectionV2}.\n * @private\n * @returns {Promise}\n */\n _offer() {\n const offerOptions = Object.assign({}, this._offerOptions);\n this._needsAnswer = true;\n if (this._shouldRestartIce) {\n this._shouldRestartIce = false;\n this._isRestartingIce = true;\n offerOptions.iceRestart = true;\n }\n\n return Promise.all(this._replaceTrackPromises.values()).then(() => {\n return this._peerConnection.createOffer(offerOptions);\n }).catch(error => {\n const errorToThrow = new MediaClientLocalDescFailedError();\n this._publishMediaWarning({\n message: 'Failed to create offer',\n code: errorToThrow.code,\n error\n });\n throw errorToThrow;\n }).then(offer => {\n if (isFirefox) {\n // NOTE(mmalavalli): We work around Chromium bug 1106157 by disabling\n // RTX in Firefox 79+. For more details about the bug, please go here:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1106157\n offer = new this._RTCSessionDescription({\n sdp: disableRtx(offer.sdp),\n type: offer.type\n });\n } else {\n offer = workaroundIssue8329(offer);\n }\n\n // NOTE(mpatwardhan): upcoming chrome versions are going to remove ssrc attributes\n // mslabel and label. See this bug https://bugs.chromium.org/p/webrtc/issues/detail?id=7110\n // and PSA: https://groups.google.com/forum/#!searchin/discuss-webrtc/PSA%7Csort:date/discuss-webrtc/jcZO-Wj0Wus/k2XvPCvoAwAJ\n // Looks like we are not referencing those attributes, but this changes goes ahead and removes them to see if it works.\n // this also helps reduce bytes on wires\n let sdp = removeSSRCAttributes(offer.sdp, ['mslabel', 'label']);\n sdp = this._peerConnection.remoteDescription\n ? filterLocalCodecs(sdp, this._peerConnection.remoteDescription.sdp)\n : sdp;\n\n let updatedSdp = this._setCodecPreferences(\n sdp,\n this._preferredAudioCodecs,\n this._preferredVideoCodecs);\n\n this._shouldOffer = false;\n if (!this._negotiationRole) {\n this._negotiationRole = 'offerer';\n }\n\n if (this._shouldApplySimulcast) {\n this._localDescriptionWithoutSimulcast = {\n type: 'offer',\n sdp: updatedSdp\n };\n updatedSdp = this._setSimulcast(updatedSdp, this._trackIdsToAttributes);\n }\n return this._setLocalDescription({\n type: 'offer',\n sdp: updatedSdp\n });\n });\n }\n\n /**\n * Get the MediaTrackSender ID of the given MediaStreamTrack ID.\n * Since a MediaTrackSender's underlying MediaStreamTrack can be\n * replaced, the corresponding IDs can mismatch.\n * @private\n * @param {Track.ID} id\n * @returns {Track.ID}\n */\n _getMediaTrackSenderId(trackId) {\n const mediaTrackSender = Array.from(this._rtpSenders.keys()).find(({ track: { id } }) => id === trackId);\n return mediaTrackSender ? mediaTrackSender.id : trackId;\n }\n\n /**\n * Add or rewrite local MediaStreamTrack IDs in the given RTCSessionDescription.\n * @private\n * @param {RTCSessionDescription} description\n * @return {RTCSessionDescription}\n */\n _addOrRewriteLocalTrackIds(description) {\n const transceivers = this._peerConnection.getTransceivers();\n const activeTransceivers = transceivers.filter(({ sender, stopped }) => !stopped && sender && sender.track);\n\n // NOTE(mmalavalli): There is no guarantee that MediaStreamTrack IDs will be present in\n // SDPs, and even if they are, there is no guarantee that they will be the same as the\n // actual MediaStreamTrack IDs. So, we add or re-write the actual MediaStreamTrack IDs\n // to the assigned m= sections here.\n const assignedTransceivers = activeTransceivers.filter(({ mid }) => mid);\n const midsToTrackIds = new Map(assignedTransceivers.map(({ mid, sender }) => [mid, this._getMediaTrackSenderId(sender.track.id)]));\n const sdp1 = addOrRewriteTrackIds(description.sdp, midsToTrackIds);\n\n // NOTE(mmalavalli): Chrome and Safari do not apply the offer until they get an answer.\n // So, we add or re-write the actual MediaStreamTrack IDs to the unassigned m= sections here.\n const unassignedTransceivers = activeTransceivers.filter(({ mid }) => !mid);\n const newTrackIdsByKind = new Map(['audio', 'video'].map(kind => [\n kind,\n unassignedTransceivers.filter(({ sender }) => sender.track.kind === kind).map(({ sender }) => this._getMediaTrackSenderId(sender.track.id))\n ]));\n const sdp2 = addOrRewriteNewTrackIds(sdp1, midsToTrackIds, newTrackIdsByKind);\n\n return new this._RTCSessionDescription({\n sdp: sdp2,\n type: description.type\n });\n }\n\n /**\n * Rollback and apply the given offer.\n * @private\n * @param {RTCSessionDescriptionInit} offer\n * @returns {Promise}\n */\n _rollbackAndApplyOffer(offer) {\n return this._setLocalDescription({ type: 'rollback' }).then(() => this._setLocalDescription(offer));\n }\n\n /**\n * Set a local description on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescription|RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _setLocalDescription(description) {\n if (description.type !== 'rollback' && this._shouldApplyDtx) {\n description = new this._RTCSessionDescription({\n sdp: enableDtxForOpus(description.sdp),\n type: description.type\n });\n }\n return this._peerConnection.setLocalDescription(description).catch(error => {\n this._log.warn(`Calling setLocalDescription with an RTCSessionDescription of type \"${description.type}\" failed with the error \"${error.message}\".`, error);\n\n const errorToThrow = new MediaClientLocalDescFailedError();\n const publishWarning = {\n message: `Calling setLocalDescription with an RTCSessionDescription of type \"${description.type}\" failed`,\n code: errorToThrow.code,\n error\n };\n\n if (description.sdp) {\n this._log.warn(`The SDP was ${description.sdp}`);\n publishWarning.sdp = description.sdp;\n }\n this._publishMediaWarning(publishWarning);\n throw errorToThrow;\n }).then(() => {\n if (description.type !== 'rollback') {\n this._localDescription = this._addOrRewriteLocalTrackIds(description);\n\n // NOTE(mmalavalli): In order for this feature to be backward compatible with older\n // SDK versions which to not support opus DTX, we append \"usedtx=1\" to the local SDP\n // only while applying it. We will not send it over the wire to prevent inadvertent\n // enabling of opus DTX in older SDKs. Newer SDKs will append \"usedtx=1\" by themselves\n // if the developer has requested opus DTX to be enabled. (JSDK-3063)\n if (this._shouldApplyDtx) {\n this._localDescription = new this._RTCSessionDescription({\n sdp: enableDtxForOpus(this._localDescription.sdp, []),\n type: this._localDescription.type\n });\n }\n\n this._localCandidates = [];\n if (description.type === 'offer') {\n this._descriptionRevision++;\n } else if (description.type === 'answer') {\n this._lastStableDescriptionRevision = this._descriptionRevision;\n negotiationCompleted(this);\n }\n this._localUfrag = getUfrag(description);\n this.emit('description', this.getState());\n }\n });\n }\n\n /**\n * Set a remote RTCSessionDescription on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _setRemoteDescription(description) {\n if (description.sdp) {\n description.sdp = this._setCodecPreferences(\n description.sdp,\n this._preferredAudioCodecs,\n this._preferredVideoCodecs);\n\n if (this._shouldApplyDtx) {\n description.sdp = enableDtxForOpus(description.sdp);\n } else {\n // NOTE(mmalavalli): Remove \"usedtx=1\" from opus's fmtp line if present\n // since DTX is disabled.\n description.sdp = enableDtxForOpus(description.sdp, []);\n }\n\n if (isFirefox) {\n // NOTE(mroberts): Do this to reduce our MediaStream count in Firefox. By\n // mapping MediaStream IDs in the SDP to \"-\", we ensure the \"track\" event\n // doesn't include any new MediaStreams in Firefox. Its `streams` member\n // will always be the empty Array.\n description.sdp = filterOutMediaStreamIds(description.sdp);\n }\n if (!this._peerConnection.remoteDescription) {\n this._isIceLite = /a=ice-lite/.test(description.sdp);\n }\n }\n description = new this._RTCSessionDescription(description);\n // eslint-disable-next-line consistent-return\n return Promise.resolve().then(() => {\n // NOTE(syerrapragada): VMS does not support H264 simulcast. So,\n // unset simulcast for sections in local offer where corresponding\n // sections in answer doesn't have vp8 as preferred codec and reapply offer.\n if (description.type === 'answer' && this._localDescriptionWithoutSimulcast) {\n // NOTE(mpatwardhan):if we were using adaptive simulcast, and if its not supported by server\n // revert simulcast even for vp8.\n const adaptiveSimulcastEntry = this._preferredVideoCodecs.find(cs => 'adaptiveSimulcast' in cs);\n const revertForAll = !!adaptiveSimulcastEntry && adaptiveSimulcastEntry.adaptiveSimulcast === false;\n const sdpWithoutSimulcastForNonVP8MediaSections = this._revertSimulcast(\n this._localDescription.sdp,\n this._localDescriptionWithoutSimulcast.sdp,\n description.sdp, revertForAll);\n this._localDescriptionWithoutSimulcast = null;\n if (sdpWithoutSimulcastForNonVP8MediaSections !== this._localDescription.sdp) {\n return this._rollbackAndApplyOffer({\n type: this._localDescription.type,\n sdp: sdpWithoutSimulcastForNonVP8MediaSections\n });\n }\n }\n }).then(() => this._peerConnection.setRemoteDescription(description)).then(() => {\n if (description.type === 'answer') {\n if (this._isRestartingIce) {\n this._log.debug('An ICE restart was in-progress and is now completed');\n this._isRestartingIce = false;\n }\n negotiationCompleted(this);\n }\n }, error => {\n this._log.warn(`Calling setRemoteDescription with an RTCSessionDescription of type \"${description.type}\" failed with the error \"${error.message}\".`, error);\n if (description.sdp) {\n this._log.warn(`The SDP was ${description.sdp}`);\n }\n throw error;\n });\n }\n\n /**\n * Update the {@link PeerConnectionV2}'s description.\n * @private\n * @param {RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _updateDescription(description) {\n switch (description.type) {\n case 'answer':\n case 'pranswer':\n if (description.revision !== this._descriptionRevision\n || this._peerConnection.signalingState !== 'have-local-offer') {\n return Promise.resolve();\n }\n this._descriptionRevision = description.revision;\n break;\n case 'close':\n return this._close();\n case 'create-offer':\n if (description.revision <= this._lastStableDescriptionRevision) {\n return Promise.resolve();\n } else if (this._needsAnswer) {\n this._queuedDescription = description;\n return Promise.resolve();\n }\n this._descriptionRevision = description.revision;\n return this._offer();\n case 'offer':\n if (description.revision <= this._lastStableDescriptionRevision\n || this._peerConnection.signalingState === 'closed') {\n return Promise.resolve();\n }\n if (this._peerConnection.signalingState === 'have-local-offer') {\n // NOTE(mpatwardhan): For a peer connection\n // 1) createOffer always generate SDP with `setup:actpass`\n // 2) when remote description is set `setup:active` - the answer generated selects the dtls role of setup:passive\n // 3) when remote description is set `setup:passive` - the answer generated selects the dtls role of setup:active\n // 4) when remote description is set `setup:actpass` - the answer generated uses the previously negotiated role (if not negotiated previously setup:active is used)\n // This test shows the behavior: https://github.com/twilio/twilio-webrtc.js/blob/master/test/integration/spec/rtcpeerconnection.js#L936\n // with glare handling (if dtls role was not negotiated before ) the generated answer will set setup:active.\n // we do not want that. lets wait for \"initial negotiation\" before attempting glare handling.\n if (this._needsAnswer && this._lastStableDescriptionRevision === 0) {\n this._queuedDescription = description;\n return Promise.resolve();\n }\n this._descriptionRevision = description.revision;\n return this._handleGlare(description);\n }\n this._descriptionRevision = description.revision;\n return this._answer(description).then(() => {});\n default:\n // Do nothing.\n }\n\n // Handle answer or pranswer.\n const revision = description.revision;\n return Promise.resolve().then(() => {\n return this._setRemoteDescription(description);\n }).catch(error => {\n const errorToThrow = new MediaClientRemoteDescFailedError();\n this._publishMediaWarning({\n message: `Calling setRemoteDescription with an RTCSessionDescription of type \"${description.type}\" failed`,\n code: errorToThrow.code,\n error,\n sdp: description.sdp\n });\n throw errorToThrow;\n }).then(() => {\n this._lastStableDescriptionRevision = revision;\n this._needsAnswer = false;\n return this._checkIceBox(description);\n }).then(() => {\n return this._queuedDescription\n && this._updateDescription(this._queuedDescription);\n }).then(() => {\n this._queuedDescription = null;\n return this._maybeReoffer(this._peerConnection.localDescription).then(() => {});\n });\n }\n\n /**\n * Update the {@link PeerConnectionV2}'s ICE candidates.\n * @private\n * @param {object} iceState\n * @returns {Promise}\n */\n _updateIce(iceState) {\n const candidates = this._remoteCandidates.update(iceState);\n return this._addIceCandidates(candidates);\n }\n\n /**\n * Add a {@link DataTrackSender} to the {@link PeerConnectionV2}.\n * @param {DataTrackSender} dataTrackSender\n * @returns {void}\n */\n addDataTrackSender(dataTrackSender) {\n if (this._dataChannels.has(dataTrackSender)) {\n return;\n }\n try {\n const dataChannelDict = {\n ordered: dataTrackSender.ordered\n };\n if (dataTrackSender.maxPacketLifeTime !== null) {\n dataChannelDict.maxPacketLifeTime = dataTrackSender.maxPacketLifeTime;\n }\n if (dataTrackSender.maxRetransmits !== null) {\n dataChannelDict.maxRetransmits = dataTrackSender.maxRetransmits;\n }\n const dataChannel = this._peerConnection.createDataChannel(dataTrackSender.id, dataChannelDict);\n dataTrackSender.addDataChannel(dataChannel);\n this._dataChannels.set(dataTrackSender, dataChannel);\n } catch (error) {\n this._log.warn(`Error creating an RTCDataChannel for DataTrack \"${dataTrackSender.id}\": ${error.message}`);\n }\n }\n\n _handleQueuedPublisherHints() {\n if (this._peerConnection.signalingState === 'stable') {\n this._mediaTrackSenderToPublisherHints.forEach(({ deferred, encodings }, mediaTrackSender) => {\n this._mediaTrackSenderToPublisherHints.delete(mediaTrackSender);\n this._setPublisherHint(mediaTrackSender, encodings)\n .then(result => deferred.resolve(result))\n .catch(error => deferred.reject(error));\n });\n }\n }\n\n /**\n * updates encodings for simulcast layers of given sender.\n * @param {RTCRtpSender} sender\n * @param {Array<{enabled: boolean, layer_index: number}>|null} encodings\n * @returns {Promise} string indicating result of the operation. can be one of\n * \"OK\", \"INVALID_HINT\", \"COULD_NOT_APPLY_HINT\", \"UNKNOWN_TRACK\"\n */\n _setPublisherHint(mediaTrackSender, encodings) {\n if (isFirefox) {\n return Promise.resolve('COULD_NOT_APPLY_HINT');\n }\n\n if (this._mediaTrackSenderToPublisherHints.has(mediaTrackSender)) {\n // skip any stale hint associated with the mediaTrackSender.\n const queuedHint = this._mediaTrackSenderToPublisherHints.get(mediaTrackSender);\n queuedHint.deferred.resolve('REQUEST_SKIPPED');\n this._mediaTrackSenderToPublisherHints.delete(mediaTrackSender);\n }\n\n const sender = this._rtpSenders.get(mediaTrackSender);\n if (!sender) {\n this._log.warn('Could not apply publisher hint because RTCRtpSender was not found');\n return Promise.resolve('UNKNOWN_TRACK');\n }\n\n if (this._peerConnection.signalingState === 'closed') {\n this._log.warn('Could not apply publisher hint because signalingState was \"closed\"');\n return Promise.resolve('COULD_NOT_APPLY_HINT');\n }\n\n if (this._peerConnection.signalingState !== 'stable') {\n // enqueue this hint to be applied when pc becomes stable.\n this._log.debug('Queuing up publisher hint because signalingState:', this._peerConnection.signalingState);\n const deferred = defer();\n this._mediaTrackSenderToPublisherHints.set(mediaTrackSender, { deferred, encodings });\n return deferred.promise;\n }\n\n const parameters = sender.getParameters();\n if (encodings !== null) {\n encodings.forEach(({ enabled, layer_index: layerIndex }) => {\n if (parameters.encodings.length > layerIndex) {\n this._log.debug(`layer:${layerIndex}, active:${parameters.encodings[layerIndex].active} => ${enabled}`);\n parameters.encodings[layerIndex].active = enabled;\n } else {\n this._log.warn(`invalid layer:${layerIndex}, active:${enabled}`);\n }\n });\n }\n\n // Note(mpatwardhan): after publisher hints are applied, overwrite with default encodings\n // to disable any encoding that shouldn't have been enabled by publisher_hints.\n // When encodings===null (that is we are asked to reset encodings for replaceTrack)\n // along with disabling encodings, clear active flag for encodings that should not be disabled\n this._maybeUpdateEncodings(sender.track, parameters.encodings, encodings === null /* trackReplaced */);\n\n return sender.setParameters(parameters).then(() => 'OK').catch(error => {\n this._log.error('Failed to apply publisher hints:', error);\n return 'COULD_NOT_APPLY_HINT';\n });\n }\n\n /**\n * Add the {@link MediaTrackSender} to the {@link PeerConnectionV2}.\n * @param {MediaTrackSender} mediaTrackSender\n * @returns {void}\n */\n addMediaTrackSender(mediaTrackSender) {\n if (this._peerConnection.signalingState === 'closed' || this._rtpSenders.has(mediaTrackSender)) {\n return;\n }\n const transceiver = this._addOrUpdateTransceiver(mediaTrackSender.track);\n const { sender } = transceiver;\n mediaTrackSender.addSender(sender, encodings => this._setPublisherHint(mediaTrackSender, encodings));\n this._rtpNewSenders.add(sender);\n this._rtpSenders.set(mediaTrackSender, sender);\n }\n\n /**\n * Close the {@link PeerConnectionV2}.\n * @returns {void}\n */\n close() {\n if (this._close()) {\n this._descriptionRevision++;\n this._localDescription = { type: 'close' };\n this.emit('description', this.getState());\n }\n }\n\n /**\n * Get the {@link DataTrackReceiver}s and the {@link MediaTrackReceiver}s on the\n * {@link PeerConnectionV2}.\n * @returns {Array} trackReceivers\n */\n getTrackReceivers() {\n return Array.from(this._dataTrackReceivers).concat(Array.from(this._mediaTrackReceivers));\n }\n\n /**\n * Get the {@link PeerConnectionV2}'s state (specifically, its description).\n * @returns {?object}\n */\n getState() {\n if (!this._localDescription) {\n return null;\n }\n\n // NOTE(mpatwardhan): Return most recent localDescription. If the most recent local description is an\n // answer, and this method is called for sending a \"sync\" message while the next remote offer is being processed,\n // we need to send the most recent stable description revision instead of the current description revision,\n // which is supposed to be for the next local answer.\n const localDescriptionRevision = this._localDescription.type === 'answer' ? this._lastStableDescriptionRevision : this._descriptionRevision;\n const localDescription = {\n type: this._localDescription.type,\n revision: localDescriptionRevision\n };\n if (this._localDescription.sdp) {\n localDescription.sdp = this._localDescription.sdp;\n }\n return {\n description: localDescription,\n id: this.id\n };\n }\n\n /**\n * Create an offer and set it on the {@link PeerConnectionV2}.\n * @returns {Promise}\n */\n offer() {\n if (this._needsAnswer || this._isRestartingIce) {\n this._shouldOffer = true;\n return Promise.resolve();\n }\n\n return this.bracket('offering', key => {\n this.transition('updating', key);\n const promise = this._needsAnswer || this._isRestartingIce ? Promise.resolve() : this._offer();\n return promise.then(() => {\n this.tryTransition('open', key);\n }, error => {\n this.tryTransition('open', key);\n throw error;\n });\n });\n }\n\n /**\n * Remove a {@link DataTrackSender} from the {@link PeerConnectionV2}.\n * @param {DataTrackSender} dataTrackSender\n * @returns {void}\n */\n removeDataTrackSender(dataTrackSender) {\n const dataChannel = this._dataChannels.get(dataTrackSender);\n if (dataChannel) {\n dataTrackSender.removeDataChannel(dataChannel);\n this._dataChannels.delete(dataTrackSender);\n dataChannel.close();\n }\n }\n\n /**\n * Remove the {@link MediaTrackSender} from the {@link PeerConnectionV2}.\n * @param {MediaTrackSender} mediaTrackSender\n * @returns {void}\n */\n removeMediaTrackSender(mediaTrackSender) {\n const sender = this._rtpSenders.get(mediaTrackSender);\n if (!sender) {\n return;\n }\n if (this._peerConnection.signalingState !== 'closed') {\n this._peerConnection.removeTrack(sender);\n }\n mediaTrackSender.removeSender(sender);\n // clean up any pending publisher hints associated with this mediaTrackSender.\n if (this._mediaTrackSenderToPublisherHints.has(mediaTrackSender)) {\n const queuedHint = this._mediaTrackSenderToPublisherHints.get(mediaTrackSender);\n queuedHint.deferred.resolve('UNKNOWN_TRACK');\n this._mediaTrackSenderToPublisherHints.delete(mediaTrackSender);\n }\n this._rtpNewSenders.delete(sender);\n this._rtpSenders.delete(mediaTrackSender);\n }\n\n /**\n * Set the RTCConfiguration on the underlying RTCPeerConnection.\n * @param {RTCConfiguration} configuration\n * @returns {void}\n */\n setConfiguration(configuration) {\n if (typeof this._peerConnection.setConfiguration === 'function') {\n this._peerConnection.setConfiguration(getConfiguration(configuration));\n }\n }\n\n /**\n * Set the ICE reconnect timeout period.\n * @param {number} period - Period in milliseconds.\n * @returns {this}\n */\n setIceReconnectTimeout(period) {\n this._iceReconnectTimeout.setDelay(period);\n this._log.debug('Updated ICE reconnection timeout period:',\n this._iceReconnectTimeout.delay);\n return this;\n }\n\n /**\n * Update the {@link PeerConnectionV2}.\n * @param {object} peerConnectionState\n * @returns {Promise}\n */\n update(peerConnectionState) {\n return this.bracket('updating', key => {\n if (this.state === 'closed') {\n return Promise.resolve();\n }\n\n this.transition('updating', key);\n\n const updates = [];\n\n if (peerConnectionState.ice) {\n updates.push(this._updateIce(peerConnectionState.ice));\n }\n\n if (peerConnectionState.description) {\n updates.push(this._updateDescription(peerConnectionState.description));\n }\n\n return Promise.all(updates).then(() => {\n this.tryTransition('open', key);\n }, error => {\n this.tryTransition('open', key);\n throw error;\n });\n });\n }\n\n /**\n * Get the {@link PeerConnectionV2}'s media statistics.\n * @returns {Promise}\n */\n getStats() {\n return getStatistics(this._peerConnection).then(response => rewriteTrackIds(this, response));\n }\n}\n\nfunction rewriteLocalTrackId(pcv2, stats) {\n const trackId = pcv2._getMediaTrackSenderId(stats.trackId);\n return Object.assign(stats, { trackId });\n}\n\nfunction rewriteTrackId(pcv2, stats) {\n const receiver = [...pcv2._mediaTrackReceivers]\n .find(receiver => receiver.track.id === stats.trackId);\n const trackId = receiver ? receiver.id : null;\n return Object.assign(stats, { trackId });\n}\n\nfunction rewriteTrackIds(pcv2, response) {\n return Object.assign(response, {\n remoteAudioTrackStats: response.remoteAudioTrackStats.map(stats => rewriteTrackId(pcv2, stats)),\n remoteVideoTrackStats: response.remoteVideoTrackStats.map(stats => rewriteTrackId(pcv2, stats)),\n localAudioTrackStats: response.localAudioTrackStats.map(stats => rewriteLocalTrackId(pcv2, stats)),\n localVideoTrackStats: response.localVideoTrackStats.map(stats => rewriteLocalTrackId(pcv2, stats)),\n });\n}\n\n/**\n * @event PeerConnectionV2#candidates\n * @param {object} candidates\n */\n\n/**\n * @event PeerConnectionV2#connectionStateChanged\n */\n\n/**\n * @event PeerConnectionV2#description\n * @param {object} description\n */\n\n/**\n * @event PeerConnectionV2#iceConnectionStateChanged\n */\n\n/**\n * @event PeerConnectionV2#trackAdded\n * @param {DataTrackReceiver|MediaTrackReceiver} trackReceiver\n */\n\nfunction getUfrag(description) {\n if (description.sdp) {\n const match = description.sdp.match(/^a=ice-ufrag:([a-zA-Z0-9+/]+)/m);\n if (match) {\n return match[1];\n }\n }\n return null;\n}\n\nfunction getConfiguration(configuration) {\n return Object.assign({\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require'\n }, configuration);\n}\n\n/**\n * Whether the MediaStreamTrack of the given RTCRTPSender is a non-ended\n * MediaStreamTrack of a given kind.\n * @private\n * @param {string} kind\n * @param {RTCRtpSender} sender\n * @return {boolean}\n */\nfunction isSenderOfKind(kind, sender) {\n const track = sender.track;\n return track && track.kind === kind && track.readyState !== 'ended';\n}\n\n/**\n * Preferred codecs.\n * @typedef {object} PreferredCodecs\n * @property {Array} audio\n * @property {Array} video\n */\n\nfunction filterOutMediaStreamIds(sdp) {\n return sdp.replace(/a=msid:[^ ]+ /g, 'a=msid:- ');\n}\n\n/**\n * Whether an RTCRtpTransceiver can be recycled.\n * @param {RTCRtpTransceiver} transceiver\n * @returns {boolean}\n */\nfunction shouldRecycleTransceiver(transceiver, pcv2) {\n return !transceiver.stopped\n && !pcv2._replaceTrackPromises.has(transceiver)\n && ['inactive', 'recvonly'].includes(transceiver.direction);\n}\n\n/**\n * Take a recycled RTCRtpTransceiver if available.\n * @param {PeerConnectionV2} pcv2\n * @param {Track.Kind} kind\n * @returns {?RTCRtpTransceiver}\n */\nfunction takeRecycledTransceiver(pcv2, kind) {\n const preferredCodecs = {\n audio: pcv2._preferredAudioCodecs.map(({ codec }) => codec.toLowerCase()),\n video: pcv2._preferredVideoCodecs.map(({ codec }) => codec.toLowerCase())\n }[kind];\n\n const recycledTransceivers = pcv2._recycledTransceivers[kind];\n const localCodec = preferredCodecs.find(codec => pcv2._localCodecs.has(codec));\n if (!localCodec) {\n return recycledTransceivers.shift();\n }\n\n const transceiver = recycledTransceivers.find(transceiver => {\n const remoteCodecMap = pcv2._remoteCodecMaps.get(transceiver.mid);\n return remoteCodecMap && remoteCodecMap.has(localCodec);\n });\n\n if (transceiver) {\n recycledTransceivers.splice(recycledTransceivers.indexOf(transceiver), 1);\n }\n return transceiver;\n}\n\n/**\n * Update the set of locally supported {@link Codec}s.\n * @param pcv2\n * @returns {void}\n */\nfunction updateLocalCodecs(pcv2) {\n const description = pcv2._peerConnection.localDescription;\n if (!description || !description.sdp) {\n return;\n }\n getMediaSections(description.sdp).forEach(section => {\n const codecMap = createCodecMapForMediaSection(section);\n codecMap.forEach((pts, codec) => pcv2._localCodecs.add(codec));\n });\n}\n\n/**\n * Update the {@link Codec} maps for all m= sections in the remote {@link RTCSessionDescription}s.\n * @param {PeerConnectionV2} pcv2\n * @returns {void}\n */\nfunction updateRemoteCodecMaps(pcv2) {\n const description = pcv2._peerConnection.remoteDescription;\n if (!description || !description.sdp) {\n return;\n }\n getMediaSections(description.sdp).forEach(section => {\n const matched = section.match(/^a=mid:(.+)$/m);\n if (!matched || !matched[1]) {\n return;\n }\n const mid = matched[1];\n const codecMap = createCodecMapForMediaSection(section);\n pcv2._remoteCodecMaps.set(mid, codecMap);\n });\n}\n\n/**\n * Update the list of recycled RTCRtpTransceivers.\n * @param {PeerConnectionV2} pcv2\n */\nfunction updateRecycledTransceivers(pcv2) {\n pcv2._recycledTransceivers.audio = [];\n pcv2._recycledTransceivers.video = [];\n pcv2._peerConnection.getTransceivers().forEach(transceiver => {\n if (shouldRecycleTransceiver(transceiver, pcv2)) {\n const track = transceiver.receiver.track;\n pcv2._recycledTransceivers[track.kind].push(transceiver);\n }\n });\n}\n\n/**\n * Perform certain updates after an SDP negotiation is completed.\n * @param {PeerConnectionV2} pcv2\n * @returns {void}\n */\nfunction negotiationCompleted(pcv2) {\n updateRecycledTransceivers(pcv2);\n updateLocalCodecs(pcv2);\n updateRemoteCodecMaps(pcv2);\n updateEncodingParameters(pcv2).then(() => {\n // if there any any publisher hints queued, apply them now.\n pcv2._handleQueuedPublisherHints();\n });\n}\n\n/**\n * Update the RTCRtpEncodingParameters of all active RTCRtpSenders.\n * @param {PeerConnectionV2} pcv2\n * @returns {void}\n */\nfunction updateEncodingParameters(pcv2) {\n const { maxAudioBitrate, maxVideoBitrate } = pcv2._encodingParameters;\n\n const maxBitrates = new Map([\n ['audio', maxAudioBitrate],\n ['video', maxVideoBitrate]\n ]);\n\n const promises = [];\n pcv2._peerConnection.getSenders().filter(sender => sender.track).forEach(sender => {\n const maxBitrate = maxBitrates.get(sender.track.kind);\n const params = sender.getParameters();\n\n if (maxBitrate === null || maxBitrate === 0) {\n removeMaxBitrate(params);\n } else if (pcv2._isChromeScreenShareTrack(sender.track)) {\n // NOTE(mpatwardhan): Sometimes (JSDK-2557) chrome does not send any bytes on screen track if MaxBitRate is set on it via setParameters,\n // To workaround this issue we will not apply maxBitrate if the track appears to be a screen share track created by chrome\n pcv2._log.warn(`Not setting maxBitrate for ${sender.track.kind} Track ${sender.track.id} because it appears to be screen share track: ${sender.track.label}`);\n } else {\n setMaxBitrate(params, maxBitrate);\n }\n\n if (!isFirefox && params.encodings.length > 0) {\n if (sender.track.kind === 'audio') {\n // NOTE(mmalavalli): \"priority\" is a per-sender property and not\n // a per-encoding-layer property. So, we set the value only on the first\n // encoding layer. Any attempt to set the value on subsequent encoding\n // layers (in the case of simulcast) will result in the Promise returned\n // by RTCRtpSender.setParameters() being rejected. With this, audio encoding\n // is prioritized the most.\n params.encodings[0].priority = 'high';\n } else if (pcv2._isChromeScreenShareTrack(sender.track)) {\n // NOTE(mmalavalli): Screen share encodings are prioritized more than those\n // of the camera.\n params.encodings[0].priority = 'medium';\n }\n if (pcv2._enableDscp) {\n // NOTE(mmalavalli): \"networkPriority\" is a per-sender property and not\n // a per-encoding-layer property. So, we set the value only on the first\n // encoding layer. Any attempt to set the value on subsequent encoding\n // layers (in the case of simulcast) will result in the Promise returned\n // by RTCRtpSender.setParameters() being rejected.\n params.encodings[0].networkPriority = 'high';\n }\n }\n\n // when a sender is reused, delete any active encodings set by server.\n const trackReplaced = pcv2._rtpNewSenders.has(sender);\n pcv2._maybeUpdateEncodings(sender.track, params.encodings, trackReplaced);\n pcv2._rtpNewSenders.delete(sender);\n\n const promise = sender.setParameters(params).catch(error => {\n pcv2._log.warn(`Error while setting encodings parameters for ${sender.track.kind} Track ${sender.track.id}: ${error.message || error.name}`);\n });\n promises.push(promise);\n });\n return Promise.all(promises);\n}\n\n/**\n * Remove maxBitrate from the RTCRtpSendParameters' encodings.\n * @param {RTCRtpSendParameters} params\n * @returns {void}\n */\nfunction removeMaxBitrate(params) {\n if (Array.isArray(params.encodings)) {\n params.encodings.forEach(encoding => delete encoding.maxBitrate);\n }\n}\n\n/**\n * Set the given maxBitrate in the RTCRtpSendParameters' encodings.\n * @param {RTCRtpSendParameters} params\n * @param {number} maxBitrate\n * @returns {void}\n */\nfunction setMaxBitrate(params, maxBitrate) {\n if (isFirefox) {\n params.encodings = [{ maxBitrate }];\n } else {\n params.encodings.forEach(encoding => {\n encoding.maxBitrate = maxBitrate;\n });\n }\n}\nmodule.exports = PeerConnectionV2;\n", "'use strict';\n\nconst { guessBrowser } = require('../../webrtc/util');\nconst PeerConnectionV2 = require('./peerconnection');\nconst MediaTrackSender = require('../../media/track/sender');\nconst QueueingEventEmitter = require('../../queueingeventemitter');\nconst util = require('../../util');\nconst { MediaConnectionError } = require('../../util/twilio-video-errors');\n\nconst isFirefox = guessBrowser() === 'firefox';\n\n/**\n * {@link PeerConnectionManager} manages multiple {@link PeerConnectionV2}s.\n * @extends QueueingEventEmitter\n * @emits PeerConnectionManager#candidates\n * @emits PeerConnectionManager#connectionStateChanged\n * @emits PeerConnectionManager#description\n * @emits PeerConnectionManager#iceConnectionStateChanged\n * @emits PeerConnectionManager#trackAdded\n */\nclass PeerConnectionManager extends QueueingEventEmitter {\n /**\n * Construct {@link PeerConnectionManager}.\n * @param {EncodingParametersImpl} encodingParameters\n * @param {PreferredCodecs} preferredCodecs\n * @param {object} options\n */\n constructor(encodingParameters, preferredCodecs, options) {\n super();\n\n options = Object.assign({\n audioContextFactory: isFirefox\n ? require('../../webaudio/audiocontext')\n : null,\n PeerConnectionV2\n }, options);\n\n const audioContext = options.audioContextFactory\n ? options.audioContextFactory.getOrCreate(this)\n : null;\n\n // NOTE(mroberts): If we're using an AudioContext, we don't need to specify\n // `offerToReceiveAudio` in RTCOfferOptions.\n const offerOptions = audioContext\n ? { offerToReceiveVideo: true }\n : { offerToReceiveAudio: true, offerToReceiveVideo: true };\n\n Object.defineProperties(this, {\n _audioContextFactory: {\n value: options.audioContextFactory\n },\n _closedPeerConnectionIds: {\n value: new Set()\n },\n _configuration: {\n writable: true,\n value: null\n },\n _configurationDeferred: {\n writable: true,\n value: util.defer()\n },\n _connectionState: {\n value: 'new',\n writable: true\n },\n _dummyAudioTrackSender: {\n value: audioContext\n ? new MediaTrackSender(createDummyAudioMediaStreamTrack(audioContext))\n : null\n },\n _encodingParameters: {\n value: encodingParameters\n },\n _iceConnectionState: {\n writable: true,\n value: 'new'\n },\n _dataTrackSenders: {\n writable: true,\n value: new Set()\n },\n _lastConnectionState: {\n value: 'new',\n writable: true\n },\n _lastIceConnectionState: {\n writable: true,\n value: 'new'\n },\n _mediaTrackSenders: {\n writable: true,\n value: new Set()\n },\n _offerOptions: {\n value: offerOptions\n },\n _peerConnections: {\n value: new Map()\n },\n _preferredCodecs: {\n value: preferredCodecs\n },\n _sessionTimeout: {\n value: null,\n writable: true\n },\n _PeerConnectionV2: {\n value: options.PeerConnectionV2\n }\n });\n }\n\n setEffectiveAdaptiveSimulcast(effectiveAdaptiveSimulcast) {\n this._peerConnections.forEach(pc => pc.setEffectiveAdaptiveSimulcast(effectiveAdaptiveSimulcast));\n this._preferredCodecs.video.forEach(cs => {\n if ('adaptiveSimulcast' in cs) {\n cs.adaptiveSimulcast = effectiveAdaptiveSimulcast;\n }\n });\n }\n\n /**\n * A summarized RTCPeerConnectionState across all the\n * {@link PeerConnectionManager}'s underlying {@link PeerConnectionV2}s.\n * @property {RTCPeerConnectionState}\n */\n get connectionState() {\n return this._connectionState;\n }\n\n /**\n * A summarized RTCIceConnectionState across all the\n * {@link PeerConnectionManager}'s underlying {@link PeerConnectionV2}s.\n * @property {RTCIceConnectionState}\n */\n get iceConnectionState() {\n return this._iceConnectionState;\n }\n\n /**\n * Close the {@link PeerConnectionV2}s which are no longer relevant.\n * @param {Array} peerConnectionStates\n * @returns {this}\n */\n _closeAbsentPeerConnections(peerConnectionStates) {\n const peerConnectionIds = new Set(peerConnectionStates.map(peerConnectionState => peerConnectionState.id));\n this._peerConnections.forEach(peerConnection => {\n if (!peerConnectionIds.has(peerConnection.id)) {\n peerConnection._close();\n }\n });\n return this;\n }\n\n /**\n * Get the {@link PeerConnectionManager}'s configuration.\n * @private\n * @returns {Promise}\n */\n _getConfiguration() {\n return this._configurationDeferred.promise;\n }\n\n /**\n * Get or create a {@link PeerConnectionV2}.\n * @private\n * @param {string} id\n * @param {object} [configuration]\n * @returns {PeerConnectionV2}\n */\n _getOrCreate(id, configuration) {\n const self = this;\n let peerConnection = this._peerConnections.get(id);\n if (!peerConnection) {\n const PeerConnectionV2 = this._PeerConnectionV2;\n\n const options = Object.assign({\n dummyAudioMediaStreamTrack: this._dummyAudioTrackSender\n ? this._dummyAudioTrackSender.track\n : null,\n offerOptions: this._offerOptions\n }, this._sessionTimeout ? {\n sessionTimeout: this._sessionTimeout\n } : {}, configuration);\n\n try {\n peerConnection = new PeerConnectionV2(id, this._encodingParameters, this._preferredCodecs, options);\n } catch (e) {\n throw new MediaConnectionError();\n }\n\n this._peerConnections.set(peerConnection.id, peerConnection);\n peerConnection.on('candidates', this.queue.bind(this, 'candidates'));\n peerConnection.on('description', this.queue.bind(this, 'description'));\n peerConnection.on('trackAdded', this.queue.bind(this, 'trackAdded'));\n peerConnection.on('stateChanged', function stateChanged(state) {\n if (state === 'closed') {\n peerConnection.removeListener('stateChanged', stateChanged);\n self._dataTrackSenders.forEach(sender => peerConnection.removeDataTrackSender(sender));\n self._mediaTrackSenders.forEach(sender => peerConnection.removeMediaTrackSender(sender));\n self._peerConnections.delete(peerConnection.id);\n self._closedPeerConnectionIds.add(peerConnection.id);\n updateConnectionState(self);\n updateIceConnectionState(self);\n }\n });\n peerConnection.on('connectionStateChanged', () => updateConnectionState(this));\n peerConnection.on('iceConnectionStateChanged', () => updateIceConnectionState(this));\n\n this._dataTrackSenders.forEach(peerConnection.addDataTrackSender, peerConnection);\n this._mediaTrackSenders.forEach(peerConnection.addMediaTrackSender, peerConnection);\n\n updateIceConnectionState(this);\n }\n return peerConnection;\n }\n\n /**\n * Close all the {@link PeerConnectionV2}s in this {@link PeerConnectionManager}.\n * @returns {this}\n */\n close() {\n this._peerConnections.forEach(peerConnection => {\n peerConnection.close();\n });\n if (this._dummyAudioTrackSender) {\n this._dummyAudioTrackSender.stop();\n }\n if (this._audioContextFactory) {\n this._audioContextFactory.release(this);\n }\n updateIceConnectionState(this);\n return this;\n }\n\n /**\n * Create a new {@link PeerConnectionV2} on this {@link PeerConnectionManager}.\n * Then, create a new offer with the newly-created {@link PeerConnectionV2}.\n * @return {Promise}\n */\n createAndOffer() {\n return this._getConfiguration().then(configuration => {\n let id;\n do {\n id = util.makeUUID();\n } while (this._peerConnections.has(id));\n\n return this._getOrCreate(id, configuration);\n }).then(peerConnection => {\n return peerConnection.offer();\n }).then(() => {\n return this;\n });\n }\n\n /**\n * Get the {@link DataTrackReceiver}s and {@link MediaTrackReceiver}s of all\n * the {@link PeerConnectionV2}s.\n * @returns {Array} trackReceivers\n */\n getTrackReceivers() {\n return util.flatMap(this._peerConnections, peerConnection => peerConnection.getTrackReceivers());\n }\n\n /**\n * Get the states of all {@link PeerConnectionV2}s.\n * @returns {Array}\n */\n getStates() {\n const peerConnectionStates = [];\n this._peerConnections.forEach(peerConnection => {\n const peerConnectionState = peerConnection.getState();\n if (peerConnectionState) {\n peerConnectionStates.push(peerConnectionState);\n }\n });\n return peerConnectionStates;\n }\n\n /**\n * Set the {@link PeerConnectionManager}'s configuration.\n * @param {object} configuration\n * @returns {this}\n */\n setConfiguration(configuration) {\n if (this._configuration) {\n this._configurationDeferred = util.defer();\n this._peerConnections.forEach(peerConnection => {\n peerConnection.setConfiguration(configuration);\n });\n }\n this._configuration = configuration;\n this._configurationDeferred.resolve(configuration);\n return this;\n }\n\n /**\n * Set the ICE reconnect timeout period for all {@link PeerConnectionV2}s.\n * @param {number} period - Period in milliseconds.\n * @returns {this}\n */\n setIceReconnectTimeout(period) {\n if (this._sessionTimeout === null) {\n this._peerConnections.forEach(peerConnection => {\n peerConnection.setIceReconnectTimeout(period);\n });\n this._sessionTimeout = period;\n }\n return this;\n }\n\n /**\n * Set the {@link DataTrackSender}s and {@link MediaTrackSender}s on the\n * {@link PeerConnectionManager}'s underlying {@link PeerConnectionV2}s.\n * @param {Array} trackSenders\n * @returns {this}\n */\n setTrackSenders(trackSenders) {\n const dataTrackSenders = new Set(trackSenders.filter(trackSender => trackSender.kind === 'data'));\n\n const mediaTrackSenders = new Set(trackSenders\n .filter(trackSender => trackSender && (trackSender.kind === 'audio' || trackSender.kind === 'video')));\n\n const changes = getTrackSenderChanges(this, dataTrackSenders, mediaTrackSenders);\n this._dataTrackSenders = dataTrackSenders;\n this._mediaTrackSenders = mediaTrackSenders;\n applyTrackSenderChanges(this, changes);\n\n return this;\n }\n\n /**\n * Update the {@link PeerConnectionManager}.\n * @param {Array} peerConnectionStates\n * @param {boolean} [synced=false]\n * @returns {Promise}\n */\n update(peerConnectionStates, synced = false) {\n if (synced) {\n this._closeAbsentPeerConnections(peerConnectionStates);\n }\n return this._getConfiguration().then(configuration => {\n return Promise.all(peerConnectionStates.map(peerConnectionState => {\n if (this._closedPeerConnectionIds.has(peerConnectionState.id)) {\n return null;\n }\n const peerConnection = this._getOrCreate(peerConnectionState.id, configuration);\n return peerConnection.update(peerConnectionState);\n }));\n }).then(() => {\n return this;\n });\n }\n\n /**\n * Get the {@link PeerConnectionManager}'s media statistics.\n * @returns {Promise.>}\n */\n getStats() {\n const peerConnections = Array.from(this._peerConnections.values());\n return Promise.all(peerConnections.map(peerConnection => peerConnection.getStats().then(response => [\n peerConnection.id,\n response\n ]))).then(responses => new Map(responses));\n }\n}\n\n/**\n * Create a dummy audio MediaStreamTrack with the given AudioContext.\n * @private\n * @param {AudioContext} audioContext\n * @return {MediaStreamTrack}\n */\nfunction createDummyAudioMediaStreamTrack(audioContext) {\n const mediaStreamDestination = audioContext.createMediaStreamDestination();\n return mediaStreamDestination.stream.getAudioTracks()[0];\n}\n\n/**\n * @event {PeerConnectionManager#candidates}\n * @param {object} candidates\n */\n\n/**\n * @event {PeerConnectionManager#connectionStateChanged}\n */\n\n/**\n * @event {PeerConnectionManager#description}\n * @param {object} description\n */\n\n/**\n * @event {PeerConnectionManager#iceConnectionStateChanged}\n */\n\n/**\n * @event {PeerConnectionManager#trackAdded}\n * @param {MediaStreamTrack|DataTrackReceiver} mediaStreamTrackOrDataTrackReceiver\n */\n\n/**\n * Apply {@link TrackSenderChanges}.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {TrackSenderChanges} changes\n * @returns {void}\n */\nfunction applyTrackSenderChanges(peerConnectionManager, changes) {\n if (changes.data.add.size\n || changes.data.remove.size\n || changes.media.add.size\n || changes.media.remove.size) {\n peerConnectionManager._peerConnections.forEach(peerConnection => {\n changes.data.remove.forEach(peerConnection.removeDataTrackSender, peerConnection);\n changes.media.remove.forEach(peerConnection.removeMediaTrackSender, peerConnection);\n changes.data.add.forEach(peerConnection.addDataTrackSender, peerConnection);\n changes.media.add.forEach(peerConnection.addMediaTrackSender, peerConnection);\n if (changes.media.add.size\n || changes.media.remove.size\n || (changes.data.add.size && !peerConnection.isApplicationSectionNegotiated)) {\n peerConnection.offer();\n }\n });\n }\n}\n\n/**\n * @interface DataTrackSenderChanges\n * @property {Set} add\n * @property {Set} remove\n */\n\n/**\n * Get the {@Link DataTrackSender} changes.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {Array} dataTrackSenders\n * @returns {DataTrackSenderChanges} changes\n */\nfunction getDataTrackSenderChanges(peerConnectionManager, dataTrackSenders) {\n const dataTrackSendersToAdd = util.difference(dataTrackSenders, peerConnectionManager._dataTrackSenders);\n const dataTrackSendersToRemove = util.difference(peerConnectionManager._dataTrackSenders, dataTrackSenders);\n return {\n add: dataTrackSendersToAdd,\n remove: dataTrackSendersToRemove\n };\n}\n\n/**\n * @interface TrackSenderChanges\n * @property {DataTrackSenderChanges} data\n * @property {MediaTrackSenderChanges} media\n */\n\n/**\n * Get {@link DataTrackSender} and {@link MediaTrackSender} changes.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {Array} dataTrackSenders\n * @param {Array} mediaTrackSenders\n * @returns {TrackSenderChanges} changes\n */\nfunction getTrackSenderChanges(peerConnectionManager, dataTrackSenders, mediaTrackSenders) {\n return {\n data: getDataTrackSenderChanges(peerConnectionManager, dataTrackSenders),\n media: getMediaTrackSenderChanges(peerConnectionManager, mediaTrackSenders)\n };\n}\n\n/**\n * @interface MediaTrackSenderChanges\n * @property {Set} add\n * @property {Set} remove\n */\n\n/**\n * Get the {@link MediaTrackSender} changes.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {Array} mediaTrackSenders\n * @returns {MediaTrackSenderChanges} changes\n */\nfunction getMediaTrackSenderChanges(peerConnectionManager, mediaTrackSenders) {\n const mediaTrackSendersToAdd = util.difference(mediaTrackSenders, peerConnectionManager._mediaTrackSenders);\n const mediaTrackSendersToRemove = util.difference(peerConnectionManager._mediaTrackSenders, mediaTrackSenders);\n return {\n add: mediaTrackSendersToAdd,\n remove: mediaTrackSendersToRemove\n };\n}\n\n/**\n * This object maps RTCIceConnectionState and RTCPeerConnectionState values to a \"rank\".\n */\nconst toRank = {\n new: 0,\n checking: 1,\n connecting: 2,\n connected: 3,\n completed: 4,\n disconnected: -1,\n failed: -2,\n closed: -3\n};\n\n/**\n * This object maps \"rank\" back to RTCIceConnectionState or RTCPeerConnectionState values.\n */\nlet fromRank;\n\n/**\n * `Object.keys` is not supported in older browsers, so we can't just\n * synchronously call it in this module; we need to defer invoking it until we\n * know we're in a modern environment (i.e., anything that supports WebRTC).\n * @returns {object} fromRank\n */\nfunction createFromRank() {\n return Object.keys(toRank).reduce((fromRank, state) => {\n return Object.assign(fromRank, { [toRank[state]]: state });\n }, {});\n}\n\n/**\n * Summarize RTCIceConnectionStates or RTCPeerConnectionStates.\n * @param {Array|Array} states\n * @returns {RTCIceConnectionState|RTCPeerConnectionState} summary\n */\nfunction summarizeIceOrPeerConnectionStates(states) {\n if (!states.length) {\n return 'new';\n }\n fromRank = fromRank || createFromRank();\n return states.reduce((state1, state2) => {\n return fromRank[Math.max(toRank[state1], toRank[state2])];\n });\n}\n\n/**\n * Update the {@link PeerConnectionManager}'s `iceConnectionState`, and emit an\n * \"iceConnectionStateChanged\" event, if necessary.\n * @param {PeerConnectionManager} pcm\n * @returns {void}\n */\nfunction updateIceConnectionState(pcm) {\n pcm._lastIceConnectionState = pcm.iceConnectionState;\n pcm._iceConnectionState = summarizeIceOrPeerConnectionStates(\n [...pcm._peerConnections.values()].map(pcv2 => pcv2.iceConnectionState));\n if (pcm.iceConnectionState !== pcm._lastIceConnectionState) {\n pcm.emit('iceConnectionStateChanged');\n }\n}\n\n/**\n * Update the {@link PeerConnectionManager}'s `connectionState`, and emit a\n * \"connectionStateChanged\" event, if necessary.\n * @param {PeerConnectionManager} pcm\n * @returns {void}\n */\nfunction updateConnectionState(pcm) {\n pcm._lastConnectionState = pcm.connectionState;\n pcm._connectionState = summarizeIceOrPeerConnectionStates(\n [...pcm._peerConnections.values()].map(pcv2 => pcv2.connectionState));\n if (pcm.connectionState !== pcm._lastConnectionState) {\n pcm.emit('connectionStateChanged');\n }\n}\n\nmodule.exports = PeerConnectionManager;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst EventEmitter = require('events');\n\nlet nInstances = 0;\nclass MediaSignaling extends EventEmitter {\n /**\n * Construct a {@link MediaSignaling}.\n * @param {Promise} getReceive\n * @param {string} channel\n */\n constructor(getReceiver, channel, options) {\n super();\n Object.defineProperties(this, {\n _instanceId: {\n value: nInstances++\n },\n channel: {\n value: channel,\n },\n _log: {\n value: options.log.createLog('default', this)\n },\n _getReceiver: {\n value: getReceiver\n },\n _receiverPromise: {\n value: null,\n writable: true,\n },\n _transport: {\n value: null,\n writable: true\n }\n });\n }\n\n get isSetup() {\n return !!this._receiverPromise;\n }\n\n toString() {\n return `[MediaSignaling #${this._instanceId}:${this.channel}]`;\n }\n\n setup(id) {\n this._teardown();\n this._log.info('setting up msp transport for id:', id);\n const receiverPromise = this._getReceiver(id).then(receiver => {\n if (receiver.kind !== 'data') {\n this._log.error('Expected a DataTrackReceiver');\n } if (this._receiverPromise !== receiverPromise) {\n return;\n }\n\n try {\n this._transport = receiver.toDataTransport();\n this.emit('ready', this._transport);\n } catch (ex) {\n this._log.error(`Failed to toDataTransport: ${ex.message}`);\n }\n receiver.once('close', () => this._teardown());\n });\n this._receiverPromise = receiverPromise;\n }\n\n _teardown() {\n if (this._transport) {\n this._log.info('Tearing down');\n this._transport = null;\n this._receiverPromise = null;\n this.emit('teardown');\n }\n }\n}\n\nmodule.exports = MediaSignaling;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\n\n/**\n * @property {?Track.SID} loudestParticipantSid\n * @emits DominantSpeakerSignaling#updated\n */\nclass DominantSpeakerSignaling extends MediaSignaling {\n /**\n * Construct an {@link DominantSpeakerSignaling}.\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'active_speaker', options);\n\n Object.defineProperties(this, {\n _loudestParticipantSid: {\n value: null,\n writable: true\n },\n });\n\n this.on('ready', transport => {\n transport.on('message', message => {\n switch (message.type) {\n case 'active_speaker':\n this._setLoudestParticipantSid(message.participant);\n break;\n default:\n break;\n }\n });\n });\n }\n\n /**\n * Get the loudest {@link Track.SID}, if known.\n * @returns {?Track.SID}\n */\n get loudestParticipantSid() {\n return this._loudestParticipantSid;\n }\n\n /**\n * @private\n * @param {Track.SID} loudestParticipantSid\n * @returns {void}\n */\n _setLoudestParticipantSid(loudestParticipantSid) {\n if (this.loudestParticipantSid === loudestParticipantSid) {\n return;\n }\n this._loudestParticipantSid = loudestParticipantSid;\n this.emit('updated');\n }\n}\n\n/**\n * @event DominantSpeakerSignaling#updated\n */\n\nmodule.exports = DominantSpeakerSignaling;\n", "'use strict';\n\n/**\n * @property {number} [availableSend] - bps (undefined in Firefox)\n * @property {number} recv - bps\n * @property {number} [rtt] - s (undefined in Firefox)\n * @property {number} send - bps\n */\nclass IceReport {\n /**\n * Construct an {@link IceReport}.\n * @param {number} send - bps\n * @param {number} recv - bps\n * @param {number} [rtt] - s\n * @param {number} [availableSend] - bps\n */\n constructor(send, recv, availableSend, rtt) {\n Object.defineProperties(this, {\n availableSend: {\n enumerable: true,\n value: availableSend\n },\n recv: {\n enumerable: true,\n value: recv\n },\n rtt: {\n enumerable: true,\n value: rtt\n },\n send: {\n enumerable: true,\n value: send\n }\n });\n }\n\n /**\n * @param {RTCStats} olderStats\n * @param {RTCStats} newerStats\n * @returns {IceReport}\n */\n static of(olderStats, newerStats) {\n const secondsElapsed = (newerStats.timestamp - olderStats.timestamp) / 1000;\n const deltaBytesSent = newerStats.bytesSent - olderStats.bytesSent;\n const deltaBytesReceived = newerStats.bytesReceived - olderStats.bytesReceived;\n const send = secondsElapsed > 0\n ? (deltaBytesSent / secondsElapsed) * 8\n : 0;\n const recv = secondsElapsed > 0\n ? (deltaBytesReceived / secondsElapsed) * 8\n : 0;\n const { availableOutgoingBitrate: availableSend, currentRoundTripTime: rtt } = newerStats;\n return new IceReport(send, recv, availableSend, rtt);\n }\n}\n\nmodule.exports = IceReport;\n", "'use strict';\n\nconst IceReport = require('./icereport');\n\n/**\n * @property {IceReport} lastReport\n * @property {?RTCStats} lastStats\n */\nclass IceReportFactory {\n /**\n * Construct an {@link IceReportFactory}.\n */\n constructor() {\n Object.defineProperties(this, {\n lastReport: {\n enumerable: true,\n value: new IceReport(0, 0),\n writable: true\n },\n lastStats: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Create an {@link IceReport}.\n * @param {RTCStats} newerStats;\n * @returns {IceReport}\n */\n next(newerStats) {\n const olderStats = this.lastStats;\n this.lastStats = newerStats;\n if (olderStats) {\n const report = olderStats.id === newerStats.id\n ? IceReport.of(olderStats, newerStats)\n : new IceReport(0, 0);\n this.lastReport = report;\n }\n return this.lastReport;\n }\n}\n\nmodule.exports = IceReportFactory;\n", "/* eslint no-undefined:0 */\n'use strict';\n\n/**\n * @param {Array} xs\n * @returns {number|undefined}\n */\nfunction average(xs) {\n xs = xs.filter(x => typeof x === 'number');\n return xs.length < 1 ? undefined : xs.reduce((y, x) => x + y) / xs.length;\n}\n\nmodule.exports = average;\n", "'use strict';\n\n/**\n * @property {StatsId} id\n * @property {TrackId} trackId\n * @property {number} bitrate - bps\n */\nclass SenderOrReceiverReport {\n /**\n * Construct a {@link SenderOrReceiverReport}.\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {number} bitrate - bps\n */\n constructor(id, trackId, bitrate) {\n Object.defineProperties(this, {\n id: {\n enumerable: true,\n value: id\n },\n trackId: {\n enumerable: true,\n value: trackId\n },\n bitrate: {\n enumerable: true,\n value: bitrate\n }\n });\n }\n}\n\nmodule.exports = SenderOrReceiverReport;\n", "'use strict';\n\n/**\n * @param {Array} xs\n * @returns {number}\n */\nfunction sum(xs) {\n return xs.reduce((y, x) => typeof x === 'number' ? x + y : y, 0);\n}\n\nmodule.exports = sum;\n", "'use strict';\n\nconst average = require('./average');\nconst SenderOrReceiverReport = require('./senderorreceiverreport');\nconst sum = require('./sum');\n\n/**\n * @interface ReceiverSummary\n * @property {number} bitrate\n * @property {number} fractionLost - 0\u20131\n * @property {number} [jitter] - s (undefined for video tracks in Chrome)\n */\n\n/**\n * @extends SenderOrReceiverReport\n * @property {number} deltaPacketsLost\n * @property {number} deltaPacketsReceived\n * @property {number} [fractionLost] - 0\u20131 (undefined in Firefox)\n * @property {number} [jitter] - s (undefined for video tracks in Chrome)\n * @property {number} phonyPacketsLost - 0\u20131\n */\nclass ReceiverReport extends SenderOrReceiverReport {\n /**\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {number} bitrate - bps\n * @param {number} deltaPacketsLost\n * @param {number} deltaPacketsReceived\n * @param {number} [fractionLost] - 0\u20131 (undefined in Firefox)\n * @param {number} [jitter] - s (undefined for video tracks in Chrome)\n */\n constructor(id, trackId, bitrate, deltaPacketsLost, deltaPacketsReceived, fractionLost, jitter) {\n super(id, trackId, bitrate);\n const phonyFractionLost = deltaPacketsReceived > 0\n ? deltaPacketsLost / deltaPacketsReceived\n : 0;\n Object.defineProperties(this, {\n deltaPacketsLost: {\n enumerable: true,\n value: deltaPacketsLost\n },\n deltaPacketsReceived: {\n enumerable: true,\n value: deltaPacketsReceived\n },\n fractionLost: {\n enumerable: true,\n value: fractionLost\n },\n jitter: {\n enumerable: true,\n value: jitter\n },\n phonyFractionLost: {\n enumerable: true,\n value: phonyFractionLost\n }\n });\n }\n\n /**\n * Create a {@link ReceiverReport}.\n * @param {string} trackId\n * @param {RTCStats} olderStats\n * @param {RTCStats} newerStats\n * @returns {ReceiverReport}\n */\n static of(trackId, olderStats, newerStats) {\n if (olderStats.id !== newerStats.id) {\n throw new Error('RTCStats IDs must match');\n }\n const secondsElapsed = (newerStats.timestamp - olderStats.timestamp) / 1000;\n const deltaBytesReceived = newerStats.bytesReceived - olderStats.bytesReceived;\n const bitrate = secondsElapsed > 0\n ? (deltaBytesReceived / secondsElapsed) * 8\n : 0;\n const deltaPacketsLost = Math.max(newerStats.packetsLost - olderStats.packetsLost, 0);\n const deltaPacketsReceived = newerStats.packetsReceived - olderStats.packetsReceived;\n const { fractionLost, jitter } = newerStats;\n return new ReceiverReport(olderStats.id, trackId, bitrate, deltaPacketsLost, deltaPacketsReceived, fractionLost, jitter);\n }\n\n /**\n * Summarize {@link ReceiverReport}s by summing and averaging their values.\n * @param {Array} reports\n * @returns {ReceiverSummary}\n */\n static summarize(reports) {\n const summaries = reports.map(report => report.summarize());\n const bitrate = sum(summaries.map(summary => summary.bitrate));\n const fractionLost = average(summaries.map(summary => summary.fractionLost));\n const jitter = average(summaries.map(summary => summary.jitter));\n return {\n bitrate,\n fractionLost,\n jitter\n };\n }\n\n /**\n * Summarize the {@link ReceiveReport}.\n * @returns {ReceiverSummary}\n */\n summarize() {\n return {\n bitrate: this.bitrate,\n fractionLost: typeof this.fractionLost === 'number' ? this.fractionLost : this.phonyFractionLost,\n jitter: this.jitter\n };\n }\n}\n\nmodule.exports = ReceiverReport;\n", "/* eslint no-undefined:0 */\n'use strict';\n\nconst average = require('./average');\nconst SenderOrReceiverReport = require('./senderorreceiverreport');\nconst sum = require('./sum');\n\n/**\n * @interface SenderSummary\n * @property {number} bitrate\n * @property {number} [rtt] - s (undefined in Chrome)\n */\n\n/**\n * @extends SenderOrReceiverReport\n * @property {number} [rtt] - s (undefined in Chrome)\n */\nclass SenderReport extends SenderOrReceiverReport {\n /**\n * Construct a {@link SenderReport}.\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {number} bitrate - bps\n * @param {number} [rtt] - s\n */\n constructor(id, trackId, bitrate, rtt) {\n super(id, trackId, bitrate);\n Object.defineProperties(this, {\n rtt: {\n enumerable: true,\n value: rtt\n }\n });\n }\n\n /**\n * Create a {@link SenderReport}.\n * @param {string} trackId\n * @param {RTCStats} olderStats\n * @param {RTCStats} newerStats\n * @param {RTCRemoteInboundRtpStreamStats} [newerRemoteStats]\n * @returns {SenderReport}\n */\n static of(trackId, olderStats, newerStats, newerRemoteStats) {\n if (olderStats.id !== newerStats.id) {\n throw new Error('RTCStats IDs must match');\n }\n const secondsElapsed = (newerStats.timestamp - olderStats.timestamp) / 1000;\n const deltaBytesSent = newerStats.bytesSent - olderStats.bytesSent;\n const bitrate = secondsElapsed > 0\n ? (deltaBytesSent / secondsElapsed) * 8\n : 0;\n const rtt = newerRemoteStats && typeof newerRemoteStats.roundTripTime === 'number'\n ? newerRemoteStats.roundTripTime / 1000\n : undefined;\n return new SenderReport(olderStats.id, trackId, bitrate, rtt);\n }\n\n /**\n * Summarize {@link SenderReport}s by summing and averaging their values.\n * @param {Array} reports\n * @returns {SenderSummary}\n */\n static summarize(reports) {\n const bitrate = sum(reports.map(report => report.bitrate));\n const rtt = average(reports.map(report => report.rtt));\n return {\n bitrate,\n rtt\n };\n }\n}\n\nmodule.exports = SenderReport;\n", "'use strict';\n\nconst ReceiverReport = require('./receiverreport');\nconst SenderReport = require('./senderreport');\n\n/**\n * @interface SenderAndReceiverReports\n * @property {Array} send\n * @property {Array} recv\n */\n\n/**\n * @interface SenderAndReceiverSummary\n * @property {SenderSummary} send\n * @property {ReceiverSummary} recv\n */\n\n/**\n * @interface PeerConnectionSummary\n * @property {IceReport} ice\n * @property {SenderSummary} send\n * @property {ReceiverSummary} recv\n * @property {SenderAndReceiverSummary} audio\n * @property {SenderAndReceiverSummary} video\n */\n\n/**\n * @property {IceReport} ice\n * @roperty {SenderAndReceiverReports} audio\n * @roperty {SenderAndReceiverReports} video\n */\nclass PeerConnectionReport {\n /**\n * Construct a {@link PeerConnectionReport}.\n * @param {IceReport} ice\n * @param {SenderAndReceiverReports} audio\n * @param {SenderAndReceiverReports} video\n */\n constructor(ice, audio, video) {\n Object.defineProperties(this, {\n ice: {\n enumerable: true,\n value: ice\n },\n audio: {\n enumerable: true,\n value: audio\n },\n video: {\n enumerable: true,\n value: video\n }\n });\n }\n\n /**\n * Summarize the {@link PeerConnectionReport} by summarizing its\n * {@link SenderReport}s and {@link ReceiverReport}s.\n * @returns {PeerConnectionSummary}\n */\n summarize() {\n const senderReports = this.audio.send.concat(this.video.send);\n const send = SenderReport.summarize(senderReports);\n\n const receiverReports = this.audio.recv.concat(this.video.recv);\n const recv = ReceiverReport.summarize(receiverReports);\n\n return {\n ice: this.ice,\n send,\n recv,\n audio: {\n send: SenderReport.summarize(this.audio.send),\n recv: ReceiverReport.summarize(this.audio.recv)\n },\n video: {\n send: SenderReport.summarize(this.video.send),\n recv: ReceiverReport.summarize(this.video.recv)\n }\n };\n }\n}\n\nmodule.exports = PeerConnectionReport;\n", "'use strict';\n\n/**\n * @property {StatsId} id\n * @property {TrackId} trackId\n * @property {RTCStats} lastStats\n */\nclass SenderOrReceiverReportFactory {\n /**\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {RTCStats} initialStats\n */\n constructor(id, trackId, initialStats) {\n Object.defineProperties(this, {\n id: {\n enumerable: true,\n value: id,\n writable: true\n },\n trackId: {\n enumerable: true,\n value: trackId,\n writable: true\n },\n lastStats: {\n enumerable: true,\n value: initialStats,\n writable: true\n }\n });\n }\n}\n\nmodule.exports = SenderOrReceiverReportFactory;\n", "'use strict';\n\nconst ReceiverReport = require('./receiverreport');\nconst SenderOrReceiverReportFactory = require('./senderorreceiverreportfactory');\n\n/**\n * @extends SenderOrReceiverReportFactory\n * @param {?ReceiverReport} lastReport\n */\nclass ReceiverReportFactory extends SenderOrReceiverReportFactory {\n /**\n * Construct a {@link ReceiverReportFactory}.\n * @param {TrackId} trackId\n * @param {RTCStats} initialStats\n */\n constructor(trackId, initialStats) {\n super(initialStats.id, trackId, initialStats);\n Object.defineProperties(this, {\n lastReport: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Create a {@link ReceiverReport}.\n * @param {TrackId} trackId\n * @param {RTCStats} newerStats\n * @returns {ReceiverReport}\n */\n next(trackId, newerStats) {\n const olderStats = this.lastStats;\n this.lastStats = newerStats;\n this.trackId = trackId;\n const report = ReceiverReport.of(trackId, olderStats, newerStats);\n this.lastReport = report;\n return report;\n }\n}\n\nmodule.exports = ReceiverReportFactory;\n", "'use strict';\n\nconst SenderOrReceiverReportFactory = require('./senderorreceiverreportfactory');\nconst SenderReport = require('./senderreport');\n\n/**\n * @extends {SenderOrReceiverReportFactory}\n * @property {?SenderReport} lastReport\n */\nclass SenderReportFactory extends SenderOrReceiverReportFactory {\n /**\n * Construct a {@link SenderReportFactory}.\n * @param {TrackId} trackId\n * @param {RTCStats} initialStats\n */\n constructor(trackId, initialStats) {\n super(initialStats.id, trackId, initialStats);\n Object.defineProperties(this, {\n lastReport: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * @param {TrackId} trackId\n * @param {RTCStats} newerStats\n * @param {RTCRemoteInboundRtpStreamStats} [newerRemoteStats]\n * @returns {SenderReport}\n */\n next(trackId, newerStats, newerRemoteStats) {\n const olderStats = this.lastStats;\n this.lastStats = newerStats;\n this.trackId = trackId;\n const report = SenderReport.of(trackId, olderStats, newerStats, newerRemoteStats);\n this.lastReport = report;\n return report;\n }\n}\n\nmodule.exports = SenderReportFactory;\n", "'use strict';\n\nconst { guessBrowser } = require('../webrtc/util');\n\nconst IceReportFactory = require('./icereportfactory');\nconst PeerConnectionReport = require('./peerconnectionreport');\nconst ReceiverReportFactory = require('./receiverreportfactory');\nconst SenderReportFactory = require('./senderreportfactory');\n\n/**\n * @typedef {string} TrackId\n */\n\n/**\n * @typedef {string} StatsId\n */\n\n/**\n * @interface SenderReportFactoriesByMediaType\n * @property {Map} audio\n * @property {Map} video\n */\n\n/**\n * @interface ReceiverReportFactoriesByMediaType\n * @property {Map} audio\n * @property {Map} video\n */\n\n/**\n * @interface SenderAndReceiverReportFactories\n * @property {Map} send\n * @property {Map} recv\n */\n\n/**\n * @interface {StatsIdsByMediaType}\n * @property {Set} audio\n * @property {Set} video\n */\n\n/**\n * @property {RTCPeerConnection} pc\n * @property {IceReportFactory} iceReportFactory\n * @property {SenderAndReceiverReportFactories} audio\n * @property {SenderAndReceiverReportFactories} video\n * @property {?PeerConnectionReport} lastReport\n */\nclass PeerConnectionReportFactory {\n /**\n * Construct a {@link PeerConnectionReportFactory}.\n * @param {RTCPeerConnection} pc\n */\n constructor(pc) {\n Object.defineProperties(this, {\n pc: {\n enumerable: true,\n value: pc\n },\n ice: {\n enumerable: true,\n value: new IceReportFactory()\n },\n audio: {\n enumerable: true,\n value: {\n send: new Map(),\n recv: new Map()\n }\n },\n video: {\n enumerable: true,\n value: {\n send: new Map(),\n recv: new Map()\n }\n },\n lastReport: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Create a {@link PeerConnectionReport}.\n * @returns {Promise}\n */\n next() {\n const updatePromise = guessBrowser() === 'firefox'\n ? updateFirefox(this)\n : updateChrome(this);\n\n return updatePromise.then(() => {\n const audioSenderReportFactories = [...this.audio.send.values()];\n const videoSenderReportFactories = [...this.video.send.values()];\n const audioReceiverReportFactories = [...this.audio.recv.values()];\n const videoReceiverReportFactories = [...this.video.recv.values()];\n\n const report = new PeerConnectionReport(\n this.ice.lastReport,\n {\n send: audioSenderReportFactories.map(factory => factory.lastReport).filter(report => report),\n recv: audioReceiverReportFactories.map(factory => factory.lastReport).filter(report => report)\n },\n {\n send: videoSenderReportFactories.map(factory => factory.lastReport).filter(report => report),\n recv: videoReceiverReportFactories.map(factory => factory.lastReport).filter(report => report)\n }\n );\n\n this.lastReport = report;\n\n return report;\n });\n }\n}\n\n/**\n * Construct a Map from MediaStreamTrack Ids to RTCStatsReports.\n * @param {Array|Array} sendersOrReceivers - each\n * RTCRtpSender should have a non-null track\n * @returns {Promise>}\n */\nfunction getSenderOrReceiverReports(sendersOrReceivers) {\n return Promise.all(sendersOrReceivers.map(senderOrReceiver => {\n const trackId = senderOrReceiver.track.id;\n return senderOrReceiver.getStats().then(report => {\n // NOTE(mroberts): We have to rewrite Ids due to this bug:\n //\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1463430\n //\n for (const stats of report.values()) {\n if (stats.type === 'inbound-rtp') {\n stats.id = `${trackId}-${stats.id}`;\n }\n }\n return [trackId, report];\n });\n })).then(pairs => new Map(pairs));\n}\n\n/**\n * @param {SenderReportFactory.constructor} SenderReportFactory\n * @param {SenderReportFactoriesByMediaType} sendersByMediaType\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?SenderReportFactory}\n *//**\n * @param {ReceiverReportFactory.constructor} ReceiverReportFactory\n * @param {ReceiverReportFactoriesByMediaType} receiversByMediaType\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?ReceiverReportFactory}\n */\nfunction getOrCreateSenderOrReceiverReportFactory(SenderOrReceiverReportFactory, sendersOrReceiversByMediaType, report, stats, trackId) {\n const sendersOrReceivers = sendersOrReceiversByMediaType[stats.mediaType];\n if (!trackId) {\n const trackStats = report.get(stats.trackId);\n if (trackStats) {\n trackId = trackStats.trackIdentifier;\n }\n }\n if (sendersOrReceivers && trackId) {\n if (sendersOrReceivers.has(stats.id)) {\n return sendersOrReceivers.get(stats.id);\n }\n const senderOrReceiverFactory = new SenderOrReceiverReportFactory(trackId, stats);\n sendersOrReceivers.set(stats.id, senderOrReceiverFactory);\n }\n return null;\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {SenderReportFactoriesByMediaType}\n */\nfunction getSenderReportFactoriesByMediaType(factory) {\n return { audio: factory.audio.send, video: factory.video.send };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {ReceiverReportFactoriesByMediaType}\n */\nfunction getReceiverReportFactoriesByMediaType(factory) {\n return { audio: factory.audio.recv, video: factory.video.recv };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?SenderReportFactory}\n */\nfunction getOrCreateSenderReportFactory(factory, report, stats, trackId) {\n return getOrCreateSenderOrReceiverReportFactory(SenderReportFactory, getSenderReportFactoriesByMediaType(factory), report, stats, trackId);\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?ReceiverReportFactory}\n */\nfunction getOrCreateReceiverReportFactory(factory, report, stats, trackId) {\n return getOrCreateSenderOrReceiverReportFactory(ReceiverReportFactory, getReceiverReportFactoriesByMediaType(factory), report, stats, trackId);\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @retuns {StatsIdsByMediaType}\n */\nfunction getSenderReportFactoryIdsByMediaType(factory) {\n return {\n audio: new Set(factory.audio.send.keys()),\n video: new Set(factory.video.send.keys())\n };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @retuns {StatsIdsByMediaType}\n */\nfunction getReceiverReportFactoryIdsByMediaType(factory) {\n return {\n audio: new Set(factory.audio.recv.keys()),\n video: new Set(factory.video.recv.keys())\n };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {StatsIdsByMediaType} senderReportFactoryIdsToDeleteByMediaType\n * @param {TrackId} [trackId]\n * @returns {void}\n */\nfunction updateSenderReports(factory, report, senderReportFactoryIdsToDeleteByMediaType, trackId) {\n for (const stats of report.values()) {\n if (stats.type === 'outbound-rtp' && !stats.isRemote) {\n if (guessBrowser() !== 'firefox' && !stats.trackId) {\n continue;\n }\n const senderReportFactoryIdsToDelete = senderReportFactoryIdsToDeleteByMediaType[stats.mediaType];\n if (senderReportFactoryIdsToDelete) {\n senderReportFactoryIdsToDelete.delete(stats.id);\n }\n const senderReportFactory = getOrCreateSenderReportFactory(factory, report, stats, trackId);\n if (senderReportFactory) {\n const remoteInboundStats = report.get(stats.remoteId);\n senderReportFactory.next(trackId || senderReportFactory.trackId, stats, remoteInboundStats);\n }\n }\n }\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {StatsIdsByMediaType} receiverReportFactoryIdsToDeleteByMediaType\n * @param {TrackId} [trackId]\n * @returns {void}\n */\nfunction updateReceiverReports(factory, report, receiverReportFactoryIdsToDeleteByMediaType, trackId) {\n for (const stats of report.values()) {\n if (stats.type === 'inbound-rtp' && !stats.isRemote) {\n const receiverReportFactoryIdsToDelete = receiverReportFactoryIdsToDeleteByMediaType[stats.mediaType];\n if (receiverReportFactoryIdsToDelete) {\n receiverReportFactoryIdsToDelete.delete(stats.id);\n }\n const receiverReportFactory = getOrCreateReceiverReportFactory(factory, report, stats, trackId);\n if (receiverReportFactory) {\n receiverReportFactory.next(trackId || receiverReportFactory.trackId, stats);\n }\n }\n }\n}\n\n/**\n * @param {SenderReportFactoriesByMediaType|ReceiverReportFactoriesByMediaType} senderOrReceiverReportFactoriesByMediaType\n * @param {StatsIdsByMediaType} senderOrReceiverReportFactoryIdsByMediaType\n * @returns {void}\n */\nfunction deleteSenderOrReceiverReportFactories(senderOrReceiverReportFactoriesByMediaType, senderOrReceiverReportFactoryIdsByMediaType) {\n for (const mediaType in senderOrReceiverReportFactoryIdsByMediaType) {\n const senderOrReceiverReportFactories = senderOrReceiverReportFactoriesByMediaType[mediaType];\n const senderOrReceiverReportFactoryIds = senderOrReceiverReportFactoryIdsByMediaType[mediaType];\n senderOrReceiverReportFactoryIds.forEach(senderOrReceiverReportFactoryId => senderOrReceiverReportFactories.delete(senderOrReceiverReportFactoryId));\n }\n}\n\n/**\n * @param {IceReportFactory} ice\n * @param {RTCStatsReport} report\n * @returns {void}\n */\nfunction updateIceReport(ice, report) {\n let selectedCandidatePair;\n for (const stats of report.values()) {\n if (stats.type === 'transport') {\n selectedCandidatePair = report.get(stats.selectedCandidatePairId);\n }\n }\n if (selectedCandidatePair) {\n ice.next(selectedCandidatePair);\n return;\n }\n for (const stats of report.values()) {\n if (stats.type === 'candidate-pair'\n && stats.nominated\n && ('selected' in stats ? stats.selected : true)) {\n ice.next(stats);\n }\n }\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {Promise}\n */\nfunction updateFirefox(factory) {\n const senders = factory.pc.getTransceivers()\n .filter(transceiver => transceiver.currentDirection && transceiver.currentDirection.match(/send/) && transceiver.sender.track)\n .map(transceiver => transceiver.sender);\n\n const receivers = factory.pc.getTransceivers()\n .filter(transceiver => transceiver.currentDirection && transceiver.currentDirection.match(/recv/))\n .map(transceiver => transceiver.receiver);\n\n return Promise.all([\n getSenderOrReceiverReports(senders),\n getSenderOrReceiverReports(receivers),\n factory.pc.getStats()\n ]).then(([senderReports, receiverReports, pcReport]) => {\n const senderReportFactoriesByMediaType = getSenderReportFactoriesByMediaType(factory);\n const senderReportFactoryIdsToDeleteByMediaType = getSenderReportFactoryIdsByMediaType(factory);\n senderReports.forEach((report, trackId) => updateSenderReports(factory, report, senderReportFactoryIdsToDeleteByMediaType, trackId));\n deleteSenderOrReceiverReportFactories(senderReportFactoriesByMediaType, senderReportFactoryIdsToDeleteByMediaType);\n\n const receiverReportFactoriesByMediaType = getReceiverReportFactoriesByMediaType(factory);\n const receiverReportFactoryIdsToDeleteByMediaType = getReceiverReportFactoryIdsByMediaType(factory);\n receiverReports.forEach((report, trackId) => updateReceiverReports(factory, report, receiverReportFactoryIdsToDeleteByMediaType, trackId));\n deleteSenderOrReceiverReportFactories(receiverReportFactoriesByMediaType, receiverReportFactoryIdsToDeleteByMediaType);\n\n updateIceReport(factory.ice, pcReport);\n });\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {Promise}\n */\nfunction updateChrome(factory) {\n return factory.pc.getStats().then(report => {\n const senderReportFactoriesByMediaType = getSenderReportFactoriesByMediaType(factory);\n const senderReportFactoryIdsToDeleteByMediaType = getSenderReportFactoryIdsByMediaType(factory);\n updateSenderReports(factory, report, senderReportFactoryIdsToDeleteByMediaType);\n deleteSenderOrReceiverReportFactories(senderReportFactoriesByMediaType, senderReportFactoryIdsToDeleteByMediaType);\n\n const receiverReportFactoriesByMediaType = getReceiverReportFactoriesByMediaType(factory);\n const receiverReportFactoryIdsToDeleteByMediaType = getReceiverReportFactoryIdsByMediaType(factory);\n updateReceiverReports(factory, report, receiverReportFactoryIdsToDeleteByMediaType);\n deleteSenderOrReceiverReportFactories(receiverReportFactoriesByMediaType, receiverReportFactoryIdsToDeleteByMediaType);\n\n updateIceReport(factory.ice, report);\n });\n}\n\nmodule.exports = PeerConnectionReportFactory;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst EventEmitter = require('events');\n\nconst PeerConnectionReportFactory = require('../../stats/peerconnectionreportfactory');\n\n/**\n * @emits NetworkQualityMonitor#updated\n */\nclass NetworkQualityMonitor extends EventEmitter {\n /**\n * Construct a {@link NetworkQualityMonitor}.\n * @param {PeerConnectionManager} manager\n * @param {NetworkQualitySignaling} signaling\n */\n constructor(manager, signaling) {\n super();\n Object.defineProperties(this, {\n _factories: {\n value: new WeakMap()\n },\n _manager: {\n value: manager\n },\n _signaling: {\n value: signaling\n }\n });\n signaling.on('updated', () => this.emit('updated'));\n }\n\n /**\n * Get the current {@link NetworkQualityLevel}, if any.\n * @returns {?NetworkQualityLevel} level - initially null\n */\n get level() {\n return this._signaling.level;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels}, if any.\n * @returns {?NetworkQualityLevels} levels - initially null\n */\n get levels() {\n return this._signaling.levels;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels} of remote participants, if any.\n * @returns {Map} remoteLevels\n */\n get remoteLevels() {\n return this._signaling.remoteLevels;\n }\n\n /**\n * Start monitoring.\n * @returns {void}\n */\n start() {\n this.stop();\n const timeout = setTimeout(() => {\n if (this._timeout !== timeout) {\n return;\n }\n next(this).then(reports => {\n if (this._timeout !== timeout) {\n return;\n }\n if (reports.length) {\n const [report] = reports;\n this._signaling.put(report);\n }\n this.start();\n });\n }, 200);\n this._timeout = timeout;\n }\n\n /**\n * Stop monitoring.\n * @returns {void}\n */\n stop() {\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n}\n\n/**\n * @param {NetworkQualityMonitor}\n * @returns {Promise}\n */\nfunction next(monitor) {\n const pcv2s = monitor._manager._peerConnections\n ? Array.from(monitor._manager._peerConnections.values())\n : [];\n\n const pcs = pcv2s\n .map(pcv2 => pcv2._peerConnection)\n .filter(pc => pc.signalingState !== 'closed');\n\n const factories = pcs.map(pc => {\n if (monitor._factories.has(pc)) {\n return monitor._factories.get(pc);\n }\n const factory = new PeerConnectionReportFactory(pc);\n monitor._factories.set(pc, factory);\n return factory;\n });\n\n const reportsOrNullPromises = factories.map(factory => factory.next().catch(() => null));\n\n return Promise.all(reportsOrNullPromises).then(reportsOrNull => reportsOrNull\n .filter(reportOrNull => reportOrNull)\n .map(report => report.summarize()));\n}\n\n/**\n * The {@link NetworkQualityLevel} changed.\n * @event NetworkQualityMonitor#updated\n */\n\nmodule.exports = NetworkQualityMonitor;\n", "'use strict';\n\nconst { defer } = require('./');\n\n/**\n * An {@link AsyncVar} is an \"asynchronous variable\" which may or may not\n * contain a value of some type T. You can put a value into the {@link AsyncVar}\n * with {@link AsyncVar#put}. Callers can take a value out of the\n * {@link AsyncVar} by queueing up with {@link AsyncVar#take}. N calls to\n * {@link AsyncVar#take} require N calls to {@link AsyncVar#put} to resolve, and\n * they resolve in order.\n */\nclass AsyncVar {\n /**\n * Construct an {@link AsyncVar}.\n */\n constructor() {\n Object.defineProperties(this, {\n _deferreds: {\n value: []\n },\n _hasValue: {\n value: false,\n writable: true\n },\n _value: {\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Put a value into the {@link AsyncVar}.\n * @param {T} value\n * @returns {this}\n */\n put(value) {\n this._hasValue = true;\n this._value = value;\n const deferred = this._deferreds.shift();\n if (deferred) {\n deferred.resolve(value);\n }\n return this;\n }\n\n /**\n * Take the value out of the {@link AsyncVar}.\n * @returns {Promise}\n */\n take() {\n if (this._hasValue && !this._deferreds.length) {\n this._hasValue = false;\n return Promise.resolve(this._value);\n }\n const deferred = defer();\n this._deferreds.push(deferred);\n return deferred.promise.then(value => {\n this._hasValue = false;\n return value;\n });\n }\n}\n\nmodule.exports = AsyncVar;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\nconst AsyncVar = require('../../util/asyncvar');\nconst Timeout = require('../../util/timeout');\n\nconst NETWORK_QUALITY_RESPONSE_TIME_MS = 5000;\n\n/**\n * @interface MediaSignalingTransport\n * @property {function(object): boolean} send\n * @emits MediaSignalingTransport#message\n */\n\n/**\n * The {@link MediaSignalingTransport} received a message.\n * @event MediaSignalingTransport#message\n * @param {object} message\n */\n\n/**\n * @interface LatencyStats\n * @property {number} jitter\n * @property {number} rtt\n * @property {number} level\n */\n\n/**\n * @interface FractionLostStats\n * @property {number} fractionLost\n * @property {number} level\n */\n\n/**\n * @interface BandwidthStats\n * @property {number} actual\n * @property {number} available\n * @property {number} level\n */\n\n/**\n * @interface SendOrRecvStats\n * @property {BandwidthStats} bandwidth\n * @property {FractionLostStats} fractionLost\n * @property {LatencyStats} latency\n */\n\n/**\n * @interface MediaLevels\n * @property {number} send\n * @property {SendOrRecvStats} sendStats\n * @property {number} recv\n * @property {SendOrRecvStats} recvStats\n */\n\n/**\n * @interface NetworkQualityLevels\n * @property {number} level\n * @property {MediaLevels} audio\n * @property {MediaLevels} video\n */\n\n/**\n * @typedef {PeerConnectionSummary} NetworkQualityInputs\n */\n\n/**\n * @classdesc The {@link NetworkQualitySignaling} class allows submitting\n * {@link NetworkQualityInputs} for computing {@link NetworkQualityLevel}. It\n * does so by sending and receiving messages over a\n * {@link MediaSignalingTransport}. The exact transport used depends on the\n * topology of the {@link Room} that {@link NetworkQualitySignaling} is being\n * used within: for P2P Rooms, we re-use the {@link TransportV2}; and for\n * Group Rooms, we use a {@link DataTransport}.\n * @emits NetworkQualitySignaling#updated\n */\nclass NetworkQualitySignaling extends MediaSignaling {\n /**\n * Construct a {@link NetworkQualitySignaling}.\n * @param {Promise} getReceiver\n * @param {NetworkQualityConfigurationImpl} networkQualityConfiguration\n */\n constructor(getReceiver, networkQualityConfiguration, options) {\n super(getReceiver, 'network_quality', options);\n\n Object.defineProperties(this, {\n _level: {\n value: null,\n writable: true\n },\n _levels: {\n value: null,\n writable: true\n },\n _remoteLevels: {\n value: new Map(),\n writable: true\n },\n _networkQualityInputs: {\n value: new AsyncVar()\n },\n _resendTimer: {\n value: new Timeout(() => {\n // and schedule next timer at x1.5 the delay..\n this._resendTimer.setDelay(this._resendTimer.delay * 1.5);\n this._sendNetworkQualityInputs();\n }, NETWORK_QUALITY_RESPONSE_TIME_MS, false),\n },\n _networkQualityReportLevels: {\n get() {\n return {\n reportLevel: networkQualityConfiguration.local,\n remoteReportLevel: networkQualityConfiguration.remote\n };\n }\n }\n });\n\n this.on('ready', transport => {\n transport.on('message', message => {\n this._log.debug('Incoming: ', message);\n switch (message.type) {\n case 'network_quality':\n this._handleNetworkQualityMessage(message);\n break;\n default:\n break;\n }\n });\n });\n\n this._sendNetworkQualityInputs();\n }\n\n /**\n * Get the current {@link NetworkQualityLevel}, if any.\n * @returns {?NetworkQualityLevel} level - initially null\n */\n get level() {\n return this._level;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels}, if any.\n * @returns {?NetworkQualityLevels} levels - initially null\n */\n get levels() {\n return this._levels;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels} of remote participants, if any.\n * @returns {Map} remoteLevels\n */\n get remoteLevels() {\n return this._remoteLevels;\n }\n\n /**\n * Check to see if the {@link NetworkQualityLevel} is new, and raise an\n * event if necessary.\n * @private\n * @param {object} message\n * @returns {void}\n */\n _handleNetworkQualityMessage(message) {\n let updated = false;\n let level = null;\n const local = message ? message.local : null;\n if (typeof local === 'number') {\n // NOTE(mroberts): In prod, we plan to only send the level.\n level = local;\n this._levels = null;\n } else if (typeof local === 'object' && local) {\n // NOTE(mroberts): In dev, we plan to send the decomposed levels. An early\n // VMS version does not compute `level` for us, so we fallback to taking\n // the minimum ourselves.\n this._levels = local;\n level = typeof local.level === 'number'\n ? local.level\n : Math.min(\n local.audio.send,\n local.audio.recv,\n local.video.send,\n local.video.recv);\n }\n if (level !== null && this.level !== level) {\n this._level = level;\n updated = true;\n }\n this._remoteLevels = message && message.remotes\n ? message.remotes.reduce((levels, obj) => {\n const oldObj = this._remoteLevels.get(obj.sid) || {};\n if (oldObj.level !== obj.level) {\n updated = true;\n }\n return levels.set(obj.sid, obj);\n }, new Map())\n : this._remoteLevels;\n\n if (updated) {\n this.emit('updated');\n }\n\n\n // score is received. so reset the timer to default timeout.\n this._resendTimer.setDelay(NETWORK_QUALITY_RESPONSE_TIME_MS);\n\n // timer is cleared only while we are sending inputs.\n // if we are already sending inputs do not send them again.\n if (this._resendTimer.isSet) {\n setTimeout(() => this._sendNetworkQualityInputs(), 1000);\n }\n }\n\n /**\n * Start sending {@link NetworkQualityInputs}.\n * @private\n * @returns {Promise}\n */\n _sendNetworkQualityInputs() {\n this._resendTimer.clear();\n return this._networkQualityInputs.take().then(networkQualityInputs => {\n if (this._transport) {\n this._transport.publish(\n createNetworkQualityInputsMessage(networkQualityInputs, this._networkQualityReportLevels));\n }\n }).finally(() => {\n this._resendTimer.start();\n });\n }\n\n /**\n * Put {@link NetworkQualityInputs} to be used for computing\n * {@link NetworkQualityLevel}.\n * @param {NetworkQualityInputs} networkQualityInputs\n * @returns {void}\n */\n put(networkQualityInputs) {\n this._networkQualityInputs.put(networkQualityInputs);\n }\n}\n\n/**\n * The {@link NetworkQualityLevel} changed.\n * @event NetworkQualitySignaling#updated\n */\n\n/**\n * @typedef {object} NetworkQualityReportLevels\n * @param {number} reportLevel\n * @param {number} remoteReportLevel\n */\n\n/**\n * @param {NetworkQualityInputs} networkQualityInputs\n * @param {NetworkQualityReportLevels} networkQualityReportLevels\n * @returns {object} message\n */\nfunction createNetworkQualityInputsMessage(networkQualityInputs, networkQualityReportLevels) {\n return Object.assign(\n { type: 'network_quality' },\n networkQualityInputs,\n networkQualityReportLevels);\n}\n\nmodule.exports = NetworkQualitySignaling;\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\n/**\n * Represents recording state\n * @extends EventEmitter\n * @property {?boolean} isEnabled\n */\nclass RecordingSignaling extends EventEmitter {\n /**\n * Construct a {@link RecordingSignaling}.\n */\n constructor() {\n super();\n Object.defineProperties(this, {\n _isEnabled: {\n value: null,\n writable: true\n },\n isEnabled: {\n enumerable: true,\n get() {\n return this._isEnabled;\n }\n }\n });\n }\n\n /**\n * Disable the {@link RecordingSignaling} if it is not already disabled.\n * @return {this}\n */\n disable() {\n return this.enable(false);\n }\n\n /**\n * Enable (or disable) the {@link RecordingSignaling} if it is not already enabled\n * (or disabled).\n * @param {boolean} [enabled=true]\n * @return {this}\n */\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n if (this.isEnabled !== enabled) {\n this._isEnabled = enabled;\n this.emit('updated');\n }\n return this;\n }\n}\n\n/**\n * Emitted whenever the {@link RecordingSignaling} is updated\n * @event RecordingSignaling#updated\n */\n\nmodule.exports = RecordingSignaling;\n", "'use strict';\n\nconst RecordingSignaling = require('../recording');\n\n/**\n * @extends RecordingSignaling\n */\nclass RecordingV2 extends RecordingSignaling {\n /**\n * Construct a {@link RecordingV2}.\n */\n constructor() {\n super();\n Object.defineProperties(this, {\n _revision: {\n value: 1,\n writable: true\n }\n });\n }\n\n /**\n * Compare the {@link RecordingV2} to a {@link RecordingV2#Representation}\n * of itself and perform any updates necessary.\n * @param {RecordingV2#Representation} recording\n * @returns {this}\n * @fires RecordingSignaling#updated\n */\n update(recording) {\n if (recording.revision < this._revision) {\n return this;\n }\n this._revision = recording.revision;\n return this.enable(recording.is_recording);\n }\n}\n\n/**\n * The Room Signaling Protocol (RSP) representation of a {@link RecordingV2}\n * @typedef {object} RecordingV2#Representation\n * @property {boolean} enabled\n * @property {number} revision\n */\n\nmodule.exports = RecordingV2;\n", "'use strict';\n\nconst DefaultRecordingSignaling = require('./recording');\nconst StateMachine = require('../statemachine');\nconst DefaultTimeout = require('../util/timeout');\nconst { buildLogLevels } = require('../util');\nconst { DEFAULT_LOG_LEVEL } = require('../util/constants');\nconst Log = require('../util/log');\n\nconst {\n MediaConnectionError,\n MediaDTLSTransportFailedError,\n SignalingConnectionDisconnectedError\n} = require('../util/twilio-video-errors');\n\nlet nInstances = 0;\n\n/*\nRoomSignaling States\n-----------------------\n\n +-----------+ +--------------+\n | | | |\n | connected |---->| disconnected |\n | | | |\n +-----------+ +--------------+\n | ^ ^\n | | |\n | | +--------------+\n | +---| |\n | | reconnecting |\n +----->| |\n +--------------+\n\n*/\n\nconst states = {\n connected: [\n 'reconnecting',\n 'disconnected'\n ],\n reconnecting: [\n 'connected',\n 'disconnected'\n ],\n disconnected: []\n};\n\n/**\n * A {@link Room} implementation\n * @extends StateMachine\n * @property {RTCPeerConnectionState} connectionState\n * @property {?Participant.SID} dominantSpeakerSid\n * @property {ParticipantSignaling} localParticipant\n * @property {RTCIceConnectionState} iceConnectionState\n * @property {string} name\n * @property {Map} participants\n * @property {RecordingSignaling} recording\n * @property {Room.SID} sid\n * @property {string} state - \"connected\", \"reconnecting\", or \"disconnected\"\n * @property {string} signalingConnectionState - \"connected\",\n * \"reconnecting\", or \"disconnected\"\n * @emits RoomSignaling#connectionStateChanged\n * @emits RoomSignaling#dominantSpeakerChanged\n * @emits RoomSignaling#iceConnectionStateChanged\n * @emits RoomSignaling#signalingConnectionStateChanged\n */\nclass RoomSignaling extends StateMachine {\n /**\n * Construct a {@link RoomSignaling}.\n * @param {ParticipantSignaling} localParticipant\n * @param {Room.SID} sid\n * @param {string} name\n * @param {object} options\n */\n constructor(localParticipant, sid, name, options) {\n options = Object.assign({\n logLevel: DEFAULT_LOG_LEVEL,\n RecordingSignaling: DefaultRecordingSignaling,\n Timeout: DefaultTimeout\n }, options);\n\n const logLevels = buildLogLevels(options.logLevel);\n\n super('connected', states);\n\n const RecordingSignaling = options.RecordingSignaling;\n\n const sessionTimeout = new options.Timeout(() => {\n this._disconnect(this._reconnectingError);\n }, options.sessionTimeout, false);\n\n Object.defineProperties(this, {\n _instanceId: {\n value: nInstances++\n },\n _log: {\n value: options.log\n ? options.log.createLog('default', this)\n : new Log('default', this, logLevels, options.loggerName)\n },\n _mediaConnectionIsReconnecting: {\n writable: true,\n value: false\n },\n _options: {\n value: options\n },\n _reconnectingError: {\n value: null,\n writable: true\n },\n _sessionTimeout: {\n value: sessionTimeout\n },\n dominantSpeakerSid: {\n enumerable: true,\n value: null,\n writable: true\n },\n localParticipant: {\n enumerable: true,\n value: localParticipant\n },\n name: {\n enumerable: true,\n value: name\n },\n participants: {\n enumerable: true,\n value: new Map()\n },\n recording: {\n enumerable: true,\n value: new RecordingSignaling()\n },\n sid: {\n enumerable: true,\n value: sid\n }\n });\n\n this.on('connectionStateChanged', () => {\n if (this.connectionState === 'failed'\n && !['disconnected', 'failed'].includes(this.iceConnectionState)) {\n this._disconnect(new MediaDTLSTransportFailedError());\n }\n });\n\n this.on('iceConnectionStateChanged', () => maybeUpdateState(this));\n this.on('signalingConnectionStateChanged', () => maybeUpdateState(this));\n\n // NOTE(mmalavalli): In case \"iceConnectionState\" is already failed, update\n // the RoomSignaling state. setTimeout() ensures that the state is updated\n // after RoomV2's constructor is fully executed, thereby making \"signalingConnectionState\"\n // available here.\n setTimeout(() => maybeUpdateState(this));\n }\n\n /**\n * Disconnect, possibly with an Error.\n * @private\n * @param {Error} [error]\n * @returns {boolean}\n */\n _disconnect(error) {\n if (this.state !== 'disconnected') {\n this.preempt('disconnected', null, [error]);\n return true;\n }\n return false;\n }\n\n toString() {\n return `[RoomSignaling #${this._instanceId}: ${this.localParticipant ? this.localParticipant.sid : 'null'}]`;\n }\n\n /**\n * Connect {@link RemoteParticipantSignaling} to the {@link RoomSignaling}.\n * @param {RemoteParticipantSignaling} participant\n * @returns {boolean}\n */\n connectParticipant(participant) {\n const self = this;\n\n if (participant.state === 'disconnected') {\n return false;\n }\n\n if (this.participants.has(participant.sid)) {\n return false;\n }\n\n this.participants.set(participant.sid, participant);\n\n participant.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n participant.removeListener('stateChanged', stateChanged);\n self.participants.delete(participant.sid);\n self.emit('participantDisconnected', participant);\n }\n });\n\n this.emit('participantConnected', participant);\n\n return true;\n }\n\n /**\n * Disconnect.\n * @returns {boolean}\n */\n disconnect() {\n return this._disconnect();\n }\n\n /**\n * Set (or unset) the Dominant Speaker.\n * @param {?Participant.SID} dominantSpeakerSid\n * @returns {void}\n */\n setDominantSpeaker(dominantSpeakerSid) {\n this.dominantSpeakerSid = dominantSpeakerSid;\n this.emit('dominantSpeakerChanged');\n }\n}\n\n/**\n * @event RoomSignaling#event:connectionStateChanged\n */\n\n/**\n * @event RoomSignaling#event:dominantSpeakerChanged\n */\n\n/**\n * {@link RemoteParticipantSignaling} connected to the {@link RoomSignaling}.\n * @event RoomSignaling#event:participantConnected\n * @param {RemoteParticipantSignaling} participantSignaling\n */\n\n/**\n * {@link RemoteParticipantSignaling} disconnected from the {@link RoomSignaling}.\n * @event RoomSignaling#event:participantDisconnected\n * @param {RemoteParticipantSignaling} participantSignaling\n */\n\n/**\n * @event RoomSignaling#event:iceConnectionStateChanged\n */\n\n/**\n * @event RoomSignaling#event:signalingConnectionStateChanged\n */\n\n/**\n * Maybe update the {@link RoomSignaling} state.\n * @param {RoomSignaling} roomSignaling\n */\nfunction maybeUpdateState(roomSignaling) {\n if (roomSignaling.state === 'disconnected' || roomSignaling.signalingConnectionState === 'disconnected') {\n roomSignaling._sessionTimeout.clear();\n return;\n }\n\n let newState;\n\n if (roomSignaling.signalingConnectionState === 'reconnecting') {\n newState = roomSignaling.signalingConnectionState;\n } else if (roomSignaling.iceConnectionState === 'failed') {\n roomSignaling._mediaConnectionIsReconnecting = true;\n newState = 'reconnecting';\n } else if (roomSignaling.iceConnectionState === 'new' || roomSignaling.iceConnectionState === 'checking') {\n newState = roomSignaling._mediaConnectionIsReconnecting ? 'reconnecting' : 'connected';\n } else {\n roomSignaling._mediaConnectionIsReconnecting = false;\n roomSignaling._reconnectingError = null;\n roomSignaling._sessionTimeout.clear();\n newState = 'connected';\n }\n\n if (newState === roomSignaling.state) {\n return;\n }\n\n if (newState === 'reconnecting') {\n roomSignaling._reconnectingError = roomSignaling.signalingConnectionState === 'reconnecting'\n ? new SignalingConnectionDisconnectedError()\n : new MediaConnectionError();\n roomSignaling._sessionTimeout.start();\n roomSignaling.preempt(newState, null, [roomSignaling._reconnectingError]);\n } else {\n roomSignaling.preempt(newState);\n }\n}\n\nmodule.exports = RoomSignaling;\n", "'use strict';\n\n/**\n * Bandwidth network quality statistics.\n * @property {?number} actual - the actual bandwidth used, in bits per second\n * @property {?number} available - an estimate of available useable bandwidth, in bits per second\n * @property {?NetworkQualityLevel} level - {@link NetworkQualityLevel} for bandwidth\n */\nclass NetworkQualityBandwidthStats {\n /**\n * Construct a {@link NetworkQualityBandwidthStats}.\n * @param {BandwidthStats} bandwidthStats\n */\n constructor({ actual = null, available = null, level = null }) {\n Object.defineProperties(this, {\n actual: {\n value: actual,\n enumerable: true\n },\n available: {\n value: available,\n enumerable: true\n },\n level: {\n value: level,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityBandwidthStats;\n", "'use strict';\n\n/**\n * Fraction lost network quality statistics.\n * @property {?number} fractionLost - packets lost\n * @property {?NetworkQualityLevel} level - {@link NetworkQualityLevel} for fraction lost\n */\nclass NetworkQualityFractionLostStats {\n /**\n * Construct a {@link NetworkQualityFractionLostStats}.\n * @param {FractionLostStats} fractionLostStats\n */\n constructor({ fractionLost = null, level = null }) {\n Object.defineProperties(this, {\n fractionLost: {\n value: fractionLost,\n enumerable: true\n },\n level: {\n value: level,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityFractionLostStats;\n", "'use strict';\n\n/**\n * Latency network quality statistics.\n * @property {?number} jitter - media jitter in seconds\n * @property {?number} rtt - round trip time in seconds\n * @property {?NetworkQualityLevel} level - {@link NetworkQualityLevel} for latency\n */\nclass NetworkQualityLatencyStats {\n /**\n * Construct a {@link NetworkQualityLatencyStats}.\n * @param {LatencyStats} latencyStats\n */\n constructor({ jitter = null, rtt = null, level = null }) {\n Object.defineProperties(this, {\n jitter: {\n value: jitter,\n enumerable: true\n },\n rtt: {\n value: rtt,\n enumerable: true\n },\n level: {\n value: level,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityLatencyStats;\n", "'use strict';\n\nconst NetworkQualityBandwidthStats = require('./networkqualitybandwidthstats');\nconst NetworkQualityFractionLostStats = require('./networkqualityfractionloststats');\nconst NetworkQualityLatencyStats = require('./networkqualitylatencystats');\n\n/**\n * Network quality statistics shared between {@link NetworkQualitySendStats} and\n * {@link NetworkQualityRecvStats} based on which a {@link Participant}'s\n * {@link NetworkQualityMediaStats}#send or\n * {@link NetworkQualityMediaStats}#recv is calculated.\n * @property {?NetworkQualityBandwidthStats} bandwidth - bandwidth statistics\n * @property {?NetworkQualityLatencyStats} latency - latency statistics\n * @property {?NetworkQualityFractionLostStats} fractionLost - fraction lost statistics\n */\nclass NetworkQualitySendOrRecvStats {\n /**\n * Construct a {@link NetworkQualitySendOrRecvStats}.\n * @param {SendOrRecvStats} sendOrRecvStats\n */\n constructor({ bandwidth = null, fractionLost = null, latency = null }) {\n Object.defineProperties(this, {\n bandwidth: {\n value: bandwidth ? new NetworkQualityBandwidthStats(bandwidth) : null,\n enumerable: true\n },\n fractionLost: {\n value: fractionLost ? new NetworkQualityFractionLostStats(fractionLost) : null,\n enumerable: true\n },\n latency: {\n value: latency ? new NetworkQualityLatencyStats(latency) : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualitySendOrRecvStats;\n", "'use strict';\n\nconst NetworkQualitySendOrRecvStats = require('./networkqualitysendorrecvstats');\n\n/**\n * {@link NetworkQualitySendOrRecvStats} based on which a {@link Participant}'s\n * {@link NetworkQualityMediaStats}#send is calculated.\n */\nclass NetworkQualitySendStats extends NetworkQualitySendOrRecvStats {\n /**\n * Construct a {@link NetworkQualitySendStats}.\n * @param {SendOrRecvStats} sendOrRecvStats\n */\n constructor(sendOrRecvStats) {\n super(sendOrRecvStats);\n }\n}\n\nmodule.exports = NetworkQualitySendStats;\n", "'use strict';\n\nconst NetworkQualitySendOrRecvStats = require('./networkqualitysendorrecvstats');\n\n/**\n * {@link NetworkQualitySendOrRecvStats} based on which a {@link Participant}'s\n * {@link NetworkQualityMediaStats}#recv is calculated.\n */\nclass NetworkQualityRecvStats extends NetworkQualitySendOrRecvStats {\n /**\n * Construct a {@link NetworkQualityRecvStats}.\n * @param {SendOrRecvStats} sendOrRecvStats\n */\n constructor(sendOrRecvStats) {\n super(sendOrRecvStats);\n }\n}\n\nmodule.exports = NetworkQualityRecvStats;\n", "'use strict';\n\nconst NetworkQualitySendStats = require('./networkqualitysendstats');\nconst NetworkQualityRecvStats = require('./networkqualityrecvstats');\n\n/**\n * Network quality statistics shared between a {@link Participant}'s audio or video.\n * @property {NetworkQualityLevel} send - {@link NetworkQualityLevel} of the\n * {@link Participant}'s published audio or video\n * @property {number} recv - {@link NetworkQualityLevel} of the\n * {@link Participant}'s subscribed audio or video\n * @property {?NetworkQualitySendOrRecvStats} sendStats - {@link NetworkQualitySendOrRecvStats}\n * based on which {@link NetworkQualityMediaStats}#send\n * is calculated\n * @property {?NetworkQualitySendOrRecvStats} recvStats - {@link NetworkQualitySendOrRecvStats}\n * based on which {@link NetworkQualityMediaStats}#recv\n * is calculated\n */\nclass NetworkQualityMediaStats {\n /**\n * Construct a {@link NetworkQualityMediaStats}.\n * @param {MediaLevels} mediaLevels\n */\n constructor({ send, recv, sendStats = null, recvStats = null }) {\n Object.defineProperties(this, {\n send: {\n value: send,\n enumerable: true\n },\n recv: {\n value: recv,\n enumerable: true\n },\n sendStats: {\n value: sendStats ? new NetworkQualitySendStats(sendStats) : null,\n enumerable: true\n },\n recvStats: {\n value: recvStats ? new NetworkQualityRecvStats(recvStats) : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityMediaStats;\n", "'use strict';\n\nconst NetworkQualityMediaStats = require('./networkqualitymediastats');\n\n/**\n * {@link NetworkQualityMediaStats} for a {@link Participant}'s audio.\n */\nclass NetworkQualityAudioStats extends NetworkQualityMediaStats {\n /**\n * Construct a {@link NetworkQualityAudioStats}.\n * @param {MediaLevels} mediaLevels\n */\n constructor(mediaLevels) {\n super(mediaLevels);\n }\n}\n\nmodule.exports = NetworkQualityAudioStats;\n", "'use strict';\n\nconst NetworkQualityMediaStats = require('./networkqualitymediastats');\n\n/**\n * {@link NetworkQualityMediaStats} for a {@link Participant}'s video.\n */\nclass NetworkQualityVideoStats extends NetworkQualityMediaStats {\n /**\n * Construct a {@link NetworkQualityVideoStats}.\n * @param {MediaLevels} mediaLevels\n */\n constructor(mediaLevels) {\n super(mediaLevels);\n }\n}\n\nmodule.exports = NetworkQualityVideoStats;\n", "'use strict';\n\nconst NetworkQualityAudioStats = require('./networkqualityaudiostats');\nconst NetworkQualityVideoStats = require('./networkqualityvideostats');\n\n/**\n * Network quality statistics for a {@link Participant}.\n * @property {NetworkQualityLevel} level - {@link NetworkQualityLevel} of the {@link Participant}\n * @property {?NetworkQualityAudioStats} audio - {@link NetworkQualityMediaStats}\n * for audio; null if {@link NetworkQualityVerbosity} is {@link NetworkQualityVerbosity}#minimal\n * or below\n * @property {?NetworkQualityVideoStats} video - {@link NetworkQualityMediaStats}\n * for video; null if {@link NetworkQualityVerbosity} is {@link NetworkQualityVerbosity}#minimal\n * or below\n */\nclass NetworkQualityStats {\n /**\n * Construct a {@link NetworkQualityStats}.\n * @param {NetworkQualityLevels} networkQualityLevels\n */\n constructor({ level, audio, video }) {\n Object.defineProperties(this, {\n level: {\n value: level,\n enumerable: true\n },\n audio: {\n value: audio ? new NetworkQualityAudioStats(audio) : null,\n enumerable: true\n },\n video: {\n value: video ? new NetworkQualityVideoStats(video) : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityStats;\n", "'use strict';\n\nconst StateMachine = require('../statemachine');\nconst NetworkQualityStats = require('../stats/networkqualitystats');\n\n/*\nParticipantSignaling States\n----------------------\n\n +------------+ +-----------+ +--------------+\n | | | | | |\n | connecting |---->| connected |---->| disconnected |\n | | | | | |\n +------------+ +-----------+ +--------------+\n | ^ ^\n | | +--------------+ |\n | |--| | |\n |--->| reconnecting |--|\n | |\n +--------------+\n*/\n\nconst states = {\n connecting: [\n 'connected'\n ],\n connected: [\n 'disconnected',\n 'reconnecting'\n ],\n reconnecting: [\n 'connected',\n 'disconnected'\n ],\n disconnected: []\n};\n\n/**\n * A {@link Participant} implementation\n * @extends StateMachine\n * @property {?string} identity\n * @property {?Participant.SID} sid\n * @property {string} state - \"connecting\", \"connected\", or \"disconnected\"\n * @property {Map} tracks\n * @emits ParticipantSignaling#networkQualityLevelChanged\n * @emits ParticipantSignaling#trackAdded\n * @emits ParticipantSignaling#trackRemoved\n */\nclass ParticipantSignaling extends StateMachine {\n /**\n * Construct a {@link ParticipantSignaling}.\n */\n constructor() {\n super('connecting', states);\n\n Object.defineProperties(this, {\n _identity: {\n writable: true,\n value: null\n },\n _networkQualityLevel: {\n value: null,\n writable: true\n },\n _networkQualityStats: {\n value: null,\n writable: true\n },\n _sid: {\n writable: true,\n value: null\n },\n identity: {\n enumerable: true,\n get() {\n return this._identity;\n }\n },\n sid: {\n enumerable: true,\n get() {\n return this._sid;\n }\n },\n tracks: {\n enumerable: true,\n value: new Map()\n }\n });\n }\n\n /**\n * Get the current {@link NetworkQualityLevel}, if any.\n * @returns {?NetworkQualityLevel} networkQualityLevel - initially null\n */\n get networkQualityLevel() {\n return this._networkQualityLevel;\n }\n\n /**\n * Get the current {@link NetworkQualityStats}\n * @returns {?NetworkQualityStats} networkQualityStats - initially null\n */\n get networkQualityStats() {\n return this._networkQualityStats;\n }\n\n /**\n * Add the {@link TrackSignaling}, MediaStreamTrack, or\n * {@link DataTrackSender} to the {@link ParticipantSignaling}.\n * @param {TrackSignaling|DataTrackSender|MediaTrackSender} track\n * @returns {this}\n * @fires ParticipantSignaling#trackAdded\n */\n addTrack(track) {\n this.tracks.set(track.id || track.sid, track);\n this.emit('trackAdded', track);\n return this;\n }\n\n /**\n * Disconnect the {@link ParticipantSignaling}.\n * @returns {boolean}\n */\n disconnect() {\n if (this.state !== 'disconnected') {\n this.preempt('disconnected');\n return true;\n }\n return false;\n }\n\n /**\n * Remove the {@link TrackSignaling}, MediaStreamTrack, or\n * {@link DataTrackSender} from the {@link ParticipantSignaling}.\n * @param {TrackSignaling|DataTrackSender|MediaTrackSender} track\n * @returns {?TrackSignaling}\n * @fires ParticipantSignaling#trackRemoved\n */\n removeTrack(track) {\n const signaling = this.tracks.get(track.id || track.sid);\n this.tracks.delete(track.id || track.sid);\n if (signaling) {\n this.emit('trackRemoved', track);\n }\n return signaling || null;\n }\n\n /**\n * @param {NetworkQualityLevel} networkQualityLevel\n * @param {?NetworkQualityLevels} [networkQualityLevels=null]\n * @returns {void}\n */\n setNetworkQualityLevel(networkQualityLevel, networkQualityLevels) {\n if (this._networkQualityLevel !== networkQualityLevel) {\n this._networkQualityLevel = networkQualityLevel;\n this._networkQualityStats = networkQualityLevels\n && (networkQualityLevels.audio || networkQualityLevels.video)\n ? new NetworkQualityStats(networkQualityLevels)\n : null;\n this.emit('networkQualityLevelChanged');\n }\n }\n\n /**\n * Connect the {@link ParticipantSignaling}.\n * @param {Participant.SID} sid\n * @param {string} identity\n * @returns {boolean}\n */\n connect(sid, identity) {\n if (this.state === 'connecting' || this.state === 'reconnecting') {\n if (!this._sid) {\n this._sid = sid;\n }\n if (!this._identity) {\n this._identity = identity;\n }\n this.preempt('connected');\n return true;\n }\n return false;\n }\n\n /**\n * Transition to \"reconnecting\" state.\n * @returns {boolean}\n */\n reconnecting() {\n if (this.state === 'connecting' || this.state === 'connected') {\n this.preempt('reconnecting');\n return true;\n }\n return false;\n }\n}\n\n/**\n * @event ParticipantSignaling#event:networkQualityLevelChanged\n */\n\n/**\n * {@link TrackSignaling} was added to the {@link ParticipantSignaling}.\n * @event ParticipantSignaling#trackAdded\n * @param {TrackSignaling} track\n */\n\n/**\n * {@link TrackSignaling} was removed from the {@link ParticipantSignaling}.\n * @event ParticipantSignaling#trackRemoved\n * @param {TrackSignaling} track\n */\n\nmodule.exports = ParticipantSignaling;\n", "'use strict';\n\nconst ParticipantSignaling = require('./participant');\n\n/**\n * A {@link Participant} implementation\n * @extends ParticipantSignaling\n * @property {string} identity\n * @property {Participant.SID} sid\n */\nclass RemoteParticipantSignaling extends ParticipantSignaling {\n /**\n * Construct a {@link RemoteParticipantSignaling}.\n * @param {Participant.SID} sid\n * @param {string} identity\n */\n constructor(sid, identity) {\n super();\n this.connect(sid, identity);\n }\n}\n\nmodule.exports = RemoteParticipantSignaling;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\n/**\n * A {@link Track} implementation\n * @extends EventEmitter\n * @property {Track.Kind} kind\n * @property {string} name\n */\nclass TrackSignaling extends EventEmitter {\n /**\n * Construct a {@link TrackSignaling}.\n * @param {string} name\n * @param {Track.Kind} kind\n * @param {boolean} isEnabled\n * @param {Track.Priority} priority\n */\n constructor(name, kind, isEnabled, priority) {\n super();\n let sid = null;\n Object.defineProperties(this, {\n _error: {\n value: null,\n writable: true\n },\n _isEnabled: {\n value: isEnabled,\n writable: true\n },\n _priority: {\n value: priority,\n writable: true\n },\n _trackTransceiver: {\n value: null,\n writable: true\n },\n _sid: {\n get() {\n return sid;\n },\n set(_sid) {\n if (sid === null) {\n sid = _sid;\n }\n }\n },\n kind: {\n enumerable: true,\n value: kind\n },\n name: {\n enumerable: true,\n value: name\n }\n });\n }\n\n /**\n * Non-null if publication or subscription failed.\n * @property {?Error} error\n */\n get error() {\n return this._error;\n }\n\n /**\n * Whether the {@link TrackSignaling} is enabled.\n * @property {boolean}\n */\n get isEnabled() {\n return this._isEnabled;\n }\n\n /**\n * The {@link TrackSignaling}'s priority.\n * @property {Track.Priority}\n */\n get priority() {\n return this._priority;\n }\n\n /**\n * The {@link TrackSignaling}'s {@link Track.SID}.\n * @property {Track.SID}\n */\n get sid() {\n return this._sid;\n }\n\n /**\n * The {@link TrackSignaling}'s {@link TrackTransceiver}.\n * @property {TrackTransceiver}\n */\n get trackTransceiver() {\n return this._trackTransceiver;\n }\n\n /**\n * Disable the {@link TrackSignaling} if it is not already disabled.\n * @return {this}\n */\n disable() {\n return this.enable(false);\n }\n\n /**\n * Enable (or disable) the {@link TrackSignaling} if it is not already enabled\n * (or disabled).\n * @param {boolean} [enabled=true]\n * @return {this}\n */\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n if (this.isEnabled !== enabled) {\n this._isEnabled = enabled;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Set the {@link TrackTransceiver} on the {@link TrackSignaling}.\n * @param {TrackTransceiver} trackTransceiver\n * @returns {this}\n */\n\n setTrackTransceiver(trackTransceiver) {\n trackTransceiver = trackTransceiver || null;\n if (this.trackTransceiver !== trackTransceiver) {\n this._trackTransceiver = trackTransceiver;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Set the SID on the {@link TrackSignaling} once.\n * @param {string} sid\n * @returns {this}\n */\n setSid(sid) {\n if (this.sid === null) {\n this._sid = sid;\n this.emit('updated');\n }\n return this;\n }\n}\n\n/**\n * Emitted whenever the {@link TrackSignaling} is updated\n * @event TrackSignaling#updated\n */\n\nmodule.exports = TrackSignaling;\n", "'use strict';\n\nconst TrackSignaling = require('./track');\n\n/**\n * A {@link RemoteTrackPublication} implementation\n * @extends TrackSignaling\n */\nclass RemoteTrackPublicationSignaling extends TrackSignaling {\n /**\n * Construct a {@link RemoteTrackPublicationSignaling}.\n * @param {Track.SID} sid\n * @param {string} name\n * @param {Track.Kind} kind\n * @param {boolean} isEnabled\n * @param {Track.Priority} priority\n * @param {boolean} isSwitchedOff\n */\n constructor(sid, name, kind, isEnabled, priority, isSwitchedOff) {\n super(name, kind, isEnabled, priority);\n Object.defineProperties(this, {\n _isSwitchedOff: {\n value: isSwitchedOff,\n writable: true\n },\n });\n this.setSid(sid);\n }\n\n /**\n * Whether the {@link RemoteTrackPublicationSignaling} is subscribed to.\n * @property {boolean}\n */\n get isSubscribed() {\n return !!this.trackTransceiver;\n }\n\n /**\n * Whether the {@link RemoteTrackPublicationSignaling} is switched off.\n * @property {boolean}\n */\n get isSwitchedOff() {\n return this._isSwitchedOff;\n }\n\n /**\n * @param {Error} error\n * @returns {this}\n */\n subscribeFailed(error) {\n if (!this.error) {\n this._error = error;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Update the publish {@link Track.Priority}.\n * @param {Track.Priority} priority\n * @returns {this}\n */\n setPriority(priority) {\n if (this._priority !== priority) {\n this._priority = priority;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Updates track switch on/off state.\n * @param {boolean} isSwitchedOff\n * @returns {this}\n */\n setSwitchedOff(isSwitchedOff) {\n if (this._isSwitchedOff !== isSwitchedOff) {\n this._isSwitchedOff = isSwitchedOff;\n this.emit('updated');\n }\n return this;\n }\n}\n\n\nmodule.exports = RemoteTrackPublicationSignaling;\n", "'use strict';\n\nconst RemoteTrackPublicationSignaling = require('../remotetrackpublication');\n\n/**\n * @extends RemoteTrackPublicationSignaling\n */\nclass RemoteTrackPublicationV2 extends RemoteTrackPublicationSignaling {\n /**\n * Construct a {@link RemoteTrackPublicationV2}.\n * @param {RemoteTrackPublicationV2#Representation} track\n * @param {boolean} isSwitchedOff\n *\n */\n constructor(track, isSwitchedOff) {\n super(track.sid, track.name, track.kind, track.enabled, track.priority, isSwitchedOff);\n }\n\n /**\n * Compare the {@link RemoteTrackPublicationV2} to a\n * {@link RemoteTrackPublicationV2#Representation} of itself and perform any\n * updates necessary.\n * @param {RemoteTrackPublicationV2#Representation} track\n * @returns {this}\n * @fires TrackSignaling#updated\n */\n update(track) {\n this.enable(track.enabled);\n this.setPriority(track.priority);\n return this;\n }\n}\n\n/**\n * The Room Signaling Protocol (RSP) representation of a {@link RemoteTrackPublicationV2}.\n * @typedef {LocalTrackPublicationV2#Representation} RemoteTrackPublicationV2#Representation\n * @property {boolean} subscribed\n */\n\nmodule.exports = RemoteTrackPublicationV2;\n", "'use strict';\n\nconst RemoteParticipantSignaling = require('../remoteparticipant');\nconst RemoteTrackPublicationV2 = require('./remotetrackpublication');\n\n/**\n * @extends RemoteParticipantSignaling\n * @property {?number} revision\n */\nclass RemoteParticipantV2 extends RemoteParticipantSignaling {\n /**\n * Construct a {@link RemoteParticipantV2}.\n * @param {object} participantState\n * @param {function(Track.SID): boolean} getInitialTrackSwitchOffState\n * @param {function(Track.SID, Track.Priority): boolean} setPriority\n * @param {function(Track.SID, ClientRenderHint): Promise} setRenderHint\n * @param {function(Track.SID): void} clearTrackHint\n * @param {object} [options]\n */\n constructor(participantState, getInitialTrackSwitchOffState, setPriority, setRenderHint, clearTrackHint, options) {\n super(participantState.sid, participantState.identity);\n\n options = Object.assign({\n RemoteTrackPublicationV2\n }, options);\n\n Object.defineProperties(this, {\n _revision: {\n writable: true,\n value: null\n },\n _RemoteTrackPublicationV2: {\n value: options.RemoteTrackPublicationV2\n },\n _getInitialTrackSwitchOffState: {\n value: getInitialTrackSwitchOffState\n },\n updateSubscriberTrackPriority: {\n value: (trackSid, priority) => setPriority(trackSid, priority)\n },\n updateTrackRenderHint: {\n value: (trackSid, renderHint) => setRenderHint(trackSid, renderHint)\n },\n clearTrackHint: {\n value: trackSid => clearTrackHint(trackSid)\n },\n revision: {\n enumerable: true,\n get() {\n return this._revision;\n }\n }\n });\n\n return this.update(participantState);\n }\n\n /**\n * @private\n */\n _getOrCreateTrack(trackState) {\n const RemoteTrackPublicationV2 = this._RemoteTrackPublicationV2;\n let track = this.tracks.get(trackState.sid);\n if (!track) {\n const isSwitchedOff = this._getInitialTrackSwitchOffState(trackState.sid);\n track = new RemoteTrackPublicationV2(trackState, isSwitchedOff);\n this.addTrack(track);\n }\n return track;\n }\n\n /**\n * Update the {@link RemoteParticipantV2} with the new state.\n * @param {object} participantState\n * @returns {this}\n */\n update(participantState) {\n if (this.revision !== null && participantState.revision <= this.revision) {\n return this;\n }\n this._revision = participantState.revision;\n\n const tracksToKeep = new Set();\n\n participantState.tracks.forEach(trackState => {\n const track = this._getOrCreateTrack(trackState);\n track.update(trackState);\n tracksToKeep.add(track);\n });\n\n this.tracks.forEach(track => {\n if (!tracksToKeep.has(track)) {\n this.removeTrack(track);\n }\n });\n\n switch (participantState.state) {\n case 'disconnected':\n this.disconnect();\n break;\n case 'reconnecting':\n this.reconnecting();\n break;\n case 'connected':\n this.connect(this.sid, this.identity);\n break;\n }\n\n return this;\n }\n}\n\nmodule.exports = RemoteParticipantV2;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\nclass TrackPrioritySignaling extends MediaSignaling {\n /**\n * Construct a {@link TrackPrioritySignaling}.\n * @param {Promise} getReceiver\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'track_priority', options);\n\n Object.defineProperties(this, {\n _enqueuedPriorityUpdates: {\n value: new Map()\n },\n });\n\n this.on('ready', transport => {\n Array.from(this._enqueuedPriorityUpdates.keys()).forEach(trackSid => {\n transport.publish({\n type: 'track_priority',\n track: trackSid,\n subscribe: this._enqueuedPriorityUpdates.get(trackSid)\n });\n // NOTE(mpatwardhan)- we do not clear _enqueuedPriorityUpdates intentionally,\n // this cache will is used to re-send the priorities in case of VMS-FailOver.\n });\n });\n }\n\n /**\n * @param {Track.SID} trackSid\n * @param {'publish'|'subscribe'} publishOrSubscribe\n * @param {Track.Priority} priority\n */\n sendTrackPriorityUpdate(trackSid, publishOrSubscribe, priority) {\n if (publishOrSubscribe !== 'subscribe') {\n throw new Error('only subscribe priorities are supported, found: ' + publishOrSubscribe);\n }\n this._enqueuedPriorityUpdates.set(trackSid, priority);\n if (this._transport) {\n this._transport.publish({\n type: 'track_priority',\n track: trackSid,\n subscribe: priority\n });\n }\n }\n}\n\nmodule.exports = TrackPrioritySignaling;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\n\n/**\n * @emits TrackSwitchOffSignalinging#updated\n */\nclass TrackSwitchOffSignaling extends MediaSignaling {\n /**\n * Construct a {@link TrackSwitchOffSignaling}.\n * @param {Promise} getReceiver\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'track_switch_off', options);\n this.on('ready', transport => {\n transport.on('message', message => {\n switch (message.type) {\n case 'track_switch_off':\n this._setTrackSwitchOffUpdates(message.off || [], message.on || []);\n break;\n default:\n break;\n }\n });\n });\n }\n\n /**\n * @private\n * @param {[Track.SID]} tracksSwitchedOff\n * @param {[Track.SID]} tracksSwitchedOn\n * @returns {void}\n */\n _setTrackSwitchOffUpdates(tracksSwitchedOff, tracksSwitchedOn) {\n this.emit('updated', tracksSwitchedOff, tracksSwitchedOn);\n }\n}\n\n/**\n * @event TrackSwitchOffSignaling#updated\n */\n\nmodule.exports = TrackSwitchOffSignaling;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\nconst Timeout = require('../../util/timeout');\nconst { isDeepEqual } = require('../../util');\nconst RENDER_HINT_RESPONSE_TIME_MS = 2000; // time to wait for server response (before resending all hints.)\n\nlet messageId = 1;\nclass RenderHintsSignaling extends MediaSignaling {\n /**\n * Construct a {@link RenderHintsSignaling}.\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'render_hints', options);\n Object.defineProperties(this, {\n _trackSidsToRenderHints: {\n value: new Map()\n },\n _responseTimer: {\n value: new Timeout(() => {\n this._sendAllHints();\n // once timer fires, for next round double the delay.\n this._responseTimer.setDelay(this._responseTimer.delay * 2);\n }, RENDER_HINT_RESPONSE_TIME_MS, false),\n }\n });\n\n this.on('ready', transport => {\n transport.on('message', message => {\n this._log.debug('Incoming: ', message);\n switch (message.type) {\n case 'render_hints':\n this._processHintResults((message && message.subscriber && message.subscriber.hints) || []);\n break;\n default:\n this._log.warn('Unknown message type: ', message.type);\n break;\n }\n });\n\n // NOTE(mpatwardhan): When transport is set (either 1st time of after vms failover)\n // resend all track states.\n this._sendAllHints();\n });\n }\n\n _sendAllHints() {\n // to force sending all hints simply mark all tracks as dirty.\n Array.from(this._trackSidsToRenderHints.keys()).forEach(trackSid => {\n const trackState = this._trackSidsToRenderHints.get(trackSid);\n if (trackState.renderDimensions) {\n trackState.isDimensionDirty = true;\n }\n\n if ('enabled' in trackState) {\n trackState.isEnabledDirty = true;\n }\n });\n this._sendHints();\n }\n\n _processHintResults(hintResults) {\n this._responseTimer.clear();\n this._responseTimer.setDelay(RENDER_HINT_RESPONSE_TIME_MS);\n hintResults.forEach(hintResult => {\n if (hintResult.result !== 'OK') {\n this._log.debug('Server error processing hint:', hintResult);\n }\n });\n this._sendHints();\n }\n\n _sendHints() {\n if (!this._transport || this._responseTimer.isSet) {\n return;\n }\n\n const hints = [];\n Array.from(this._trackSidsToRenderHints.keys()).forEach(trackSid => {\n const trackState = this._trackSidsToRenderHints.get(trackSid);\n if (trackState.isEnabledDirty || trackState.isDimensionDirty) {\n const mspHint = {\n 'track': trackSid,\n };\n if (trackState.isEnabledDirty) {\n mspHint.enabled = trackState.enabled;\n trackState.isEnabledDirty = false;\n }\n if (trackState.isDimensionDirty) {\n // eslint-disable-next-line camelcase\n mspHint.render_dimensions = trackState.renderDimensions;\n trackState.isDimensionDirty = false;\n }\n hints.push(mspHint);\n }\n });\n\n if (hints.length > 0) {\n const payLoad = {\n type: 'render_hints',\n subscriber: {\n id: messageId++,\n hints\n }\n };\n this._log.debug('Outgoing: ', payLoad);\n this._transport.publish(payLoad);\n this._responseTimer.start();\n }\n }\n\n /**\n * @param {Track.SID} trackSid\n * @param {ClientRenderHint} renderHint\n */\n setTrackHint(trackSid, renderHint) {\n const trackState = this._trackSidsToRenderHints.get(trackSid) || { isEnabledDirty: false, isDimensionDirty: false };\n if ('enabled' in renderHint && trackState.enabled !== renderHint.enabled) {\n trackState.enabled = !!renderHint.enabled;\n trackState.isEnabledDirty = true;\n }\n\n if (renderHint.renderDimensions && !isDeepEqual(renderHint.renderDimensions, trackState.renderDimensions)) {\n // eslint-disable-next-line camelcase\n trackState.renderDimensions = renderHint.renderDimensions;\n trackState.isDimensionDirty = true;\n }\n this._trackSidsToRenderHints.set(trackSid, trackState);\n this._sendHints();\n }\n\n /**\n * must be called when track is unsubscribed.\n * @param {Track.SID} trackSid\n */\n clearTrackHint(trackSid) {\n this._trackSidsToRenderHints.delete(trackSid);\n }\n}\n\n\nmodule.exports = RenderHintsSignaling;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\n\nlet messageId = 1;\nclass PublisherHintsSignaling extends MediaSignaling {\n /**\n * Construct a {@link RenderHintsSignaling}.\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'publisher_hints', options);\n this.on('ready', transport => {\n this._log.debug('publisher_hints transport ready:', transport);\n transport.on('message', message => {\n this._log.debug('Incoming: ', message);\n switch (message.type) {\n case 'publisher_hints':\n if (message.publisher && message.publisher.hints && message.publisher.id) {\n this._processPublisherHints(message.publisher.hints, message.publisher.id);\n }\n break;\n default:\n this._log.warn('Unknown message type: ', message.type);\n break;\n }\n });\n });\n }\n\n sendTrackReplaced({ trackSid }) {\n if (!this._transport) {\n return;\n }\n\n const payLoad = {\n type: 'client_reset',\n track: trackSid,\n id: messageId++\n };\n this._log.debug('Outgoing: ', payLoad);\n this._transport.publish(payLoad);\n }\n\n sendHintResponse({ id, hints }) {\n if (!this._transport) {\n return;\n }\n const payLoad = {\n type: 'publisher_hints',\n id,\n hints\n };\n this._log.debug('Outgoing: ', payLoad);\n this._transport.publish(payLoad);\n }\n\n /**\n * @private\n */\n _processPublisherHints(hints, id) {\n try {\n this.emit('updated', hints, id);\n } catch (ex) {\n this._log.error('error processing hints:', ex);\n }\n }\n}\n\n\nmodule.exports = PublisherHintsSignaling;\n", "/* eslint-disable no-console */\n'use strict';\n\nconst DominantSpeakerSignaling = require('./dominantspeakersignaling');\nconst NetworkQualityMonitor = require('./networkqualitymonitor');\nconst NetworkQualitySignaling = require('./networkqualitysignaling');\nconst RecordingV2 = require('./recording');\nconst RoomSignaling = require('../room');\nconst RemoteParticipantV2 = require('./remoteparticipant');\nconst StatsReport = require('../../stats/statsreport');\nconst TrackPrioritySignaling = require('./trackprioritysignaling');\nconst TrackSwitchOffSignaling = require('./trackswitchoffsignaling');\nconst RenderHintsSignaling = require('./renderhintssignaling');\nconst PublisherHintsSignaling = require('./publisherhintsignaling.js');\n\n\nconst {\n constants: { DEFAULT_SESSION_TIMEOUT_SEC },\n createBandwidthProfilePayload,\n defer,\n difference,\n filterObject,\n flatMap,\n oncePerTick\n} = require('../../util');\n\nconst MovingAverageDelta = require('../../util/movingaveragedelta');\nconst { createTwilioError } = require('../../util/twilio-video-errors');\n\nconst STATS_PUBLISH_INTERVAL_MS = 10000;\n\n/**\n * @extends RoomSignaling\n */\nclass RoomV2 extends RoomSignaling {\n constructor(localParticipant, initialState, transport, peerConnectionManager, options) {\n initialState.options = Object.assign({\n session_timeout: DEFAULT_SESSION_TIMEOUT_SEC\n }, initialState.options);\n\n options = Object.assign({\n DominantSpeakerSignaling,\n NetworkQualityMonitor,\n NetworkQualitySignaling,\n RecordingSignaling: RecordingV2,\n RemoteParticipantV2,\n TrackPrioritySignaling,\n TrackSwitchOffSignaling,\n bandwidthProfile: null,\n sessionTimeout: initialState.options.session_timeout * 1000,\n statsPublishIntervalMs: STATS_PUBLISH_INTERVAL_MS\n }, options);\n\n localParticipant.setBandwidthProfile(options.bandwidthProfile);\n\n const { options: { signaling_region: signalingRegion, audio_processors: audioProcessors = [] } } = initialState;\n localParticipant.setSignalingRegion(signalingRegion);\n\n\n if (audioProcessors.includes('krisp')) {\n // Note(mpatwardhan): we add rnnoise as allowed_processor to enable testing our pipeline e2e.\n audioProcessors.push('rnnoise');\n }\n\n localParticipant.setAudioProcessors(audioProcessors);\n\n peerConnectionManager.setIceReconnectTimeout(options.sessionTimeout);\n\n super(localParticipant, initialState.sid, initialState.name, options);\n\n const getTrackReceiver = id => this._getTrackReceiver(id);\n const log = this._log;\n\n Object.defineProperties(this, {\n _disconnectedParticipantRevisions: {\n value: new Map()\n },\n _NetworkQualityMonitor: {\n value: options.NetworkQualityMonitor\n },\n _lastBandwidthProfileRevision: {\n value: localParticipant.bandwidthProfileRevision,\n writable: true\n },\n _mediaStatesWarningsRevision: {\n value: 0,\n writable: true\n },\n _networkQualityMonitor: {\n value: null,\n writable: true\n },\n _networkQualityConfiguration: {\n value: localParticipant.networkQualityConfiguration\n },\n _peerConnectionManager: {\n value: peerConnectionManager\n },\n _published: {\n value: new Map()\n },\n _publishedRevision: {\n value: 0,\n writable: true\n },\n _RemoteParticipantV2: {\n value: options.RemoteParticipantV2\n },\n _subscribed: {\n value: new Map()\n },\n _subscribedRevision: {\n value: 0,\n writable: true\n },\n _subscriptionFailures: {\n value: new Map()\n },\n _dominantSpeakerSignaling: {\n value: new options.DominantSpeakerSignaling(getTrackReceiver, { log })\n },\n _networkQualitySignaling: {\n value: new options.NetworkQualitySignaling(\n getTrackReceiver,\n localParticipant.networkQualityConfiguration,\n { log }\n )\n },\n _renderHintsSignaling: {\n value: new RenderHintsSignaling(getTrackReceiver, { log }),\n },\n _publisherHintsSignaling: {\n value: new PublisherHintsSignaling(getTrackReceiver, { log }),\n },\n _trackPrioritySignaling: {\n value: new options.TrackPrioritySignaling(getTrackReceiver, { log }),\n },\n _trackSwitchOffSignaling: {\n value: new options.TrackSwitchOffSignaling(getTrackReceiver, { log }),\n },\n _pendingSwitchOffStates: {\n value: new Map()\n },\n _transport: {\n value: transport\n },\n _trackReceiverDeferreds: {\n value: new Map()\n },\n mediaRegion: {\n enumerable: true,\n value: initialState.options.media_region || null\n }\n });\n\n this._initTrackSwitchOffSignaling();\n this._initDominantSpeakerSignaling();\n this._initNetworkQualityMonitorSignaling();\n this._initPublisherHintSignaling();\n\n handleLocalParticipantEvents(this, localParticipant);\n handlePeerConnectionEvents(this, peerConnectionManager);\n handleTransportEvents(this, transport);\n periodicallyPublishStats(this, transport, options.statsPublishIntervalMs);\n\n this._update(initialState);\n\n // NOTE(mpatwardhan) after initial state we know if publisher_hints are enabled or not\n // if they are not enabled. we need to undo simulcast that was enabled with initial offer.\n this._peerConnectionManager.setEffectiveAdaptiveSimulcast(this._publisherHintsSignaling.isSetup);\n }\n\n /**\n * The PeerConnection state.\n * @property {RTCPeerConnectionState}\n */\n get connectionState() {\n return this._peerConnectionManager.connectionState;\n }\n\n /**\n * The Signaling Connection State.\n * @property {string} - \"connected\", \"reconnecting\", \"disconnected\"\n */\n get signalingConnectionState() {\n return this._transport.state === 'syncing'\n ? 'reconnecting'\n : this._transport.state;\n }\n\n /**\n * The Ice Connection State.\n * @property {RTCIceConnectionState}\n */\n get iceConnectionState() {\n return this._peerConnectionManager.iceConnectionState;\n }\n\n /**\n * @private\n */\n _deleteTrackReceiverDeferred(id) {\n return this._trackReceiverDeferreds.delete(id);\n }\n\n /**\n * @private\n */\n _getOrCreateTrackReceiverDeferred(id) {\n const deferred = this._trackReceiverDeferreds.get(id) || defer();\n const trackReceivers = this._peerConnectionManager.getTrackReceivers();\n\n // NOTE(mmalavalli): In Firefox, there can be instances where a MediaStreamTrack\n // for the given Track ID already exists, for example, when a Track is removed\n // and added back. If that is the case, then we should resolve 'deferred'.\n const trackReceiver = trackReceivers.find(trackReceiver => trackReceiver.id === id && trackReceiver.readyState !== 'ended');\n\n if (trackReceiver) {\n deferred.resolve(trackReceiver);\n } else {\n // NOTE(mmalavalli): Only add the 'deferred' to the map if it's not\n // resolved. This will prevent old copies of the MediaStreamTrack from\n // being used when the remote peer removes and re-adds a MediaStreamTrack.\n this._trackReceiverDeferreds.set(id, deferred);\n }\n\n return deferred;\n }\n\n /**\n * @private\n */\n _addTrackReceiver(trackReceiver) {\n const deferred = this._getOrCreateTrackReceiverDeferred(trackReceiver.id);\n deferred.resolve(trackReceiver);\n return this;\n }\n\n /**\n * @private\n */\n _disconnect(error) {\n const didDisconnect = super._disconnect.call(this, error);\n if (didDisconnect) {\n this._teardownNetworkQualityMonitor();\n this._transport.disconnect();\n this._peerConnectionManager.close();\n }\n\n this.localParticipant.tracks.forEach(track => {\n track.publishFailed(error || new Error('LocalParticipant disconnected'));\n });\n\n return didDisconnect;\n }\n\n /**\n * @private\n */\n _getTrackReceiver(id) {\n return this._getOrCreateTrackReceiverDeferred(id).promise.then(trackReceiver => {\n this._deleteTrackReceiverDeferred(id);\n return trackReceiver;\n });\n }\n\n /**\n * @private\n */\n _getInitialTrackSwitchOffState(trackSid) {\n const initiallySwitchedOff = this._pendingSwitchOffStates.get(trackSid) || false;\n this._pendingSwitchOffStates.delete(trackSid);\n if (initiallySwitchedOff) {\n this._log.warn(`[${trackSid}] was initially switched off! `);\n }\n return initiallySwitchedOff;\n }\n\n\n /**\n * @private\n */\n _getTrackSidsToTrackSignalings() {\n const trackSidsToTrackSignalings = flatMap(this.participants, participant => Array.from(participant.tracks));\n return new Map(trackSidsToTrackSignalings);\n }\n\n /**\n * @private\n */\n _getOrCreateRemoteParticipant(participantState) {\n const RemoteParticipantV2 = this._RemoteParticipantV2;\n let participant = this.participants.get(participantState.sid);\n const self = this;\n if (!participant) {\n participant = new RemoteParticipantV2(\n participantState,\n trackSid => this._getInitialTrackSwitchOffState(trackSid),\n (trackSid, priority) => this._trackPrioritySignaling.sendTrackPriorityUpdate(trackSid, 'subscribe', priority),\n (trackSid, hint) => this._renderHintsSignaling.setTrackHint(trackSid, hint),\n trackSid => this._renderHintsSignaling.clearTrackHint(trackSid)\n );\n participant.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n participant.removeListener('stateChanged', stateChanged);\n self.participants.delete(participant.sid);\n self._disconnectedParticipantRevisions.set(participant.sid, participant.revision);\n }\n });\n this.connectParticipant(participant);\n }\n return participant;\n }\n\n /**\n * @private\n */\n _getState() {\n return {\n participant: this.localParticipant.getState()\n };\n }\n\n /**\n * @private\n */\n _maybeAddBandwidthProfile(update) {\n const { bandwidthProfile, bandwidthProfileRevision } = this.localParticipant;\n if (bandwidthProfile && this._lastBandwidthProfileRevision < bandwidthProfileRevision) {\n this._lastBandwidthProfileRevision = bandwidthProfileRevision;\n return Object.assign({\n bandwidth_profile: createBandwidthProfilePayload(bandwidthProfile)\n }, update);\n }\n return update;\n }\n /**\n * @private\n */\n _publishNewLocalParticipantState() {\n this._transport.publish(this._maybeAddBandwidthProfile(this._getState()));\n }\n\n /**\n * @private\n */\n _publishPeerConnectionState(peerConnectionState) {\n /* eslint camelcase:0 */\n this._transport.publish(Object.assign({\n peer_connections: [peerConnectionState]\n }, this._getState()));\n }\n\n /**\n * @private\n */\n _update(roomState) {\n if (roomState.subscribed && roomState.subscribed.revision > this._subscribedRevision) {\n this._subscribedRevision = roomState.subscribed.revision;\n roomState.subscribed.tracks.forEach(trackState => {\n if (trackState.id) {\n this._subscriptionFailures.delete(trackState.sid);\n this._subscribed.set(trackState.sid, trackState.id);\n } else if (trackState.error && !this._subscriptionFailures.has(trackState.sid)) {\n this._subscriptionFailures.set(trackState.sid, trackState.error);\n }\n });\n\n const subscribedTrackSids = new Set(roomState.subscribed.tracks\n .filter(trackState => !!trackState.id)\n .map(trackState => trackState.sid));\n\n this._subscribed.forEach((trackId, trackSid) => {\n if (!subscribedTrackSids.has(trackSid)) {\n this._subscribed.delete(trackSid);\n }\n });\n }\n\n const participantsToKeep = new Set();\n\n // eslint-disable-next-line no-warning-comments\n // TODO(mroberts): Remove me once the Server is fixed.\n (roomState.participants || []).forEach(participantState => {\n if (participantState.sid === this.localParticipant.sid) {\n return;\n }\n\n // NOTE(mmalavalli): If the incoming revision for a disconnected Participant is less than or\n // equal to the revision when it was disconnected, then the state is old and can be ignored.\n // Otherwise, the Participant was most likely disconnected in a Large Group Room when it\n // stopped publishing media, and hence needs to be re-added.\n const disconnectedParticipantRevision = this._disconnectedParticipantRevisions.get(participantState.sid);\n if (disconnectedParticipantRevision && participantState.revision <= disconnectedParticipantRevision) {\n return;\n }\n\n if (disconnectedParticipantRevision) {\n this._disconnectedParticipantRevisions.delete(participantState.sid);\n }\n const participant = this._getOrCreateRemoteParticipant(participantState);\n participant.update(participantState);\n participantsToKeep.add(participant);\n });\n\n if (roomState.type === 'synced') {\n this.participants.forEach(participant => {\n if (!participantsToKeep.has(participant)) {\n participant.disconnect();\n }\n });\n }\n\n handleSubscriptions(this);\n\n // eslint-disable-next-line no-warning-comments\n // TODO(mroberts): Remove me once the Server is fixed.\n /* eslint camelcase:0 */\n if (roomState.peer_connections) {\n this._peerConnectionManager.update(roomState.peer_connections, roomState.type === 'synced');\n }\n\n if (roomState.recording) {\n this.recording.update(roomState.recording);\n }\n\n if (roomState.published && roomState.published.revision > this._publishedRevision) {\n this._publishedRevision = roomState.published.revision;\n roomState.published.tracks.forEach(track => {\n if (track.sid) {\n this._published.set(track.id, track.sid);\n }\n });\n this.localParticipant.update(roomState.published);\n }\n\n if (roomState.participant) {\n this.localParticipant.connect(\n roomState.participant.sid,\n roomState.participant.identity);\n }\n\n [\n this._dominantSpeakerSignaling,\n this._networkQualitySignaling,\n this._trackPrioritySignaling,\n this._trackSwitchOffSignaling,\n this._renderHintsSignaling,\n this._publisherHintsSignaling\n ].forEach(mediaSignaling => {\n const channel = mediaSignaling.channel;\n if (!mediaSignaling.isSetup\n && roomState.media_signaling\n && roomState.media_signaling[channel]\n && roomState.media_signaling[channel].transport\n && roomState.media_signaling[channel].transport.type === 'data-channel') {\n mediaSignaling.setup(roomState.media_signaling[channel].transport.label);\n }\n });\n\n if (roomState.type === 'warning' && roomState.states &&\n roomState.states.revision > this._mediaStatesWarningsRevision) {\n this._mediaStatesWarningsRevision = roomState.states.revision;\n this.localParticipant.updateMediaStates(roomState.states);\n }\n\n return this;\n }\n\n _initPublisherHintSignaling() {\n this._publisherHintsSignaling.on('updated', (hints, id) => {\n Promise.all(hints.map(hint => {\n return this.localParticipant.setPublisherHint(hint.track, hint.encodings).then(result => {\n return { track: hint.track, result };\n });\n })).then(hintResponses => {\n this._publisherHintsSignaling.sendHintResponse({ id, hints: hintResponses });\n });\n });\n\n const handleReplaced = track => {\n if (track.kind === 'video') {\n track.trackTransceiver.on('replaced', () => {\n this._publisherHintsSignaling.sendTrackReplaced({ trackSid: track.sid });\n });\n }\n };\n\n // hook up for any existing and new tracks getting replaced.\n Array.from(this.localParticipant.tracks.values()).forEach(track => handleReplaced(track));\n this.localParticipant.on('trackAdded', track => handleReplaced(track));\n }\n\n _initTrackSwitchOffSignaling() {\n this._trackSwitchOffSignaling.on('updated', (tracksOff, tracksOn) => {\n try {\n this._log.debug('received trackSwitch: ', { tracksOn, tracksOff });\n const trackUpdates = new Map();\n tracksOn.forEach(trackSid => trackUpdates.set(trackSid, true));\n tracksOff.forEach(trackSid => {\n if (trackUpdates.get(trackSid)) {\n // NOTE(mpatwardhan): This means that VIDEO-3762 has been reproduced.\n this._log.warn(`${trackSid} is DUPLICATED in both tracksOff and tracksOn list`);\n }\n trackUpdates.set(trackSid, false);\n });\n this.participants.forEach(participant => {\n participant.tracks.forEach(track => {\n const isOn = trackUpdates.get(track.sid);\n if (typeof isOn !== 'undefined') {\n track.setSwitchedOff(!isOn);\n trackUpdates.delete(track.sid);\n }\n });\n });\n // NOTE(mpatwardhan): Cache any notification about the tracks that we do not yet know about.\n trackUpdates.forEach((isOn, trackSid) => this._pendingSwitchOffStates.set(trackSid, !isOn));\n } catch (ex) {\n this._log.error('error processing track switch off:', ex);\n }\n });\n }\n\n _initDominantSpeakerSignaling() {\n this._dominantSpeakerSignaling.on('updated', () => this.setDominantSpeaker(this._dominantSpeakerSignaling.loudestParticipantSid));\n }\n\n _initNetworkQualityMonitorSignaling() {\n this._networkQualitySignaling.on('ready', () => {\n const networkQualityMonitor = new this._NetworkQualityMonitor(this._peerConnectionManager, this._networkQualitySignaling);\n this._networkQualityMonitor = networkQualityMonitor;\n networkQualityMonitor.on('updated', () => {\n if (this.iceConnectionState === 'failed') {\n return;\n }\n this.localParticipant.setNetworkQualityLevel(\n networkQualityMonitor.level,\n networkQualityMonitor.levels);\n this.participants.forEach(participant => {\n const levels = networkQualityMonitor.remoteLevels.get(participant.sid);\n if (levels) {\n participant.setNetworkQualityLevel(levels.level, levels);\n }\n });\n });\n networkQualityMonitor.start();\n });\n this._networkQualitySignaling.on('teardown', () => this._teardownNetworkQualityMonitor());\n }\n\n _teardownNetworkQualityMonitor() {\n if (this._networkQualityMonitor) {\n this._networkQualityMonitor.stop();\n this._networkQualityMonitor = null;\n }\n }\n\n /**\n * Get the {@link RoomV2}'s media statistics.\n * @returns {Promise.>}\n */\n getStats() {\n return this._peerConnectionManager.getStats().then(responses =>\n new Map(Array.from(responses).map(([id, response]) =>\n [id, Object.assign({}, response, {\n localAudioTrackStats: filterAndAddLocalTrackSids(this, response.localAudioTrackStats),\n localVideoTrackStats: filterAndAddLocalTrackSids(this, response.localVideoTrackStats),\n remoteAudioTrackStats: filterAndAddRemoteTrackSids(this, response.remoteAudioTrackStats),\n remoteVideoTrackStats: filterAndAddRemoteTrackSids(this, response.remoteVideoTrackStats)\n })]\n ))\n );\n }\n}\n\n/**\n * Filter out {@link TrackStats} that aren't in the collection while also\n * stamping their Track SIDs.\n * @param {Map} idToSid\n * @param {Array} trackStats\n * @returns {Array}\n */\nfunction filterAndAddTrackSids(idToSid, trackStats) {\n return trackStats.reduce((trackStats, trackStat) => {\n const trackSid = idToSid.get(trackStat.trackId);\n return trackSid\n ? [Object.assign({}, trackStat, { trackSid })].concat(trackStats)\n : trackStats;\n }, []);\n}\n\n/**\n * Filter out {@link LocalTrackStats} that aren't currently published while also\n * stamping their Track SIDs.\n * @param {RoomV2} roomV2\n * @param {Array} localTrackStats\n * @returns {Array}\n */\nfunction filterAndAddLocalTrackSids(roomV2, localTrackStats) {\n return filterAndAddTrackSids(roomV2._published, localTrackStats);\n}\n\n/**\n * Filter out {@link RemoteTrackStats} that aren't currently subscribed while\n * also stamping their Track SIDs.\n * @param {RoomV2} roomV2\n * @param {Array} remoteTrackStats\n * @returns {Array}\n */\nfunction filterAndAddRemoteTrackSids(roomV2, remoteTrackStats) {\n const idToSid = new Map(Array.from(roomV2._subscribed.entries()).map(([sid, id]) => [id, sid]));\n return filterAndAddTrackSids(idToSid, remoteTrackStats);\n}\n\n/**\n * @typedef {object} RoomV2#Representation\n * @property {string} name\n * @property {LocalParticipantV2#Representation} participant\n * @property {?Array} participants\n * @property {?Array} peer_connections\n * @property {?RecordingV2#Representation} recording\n * @property {string} sid\n */\n\nfunction handleLocalParticipantEvents(roomV2, localParticipant) {\n const localParticipantUpdated = oncePerTick(() => {\n roomV2._publishNewLocalParticipantState();\n });\n\n const renegotiate = oncePerTick(() => {\n const trackSenders = flatMap(localParticipant.tracks, trackV2 => trackV2.trackTransceiver);\n roomV2._peerConnectionManager.setTrackSenders(trackSenders);\n });\n\n localParticipant.on('trackAdded', renegotiate);\n localParticipant.on('trackRemoved', renegotiate);\n localParticipant.on('updated', localParticipantUpdated);\n\n roomV2.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n localParticipant.removeListener('trackAdded', renegotiate);\n localParticipant.removeListener('trackRemoved', renegotiate);\n localParticipant.removeListener('updated', localParticipantUpdated);\n roomV2.removeListener('stateChanged', stateChanged);\n localParticipant.disconnect();\n }\n });\n\n roomV2.on('signalingConnectionStateChanged', () => {\n const { localParticipant, signalingConnectionState } = roomV2;\n const { identity, sid } = localParticipant;\n switch (signalingConnectionState) {\n case 'connected':\n localParticipant.connect(sid, identity);\n break;\n case 'reconnecting':\n localParticipant.reconnecting();\n break;\n }\n });\n}\n\nfunction handlePeerConnectionEvents(roomV2, peerConnectionManager) {\n peerConnectionManager.on('description', function onDescription(description) {\n roomV2._publishPeerConnectionState(description);\n });\n peerConnectionManager.dequeue('description');\n\n peerConnectionManager.on('candidates', function onCandidates(candidates) {\n roomV2._publishPeerConnectionState(candidates);\n });\n peerConnectionManager.dequeue('candidates');\n\n peerConnectionManager.on('trackAdded', roomV2._addTrackReceiver.bind(roomV2));\n peerConnectionManager.dequeue('trackAdded');\n peerConnectionManager.getTrackReceivers().forEach(roomV2._addTrackReceiver, roomV2);\n\n peerConnectionManager.on('connectionStateChanged', () => {\n roomV2.emit('connectionStateChanged');\n });\n\n peerConnectionManager.on('iceConnectionStateChanged', () => {\n roomV2.emit('iceConnectionStateChanged');\n if (roomV2.iceConnectionState === 'failed') {\n if (roomV2.localParticipant.networkQualityLevel !== null) {\n roomV2.localParticipant.setNetworkQualityLevel(0);\n }\n roomV2.participants.forEach(participant => {\n if (participant.networkQualityLevel !== null) {\n participant.setNetworkQualityLevel(0);\n }\n });\n }\n });\n}\n\nfunction handleTransportEvents(roomV2, transport) {\n transport.on('message', roomV2._update.bind(roomV2));\n transport.on('stateChanged', function stateChanged(state, error) {\n if (state === 'disconnected') {\n if (roomV2.state !== 'disconnected') {\n roomV2._disconnect(error);\n }\n transport.removeListener('stateChanged', stateChanged);\n }\n roomV2.emit('signalingConnectionStateChanged');\n });\n}\n\n/**\n * Periodically publish {@link StatsReport}s.\n * @private\n * @param {RoomV2} roomV2\n * @param {Transport} transport\n * @param {Number} intervalMs\n */\nfunction periodicallyPublishStats(roomV2, transport, intervalMs) {\n const movingAverageDeltas = new Map();\n let oddPublishCount = false;\n const interval = setInterval(() => {\n roomV2.getStats().then(stats => {\n oddPublishCount = !oddPublishCount;\n stats.forEach((response, id) => {\n // NOTE(mmalavalli): A StatsReport is used to publish a \"stats-report\"\n // event instead of using StandardizedStatsResponse directly because\n // StatsReport will add zeros to properties that do not exist.\n const report = new StatsReport(id, response, true /* prepareForInsights */);\n\n // NOTE(mmalavalli): Since A/V sync metrics are not part of the StatsReport class,\n // we add them to the insights payload here.\n transport.publishEvent('quality', 'stats-report', 'info', {\n audioTrackStats: report.remoteAudioTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToRemoteTrackStats(trackStat, response.remoteAudioTrackStats[i], movingAverageDeltas)),\n localAudioTrackStats: report.localAudioTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToLocalTrackStats(trackStat, response.localAudioTrackStats[i], movingAverageDeltas)),\n localVideoTrackStats: report.localVideoTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToLocalTrackStats(trackStat, response.localVideoTrackStats[i], movingAverageDeltas)),\n peerConnectionId: report.peerConnectionId,\n videoTrackStats: report.remoteVideoTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToRemoteTrackStats(trackStat, response.remoteVideoTrackStats[i], movingAverageDeltas)),\n });\n\n // NOTE(mmalavalli): Clean up entries for Tracks that are no longer published or subscribed to.\n const keys = flatMap([\n 'localAudioTrackStats',\n 'localVideoTrackStats',\n 'remoteAudioTrackStats',\n 'remoteVideoTrackStats'\n ], prop => report[prop].map(({ ssrc, trackSid }) => `${trackSid}+${ssrc}`));\n const movingAverageDeltaKeysToBeRemoved = difference(Array.from(movingAverageDeltas.keys()), keys);\n movingAverageDeltaKeysToBeRemoved.forEach(key => movingAverageDeltas.delete(key));\n\n if (oddPublishCount) {\n // NOTE(mmalavalli): null properties of the \"active-ice-candidate-pair\"\n // payload are assigned default values until the Insights gateway\n // accepts null values.\n const activeIceCandidatePair = replaceNullsWithDefaults(\n response.activeIceCandidatePair,\n report.peerConnectionId);\n\n transport.publishEvent(\n 'quality',\n 'active-ice-candidate-pair',\n 'info',\n activeIceCandidatePair);\n }\n });\n }, () => {\n // Do nothing.\n });\n }, intervalMs);\n\n roomV2.on('stateChanged', function onStateChanged(state) {\n if (state === 'disconnected') {\n clearInterval(interval);\n roomV2.removeListener('stateChanged', onStateChanged);\n }\n });\n}\n\nfunction handleSubscriptions(room) {\n const trackSidsToTrackSignalings = room._getTrackSidsToTrackSignalings();\n\n room._subscriptionFailures.forEach((error, trackSid) => {\n const trackSignaling = trackSidsToTrackSignalings.get(trackSid);\n if (trackSignaling) {\n room._subscriptionFailures.delete(trackSid);\n trackSignaling.subscribeFailed(createTwilioError(error.code, error.message));\n }\n });\n\n trackSidsToTrackSignalings.forEach(trackSignaling => {\n const trackId = room._subscribed.get(trackSignaling.sid);\n if (!trackId || (trackSignaling.isSubscribed && trackSignaling.trackTransceiver.id !== trackId)) {\n trackSignaling.setTrackTransceiver(null);\n }\n if (trackId) {\n room._getTrackReceiver(trackId).then(trackReceiver => trackSignaling.setTrackTransceiver(trackReceiver));\n }\n });\n}\n\n/**\n * NOTE(mmalavalli): Since A/V sync metrics are not part of the public StatsReport class, we add them\n * only for reporting purposes.\n * @private\n */\nfunction addAVSyncMetricsToLocalTrackStats(trackStats, trackResponse, movingAverageDeltas) {\n const {\n framesEncoded,\n packetsSent,\n totalEncodeTime,\n totalPacketSendDelay\n } = trackResponse;\n const augmentedTrackStats = Object.assign({}, trackStats);\n const key = `${trackStats.trackSid}+${trackStats.ssrc}`;\n const trackMovingAverageDeltas = movingAverageDeltas.get(key) || new Map();\n\n if (typeof totalEncodeTime === 'number' && typeof framesEncoded === 'number') {\n const trackAvgEncodeDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgEncodeDelay')\n || new MovingAverageDelta();\n trackAvgEncodeDelayMovingAverageDelta.putSample(totalEncodeTime * 1000, framesEncoded);\n augmentedTrackStats.avgEncodeDelay = Math.round(trackAvgEncodeDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgEncodeDelay', trackAvgEncodeDelayMovingAverageDelta);\n }\n if (typeof totalPacketSendDelay === 'number' && typeof packetsSent === 'number') {\n const trackAvgPacketSendDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgPacketSendDelay')\n || new MovingAverageDelta();\n trackAvgPacketSendDelayMovingAverageDelta.putSample(totalPacketSendDelay * 1000, packetsSent);\n augmentedTrackStats.avgPacketSendDelay = Math.round(trackAvgPacketSendDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgPacketSendDelay', trackAvgPacketSendDelayMovingAverageDelta);\n }\n movingAverageDeltas.set(key, trackMovingAverageDeltas);\n return augmentedTrackStats;\n}\n\n/**\n * NOTE(mmalavalli): Since A/V sync metrics are not part of the public StatsReport class, we add them\n * only for reporting purposes.\n * @private\n */\nfunction addAVSyncMetricsToRemoteTrackStats(trackStats, trackResponse, movingAverageDeltas) {\n const {\n estimatedPlayoutTimestamp,\n framesDecoded,\n jitterBufferDelay,\n jitterBufferEmittedCount,\n totalDecodeTime\n } = trackResponse;\n const augmentedTrackStats = Object.assign({}, trackStats);\n const key = `${trackStats.trackSid}+${trackStats.ssrc}`;\n const trackMovingAverageDeltas = movingAverageDeltas.get(key) || new Map();\n\n if (typeof estimatedPlayoutTimestamp === 'number') {\n augmentedTrackStats.estimatedPlayoutTimestamp = estimatedPlayoutTimestamp;\n }\n if (typeof framesDecoded === 'number' && typeof totalDecodeTime === 'number') {\n const trackAvgDecodeDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgDecodeDelay')\n || new MovingAverageDelta();\n trackAvgDecodeDelayMovingAverageDelta.putSample(totalDecodeTime * 1000, framesDecoded);\n augmentedTrackStats.avgDecodeDelay = Math.round(trackAvgDecodeDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgDecodeDelay', trackAvgDecodeDelayMovingAverageDelta);\n }\n if (typeof jitterBufferDelay === 'number' && typeof jitterBufferEmittedCount === 'number') {\n const trackAvgJitterBufferDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgJitterBufferDelay')\n || new MovingAverageDelta();\n trackAvgJitterBufferDelayMovingAverageDelta.putSample(jitterBufferDelay * 1000, jitterBufferEmittedCount);\n augmentedTrackStats.avgJitterBufferDelay = Math.round(trackAvgJitterBufferDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgJitterBufferDelay', trackAvgJitterBufferDelayMovingAverageDelta);\n }\n movingAverageDeltas.set(key, trackMovingAverageDeltas);\n return augmentedTrackStats;\n}\n\nfunction replaceNullsWithDefaults(activeIceCandidatePair, peerConnectionId) {\n activeIceCandidatePair = Object.assign({\n availableIncomingBitrate: 0,\n availableOutgoingBitrate: 0,\n bytesReceived: 0,\n bytesSent: 0,\n consentRequestsSent: 0,\n currentRoundTripTime: 0,\n lastPacketReceivedTimestamp: 0,\n lastPacketSentTimestamp: 0,\n nominated: false,\n peerConnectionId: peerConnectionId,\n priority: 0,\n readable: false,\n requestsReceived: 0,\n requestsSent: 0,\n responsesReceived: 0,\n responsesSent: 0,\n retransmissionsReceived: 0,\n retransmissionsSent: 0,\n state: 'failed',\n totalRoundTripTime: 0,\n transportId: '',\n writable: false\n }, filterObject(activeIceCandidatePair || {}, null));\n\n activeIceCandidatePair.localCandidate = Object.assign({\n candidateType: 'host',\n deleted: false,\n ip: '',\n port: 0,\n priority: 0,\n protocol: 'udp',\n url: ''\n }, filterObject(activeIceCandidatePair.localCandidate || {}, null));\n\n activeIceCandidatePair.remoteCandidate = Object.assign({\n candidateType: 'host',\n ip: '',\n port: 0,\n priority: 0,\n protocol: 'udp',\n url: ''\n }, filterObject(activeIceCandidatePair.remoteCandidate || {}, null));\n\n return activeIceCandidatePair;\n}\n\nmodule.exports = RoomV2;\n", "'use strict';\n\nconst StateMachine = require('../../statemachine');\nconst TwilioConnection = require('../../twilioconnection');\nconst DefaultBackoff = require('../../util/backoff');\nconst { reconnectBackoffConfig } = require('../../util/constants');\nconst Timeout = require('../../util/timeout');\nconst { SDK_NAME, SDK_VERSION, SDP_FORMAT } = require('../../util/constants');\n\nconst {\n createBandwidthProfilePayload,\n createMediaSignalingPayload,\n createMediaWarningsPayload,\n createSubscribePayload,\n getUserAgent,\n isNonArrayObject\n} = require('../../util');\n\nconst {\n createTwilioError,\n RoomCompletedError,\n SignalingConnectionError,\n SignalingServerBusyError,\n} = require('../../util/twilio-video-errors');\n\nconst ICE_VERSION = 1;\nconst RSP_VERSION = 2;\n\n/*\nTwilioConnectionTransport States\n----------------\n\n +-----------+\n | |\n | syncing |---------+\n | | |\n +-----------+ |\n ^ | |\n | | |\n | v v\n +------------+ +-----------+ +--------------+\n | | | | | |\n | connecting |--->| connected |--->| disconnected |\n | | | | | |\n +------------+ +-----------+ +--------------+\n | ^\n | |\n | |\n +------------------------------+\n\n*/\n\nconst states = {\n connecting: [\n 'connected',\n 'disconnected'\n ],\n connected: [\n 'disconnected',\n 'syncing'\n ],\n syncing: [\n 'connected',\n 'disconnected'\n ],\n disconnected: []\n};\n\n/**\n * A {@link TwilioConnectionTransport} supports sending and receiving Room Signaling Protocol\n * (RSP) messages. It also supports RSP requests, such as Sync and Disconnect.\n * @extends StateMachine\n * @emits TwilioConnectionTransport#connected\n * @emits TwilioConnectionTransport#message\n */\nclass TwilioConnectionTransport extends StateMachine {\n /**\n * Construct a {@link TwilioConnectionTransport}.\n * @param {?string} name\n * @param {string} accessToken\n * @param {ParticipantSignaling} localParticipant\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {string} wsServer\n * @param {object} [options]\n */\n constructor(name, accessToken, localParticipant, peerConnectionManager, wsServer, options) {\n options = Object.assign({\n Backoff: DefaultBackoff,\n TwilioConnection,\n iceServers: null,\n trackPriority: true,\n trackSwitchOff: true,\n renderHints: true,\n userAgent: getUserAgent()\n }, options);\n super('connecting', states);\n\n\n Object.defineProperties(this, {\n _accessToken: {\n value: accessToken\n },\n _automaticSubscription: {\n value: options.automaticSubscription\n },\n _bandwidthProfile: {\n value: options.bandwidthProfile\n },\n _dominantSpeaker: {\n value: options.dominantSpeaker\n },\n _adaptiveSimulcast: {\n value: options.adaptiveSimulcast\n },\n _eventObserver: {\n value: options.eventObserver,\n writable: false\n },\n _renderHints: {\n value: options.renderHints\n },\n _iceServersStatus: {\n value: Array.isArray(options.iceServers)\n ? 'overrode'\n : 'acquire'\n },\n _localParticipant: {\n value: localParticipant\n },\n _name: {\n value: name,\n },\n _networkQuality: {\n value: isNonArrayObject(options.networkQuality) || options.networkQuality\n },\n _notifyWarnings: {\n value: options.notifyWarnings\n },\n _options: {\n value: options\n },\n _peerConnectionManager: {\n value: peerConnectionManager\n },\n _sessionTimer: {\n value: null,\n writable: true\n },\n _sessionTimeoutMS: {\n value: 0, // initially 0, set only after 1st successful connection.\n writable: true\n },\n _reconnectBackoff: {\n value: new options.Backoff(reconnectBackoffConfig)\n },\n _session: {\n value: null,\n writable: true\n },\n _trackPriority: {\n value: options.trackPriority\n },\n _trackSwitchOff: {\n value: options.trackSwitchOff\n },\n _twilioConnection: {\n value: null,\n writable: true\n },\n _updatesReceived: {\n value: []\n },\n _updatesToSend: {\n value: []\n },\n _userAgent: {\n value: options.userAgent\n },\n _wsServer: {\n value: wsServer\n }\n });\n\n\n setupTransport(this);\n }\n\n /**\n * Create a Connect, Sync or Disconnect RSP message.\n * @private\n * @returns {?object}\n */\n _createConnectOrSyncOrDisconnectMessage() {\n if (this.state === 'connected') {\n return null;\n }\n\n if (this.state === 'disconnected') {\n return {\n session: this._session,\n type: 'disconnect',\n version: RSP_VERSION\n };\n }\n\n const type = {\n connecting: 'connect',\n syncing: 'sync'\n }[this.state];\n\n const message = {\n name: this._name,\n participant: this._localParticipant.getState(),\n peer_connections: this._peerConnectionManager.getStates(),\n type,\n version: RSP_VERSION\n };\n\n if (message.type === 'connect') {\n message.ice_servers = this._iceServersStatus;\n\n message.publisher = {\n name: SDK_NAME,\n sdk_version: SDK_VERSION,\n user_agent: this._userAgent\n };\n\n if (this._bandwidthProfile) {\n message.bandwidth_profile = createBandwidthProfilePayload(\n this._bandwidthProfile);\n }\n\n if (this._notifyWarnings) {\n message.participant.media_warnings = createMediaWarningsPayload(\n this._notifyWarnings);\n }\n\n message.media_signaling = createMediaSignalingPayload(\n this._dominantSpeaker,\n this._networkQuality,\n this._trackPriority,\n this._trackSwitchOff,\n this._adaptiveSimulcast,\n this._renderHints);\n\n message.subscribe = createSubscribePayload(this._automaticSubscription);\n message.format = SDP_FORMAT;\n message.token = this._accessToken;\n } else if (message.type === 'sync') {\n message.session = this._session;\n message.token = this._accessToken;\n } else if (message.type === 'update') {\n message.session = this._session;\n }\n\n return message;\n }\n\n /**\n * Create an \"ice\" message.\n * @private\n */\n _createIceMessage() {\n return {\n edge: 'roaming', // roaming here means use same edge as signaling.\n token: this._accessToken,\n type: 'ice',\n version: ICE_VERSION\n };\n }\n\n /**\n * Send a Connect, Sync or Disconnect RSP message.\n * @private\n */\n _sendConnectOrSyncOrDisconnectMessage() {\n const message = this._createConnectOrSyncOrDisconnectMessage();\n if (message) {\n this._twilioConnection.sendMessage(message);\n }\n }\n\n /**\n * Disconnect the {@link TwilioConnectionTransport}. Returns true if calling the method resulted\n * in disconnection.\n * @param {TwilioError} [error]\n * @returns {boolean}\n */\n disconnect(error) {\n if (this.state !== 'disconnected') {\n this.preempt('disconnected', null, [error]);\n this._sendConnectOrSyncOrDisconnectMessage();\n this._twilioConnection.close();\n return true;\n }\n return false;\n }\n\n /**\n * Publish an RSP Update. Returns true if calling the method resulted in\n * publishing (or eventually publishing) the update.\n * @param {object} update\n * @returns {boolean}\n */\n publish(update) {\n switch (this.state) {\n case 'connected':\n this._twilioConnection.sendMessage(Object.assign({\n session: this._session,\n type: 'update',\n version: RSP_VERSION\n }, update));\n return true;\n case 'connecting':\n case 'syncing':\n this._updatesToSend.push(update);\n return true;\n case 'disconnected':\n default:\n return false;\n }\n }\n\n /**\n * Publish (or queue) an event to the Insights gateway.\n * @param {string} group - Event group name\n * @param {string} name - Event name\n * @param {string} level - Event level\n * @param {object} payload - Event payload\n * @returns {void}\n */\n publishEvent(group, name, level, payload) {\n this._eventObserver.emit('event', { group, name, level, payload });\n }\n\n /**\n * Sync the {@link TwilioConnectionTransport}. Returns true if calling the method resulted in\n * syncing.\n * @returns {boolean}\n */\n sync() {\n if (this.state === 'connected') {\n this.preempt('syncing');\n this._sendConnectOrSyncOrDisconnectMessage();\n return true;\n }\n return false;\n }\n\n /**\n * @private\n * @returns {void}\n */\n _setSession(session, sessionTimeout) {\n this._session = session;\n this._sessionTimeoutMS = sessionTimeout * 1000;\n }\n\n /**\n * Determines if we should attempt reconnect.\n * returns a Promise to wait on before attempting to\n * reconnect. returns null if its not okay to reconnect.\n * @private\n * @returns {Promise}\n */\n _getReconnectTimer() {\n if (this._sessionTimeoutMS === 0) {\n // this means either we have never connected.\n // or we timed out while trying to reconnect\n // In either case we do not want to reconnect.\n return null;\n }\n\n // start session timer\n if (!this._sessionTimer) {\n this._sessionTimer = new Timeout(() => {\n // ensure that _clearReconnectTimer wasn't\n // called while we were waiting.\n if (this._sessionTimer) {\n // do not allow any more reconnect attempts.\n this._sessionTimeoutMS = 0;\n }\n }, this._sessionTimeoutMS);\n }\n\n // return promise that waits with exponential backoff.\n return new Promise(resolve => {\n this._reconnectBackoff.backoff(resolve);\n });\n }\n\n /**\n * clears the session reconnect timer.\n *\n * @private\n * @returns {void}\n */\n _clearReconnectTimer() {\n this._reconnectBackoff.reset();\n if (this._sessionTimer) {\n this._sessionTimer.clear();\n this._sessionTimer = null;\n }\n }\n}\n\n/**\n * @event TwilioConnectionTransport#connected\n * @param {object} initialState\n */\n\n/**\n * @event TwilioConnectionTransport#message\n * @param {object} peerConnections\n */\n\nfunction reducePeerConnections(peerConnections) {\n return Array.from(peerConnections.reduce((peerConnectionsById, update) => {\n const reduced = peerConnectionsById.get(update.id) || update;\n\n // First, reduce the top-level `description` property.\n if (!reduced.description && update.description) {\n reduced.description = update.description;\n } else if (reduced.description && update.description) {\n if (update.description.revision > reduced.description.revision) {\n reduced.description = update.description;\n }\n }\n\n // Then, reduce the top-level `ice` property.\n if (!reduced.ice && update.ice) {\n reduced.ice = update.ice;\n } else if (reduced.ice && update.ice) {\n if (update.ice.revision > reduced.ice.revision) {\n reduced.ice = update.ice;\n }\n }\n\n // Finally, update the map.\n peerConnectionsById.set(reduced.id, reduced);\n return peerConnectionsById;\n }, new Map()).values());\n}\n\nfunction reduceUpdates(updates) {\n return updates.reduce((reduced, update) => {\n // First, reduce the top-level `participant` property.\n if (!reduced.participant && update.participant) {\n reduced.participant = update.participant;\n } else if (reduced.participant && update.participant) {\n if (update.participant.revision > reduced.participant.revision) {\n reduced.participant = update.participant;\n }\n }\n\n // Then, reduce the top-level `peer_connections` property.\n /* eslint camelcase:0 */\n if (!reduced.peer_connections && update.peer_connections) {\n reduced.peer_connections = reducePeerConnections(update.peer_connections);\n } else if (reduced.peer_connections && update.peer_connections) {\n reduced.peer_connections = reducePeerConnections(\n reduced.peer_connections.concat(update.peer_connections));\n }\n return reduced;\n }, {});\n}\n\nfunction setupTransport(transport) {\n function createOrResetTwilioConnection() {\n if (transport.state === 'disconnected') {\n return;\n }\n if (transport._twilioConnection) {\n transport._twilioConnection.removeListener('message', handleMessage);\n }\n const { _iceServersStatus, _options, _wsServer, state } = transport;\n const { TwilioConnection } = _options;\n\n const twilioConnection = new TwilioConnection(_wsServer, Object.assign({\n helloBody: state === 'connecting' && _iceServersStatus === 'acquire'\n ? transport._createIceMessage()\n : transport._createConnectOrSyncOrDisconnectMessage()\n }, _options));\n\n twilioConnection.once('close', reason => {\n if (reason === TwilioConnection.CloseReason.LOCAL) {\n disconnect();\n } else {\n disconnect(new Error(reason));\n }\n });\n\n twilioConnection.on('message', handleMessage);\n transport._twilioConnection = twilioConnection;\n }\n\n function disconnect(error) {\n if (transport.state === 'disconnected') {\n return;\n }\n if (!error) {\n transport.disconnect();\n return;\n }\n\n const reconnectTimer = transport._getReconnectTimer();\n if (!reconnectTimer) {\n const twilioError = error.message === TwilioConnection.CloseReason.BUSY\n ? new SignalingServerBusyError()\n : new SignalingConnectionError();\n transport.disconnect(twilioError);\n return;\n }\n\n if (transport.state === 'connected') {\n transport.preempt('syncing');\n }\n\n reconnectTimer.then(createOrResetTwilioConnection);\n }\n\n function handleMessage(message) {\n if (transport.state === 'disconnected') {\n return;\n }\n if (message.type === 'error') {\n transport.disconnect(createTwilioError(message.code, message.message));\n return;\n }\n switch (transport.state) {\n case 'connected':\n switch (message.type) {\n case 'connected':\n case 'synced':\n case 'update':\n case 'warning':\n transport.emit('message', message);\n return;\n case 'disconnected':\n transport.disconnect(message.status === 'completed'\n ? new RoomCompletedError()\n : null);\n return;\n default:\n // Do nothing.\n return;\n }\n case 'connecting':\n switch (message.type) {\n case 'iced':\n transport._options.onIced(message.ice_servers).then(() => {\n transport._sendConnectOrSyncOrDisconnectMessage();\n });\n return;\n case 'connected':\n transport._setSession(message.session, message.options.session_timeout);\n transport.emit('connected', message);\n transport.preempt('connected');\n return;\n case 'synced':\n case 'update':\n transport._updatesReceived.push(message);\n return;\n case 'disconnected':\n transport.disconnect(message.status === 'completed'\n ? new RoomCompletedError()\n : null);\n return;\n default:\n // Do nothing.\n return;\n }\n case 'syncing':\n switch (message.type) {\n case 'connected':\n case 'update':\n transport._updatesReceived.push(message);\n return;\n case 'synced':\n transport._clearReconnectTimer();\n transport.emit('message', message);\n transport.preempt('connected');\n return;\n case 'disconnected':\n transport.disconnect(message.status === 'completed'\n ? new RoomCompletedError()\n : null);\n return;\n default:\n // Do nothing.\n return;\n }\n default:\n // Impossible\n return;\n }\n }\n\n transport.on('stateChanged', function stateChanged(state) {\n switch (state) {\n case 'connected': {\n const updates = transport._updatesToSend.splice(0);\n if (updates.length) {\n transport.publish(reduceUpdates(updates));\n }\n transport._updatesReceived.splice(0).forEach(update => transport.emit('message', update));\n return;\n }\n case 'disconnected':\n transport._twilioConnection.removeListener('message', handleMessage);\n transport.removeListener('stateChanged', stateChanged);\n return;\n case 'syncing':\n // Do nothing.\n return;\n default:\n // Impossible\n return;\n }\n });\n\n const { _options, _iceServersStatus } = transport;\n const { iceServers, onIced } = _options;\n\n if (_iceServersStatus === 'overrode') {\n onIced(iceServers).then(createOrResetTwilioConnection);\n } else {\n createOrResetTwilioConnection();\n }\n}\n\nmodule.exports = TwilioConnectionTransport;\n", "'use strict';\n\nconst CancelablePromise = require('../../util/cancelablepromise');\nconst DefaultPeerConnectionManager = require('./peerconnectionmanager');\nconst DefaultRoomV2 = require('./room');\nconst DefaultTransport = require('./twilioconnectiontransport');\n\nconst {\n SignalingConnectionDisconnectedError,\n SignalingIncomingMessageInvalidError\n} = require('../../util/twilio-video-errors');\n\nconst { flatMap, createRoomConnectEventPayload } = require('../../util');\n\nfunction createCancelableRoomSignalingPromise(token, wsServer, localParticipant, encodingParameters, preferredCodecs, options) {\n options = Object.assign({\n PeerConnectionManager: DefaultPeerConnectionManager,\n RoomV2: DefaultRoomV2,\n Transport: DefaultTransport\n }, options);\n\n const adaptiveSimulcast = preferredCodecs.video[0] && preferredCodecs.video[0].adaptiveSimulcast === true;\n const { PeerConnectionManager, RoomV2, Transport, iceServers, log } = options;\n const peerConnectionManager = new PeerConnectionManager(encodingParameters, preferredCodecs, options);\n const trackSenders = flatMap(localParticipant.tracks, trackV2 => [trackV2.trackTransceiver]);\n peerConnectionManager.setTrackSenders(trackSenders);\n\n const cancellationError = new Error('Canceled');\n\n let transport;\n\n const cancelablePromise = new CancelablePromise((resolve, reject, isCanceled) => {\n const onIced = iceServers => {\n if (isCanceled()) {\n reject(cancellationError);\n return Promise.reject(cancellationError);\n }\n log.debug('Got ICE servers:', iceServers);\n options.iceServers = iceServers;\n peerConnectionManager.setConfiguration(options);\n\n return peerConnectionManager.createAndOffer().then(() => {\n if (isCanceled()) {\n reject(cancellationError);\n throw cancellationError;\n }\n log.debug('createAndOffer() succeeded.');\n // NOTE(mmalavalli): PeerConnectionManager#createAndOffer() queues the\n // initial offer in the event queue for the 'description' event. So,\n // we are dequeueing to prevent the spurious 'update' message sent by\n // the client after connecting to a room.\n peerConnectionManager.dequeue('description');\n }).catch(error => {\n log.error('createAndOffer() failed:', error);\n reject(error);\n throw error;\n });\n };\n\n const {\n automaticSubscription,\n bandwidthProfile,\n dominantSpeaker,\n environment,\n eventObserver,\n loggerName,\n logLevel,\n name,\n networkMonitor,\n networkQuality,\n notifyWarnings,\n realm,\n sdpSemantics,\n } = options;\n\n // decide which msp channels to request\n // dominantSpeaker, networkQuality\n const trackPriority = !!bandwidthProfile;\n const trackSwitchOff = !!bandwidthProfile;\n const renderHints = !!bandwidthProfile &&\n (options.clientTrackSwitchOffControl !== 'disabled' || options.contentPreferencesMode !== 'disabled');\n\n const transportOptions = Object.assign({\n adaptiveSimulcast,\n automaticSubscription,\n dominantSpeaker,\n environment,\n eventObserver,\n loggerName,\n logLevel,\n networkMonitor,\n networkQuality,\n notifyWarnings,\n iceServers,\n onIced,\n realm,\n renderHints,\n sdpSemantics,\n trackPriority,\n trackSwitchOff\n }, bandwidthProfile ? {\n bandwidthProfile\n } : {});\n\n transport = new Transport(\n name,\n token,\n localParticipant,\n peerConnectionManager,\n wsServer,\n transportOptions);\n\n const connectEventPayload = createRoomConnectEventPayload(options);\n eventObserver.emit('event', connectEventPayload);\n\n transport.once('connected', initialState => {\n log.debug('Transport connected:', initialState);\n if (isCanceled()) {\n reject(cancellationError);\n return;\n }\n const { participant: localParticipantState } = initialState;\n if (!localParticipantState) {\n reject(new SignalingIncomingMessageInvalidError());\n return;\n }\n resolve(new RoomV2(localParticipant, initialState, transport, peerConnectionManager, options));\n });\n\n transport.once('stateChanged', (state, error) => {\n if (state === 'disconnected') {\n transport = null;\n reject(error || new SignalingConnectionDisconnectedError());\n } else {\n log.debug('Transport state changed:', state);\n }\n });\n }, () => {\n if (transport) {\n transport.disconnect();\n transport = null;\n }\n });\n\n cancelablePromise.catch(() => {\n if (transport) {\n transport.disconnect();\n transport = null;\n }\n peerConnectionManager.close();\n });\n\n return cancelablePromise;\n}\n\nmodule.exports = createCancelableRoomSignalingPromise;\n", "'use strict';\n\nconst ParticipantSignaling = require('./participant');\n\nclass LocalParticipantSignaling extends ParticipantSignaling {\n constructor() {\n super();\n Object.defineProperties(this, {\n _publicationsToTrackSenders: {\n value: new Map()\n },\n _trackSendersToPublications: {\n value: new Map()\n }\n });\n }\n\n /**\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @param {?NoiseCancellationVendor} noiseCancellationVendor\n * @returns {LocalTrackPublicationSignaling} publication\n */\n addTrack(trackSender, name, priority, noiseCancellationVendor = null) {\n const publication = this._createLocalTrackPublicationSignaling(trackSender, name, priority, noiseCancellationVendor);\n this._trackSendersToPublications.set(trackSender, publication);\n this._publicationsToTrackSenders.set(publication, trackSender);\n super.addTrack(publication);\n return this;\n }\n\n /**\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @returns {?LocalTrackPublicationSignaling}\n */\n getPublication(trackSender) {\n return this._trackSendersToPublications.get(trackSender) || null;\n }\n\n /**\n * @param {LocalTrackPublicationSignaling} trackPublication\n * @returns {?DataTrackSender|MediaTrackSender}\n */\n getSender(trackPublication) {\n return this._publicationsToTrackSenders.get(trackPublication) || null;\n }\n\n /**\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @returns {?LocalTrackPublicationSignaling}\n */\n removeTrack(trackSender) {\n const publication = this._trackSendersToPublications.get(trackSender);\n if (!publication) {\n return null;\n }\n this._trackSendersToPublications.delete(trackSender);\n this._publicationsToTrackSenders.delete(publication);\n const didDelete = super.removeTrack(publication);\n if (didDelete) {\n publication.stop();\n }\n return publication;\n }\n}\n\nmodule.exports = LocalParticipantSignaling;\n", "'use strict';\n\nconst TrackSignaling = require('./track');\n\n/**\n * A {@link LocalTrackPublication} implementation\n * @extends TrackSignaling\n * @property {Track.ID} id\n */\nclass LocalTrackPublicationSignaling extends TrackSignaling {\n /**\n * Construct a {@link LocalTrackPublicationSignaling}. {@link TrackSenders}\n * are always cloned.\n * @param {DataTrackSender|MediaTrackSender} trackSender - the {@link TrackSender}\n * of the {@link LocalTrack} to be published\n * @param {string} name - the name of the {@link LocalTrack} to be published\n * @param {Track.Priority} priority - initial {@link Track.Priority}\n */\n constructor(trackSender, name, priority) {\n // NOTE(lrivas): Safely clone a media stream track while preserving the original\n // enabled state. This is needed because Safari 18 incorrectly enables tracks\n // during cloning. Bug report: https://bugs.webkit.org/show_bug.cgi?id=281758\n const clonedTrackSender = trackSender.clone();\n if (trackSender.kind !== 'data') {\n clonedTrackSender.track.enabled = trackSender.track.enabled;\n }\n trackSender = clonedTrackSender;\n\n const enabled = trackSender.kind === 'data' ? true : trackSender.track.enabled;\n super(name, trackSender.kind, enabled, priority);\n this.setTrackTransceiver(trackSender);\n Object.defineProperties(this, {\n _updatedPriority: {\n value: priority,\n writable: true\n },\n id: {\n enumerable: true,\n value: trackSender.id\n }\n });\n }\n\n /**\n * The updated {@link Track.Priority} of the {@link LocalTrack}.\n * @property {Track.priority}\n */\n get updatedPriority() {\n return this._updatedPriority;\n }\n\n /**\n * Enable (or disable) the {@link LocalTrackPublicationSignaling} if it is not\n * already enabled (or disabled). This also updates the cloned\n * {@link MediaTrackSender}'s MediaStreamTracks `enabled` state.\n * @param {boolean} [enabled=true]\n * @return {this}\n */\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n this.trackTransceiver.track.enabled = enabled;\n return super.enable(enabled);\n }\n\n /**\n * Rejects the SID's deferred promise with the given Error.\n * @param {Error} error\n * @returns {this}\n */\n publishFailed(error) {\n if (setError(this, error)) {\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Update the {@link Track.Priority} of the published {@link LocalTrack}.\n * @param {Track.priority} priority\n * @returns {this}\n */\n setPriority(priority) {\n if (this._updatedPriority !== priority) {\n this._updatedPriority = priority;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Set the published {@link LocalTrack}'s {@link Track.SID}.\n * @param {Track.SID} sid\n * @returns {this}\n */\n setSid(sid) {\n if (this._error) {\n return this;\n }\n return super.setSid.call(this, sid);\n }\n\n /**\n * Stop the cloned {@link TrackSender}.\n * @returns {void}\n */\n stop() {\n this.trackTransceiver.stop();\n }\n}\n\n/**\n * @param {LocalTrackPublication} publication\n * @param {Error} error\n * @returns {boolean} updated\n */\nfunction setError(publication, error) {\n if (publication._sid !== null || publication._error) {\n return false;\n }\n publication._error = error;\n return true;\n}\n\nmodule.exports = LocalTrackPublicationSignaling;\n", "'use strict';\n\nconst LocalTrackPublicationSignaling = require('../localtrackpublication');\nconst TwilioWarning = require('../../util/twiliowarning');\nconst createTwilioError = require('../../util/twilio-video-errors').createTwilioError;\n\n/**\n * @extends LocalTrackPublicationSignaling\n */\nclass LocalTrackPublicationV2 extends LocalTrackPublicationSignaling {\n /**\n * Construct a {@link LocalTrackPublicationV2}.\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @param {?NoiseCancellationVendor} noiseCancellationVendor\n * @param {object} [options]\n */\n constructor(trackSender, name, priority, noiseCancellationVendor, options) {\n super(trackSender, name, priority);\n\n Object.defineProperties(this, {\n _log: {\n value: options.log.createLog('default', this)\n },\n _mediaStates: {\n value: { recordings: null },\n writable: true\n },\n _noiseCancellationVendor: {\n value: noiseCancellationVendor,\n }\n });\n }\n\n /**\n * Get the {@link LocalTrackPublicationV2#Representation} of a given {@link TrackSignaling}.\n * @returns {LocalTrackPublicationV2#Representation} - without the SID\n */\n getState() {\n const state = {\n enabled: this.isEnabled,\n id: this.id,\n kind: this.kind,\n name: this.name,\n priority: this.updatedPriority,\n };\n\n if (this._noiseCancellationVendor) {\n // eslint-disable-next-line camelcase\n state.audio_processor = this._noiseCancellationVendor;\n }\n return state;\n }\n\n toString() {\n return `[LocalTrackPublicationV2: ${this.sid}]`;\n }\n\n /**\n * Compare the {@link LocalTrackPublicationV2} to a {@link LocalTrackPublicationV2#Representation} of itself\n * and perform any updates necessary.\n * @param {PublishedTrack} track\n * @returns {this}\n * @fires TrackSignaling#updated\n */\n update(track) {\n switch (track.state) {\n case 'ready':\n this.setSid(track.sid);\n break;\n case 'failed': {\n const error = track.error;\n this.publishFailed(createTwilioError(error.code, error.message));\n break;\n }\n default: // 'created'\n break;\n }\n return this;\n }\n\n updateMediaStates(mediaStates) {\n if (!mediaStates || !mediaStates.recordings ||\n this._mediaStates.recordings === mediaStates.recordings) {\n return this;\n }\n this._mediaStates.recordings = mediaStates.recordings;\n switch (this._mediaStates.recordings) {\n case 'OK':\n this._log.info('Warnings have cleared.');\n this.emit('warningsCleared');\n break;\n case 'NO_MEDIA':\n this._log.warn('Recording media lost.');\n this.emit('warning', TwilioWarning.recordingMediaLost);\n break;\n default:\n this._log.warn(`Unknown media state detected: ${this._mediaStates.recordings}`);\n break;\n }\n return this;\n }\n}\n\n/**\n * The Room Signaling Protocol (RSP) representation of a {@link LocalTrackPublicationV2}.\n * @typedef {object} LocalTrackPublicationV2#Representation\n * @property {boolean} enabled\n * @property {Track.ID} id\n * @property {Track.Kind} kind\n * @property {string} name\n * @priority {Track.Priority} priority\n * @property {Track.SID} sid\n */\n\nmodule.exports = LocalTrackPublicationV2;\n", "'use strict';\n\nconst LocalParticipantSignaling = require('../localparticipant');\nconst LocalTrackPublicationV2 = require('./localtrackpublication');\nconst { DEFAULT_LOG_LEVEL } = require('../../util/constants');\nconst Log = require('../../util/log');\nconst { buildLogLevels, isDeepEqual } = require('../../util');\n\n/**\n * @extends ParticipantSignaling\n * @property {BandwidthProfileOptions} bandwidthProfile\n * @property {NetworkQualityConfigurationImpl} networkQualityConfiguration\n * @property {number} revision\n * @emits LocalParticipantV2#updated\n */\nclass LocalParticipantV2 extends LocalParticipantSignaling {\n /**\n * Construct a {@link LocalParticipantV2}.\n * @param {EncodingParametersImpl} encodingParameters\n * @param {NetworkQualityConfigurationImpl} networkQualityConfiguration\n * @param {object} [options]\n */\n constructor(encodingParameters, networkQualityConfiguration, options) {\n options = Object.assign({\n logLevel: DEFAULT_LOG_LEVEL,\n LocalTrackPublicationV2\n }, options);\n\n super();\n\n const logLevels = buildLogLevels(options.logLevel);\n\n Object.defineProperties(this, {\n _bandwidthProfile: {\n value: null,\n writable: true\n },\n _bandwidthProfileRevision: {\n value: 0,\n writable: true\n },\n _encodingParameters: {\n value: encodingParameters\n },\n _removeListeners: {\n value: new Map()\n },\n _LocalTrackPublicationV2: {\n value: options.LocalTrackPublicationV2\n },\n _log: {\n value: options.log\n ? options.log.createLog('default', this)\n : new Log('default', this, logLevels, options.loggerName)\n },\n _publishedRevision: {\n writable: true,\n value: 0\n },\n _revision: {\n writable: true,\n value: 1\n },\n _signalingRegion: {\n value: null,\n writable: true\n },\n audioProcessors: {\n value: [],\n writable: true\n },\n bandwidthProfile: {\n enumerable: true,\n get() {\n return this._bandwidthProfile;\n }\n },\n bandwidthProfileRevision: {\n enumerable: true,\n get() {\n return this._bandwidthProfileRevision;\n }\n },\n networkQualityConfiguration: {\n enumerable: true,\n value: networkQualityConfiguration\n },\n revision: {\n enumerable: true,\n get() {\n return this._revision;\n }\n },\n signalingRegion: {\n enumerable: true,\n get() {\n return this._signalingRegion;\n }\n }\n });\n }\n\n toString() {\n return `[LocalParticipantSignaling: ${this.sid}]`;\n }\n\n /**\n * Set the signalingRegion.\n * @param {string} signalingRegion.\n */\n setSignalingRegion(signalingRegion) {\n if (!this._signalingRegion) {\n this._signalingRegion = signalingRegion;\n }\n }\n\n /**\n * Update the {@link BandwidthProfileOptions}.\n * @param {BandwidthProfileOptions} bandwidthProfile\n */\n setBandwidthProfile(bandwidthProfile) {\n if (!isDeepEqual(this._bandwidthProfile, bandwidthProfile)) {\n // NOTE(mmalavalli): Object.assign() copies the values of only\n // the top level properties. In order to deep copy the object, we\n // stringify and parse the object.\n this._bandwidthProfile = JSON.parse(JSON.stringify(bandwidthProfile));\n this._bandwidthProfileRevision++;\n this.didUpdate();\n }\n }\n\n /**\n * Sets the AudioProcessors enabled for this room.\n * @param {string[]} audioProcessors\n */\n setAudioProcessors(audioProcessors) {\n this.audioProcessors = audioProcessors;\n }\n\n /**\n * returns current {@link EncodingParametersImpl}.\n * @returns {EncodingParametersImpl}\n */\n getParameters() {\n return this._encodingParameters;\n }\n\n /**\n * Set the {@link EncodingParameters}.\n * @param {?EncodingParameters} encodingParameters\n * @returns {this}\n */\n setParameters(encodingParameters) {\n this._encodingParameters.update(encodingParameters);\n return this;\n }\n\n /**\n * Update the {@link LocalParticipantV2} with the new state.\n * @param {Published} published\n * @returns {this}\n */\n update(published) {\n if (this._publishedRevision >= published.revision) {\n return this;\n }\n\n this._publishedRevision = published.revision;\n\n published.tracks.forEach(function(publicationState) {\n const localTrackPublicationV2 = this.tracks.get(publicationState.id);\n if (localTrackPublicationV2) {\n localTrackPublicationV2.update(publicationState);\n }\n }, this);\n\n return this;\n }\n\n updateMediaStates(mediaStates) {\n if (!mediaStates || !mediaStates.tracks) {\n return this;\n }\n\n Array.from(this.tracks.values()).forEach(publication => {\n const states = mediaStates.tracks[publication.sid];\n if (states) {\n publication.updateMediaStates(states);\n }\n });\n return this;\n }\n\n /**\n * @protected\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @param {?NoiseCancellationVendor} noiseCancellationVendor\n * @returns {LocalTrackPublicationV2}\n */\n _createLocalTrackPublicationSignaling(trackSender, name, priority, noiseCancellationVendor) {\n return new this._LocalTrackPublicationV2(trackSender, name, priority, noiseCancellationVendor, { log: this._log });\n }\n\n /**\n * Add a {@link LocalTrackPublicationV2} for the given {@link DataTrackSender}\n * or {@link MediaTrackSender} to the {@link LocalParticipantV2}.\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @returns {this}\n */\n addTrack(trackSender, name, priority, noiseCancellationVendor) {\n super.addTrack(trackSender, name, priority, noiseCancellationVendor);\n const publication = this.getPublication(trackSender);\n\n let {\n isEnabled,\n updatedPriority\n } = publication;\n\n const updated = () => {\n // NOTE(mmalavalli): The LocalParticipantV2's state is only published if\n // the \"updated\" event is emitted due to LocalTrackPublicationV2's\n // .isEnabled or .updatedPriority being changed. We do not publish if it is fired due to the\n // LocalTrackPublicationV2's .sid being set.\n if (isEnabled !== publication.isEnabled || updatedPriority !== publication.updatedPriority) {\n this.didUpdate();\n isEnabled = publication.isEnabled;\n updatedPriority = publication.updatedPriority;\n }\n };\n\n publication.on('updated', updated);\n\n this._removeListener(publication);\n this._removeListeners.set(publication, () => publication.removeListener('updated', updated));\n\n this.didUpdate();\n\n return this;\n }\n\n /**\n * @private\n * @param {LocalTrackPublicationV2} publication\n * @returns {void}\n */\n _removeListener(publication) {\n const removeListener = this._removeListeners.get(publication);\n if (removeListener) {\n removeListener();\n }\n }\n\n /**\n * Get the current state of the {@link LocalParticipantV2}.\n * @returns {object}\n */\n getState() {\n return {\n revision: this.revision,\n tracks: Array.from(this.tracks.values()).map(track => track.getState())\n };\n }\n\n /**\n * Increment the revision for the {@link LocalParticipantV2}.\n * @private\n * @returns {void}\n */\n didUpdate() {\n this._revision++;\n this.emit('updated');\n }\n\n /**\n * Remove the {@link LocalTrackPublicationV2} for the given {@link DataTrackSender}\n * or {@link MediaTrackSender} from the {@link LocalParticipantV2}.\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @returns {?LocalTrackPublicationV2}\n */\n removeTrack(trackSender) {\n const publication = super.removeTrack(trackSender);\n if (publication) {\n trackSender.removeClone(publication.trackTransceiver);\n this._removeListener(publication);\n this.didUpdate();\n }\n return publication;\n }\n\n /**\n * Updates the verbosity of network quality information.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration\n * @returns {void}\n */\n setNetworkQualityConfiguration(networkQualityConfiguration) {\n this.networkQualityConfiguration.update(networkQualityConfiguration);\n }\n\n /**\n * updates encodings for simulcast layers.\n * @param {Track.SID} trackSid\n * @param {Array<{enabled: boolean, layer_index: number}>} encodings\n * @returns {Promise} string indicating result of the operation. can be one of\n * \"OK\", \"INVALID_HINT\", \"COULD_NOT_APPLY_HINT\", \"UNKNOWN_TRACK\"\n */\n setPublisherHint(trackSid, encodings) {\n const trackSignaling = Array.from(this.tracks.values()).find(trackPub => trackPub.sid === trackSid);\n if (!trackSignaling) {\n this._log.warn(`track:${trackSid} not found`);\n return Promise.resolve('UNKNOWN_TRACK');\n }\n return trackSignaling.trackTransceiver.setPublisherHint(encodings);\n }\n}\n\n\n/**\n * @interface Published\n * @property {number} revision\n * @property {Array} tracks\n */\n\n/**\n * @typedef {CreatedTrack|ReadyTrack|FailedTrack} PublishedTrack\n */\n\n/**\n * @interface CreatedTrack\n * @property {Track.ID} id\n * @property {string} state - \"created\"\n */\n\n/**\n * @interface ReadyTrack\n * @property {Track.ID} id\n * @property {Track.SID} sid\n * @property {string} state - \"ready\"\n */\n\n/**\n * @interface FailedTrack\n * @property {Track.ID} id\n * @property {TrackError} error\n * @property {string} state - \"failed\"\n */\n\n/**\n * @interface TrackError\n * @property {number} code\n * @property {string} message\n */\n\n/**\n * @event LocalParticipantV2#updated\n */\n\nmodule.exports = LocalParticipantV2;\n", "/* eslint consistent-return:0 */\n'use strict';\n\nconst ParticipantSignaling = require('./participant');\nconst RoomSignaling = require('./room');\nconst StateMachine = require('../statemachine');\n\n/*\nSignaling States\n----------------\n\n +---------+\n | |\n | opening |\n +--->| |\n | +---------+\n +--------+ | | +------+\n | |<--+ +-->| |\n | closed |<----------| open |\n | |<--+ +-->| |\n +--------+ | | +------+\n +---------+ |\n | |<--+\n | closing |\n | |\n +---------+\n\n*/\n\nconst states = {\n closed: [\n 'opening'\n ],\n opening: [\n 'closed',\n 'open'\n ],\n open: [\n 'closed',\n 'closing'\n ],\n closing: [\n 'closed',\n 'open'\n ]\n};\n\n/**\n * @extends StateMachine\n * @property {string} state - one of \"closed\", \"opening\", \"open\", or \"closing\"\n */\nclass Signaling extends StateMachine {\n /**\n * Construct {@link Signaling}.\n */\n constructor() {\n super('closed', states);\n }\n\n /**\n * @private\n */\n // NOTE(mroberts): This is a dummy implementation suitable for testing.\n _close(key) {\n this.transition('closing', key);\n this.transition('closed', key);\n return Promise.resolve(this);\n }\n\n /**\n * @private\n */\n // NOTE(mroberts): This is a dummy implementation suitable for testing.\n _connect(\n localParticipant,\n token,\n encodingParameters,\n preferredCodecs,\n options\n ) {\n localParticipant.connect('PA00000000000000000000000000000000', 'test');\n const sid = 'RM00000000000000000000000000000000';\n const promise = Promise.resolve(new RoomSignaling(localParticipant, sid, options));\n promise.cancel = function cancel() {};\n return promise;\n }\n\n /**\n * @private\n */\n // NOTE(mroberts): This is a dummy implementation suitable for testing.\n _open(key) {\n this.transition('opening', key);\n this.transition('open', key);\n return Promise.resolve(this);\n }\n\n /**\n * Close the {@link Signaling}.\n * @returns {Promise}\n */\n close() {\n return this.bracket('close', key => {\n switch (this.state) {\n case 'closed':\n return this;\n case 'open':\n return this._close(key);\n default:\n throw new Error(`Unexpected Signaling state \"${this.state}\"`);\n }\n });\n }\n\n /**\n * Connect to a {@link RoomSignaling}.\n * @param {ParticipantSignaling} localParticipant\n * @param {string} token\n * @param {EncodingParametersImpl} encodingParameters\n * @param {PreferredCodecs} preferredCodecs\n * @param {object} options\n * @returns {Promise>}\n */\n connect(\n localParticipant,\n token,\n encodingParameters,\n preferredCodecs,\n options\n ) {\n const self = this;\n return this.bracket('connect', function transition(key) {\n switch (self.state) {\n case 'closed':\n return self._open(key).then(transition.bind(null, key));\n case 'open':\n // NOTE(mroberts): We don't need to hold the lock in _connect. Instead,\n // we just need to ensure the Signaling remains open.\n self.releaseLockCompletely(key);\n return self._connect(localParticipant, token, encodingParameters, preferredCodecs, options);\n default:\n throw new Error(`Unexpected Signaling state \"${self.state}\"`);\n }\n });\n }\n\n /**\n * Create a local {@link ParticipantSignaling}.\n * @returns {ParticipantSignaling}\n */\n createLocalParticipantSignaling() {\n return new ParticipantSignaling();\n }\n\n /**\n * Open the {@link Signaling}.\n * @returns {Promise}\n */\n open() {\n return this.bracket('open', key => {\n switch (this.state) {\n case 'closed':\n return this._open(key);\n case 'open':\n return this;\n default:\n throw new Error(`Unexpected Signaling state \"${this.state}\"`);\n }\n });\n }\n}\n\nmodule.exports = Signaling;\n", "'use strict';\n\nconst defaultCreateCancelableRoomSignalingPromise = require('./cancelableroomsignalingpromise');\nconst LocalParticipantV2 = require('./localparticipant');\nconst Signaling = require('../');\n\n/**\n * {@link SignalingV2} implements version 2 of our signaling protocol.\n * @extends Signaling\n */\nclass SignalingV2 extends Signaling {\n /**\n * Construct {@link SignalingV2}.\n * @param {string} wsServer\n * @param {?object} [options={}]\n */\n constructor(wsServer, options) {\n /* eslint new-cap:0 */\n options = Object.assign({\n createCancelableRoomSignalingPromise: defaultCreateCancelableRoomSignalingPromise\n }, options);\n\n super();\n\n Object.defineProperties(this, {\n _createCancelableRoomSignalingPromise: {\n value: options.createCancelableRoomSignalingPromise\n },\n _options: {\n value: options\n },\n _wsServer: {\n value: wsServer\n }\n });\n }\n\n /**\n * @private\n */\n _connect(\n localParticipant,\n token,\n encodingParameters,\n preferredCodecs,\n options\n ) {\n options = Object.assign({}, this._options, options);\n return this._createCancelableRoomSignalingPromise.bind(\n null,\n token,\n this._wsServer,\n localParticipant,\n encodingParameters,\n preferredCodecs,\n options);\n }\n\n createLocalParticipantSignaling(encodingParameters, networkQualityConfiguration) {\n return new LocalParticipantV2(encodingParameters, networkQualityConfiguration);\n }\n}\n\nmodule.exports = SignalingV2;\n", "'use strict';\n\nconst { MediaStreamTrack } = require('./webrtc');\nconst { guessBrowser, guessBrowserVersion, isCodecSupported } = require('./webrtc/util');\nconst createCancelableRoomPromise = require('./cancelableroompromise');\nconst EncodingParametersImpl = require('./encodingparameters');\nconst LocalParticipant = require('./localparticipant');\nconst InsightsPublisher = require('./util/insightspublisher');\nconst NullInsightsPublisher = require('./util/insightspublisher/null');\n\nconst {\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack\n} = require('./media/track/es5');\n\nconst NetworkQualityConfigurationImpl = require('./networkqualityconfiguration');\nconst Room = require('./room');\nconst SignalingV2 = require('./signaling/v2');\n\nconst {\n asLocalTrack,\n buildLogLevels,\n filterObject,\n isNonArrayObject\n} = require('./util');\n\nconst {\n DEFAULT_ENVIRONMENT,\n DEFAULT_LOG_LEVEL,\n DEFAULT_LOGGER_NAME,\n DEFAULT_REALM,\n DEFAULT_REGION,\n WS_SERVER,\n SDK_NAME,\n SDK_VERSION,\n typeErrors: E\n} = require('./util/constants');\n\nconst CancelablePromise = require('./util/cancelablepromise');\nconst EventObserver = require('./util/eventobserver');\nconst DefaultLog = require('./util/log');\nconst { validateBandwidthProfile } = require('./util/validate');\n\nconst safariVersion = guessBrowser() === 'safari' && guessBrowserVersion();\n\n// This is used to make out which connect() call a particular Log statement\n// belongs to. Each call to connect() increments this counter.\nlet connectCalls = 0;\n\nlet didPrintSafariWarning = false;\nlet isSafariWithoutVP8Support = false;\n\nif (safariVersion) {\n const { major: safariMajorVersion, minor: safariMinorVersion } = safariVersion;\n isSafariWithoutVP8Support = safariMajorVersion < 12 || (safariMajorVersion === 12 && safariMinorVersion < 1);\n}\n\nconst deprecatedConnectOptionsProps = new Set([\n { didWarn: false, shouldDelete: true, name: 'abortOnIceServersTimeout' },\n { didWarn: false, shouldDelete: true, name: 'dscpTagging', newName: 'enableDscp' },\n { didWarn: false, shouldDelete: true, name: 'iceServersTimeout' },\n { didWarn: false, shouldDelete: false, name: 'eventListener', newName: 'Video.Logger' },\n { didWarn: false, shouldDelete: false, name: 'logLevel', newName: 'Video.Logger' },\n]);\n\nconst deprecatedBandwidthProfileOptions = new Set([\n { didWarn: false, shouldDelete: false, name: 'maxTracks', newName: 'bandwidthProfile.video.clientTrackSwitchOffControl' },\n { didWarn: false, shouldDelete: false, name: 'renderDimensions', newName: 'bandwidthProfile.video.contentPreferencesMode' },\n]);\n\n/**\n * Connect to a {@link Room}.\n *

\n * By default, this will automatically acquire an array containing a\n * {@link LocalAudioTrack} and {@link LocalVideoTrack} before connecting to\n * the {@link Room}. These will be stopped when you disconnect from the\n * {@link Room}.\n *

\n * You can override the default behavior by specifying\n * options. For example, rather than acquiring a\n * {@link LocalAudioTrack} and {@link LocalVideoTrack} automatically, you can\n * pass your own array which you can stop yourself. See {@link ConnectOptions}\n * for more information.\n * @alias module:twilio-video.connect\n * @param {string} token - The Access Token string\n * @param {ConnectOptions} [options] - Options to override the default behavior, invalid options are ignored.\n * @returns {CancelablePromise}\n * @throws {RangeError}\n * @throws {TwilioError}\n * @throws {TypeError}\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n * Video.connect(token, {\n * name: 'my-cool-room'\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n\n * room.once('disconnected', function() {\n * console.log('You left the Room:', room.name);\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n *\n * // Connect with audio-only\n * Video.connect(token, {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n *\n * room.once('disconnected', function() {\n * console.log('You left the Room:', room.name);\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n *\n * // Connect with media acquired using getUserMedia()\n * navigator.mediaDevices.getUserMedia({\n * audio: true,\n * video: true\n * }).then(function(mediaStream) {\n * return Video.connect(token, {\n * name: 'my-cool-room',\n * tracks: mediaStream.getTracks()\n * });\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n *\n * room.once('disconnected', function() {\n * console.log('You left the Room:', room.name);\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n *\n * // Connect with custom names for LocalAudioTrack and LocalVideoTrack\n * Video.connect(token, {\n * name: 'my-cool-room'\n * audio: { name: 'microphone' },\n * video: { name: 'camera' }\n * }).then(function(room) {\n * room.localParticipants.trackPublications.forEach(function(publication) {\n * console.log('The LocalTrack \"' + publication.trackName + '\" was successfully published');\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * // Accessing the SDK logger\n * var { Logger, connect } = require('twilio-video');\n * var token = getAccessToken();\n *\n * var logger = Logger.getLogger('twilio-video');\n *\n * // Listen for logs\n * var originalFactory = logger.methodFactory;\n * logger.methodFactory = function (methodName, logLevel, loggerName) {\n * var method = originalFactory(methodName, logLevel, loggerName);\n *\n * return function (datetime, logLevel, component, message, data) {\n * method(datetime, logLevel, component, message, data);\n * // Send to your own server\n * postDataToServer(arguments);\n * };\n * };\n * logger.setLevel('debug');\n *\n * connect(token, {\n * name: 'my-cool-room'\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n */\nfunction connect(token, options) {\n if (typeof options === 'undefined') {\n options = {};\n }\n if (!isNonArrayObject(options)) {\n return CancelablePromise.reject(E.INVALID_TYPE('options', 'object'));\n }\n\n const Log = options.Log || DefaultLog;\n const loggerName = options.loggerName || DEFAULT_LOGGER_NAME;\n const logLevel = options.logLevel || DEFAULT_LOG_LEVEL;\n const logLevels = buildLogLevels(logLevel);\n const logComponentName = `[connect #${++connectCalls}]`;\n\n let log;\n try {\n log = new Log('default', logComponentName, logLevels, loggerName);\n } catch (error) {\n return CancelablePromise.reject(error);\n }\n\n // NOTE(csantos): Log a warning for the deprecated ConnectOptions properties.\n // The warning is displayed only for the first call to connect() per browser session.\n // Additionally, the options that are no longer needed will be removed.\n deprecateOptions(options, log, deprecatedConnectOptionsProps);\n\n const adaptiveSimulcast = options.preferredVideoCodecs === 'auto';\n if (adaptiveSimulcast) {\n // NOTE(mpatwardhan): enable adaptiveSimulcast.\n options.preferredVideoCodecs = [{ codec: 'VP8', simulcast: true, adaptiveSimulcast: true }];\n }\n\n if (options.maxVideoBitrate && adaptiveSimulcast) {\n log.error('ConnectOptions \"maxVideoBitrate\" is not compatible with \"preferredVideoCodecs=auto\"');\n return CancelablePromise.reject(E.ILLEGAL_INVOKE('connect',\n 'ConnectOptions \"maxVideoBitrate\" is not compatible with \"preferredVideoCodecs=auto\"'));\n }\n\n options = Object.assign({\n automaticSubscription: true,\n dominantSpeaker: false,\n enableDscp: false,\n environment: DEFAULT_ENVIRONMENT,\n eventListener: null,\n insights: true,\n LocalAudioTrack,\n LocalDataTrack,\n LocalParticipant,\n LocalVideoTrack,\n Log,\n MediaStreamTrack,\n loggerName,\n logLevel,\n maxAudioBitrate: null,\n maxVideoBitrate: null,\n name: null,\n networkMonitor: true,\n networkQuality: false,\n preferredAudioCodecs: [],\n preferredVideoCodecs: [],\n realm: DEFAULT_REALM,\n region: DEFAULT_REGION,\n signaling: SignalingV2\n }, filterObject(options));\n\n /* eslint new-cap:0 */\n const eventPublisherOptions = {};\n if (typeof options.wsServerInsights === 'string') {\n eventPublisherOptions.gateway = options.wsServerInsights;\n }\n const EventPublisher = options.insights ? InsightsPublisher : NullInsightsPublisher;\n const eventPublisher = new EventPublisher(\n token,\n SDK_NAME,\n SDK_VERSION,\n options.environment,\n options.realm,\n eventPublisherOptions);\n\n const wsServer = WS_SERVER(options.environment, options.region);\n const eventObserver = new EventObserver(eventPublisher, Date.now(), log, options.eventListener);\n options = Object.assign({ eventObserver, wsServer }, options);\n options.log = log;\n\n // NOTE(mroberts): Print the Safari warning once if the log-level is at least\n // \"warn\", i.e. neither \"error\" nor \"off\".\n // NOTE(mmalavalli): Print the Safari warning only for versions 12.0 and below.\n if (isSafariWithoutVP8Support\n && !didPrintSafariWarning\n && (log.logLevel !== 'error' && log.logLevel !== 'off')) {\n didPrintSafariWarning = true;\n log.warn([\n 'Support for Safari 12.0 and below is limited because it does not support VP8.',\n 'This means you may experience codec issues in Group Rooms. You may also',\n 'experience codec issues in Peer-to-Peer (P2P) Rooms containing Android- or',\n 'iOS-based Participants who do not support H.264. However, P2P Rooms',\n 'with browser-based Participants should work. For more information, please',\n 'refer to this guide: https://www.twilio.com/docs/video/javascript-v2-developing-safari-11'\n ].join(' '));\n }\n\n if (typeof token !== 'string') {\n return CancelablePromise.reject(E.INVALID_TYPE('token', 'string'));\n }\n\n // NOTE(mmalavalli): The Room \"name\" in \"options\" was being used\n // as the LocalTrack name in asLocalTrack(). So we pass a copy of\n // \"options\" without the \"name\".\n const localTrackOptions = Object.assign({}, options);\n delete localTrackOptions.name;\n\n if ('tracks' in options) {\n if (!Array.isArray(options.tracks)) {\n return CancelablePromise.reject(E.INVALID_TYPE('options.tracks',\n 'Array of LocalAudioTrack, LocalVideoTrack or MediaStreamTrack'));\n }\n try {\n options.tracks = options.tracks.map(track => asLocalTrack(track, localTrackOptions));\n } catch (error) {\n return CancelablePromise.reject(error);\n }\n }\n\n const error = validateBandwidthProfile(options.bandwidthProfile);\n if (error) {\n return CancelablePromise.reject(error);\n }\n\n // Note(mpatwardhan): \"clientTrackSwitchOffControl\" allows tracks to be switched off\n // and \"contentPreferencesMode\" allows track dimensions to be specified dynamically.\n // The properties can have one of the three values internally:\n // 1) \"auto\" = sdk will decide and send the hints.\n // 2) \"manual\" - app can use api to send the hints.\n // 3) \"disabled\" = do not enable this feature. (this is internal only value)\n // 'disabled' is needed because clientTrackSwitchOffControl and contentPreferencesMode are incompatible with\n // deprecated properties maxTracks and renderDimensions respectively. once we make @breaking_version_change\n // we can remove 'disabled' state along with maxTracks and renderDimensions.\n options.clientTrackSwitchOffControl = 'disabled'; // should sdk turn off idle tracks automatically?\n options.contentPreferencesMode = 'disabled'; // should sdk use video element dimensions for content hints?\n if (options.bandwidthProfile) {\n options.clientTrackSwitchOffControl = 'auto';\n options.contentPreferencesMode = 'auto';\n if (options.bandwidthProfile.video) {\n\n // log any warnings about deprecated bwp options\n deprecateOptions(options.bandwidthProfile.video, log, deprecatedBandwidthProfileOptions);\n\n if ('maxTracks' in options.bandwidthProfile.video) {\n // when deprecated maxTracks is specified. disable clientTrackSwitchOffControl\n options.clientTrackSwitchOffControl = 'disabled';\n } else if (options.bandwidthProfile.video.clientTrackSwitchOffControl === 'manual') {\n options.clientTrackSwitchOffControl = 'manual';\n } else {\n options.clientTrackSwitchOffControl = 'auto';\n }\n\n if ('renderDimensions' in options.bandwidthProfile.video) {\n options.contentPreferencesMode = 'disabled';\n } else if (options.bandwidthProfile.video.contentPreferencesMode === 'manual') {\n options.contentPreferencesMode = 'manual';\n } else {\n options.contentPreferencesMode = 'auto';\n }\n }\n }\n\n const Signaling = options.signaling;\n const signaling = new Signaling(options.wsServer, options);\n\n log.info('Connecting to a Room');\n log.debug('Options:', options);\n\n const encodingParameters = new EncodingParametersImpl({\n maxAudioBitrate: options.maxAudioBitrate,\n maxVideoBitrate: options.maxVideoBitrate\n }, adaptiveSimulcast);\n\n const preferredCodecs = {\n audio: options.preferredAudioCodecs.map(normalizeCodecSettings),\n video: options.preferredVideoCodecs.map(normalizeCodecSettings)\n };\n\n const networkQualityConfiguration = new NetworkQualityConfigurationImpl(\n isNonArrayObject(options.networkQuality) ? options.networkQuality : {}\n );\n\n // Log warnings for any unsupported preferred codecs.\n ['audio', 'video'].forEach(\n kind => preferredCodecs[kind].forEach(\n ({ codec }) => isCodecSupported(codec, kind).then(\n isSupported => !isSupported && log.warn(\n `The preferred ${kind} codec \"${codec}\" will be ignored as it is not supported by the browser.`\n )\n )\n )\n );\n\n // Create a CancelableRoomPromise that resolves after these steps:\n // 1 - Get the LocalTracks.\n // 2 - Create the LocalParticipant using options.tracks.\n // 3 - Connect to rtc-room-service and create the RoomSignaling.\n // 4 - Create the Room and then resolve the CancelablePromise.\n const cancelableRoomPromise = createCancelableRoomPromise(\n getLocalTracks.bind(null, options),\n createLocalParticipant.bind(null, signaling, log, encodingParameters, networkQualityConfiguration, options),\n createRoomSignaling.bind(null, token, options, signaling, encodingParameters, preferredCodecs),\n createRoom.bind(null, options));\n\n cancelableRoomPromise.then(room => {\n eventPublisher.connect(room.sid, room.localParticipant.sid);\n log.info('Connected to Room:', room.toString());\n log.info('Room name:', room.name);\n log.debug('Room:', room);\n room.once('disconnected', () => eventPublisher.disconnect());\n return room;\n }, error => {\n eventPublisher.disconnect();\n if (cancelableRoomPromise._isCanceled) {\n log.info('Attempt to connect to a Room was canceled');\n } else {\n log.info('Error while connecting to a Room:', error);\n }\n });\n\n return cancelableRoomPromise;\n}\n\n/**\n * You may pass these options to {@link connect} in order to override the\n * default behavior.\n * @typedef {object} ConnectOptions\n * @property {boolean|CreateLocalTracksOptions|CreateLocalAudioTrackOptions} [audio=true] - Whether or not to\n * get local audio with getUserMedia when tracks\n * are not provided.\n * @property {boolean} [automaticSubscription=true] - By default, you will subscribe\n * to all RemoteTracks shared by other Participants in a Room. You can now override this\n * behavior by setting this flag to false. It will make sure that you will\n * not subscribe to any RemoteTrack in a Group or Small Group Room. Setting it to\n * true, or not setting it at all preserves the default behavior. This\n * flag does not have any effect in a Peer-to-Peer Room.\n * @property {BandwidthProfileOptions} [bandwidthProfile] - You can optionally configure\n * how your available downlink bandwidth is shared among the RemoteTracks you have subscribed\n * to in a Group Room. By default, bandwidth is shared equally among the RemoteTracks.\n * This has no effect in Peer-to-Peer Rooms.\n * @property {boolean} [dominantSpeaker=false] - Whether to enable the Dominant\n * Speaker API or not. This only takes effect in Group Rooms.\n * @property {boolean} [dscpTagging=false] - (deprecated: use \"enableDscp\" instead)\n * DSCP tagging allows you to request enhanced QoS treatment for RTP media packets from any\n * firewall that the client may be behind. Setting this option to true will\n * request DSCP tagging for media packets on supported browsers (only Chrome supports this\n * as of now). Audio packets will be sent with DSCP header value set to 0xb8 which corresponds\n * to Expedited Forwarding (EF). Video packets will be sent with DSCP header value set to 0x88\n * which corresponds to Assured Forwarding (AF41).\n * @property {boolean} [enableDscp=false] - DSCP tagging allows you to request enhanced\n * QoS treatment for RTP media packets from any firewall that the client may be behind.\n * Setting this option to true will request DSCP tagging for media packets\n * on supported browsers (only Chrome supports this as of now). Audio packets will be\n * sent with DSCP header value set to 0xb8 which corresponds to Expedited Forwarding (EF).\n * Video packets will be sent with DSCP header value set to 0x88 which corresponds to\n * Assured Forwarding (AF41).\n * @property {EventListener} [eventListener] - (deprecated: use [Video.Logger](module-twilio-video.html)\n * you can listen to fine-grained events related to signaling and media that are\n * not available in the public APIs. These events might be useful for your own reporting\n * and diagnostics.\n * @property {Array} iceServers - Override the STUN and TURN\n * servers used when connecting to {@link Room}s\n * @property {RTCIceTransportPolicy} [iceTransportPolicy=\"all\"] - Override the\n * ICE transport policy to be one of \"relay\" or \"all\"\n * @property {boolean} [insights=true] - Whether publishing events\n * to the Insights gateway is enabled or not\n * @property {?number} [maxAudioBitrate=null] - Max outgoing audio bitrate (bps);\n * A null or a 0 value does not set any bitrate limit;\n * This value is set as a hint for variable bitrate codecs, but will not take\n * effect for fixed bitrate codecs; Based on our tests, Chrome, Firefox and Safari\n * support a bitrate range of 12000 bps to 256000 bps for Opus codec; This parameter\n * has no effect on iSAC, PCMU and PCMA codecs\n * @property {?number} [maxVideoBitrate=null] - Max outgoing video bitrate (bps);\n * A null or 0 value does not set any bitrate limit;\n * This value is set as a hint for variable bitrate codecs, but will not take\n * effect for fixed bitrate codecs; Based on our tests, Chrome, Firefox and Safari\n * all seem to support an average bitrate range of 20000 bps (20 kbps) to\n * 8000000 bps (8 mbps) for a 720p VideoTrack\n * This parameter must not be set when when preferredVideoCodecs is set to `auto`.\n * @property {?string} [name=null] - Set to connect to a {@link Room} by name\n * @property {boolean|NetworkQualityConfiguration} [networkQuality=false] - Whether to enable the Network\n * Quality API or not. This only takes effect in Group Rooms. Pass a {@link NetworkQualityConfiguration}\n * to configure verbosity levels for network quality information for {@link LocalParticipant}\n * and {@link RemoteParticipant}s. A true value will set the {@link NetworkQualityVerbosity}\n * for the {@link LocalParticipant} to {@link NetworkQualityVerbosity}#minimal\n * and the {@link NetworkQualityVerbosity} for {@link RemoteParticipant}s to\n * {@link NetworkQualityVerbosity}#none.\n * @property {Array} [notifyWarnings=[]] - The SDK raises warning events when it\n * detects certain conditions. You can implement callbacks on these events to act on them, or to alert\n * the user of an issue. Subsequently, \"warningsCleared\" event is raised when conditions have returned\n * to normal. You can listen to these events by specifying an array of warning. By default,\n * this array is empty and no warning events will be raised.\n * Possible values include recording-media-lost, which is raised when the media server\n * has not detected any media on the published track that is being recorded in the past 30 seconds.\n * This usually happens when there are network interruptions or when the track has stopped.\n * This warning is raised by {@link LocalTrackPublication}, {@link LocalParticipant}, and {@link Room} object.\n * @property {string} [region='gll'] - Preferred signaling region; By default, you will be connected to the\n * nearest signaling server determined by latency based routing. Setting a value other\n * than gll bypasses routing and guarantees that signaling traffic will be\n * terminated in the region that you prefer. Please refer to this table\n * for the list of supported signaling regions.\n * @property {Array} [preferredAudioCodecs=[]] - Preferred audio codecs;\n * An empty array preserves the current audio codec preference order.\n * @property {Array|VideoEncodingMode} [preferredVideoCodecs=[]] -\n * Preferred video codecs; when set to 'VideoEncodingMode.Auto', SDK manages the video codec,\n * by preferring VP8 simulcast in group rooms. It also enables adaptive simulcast, which allows SDK\n * to turn off simulcast layers that are not needed for efficient bandwidth and CPU usage.\n * An empty array preserves the current video codec.\n * preference order. If you want to set a preferred video codec on a Group Room,\n * you will need to create the Room using the REST API and set the\n * VideoCodecs property.\n * See \n * here for more information.\n * @property {LogLevel|LogLevels} [logLevel='warn'] - (deprecated: use [Video.Logger](module-twilio-video.html) instead.\n * See [examples](module-twilio-video.html#.connect) for details)\n * Set the default log verbosity\n * of logging. Passing a {@link LogLevel} string will use the same\n * level for all components. Pass a {@link LogLevels} to set specific log\n * levels.\n * @property {string} [loggerName='twilio-video'] - The name of the logger. Use this name when accessing the logger used by the SDK.\n * See [examples](module-twilio-video.html#.connect) for details.\n * @property {Array} [tracks] - The\n * {@link LocalTrack}s or MediaStreamTracks with which to join the\n * {@link Room}. These tracks can be obtained either by calling\n * {@link createLocalTracks}, or by constructing them from the MediaStream\n * obtained by calling getUserMedia().\n * @property {boolean|CreateLocalTrackOptions} [video=true] - Whether or not to\n * get local video with getUserMedia when tracks\n * are not provided.\n */\n\n/**\n * {@link BandwidthProfileOptions} allows you to configure how your available downlink\n * bandwidth is shared among the RemoteTracks you have subscribed to in a Group Room.\n * @typedef {object} BandwidthProfileOptions\n * @property {VideoBandwidthProfileOptions} [video] - Optional parameter to configure\n * how your available downlink bandwidth is shared among the {@link RemoteVideoTrack}s you\n * have subscribed to in a Group Room.\n */\n\n/**\n * {@link VideoBandwidthProfileOptions} allows you to configure how your available downlink\n * bandwidth is shared among the {@link RemoteVideoTrack}s you have subscribed to in a Group Room.\n * @typedef {object} VideoBandwidthProfileOptions\n * @property {Track.Priority} [dominantSpeakerPriority=\"standard\"] - Optional parameter to\n * specify the minimum subscribe {@link Track.Priority} of the Dominant Speaker's {@link RemoteVideoTrack}s.\n * This means that the Dominant Speaker's {@link RemoteVideoTrack}s that are published with\n * lower {@link Track.Priority} will be subscribed to with the {@link Track.Priority} specified here.\n * This has no effect on {@link RemoteVideoTrack}s published with higher {@link Track.Priority}, which will\n * still be subscribed to with with the same {@link Track.Priority}. If not specified, this defaults to \"standard\".\n * This parameter only applies to a Group Room Participant when {@link ConnectOptions}.dominantSpeaker is set to true.\n * @property {number} [maxSubscriptionBitrate] - Optional parameter to specify the maximum\n * downlink video bandwidth in bits per second (bps). By default, there are no limits on\n * the downlink video bandwidth.\n * @property {ClientTrackSwitchOffControl} [clientTrackSwitchOffControl=\"auto\"] - Optional parameter that determines\n * when to turn the {@link RemoteVideoTrack} on or off. When set to \"auto\", SDK will use the visibility of the\n * attached elements to determine if the {@link RemoteVideoTrack} should be turned off or on. When the attached video elements become invisible the {@link RemoteVideoTrack} will\n * be turned off, and when elements become visible they will be turned on. When set to \"manual\" you can turn the {@link RemoteVideoTrack}\n * on and off using the api {@link RemoteVideoTrack#switchOn} and {@link RemoteVideoTrack#switchOff} respectively.\n * @property {VideoContentPreferencesMode} [contentPreferencesMode=\"auto\"] - This Optional parameter configures\n * the mode for specifying content preferences for the {@link RemoteVideoTrack}. When set to \"auto\" the\n * SDK determines the render dimensions by inspecting the attached video elements. {@link RemoteVideoTrack}s rendered in smaller video elements\n * will receive a lower resolution stream compared to the video rendered in larger video elements. When set to \"manual\" you can set\n * the dimensions programmatically by calling {@link RemoteVideoTrack#setContentPreferences}.\n * @property {number} [maxTracks] - (deprecated: use \"clientTrackSwitchOffControl\" instead). Optional\n * parameter to specify the maximum number of visible {@link RemoteVideoTrack}s, which will be selected based on\n * {@link Track.Priority} and an N-Loudest policy. By default there are no limits on the number of visible {@link RemoteVideoTrack}s.\n * 0 or a negative value will remove any limit on the maximum number of visible {@link RemoteVideoTrack}s.\n * @property {BandwidthProfileMode} [mode=\"grid\"] - Optional parameter to specify how the {@link RemoteVideoTrack}s'\n * TrackPriority values are mapped to bandwidth allocation in Group Rooms. This defaults to \"grid\",\n * which results in equal bandwidth share allocation to all {@link RemoteVideoTrack}s.\n * @property {VideoRenderDimensions} [renderDimensions] - (deprecated: use \"contentPreferencesMode\" instead). Optional\n * parameter to specify the desired render dimensions of {@link RemoteVideoTrack}s.\n * @property {TrackSwitchOffMode} [trackSwitchOffMode=\"predicted\"] - Optional parameter to configure\n * how {@link RemoteVideoTrack}s are switched off in response to bandwidth pressure. Defaults to \"predicted\".\n */\n\n/**\n * @deprecated\n * {@link VideoRenderDimensions} allows you to specify the desired render dimensions of {@link RemoteVideoTrack}s.\n * You can specify 'auto' for this field - which is also default value - based on {@link Track.Priority}. The bandwidth allocation algorithm will distribute the available downlink bandwidth\n * proportional to the requested render dimensions. This is just an input for calculating the bandwidth to be allocated\n * and does not affect the actual resolution of the {@link RemoteVideoTrack}s.\n * @typedef {object} VideoRenderDimensions\n * @property {VideoTrack.Dimensions} [high] - Optional parameter to specify the desired rendering dimensions of\n * {@link RemoteVideoTrack} whose {@link Track.Priority} is \"high\". 0 or a negative value will result in the lowest\n * possible resolution. This defaults to 1280 x 720 (HD).\n * @property {VideoTrack.Dimensions} [low] - Optional parameter to specify the desired rendering dimensions of\n * {@link RemoteVideoTrack} whose {@link Track.Priority} is \"low\". 0 or a negative value will result in the lowest\n * possible resolution. This defaults to 176 x 144 (QCIF).\n * @property {VideoTrack.Dimensions} [standard] - Optional parameter to specify the desired rendering dimensions of\n * {@link RemoteVideoTrack} whose {@link Track.Priority} is \"standard\". 0 or a negative value will result in the lowest\n * possible resolution. This defaults to 640 x 480 (VGA).\n */\n\n/**\n * Configure verbosity levels for network quality information for\n * {@link LocalParticipant} and {@link RemoteParticipant}s.\n * @typedef {object} NetworkQualityConfiguration\n * @property {NetworkQualityVerbosity} [local=1] - Verbosity level for {@link LocalParticipant}\n * @property {NetworkQualityVerbosity} [remote=0] - Verbosity level for {@link RemoteParticipant}s\n */\n\n/**\n * You may pass these levels to {@link ConnectOptions} to override\n * log levels for individual components.\n * @typedef {object} LogLevels\n * @property {LogLevel} [default='warn'] - Log level for 'default' modules.\n * @property {LogLevel} [media='warn'] - Log level for 'media' modules.\n * @property {LogLevel} [signaling='warn'] - Log level for 'signaling' modules.\n * @property {LogLevel} [webrtc='warn'] - Log level for 'webrtc' modules.\n */\n\n/**\n * Audio codec settings.\n * @typedef {object} AudioCodecSettings\n * @property {AudioCodec} codec - Audio codec name\n */\n\n/**\n * Opus codec settings.\n * @typedef {AudioCodecSettings} OpusCodecSettings\n * @property {AudioCodec} name - \"opus\"\n * @property {boolean} [dtx=true] - Enable/disable discontinuous transmission (DTX);\n * If enabled all published {@link LocalAudioTrack}s will reduce the outgoing bitrate\n * to near-zero whenever speech is not detected, resulting in bandwidth and CPU savings;\n * It defaults to true.\n */\n\n/**\n * Video codec settings.\n * @typedef {object} VideoCodecSettings\n * @property {VideoCodec} codec - Video codec name\n */\n\n/**\n * VP8 codec settings.\n * @typedef {VideoCodecSettings} VP8CodecSettings\n * @property {VideoCodec} name - \"VP8\"\n * @property {boolean} [simulcast=false] - Enable/disable VP8 simulcast; If\n * enabled, Twilio's Video SDK will send three video streams of different\n * qualities\n */\n\n/**\n * Names of the supported audio codecs.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst AudioCodec = {\n isac: 'isac',\n opus: 'opus',\n PCMA: 'PCMA',\n PCMU: 'PCMU'\n};\n\n/**\n * Names of the supported VideoEncodingMode.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst VideoEncodingMode = {\n Auto: 'auto',\n};\n\n\n/**\n * Names of the supported video codecs.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst VideoCodec = {\n H264: 'H264',\n VP8: 'VP8'\n};\n// VP9 is supported by most browsers, but backend doesn't at the moment.\n// Hide it from public documentation until then.\nVideoCodec.VP9 = 'VP9';\n\n/**\n * Levels for logging verbosity.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst LogLevel = {\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n off: 'off'\n};\n\n/**\n * The verbosity level of network quality information of a {@link Participant}.\n * @enum {number}\n */\n// eslint-disable-next-line\nconst NetworkQualityVerbosity = {\n /**\n * Nothing is reported for the {@link Participant}. This has no effect and\n * defaults to {@link NetworkQualityVerbosity}#minimal\n * for the {@link LocalParticipant}.\n */\n none: 0,\n /**\n * Reports {@link NetworkQualityLevel} for the {@link Participant}.\n */\n minimal: 1,\n /**\n * Reports {@link NetworkQualityLevel} and {@link NetworkQualityStats} for the {@link Participant}.\n * {@link NetworkQualityStats} is populated with audio and video {@link NetworkQualityLevel}s\n * based on which the {@link Participant}'s {@link NetworkQualityLevel} is calculated.\n */\n moderate: 2,\n /**\n * Reports {@link NetworkQualityLevel} and {@link NetworkQualityStats} for the {@link Participant}.\n * {@link NetworkQualityStats} is populated with audio and Video {@link NetworkQualityLevel}s\n * and their corresponding {@link NetworkQualityMediaStats} based on which the\n * {@link Participant}'s {@link NetworkQualityLevel} is calculated.\n */\n detailed: 3\n};\n\n\n/**\n * {@link TrackSwitchOffMode} specifies when {@link RemoteVideoTrack}s' are switched off.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst TrackSwitchOffMode = {\n /**\n * In this mode, {@link RemoteVideoTrack}s are switched off only when network congestion\n * is detected.\n */\n detected: 'detected',\n\n /**\n * In this mode, {@link RemoteVideoTrack}s are pro-actively switched off when network\n * congestion is predicted by the bandwidth estimation mechanism.\n */\n predicted: 'predicted',\n\n /**\n * In this mode, {@link RemoteVideoTrack}s are not switched off. Instead in response to network\n * congestion, tracks will be adjusted to lower quality.\n */\n disabled: 'disabled'\n};\n\n/**\n * {@link BandwidthProfileMode} specifies how {@link RemoteVideoTrack}s' {@link Track.Priority} values\n * are mapped to bandwidth allocation in Group Rooms.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst BandwidthProfileMode = {\n /**\n * This mode is for use cases where all the subscribed {@link RemoteVideoTrack}s are\n * equally important. The bandwidth allocation algorithm will share the available\n * downlink bandwidth equally among the subscribed {@link RemoteVideoTrack}s, irrespective\n * of their {@link Track.Priority}. In case of insufficient downlink bandwidth, the lower\n * priority {@link RemoteVideoTrack}s are switched off.\n */\n grid: 'grid',\n /**\n * This mode is for use cases where some {@link RemoteVideoTrack}s are prioritized more than\n * others. However, the lower priority {@link RemoteVideoTrack}s still need to be visible.\n * The bandwidth allocation algorithm will share the available downlink bandwidth proportional\n * to the requested {@link VideoRenderDimensions} corresponding to their {@link Track.Priority}.\n * In case of insufficient downlink bandwidth, the quality of higher priority {@link RemoteVideoTrack}s\n * may be degraded to avoid switching off lower priority {@link RemoteVideoTrack}s.\n */\n collaboration: 'collaboration',\n /**\n * This mode is for use cases where some {@link RemoteVideoTrack}s are deemed critical and must\n * be preserved at any cost over the other {@link RemoteVideoTrack}s. The bandwidth allocation\n * algorithm will allocate as big a share of the available downlink bandwidth as it possibly\n * can to the higher priority {@link RemoteVideoTrack}s, and only then consider the lower priority\n * {@link RemoteVideoTrack}s. In case of insufficient downlink bandwidth, the lower priority\n * {@link RemoteVideoTrack}s are switched off in order to preserve the quality of the higher\n * priority {@link RemoteVideoTrack}s.\n */\n presentation: 'presentation'\n};\n\n/**\n * {@link VideoContentPreferencesMode} specifies how {@link RemoteVideoTrack}s' render dimensions are\n * decided by the SDK.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst VideoContentPreferencesMode = {\n /**\n * when set to auto, SDK uses the sizes of the video elements attached to the to the {@link RemoteVideoTrack} dynamically to\n * decide the render dimensions. {@link RemoteVideoTrack}s rendered in smaller video elements will be given smaller bandwidth allocation\n * compared to the tracks rendered in large video elements.\n */\n auto: 'auto',\n /**\n * When set to manual, application can use {@link RemoteVideoTrack#setContentPreference} to set the\n * desired render dimensions for the {@link RemoteVideoTrack}.\n */\n manual: 'manual'\n};\n\n\n/**\n * {@link ClientTrackSwitchOffControl} specifies how {@link RemoteVideoTrack}s' turned on and off\n * @enum {string}\n */\n// eslint-disable-next-line\nconst ClientTrackSwitchOffControl = {\n /**\n * when set to auto, SDK uses the visibility of the video elements attached to the to the {@link RemoteVideoTrack} to decide.\n * on turning tracks on or off. The track that are not attached to any video elements or not visible on the screen will be turned\n * off automatically.\n */\n auto: 'auto',\n\n /**\n * When set to manual, application can use {@link RemoteVideoTrack}s switchOff and switchOn apis to control turn the track on or off.\n */\n manual: 'manual'\n};\n\n\n/**\n * Names of the supported levels for {@link EventListenerEvent}s.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst EventListenerLevel = {\n debug: 'debug',\n error: 'error',\n info: 'info',\n warning: 'warning'\n};\n\n/**\n * Names of the supported groups for {@link EventListenerEvent}s.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst EventListenerGroup = {\n /**\n * Events associated with the connection to Twilio's signaling server\n */\n signaling: 'signaling'\n};\n\n/**\n * An {@link EventListener} allows you to listen to fine-grained {@link EventListenerEvent}s related\n * to signaling and media that are not available in the public APIs, which might be useful for your own\n * reporting and diagnostics.\n * @typedef {EventEmitter} EventListener\n * @example\n * const { EventEmitter } = require('events');\n * const { connect } = require('twilio-video');\n *\n * const eventListener = new EventEmitter();\n * eventListener.on('event', function(event) {\n * console.log('The SDK raised an event:', event);\n * });\n *\n * connect('token', {\n * eventListener: eventListener\n * });\n */\n\n/**\n * The SDK raised an {@link EventListenerEvent}.\n * @event EventListener#event\n * @param {EventListenerEvent} event - Context about the event raised by the SDK.\n * This can be one of the following:\n * * {@link EventListenerClosedEvent}\n * * {@link EventListenerConnectingEvent}\n * * {@link EventListenerEarlyEvent}\n * * {@link EventListenerOpenEvent}\n * * {@link EventListenerWaitingEvent}\n */\n\n/**\n * An {@link EventListenerEvent} provides context about an event raised by the SDK on the\n * {@link EventListener}. Apart from the properties listed here, it may also include some\n * event-specific data within an optional \"payload\" property. The different types of\n * {@link EventListenerEvent}s are listed below:\n * * {@link EventListenerClosedEvent}\n * * {@link EventListenerConnectingEvent}\n * * {@link EventListenerEarlyEvent}\n * * {@link EventListenerOpenEvent}\n * * {@link EventListenerWaitingEvent}\n * @typedef {object} EventListenerEvent\n * @property {number} elapsedTime - The time elapsed in milliseconds since connect() was called\n * @property {EventListenerGroup} group - The group under which the event is classified\n * @property {EventListenerLevel} level - The verbosity level of the event, which can be one of \"debug\", \"error\", \"info\", \"warning\"\n * @property {string} name - The name of the event\n * @property {*} [payload] - Optional event-specific data\n * @property {number} timestamp - The time in milliseconds relative to the Unix Epoch when the event was raised\n */\n\n/**\n * The connection to Twilio's signaling server was closed.\n * @typedef {EventListenerEvent} EventListenerClosedEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level - 'info' if the connection was closed by the client, 'error' otherwise\n * @property {string} name='closed'\n * @property {{reason: string}} payload - Reason for the connection being closed. It can be one of\n * 'busy', 'failed', 'local', 'remote' or 'timeout'\n */\n\n/**\n * The SDK is connecting to Twilio's signaling server.\n * @typedef {EventListenerEvent} EventListenerConnectingEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='info'\n * @property {string} name='connecting'\n */\n\n/**\n * The SDK is about to connect to Twilio's signaling server.\n * @typedef {EventListenerEvent} EventListenerEarlyEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='info'\n * @property {string} name='early'\n */\n\n/**\n * The SDK has established a signaling connection to Twilio's signaling server.\n * @typedef {EventListenerEvent} EventListenerOpenEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='info'\n * @property {string} name='open'\n */\n\n/**\n * The SDK is waiting to retry connecting th Twilio's signaling server. This can\n * happen if the server is busy with too many connection requests.\n * @typedef {EventListenerEvent} EventListenerWaitingEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='warning'\n * @property {string} name='waiting'\n */\n\n\nfunction deprecateOptions(options, log, deprecationTable) {\n deprecationTable.forEach(prop => {\n const { didWarn, name, newName, shouldDelete } = prop;\n if (name in options && typeof options[name] !== 'undefined') {\n if (newName && shouldDelete) {\n options[newName] = options[name];\n }\n if (shouldDelete) {\n delete options[name];\n }\n if (!didWarn && !['error', 'off'].includes(log.level)) {\n log.warn(`The ConnectOptions \"${name}\" is ${newName\n ? `deprecated and scheduled for removal. Please use \"${newName}\" instead.`\n : 'no longer applicable and will be ignored.'}`);\n prop.didWarn = true;\n }\n }\n });\n}\n\nfunction createLocalParticipant(signaling, log, encodingParameters, networkQualityConfiguration, options, localTracks) {\n const localParticipantSignaling = signaling.createLocalParticipantSignaling(encodingParameters, networkQualityConfiguration);\n log.debug('Creating a new LocalParticipant:', localParticipantSignaling);\n return new options.LocalParticipant(localParticipantSignaling, localTracks, options);\n}\n\nfunction createRoom(options, localParticipant, roomSignaling) {\n const room = new Room(localParticipant, roomSignaling, options);\n const log = options.log;\n\n log.debug('Creating a new Room:', room);\n roomSignaling.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n log.info('Disconnected from Room:', room.toString());\n roomSignaling.removeListener('stateChanged', stateChanged);\n }\n });\n\n return room;\n}\n\nfunction createRoomSignaling(token, options, signaling, encodingParameters, preferredCodecs, localParticipant) {\n options.log.debug('Creating a new RoomSignaling');\n return signaling.connect(\n localParticipant._signaling,\n token,\n encodingParameters,\n preferredCodecs,\n options);\n}\n\nfunction getLocalTracks(options, handleLocalTracks) {\n const log = options.log;\n\n options.shouldStopLocalTracks = !options.tracks;\n if (options.shouldStopLocalTracks) {\n log.info('LocalTracks were not provided, so they will be acquired '\n + 'automatically before connecting to the Room. LocalTracks will '\n + 'be released if connecting to the Room fails or if the Room '\n + 'is disconnected');\n } else {\n log.info('Getting LocalTracks');\n log.debug('Options:', options);\n }\n\n return options.createLocalTracks(options).then(function getLocalTracksSucceeded(localTracks) {\n const promise = handleLocalTracks(localTracks);\n\n promise.catch(function handleLocalTracksFailed() {\n if (options.shouldStopLocalTracks) {\n log.info('The automatically acquired LocalTracks will now be stopped');\n localTracks.forEach(track => {\n track.stop();\n });\n }\n });\n\n return promise;\n });\n}\n\nfunction normalizeCodecSettings(nameOrSettings) {\n const settings = typeof nameOrSettings === 'string'\n ? { codec: nameOrSettings }\n : nameOrSettings;\n switch (settings.codec.toLowerCase()) {\n case 'opus': {\n return Object.assign({ dtx: true }, settings);\n }\n case 'vp8': {\n return Object.assign({ simulcast: false }, settings);\n }\n default: {\n return settings;\n }\n }\n}\n\nmodule.exports = connect;\n", "'use strict';\n\nconst { DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME } = require('./util/constants');\n\n/**\n * Request a {@link LocalAudioTrack} or {@link LocalVideoTrack}.\n * @param {Track.Kind} kind - \"audio\" or \"video\"\n * @param {CreateLocalTrackOptions} [options]\n * @returns {Promise}\n * @private\n */\nfunction createLocalTrack(kind, options) {\n options = Object.assign({\n loggerName: DEFAULT_LOGGER_NAME,\n logLevel: DEFAULT_LOG_LEVEL,\n }, options);\n\n const createOptions = {};\n createOptions.loggerName = options.loggerName;\n createOptions.logLevel = options.logLevel;\n delete options.loggerName;\n delete options.logLevel;\n\n const createLocalTracks = options.createLocalTracks;\n delete options.createLocalTracks;\n createOptions[kind] = Object.keys(options).length > 0 ? options : true;\n\n return createLocalTracks(createOptions).then(localTracks => localTracks[0]);\n}\n\n/**\n * Request a {@link LocalAudioTrack}.\n * @alias module:twilio-video.createLocalAudioTrack\n * @param {CreateLocalTracksOptions|CreateLocalAudioTrackOptions} [options] - Options for requesting a {@link LocalAudioTrack}\n * @returns {Promise}\n * @example\n * var Video = require('twilio-video');\n *\n * // Connect to the Room with just video\n * Video.connect('my-token', {\n * name: 'my-cool-room',\n * video: true\n * }).then(function(room) {\n * // Add audio after connecting to the Room\n * Video.createLocalAudioTrack().then(function(localTrack) {\n * room.localParticipant.publishTrack(localTrack);\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n *\n * // Request the LocalAudioTrack with a custom name\n * // and krisp noise cancellation\n * Video.createLocalAudioTrack({\n * name: 'microphone',\n * noiseCancellationOptions: {\n * vendor: 'krisp',\n * sdkAssetsPath: '/twilio-krisp-audio-plugin/1.0.0/dist'\n * }\n * });\n */\nfunction createLocalAudioTrack(options) {\n return createLocalTrack('audio', options);\n}\n\n/**\n * Request a {@link LocalVideoTrack}. Note that on mobile browsers,\n * the camera can be reserved by only one {@link LocalVideoTrack} at any given\n * time. If you attempt to create a second {@link LocalVideoTrack}, video frames\n * will no longer be supplied to the first {@link LocalVideoTrack}.\n * @alias module:twilio-video.createLocalVideoTrack\n * @param {CreateLocalTrackOptions} [options] - Options for requesting a {@link LocalVideoTrack}\n * @returns {Promise}\n * @example\n * var Video = require('twilio-video');\n *\n * // Connect to the Room with just audio\n * Video.connect('my-token', {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * // Add video after connecting to the Room\n * Video.createLocalVideoTrack().then(function(localTrack) {\n * room.localParticipant.publishTrack(localTrack);\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n *\n * // Request the default LocalVideoTrack with a custom name\n * Video.createLocalVideoTrack({ name: 'camera' }).then(function(localTrack) {\n * console.log(localTrack.name); // 'camera'\n * });\n */\nfunction createLocalVideoTrack(options) {\n return createLocalTrack('video', options);\n}\n\n/**\n * {@link NoiseCancellationVendor} specifies the 3rd party plugin to use for noise cancellation.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst NoiseCancellationVendor = {\n /**\n * This plugin can be found by requesting access with this form {@link https://forms.gle/eeFyoGJj1mgMrxN88}\n */\n krisp: 'krisp',\n};\n\n/**\n * You can use 3rd party noise cancellation plugin when creating {@link LocalAudioTrack}\n * By specifying these options. This is a beta feature.\n * @typedef {object} NoiseCancellationOptions\n * @property {NoiseCancellationVendor} vendor - Specifies the vendor library to use\n * You need to obtain and host the library files on your web server.\n * @property {string} sdkAssetsPath - Specifies path where vendor library files are\n * hosted on your web server.\n */\n\n/**\n * Create {@link LocalAudioTrack} options.\n * @typedef {CreateLocalTrackOptions} CreateLocalAudioTrackOptions\n * @property {boolean} [workaroundWebKitBug180748=false] - setting this\n * attempts to workaround WebKit Bug 180748, where, in Safari, getUserMedia may return a silent audio\n * MediaStreamTrack.\n * @property {DefaultDeviceCaptureMode} [defaultDeviceCaptureMode=\"auto\"] - This optional property only applies if the\n * {@link LocalAudioTrack} is capturing from the default audio input device connected to a desktop or laptop. When the\n * property is set to \"auto\", the LocalAudioTrack restarts whenever the default audio input device changes, in order to\n * capture audio from the new default audio input device. For example, when a bluetooth audio headset is connected to a\n * Macbook, the LocalAudioTrack will start capturing audio from the headset microphone. When the headset is disconnected,\n * the LocalAudioTrack will start capturing audio from the Macbook microphone. When the property is set to \"manual\", the\n * LocalAudioTrack continues to capture from the same audio input device even after the default audio input device changes.\n * When the property is not specified, it defaults to \"auto\".\n * @property {NoiseCancellationOptions} [noiseCancellationOptions] - This optional property enables using 3rd party plugins\n * for noise cancellation.\n */\n\n/**\n * Create {@link LocalTrack} options. Apart from the properties listed here, you can\n * also specify any of the MediaTrackConstraints\n * properties.\n * @typedef {MediaTrackConstraints} CreateLocalTrackOptions\n * @property {LogLevel|LogLevels} [logLevel='warn'] - (deprecated: use [Video.Logger](module-twilio-video.html) instead.\n * See [examples](module-twilio-video.html#.connect) for details)\n * Set the default log verbosity\n * of logging. Passing a {@link LogLevel} string will use the same\n * level for all components. Pass a {@link LogLevels} to set specific log\n * levels.\n * @property {string} [loggerName='twilio-video'] - The name of the logger. Use this name when accessing the logger used by the SDK.\n * See [examples](module-twilio-video.html#.connect) for details.\n * @property {string} [name] - The {@link LocalTrack}'s name; by default,\n * it is set to the {@link LocalTrack}'s ID.\n */\n\nmodule.exports = {\n audio: createLocalAudioTrack,\n video: createLocalVideoTrack\n};\n", "'use strict';\n\nconst { guessBrowser, support: isWebRTCSupported } = require('../webrtc/util');\nconst { getSdpFormat } = require('../webrtc/util/sdp');\nconst { isAndroid, isMobile, isNonChromiumEdge, rebrandedChromeBrowser, mobileWebKitBrowser } = require('./browserdetection');\n\nconst SUPPORTED_CHROME_BASED_BROWSERS = [\n 'crios',\n 'edg',\n 'edge',\n 'electron',\n 'headlesschrome'\n];\nconst SUPPORTED_ANDROID_BROWSERS = [\n 'chrome',\n 'firefox'\n];\nconst SUPPORTED_IOS_BROWSERS = [\n 'chrome',\n 'safari'\n];\n// Currently none. Add 'brave', 'edg', and 'edge' here once we start supporting them\nconst SUPPORTED_MOBILE_WEBKIT_BASED_BROWSERS = [];\n\n/**\n * Check if the current browser is officially supported by twilio-video.js.\n * @returns {boolean}\n */\nfunction isSupported() {\n const browser = guessBrowser();\n\n // NOTE (csantos): Return right away if there is no browser detected\n // to prevent unnecessary checks which could lead to errors\n if (!browser) {\n return false;\n }\n\n const rebrandedChrome = rebrandedChromeBrowser(browser);\n const mobileWebKit = mobileWebKitBrowser(browser);\n const supportedMobileBrowsers = isAndroid() ?\n SUPPORTED_ANDROID_BROWSERS : SUPPORTED_IOS_BROWSERS;\n\n return !!browser\n && isWebRTCSupported()\n && getSdpFormat() === 'unified'\n && (!rebrandedChrome || SUPPORTED_CHROME_BASED_BROWSERS.includes(rebrandedChrome))\n && !isNonChromiumEdge(browser)\n && (!mobileWebKit || SUPPORTED_MOBILE_WEBKIT_BASED_BROWSERS.includes(mobileWebKit))\n && (!isMobile() || supportedMobileBrowsers.includes(browser));\n}\n\nmodule.exports = isSupported;\n", "'use strict';\n\nimport type { ConnectOptions, CreateLocalTrackOptions, CreateLocalAudioTrackOptions } from '../tsdef/types';\nimport type { LocalAudioTrack as LocalAudioTrackType } from '../tsdef/LocalAudioTrack';\nimport type { LocalVideoTrack as LocalVideoTrackType } from '../tsdef/LocalVideoTrack';\nimport type { Log } from '../tsdef/loglevel';\nimport type { Room } from '../tsdef/Room';\nimport { createLocalTracks } from './createlocaltracks';\nimport { runPreflight } from './preflight/preflighttest';\n\n\nconst internals = {\n connect: require('./connect'),\n createLocalAudioTrack: require('./createlocaltrack').audio,\n createLocalVideoTrack: require('./createlocaltrack').video,\n isSupported: require('./util/support')(),\n version: require('../package.json').version,\n Logger: require('./vendor/loglevel'),\n LocalAudioTrack: require('./media/track/es5').LocalAudioTrack,\n LocalDataTrack: require('./media/track/es5').LocalDataTrack,\n LocalVideoTrack: require('./media/track/es5').LocalVideoTrack\n};\n\nfunction connect(token: string, options?: ConnectOptions): Promise {\n const internalOptions = {\n createLocalTracks,\n ...options\n };\n return internals.connect(token, internalOptions);\n}\n\nfunction createLocalAudioTrack(options?: CreateLocalTrackOptions|CreateLocalAudioTrackOptions): Promise {\n const internalOptions = {\n createLocalTracks,\n ...options\n };\n return internals.createLocalAudioTrack(internalOptions);\n}\n\nfunction createLocalVideoTrack(options?: CreateLocalTrackOptions): Promise {\n const internalOptions = {\n createLocalTracks,\n ...options\n };\n return internals.createLocalVideoTrack(internalOptions);\n}\n\n/**\n * @module twilio-video\n * @property {boolean} isSupported - true if the current browser is officially\n * supported by twilio-video.js; In this context, \"supported\" means that\n * twilio-video.js has been extensively tested with this browser; This\n * table\n * specifies the list of officially supported browsers.\n *\n * @property {object} Logger - The loglevel\n * module used by the SDK. Use this object to access the internal loggers and perform actions as defined by the\n * loglevel APIs.\n * See [connect](#.connect) for examples.\n *\n * @property {string} version - current version of twilio-video.js.\n */\n\nconst isSupported: boolean = internals.isSupported;\nconst version: boolean = internals.version;\nconst Logger: Log.RootLogger = internals.Logger;\nconst LocalAudioTrack = internals.LocalAudioTrack;\nconst LocalVideoTrack = internals.LocalVideoTrack;\nconst LocalDataTrack = internals.LocalDataTrack;\n\nmodule.exports = {\n connect,\n createLocalAudioTrack,\n createLocalVideoTrack,\n createLocalTracks,\n runPreflight,\n isSupported,\n version,\n Logger,\n LocalAudioTrack,\n LocalVideoTrack,\n LocalDataTrack,\n};\n", "/*\nUnobtrusive JavaScript\nhttps://github.com/rails/rails/blob/main/actionview/app/javascript\nReleased under the MIT license\n */\nconst linkClickSelector = \"a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]\";\n\nconst buttonClickSelector = {\n selector: \"button[data-remote]:not([form]), button[data-confirm]:not([form])\",\n exclude: \"form button\"\n};\n\nconst inputChangeSelector = \"select[data-remote], input[data-remote], textarea[data-remote]\";\n\nconst formSubmitSelector = \"form:not([data-turbo=true])\";\n\nconst formInputClickSelector = \"form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])\";\n\nconst formDisableSelector = \"input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled\";\n\nconst formEnableSelector = \"input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled\";\n\nconst fileInputSelector = \"input[name][type=file]:not([disabled])\";\n\nconst linkDisableSelector = \"a[data-disable-with], a[data-disable]\";\n\nconst buttonDisableSelector = \"button[data-remote][data-disable-with], button[data-remote][data-disable]\";\n\nlet nonce = null;\n\nconst loadCSPNonce = () => {\n const metaTag = document.querySelector(\"meta[name=csp-nonce]\");\n return nonce = metaTag && metaTag.content;\n};\n\nconst cspNonce = () => nonce || loadCSPNonce();\n\nconst m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;\n\nconst matches = function(element, selector) {\n if (selector.exclude) {\n return m.call(element, selector.selector) && !m.call(element, selector.exclude);\n } else {\n return m.call(element, selector);\n }\n};\n\nconst EXPANDO = \"_ujsData\";\n\nconst getData = (element, key) => element[EXPANDO] ? element[EXPANDO][key] : undefined;\n\nconst setData = function(element, key, value) {\n if (!element[EXPANDO]) {\n element[EXPANDO] = {};\n }\n return element[EXPANDO][key] = value;\n};\n\nconst $ = selector => Array.prototype.slice.call(document.querySelectorAll(selector));\n\nconst isContentEditable = function(element) {\n var isEditable = false;\n do {\n if (element.isContentEditable) {\n isEditable = true;\n break;\n }\n element = element.parentElement;\n } while (element);\n return isEditable;\n};\n\nconst csrfToken = () => {\n const meta = document.querySelector(\"meta[name=csrf-token]\");\n return meta && meta.content;\n};\n\nconst csrfParam = () => {\n const meta = document.querySelector(\"meta[name=csrf-param]\");\n return meta && meta.content;\n};\n\nconst CSRFProtection = xhr => {\n const token = csrfToken();\n if (token) {\n return xhr.setRequestHeader(\"X-CSRF-Token\", token);\n }\n};\n\nconst refreshCSRFTokens = () => {\n const token = csrfToken();\n const param = csrfParam();\n if (token && param) {\n return $('form input[name=\"' + param + '\"]').forEach((input => input.value = token));\n }\n};\n\nconst AcceptHeaders = {\n \"*\": \"*/*\",\n text: \"text/plain\",\n html: \"text/html\",\n xml: \"application/xml, text/xml\",\n json: \"application/json, text/javascript\",\n script: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n};\n\nconst ajax = options => {\n options = prepareOptions(options);\n var xhr = createXHR(options, (function() {\n const response = processResponse(xhr.response != null ? xhr.response : xhr.responseText, xhr.getResponseHeader(\"Content-Type\"));\n if (Math.floor(xhr.status / 100) === 2) {\n if (typeof options.success === \"function\") {\n options.success(response, xhr.statusText, xhr);\n }\n } else {\n if (typeof options.error === \"function\") {\n options.error(response, xhr.statusText, xhr);\n }\n }\n return typeof options.complete === \"function\" ? options.complete(xhr, xhr.statusText) : undefined;\n }));\n if (options.beforeSend && !options.beforeSend(xhr, options)) {\n return false;\n }\n if (xhr.readyState === XMLHttpRequest.OPENED) {\n return xhr.send(options.data);\n }\n};\n\nvar prepareOptions = function(options) {\n options.url = options.url || location.href;\n options.type = options.type.toUpperCase();\n if (options.type === \"GET\" && options.data) {\n if (options.url.indexOf(\"?\") < 0) {\n options.url += \"?\" + options.data;\n } else {\n options.url += \"&\" + options.data;\n }\n }\n if (!(options.dataType in AcceptHeaders)) {\n options.dataType = \"*\";\n }\n options.accept = AcceptHeaders[options.dataType];\n if (options.dataType !== \"*\") {\n options.accept += \", */*; q=0.01\";\n }\n return options;\n};\n\nvar createXHR = function(options, done) {\n const xhr = new XMLHttpRequest;\n xhr.open(options.type, options.url, true);\n xhr.setRequestHeader(\"Accept\", options.accept);\n if (typeof options.data === \"string\") {\n xhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded; charset=UTF-8\");\n }\n if (!options.crossDomain) {\n xhr.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\");\n CSRFProtection(xhr);\n }\n xhr.withCredentials = !!options.withCredentials;\n xhr.onreadystatechange = function() {\n if (xhr.readyState === XMLHttpRequest.DONE) {\n return done(xhr);\n }\n };\n return xhr;\n};\n\nvar processResponse = function(response, type) {\n if (typeof response === \"string\" && typeof type === \"string\") {\n if (type.match(/\\bjson\\b/)) {\n try {\n response = JSON.parse(response);\n } catch (error) {}\n } else if (type.match(/\\b(?:java|ecma)script\\b/)) {\n const script = document.createElement(\"script\");\n script.setAttribute(\"nonce\", cspNonce());\n script.text = response;\n document.head.appendChild(script).parentNode.removeChild(script);\n } else if (type.match(/\\b(xml|html|svg)\\b/)) {\n const parser = new DOMParser;\n type = type.replace(/;.+/, \"\");\n try {\n response = parser.parseFromString(response, type);\n } catch (error1) {}\n }\n }\n return response;\n};\n\nconst href = element => element.href;\n\nconst isCrossDomain = function(url) {\n const originAnchor = document.createElement(\"a\");\n originAnchor.href = location.href;\n const urlAnchor = document.createElement(\"a\");\n try {\n urlAnchor.href = url;\n return !((!urlAnchor.protocol || urlAnchor.protocol === \":\") && !urlAnchor.host || originAnchor.protocol + \"//\" + originAnchor.host === urlAnchor.protocol + \"//\" + urlAnchor.host);\n } catch (e) {\n return true;\n }\n};\n\nlet preventDefault;\n\nlet {CustomEvent: CustomEvent} = window;\n\nif (typeof CustomEvent !== \"function\") {\n CustomEvent = function(event, params) {\n const evt = document.createEvent(\"CustomEvent\");\n evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);\n return evt;\n };\n CustomEvent.prototype = window.Event.prototype;\n ({preventDefault: preventDefault} = CustomEvent.prototype);\n CustomEvent.prototype.preventDefault = function() {\n const result = preventDefault.call(this);\n if (this.cancelable && !this.defaultPrevented) {\n Object.defineProperty(this, \"defaultPrevented\", {\n get() {\n return true;\n }\n });\n }\n return result;\n };\n}\n\nconst fire = (obj, name, data) => {\n const event = new CustomEvent(name, {\n bubbles: true,\n cancelable: true,\n detail: data\n });\n obj.dispatchEvent(event);\n return !event.defaultPrevented;\n};\n\nconst stopEverything = e => {\n fire(e.target, \"ujs:everythingStopped\");\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n};\n\nconst delegate = (element, selector, eventType, handler) => element.addEventListener(eventType, (function(e) {\n let {target: target} = e;\n while (!!(target instanceof Element) && !matches(target, selector)) {\n target = target.parentNode;\n }\n if (target instanceof Element && handler.call(target, e) === false) {\n e.preventDefault();\n e.stopPropagation();\n }\n}));\n\nconst toArray = e => Array.prototype.slice.call(e);\n\nconst serializeElement = (element, additionalParam) => {\n let inputs = [ element ];\n if (matches(element, \"form\")) {\n inputs = toArray(element.elements);\n }\n const params = [];\n inputs.forEach((function(input) {\n if (!input.name || input.disabled) {\n return;\n }\n if (matches(input, \"fieldset[disabled] *\")) {\n return;\n }\n if (matches(input, \"select\")) {\n toArray(input.options).forEach((function(option) {\n if (option.selected) {\n params.push({\n name: input.name,\n value: option.value\n });\n }\n }));\n } else if (input.checked || [ \"radio\", \"checkbox\", \"submit\" ].indexOf(input.type) === -1) {\n params.push({\n name: input.name,\n value: input.value\n });\n }\n }));\n if (additionalParam) {\n params.push(additionalParam);\n }\n return params.map((function(param) {\n if (param.name) {\n return `${encodeURIComponent(param.name)}=${encodeURIComponent(param.value)}`;\n } else {\n return param;\n }\n })).join(\"&\");\n};\n\nconst formElements = (form, selector) => {\n if (matches(form, \"form\")) {\n return toArray(form.elements).filter((el => matches(el, selector)));\n } else {\n return toArray(form.querySelectorAll(selector));\n }\n};\n\nconst handleConfirmWithRails = rails => function(e) {\n if (!allowAction(this, rails)) {\n stopEverything(e);\n }\n};\n\nconst confirm = (message, element) => window.confirm(message);\n\nvar allowAction = function(element, rails) {\n let callback;\n const message = element.getAttribute(\"data-confirm\");\n if (!message) {\n return true;\n }\n let answer = false;\n if (fire(element, \"confirm\")) {\n try {\n answer = rails.confirm(message, element);\n } catch (error) {}\n callback = fire(element, \"confirm:complete\", [ answer ]);\n }\n return answer && callback;\n};\n\nconst handleDisabledElement = function(e) {\n const element = this;\n if (element.disabled) {\n stopEverything(e);\n }\n};\n\nconst enableElement = e => {\n let element;\n if (e instanceof Event) {\n if (isXhrRedirect(e)) {\n return;\n }\n element = e.target;\n } else {\n element = e;\n }\n if (isContentEditable(element)) {\n return;\n }\n if (matches(element, linkDisableSelector)) {\n return enableLinkElement(element);\n } else if (matches(element, buttonDisableSelector) || matches(element, formEnableSelector)) {\n return enableFormElement(element);\n } else if (matches(element, formSubmitSelector)) {\n return enableFormElements(element);\n }\n};\n\nconst disableElement = e => {\n const element = e instanceof Event ? e.target : e;\n if (isContentEditable(element)) {\n return;\n }\n if (matches(element, linkDisableSelector)) {\n return disableLinkElement(element);\n } else if (matches(element, buttonDisableSelector) || matches(element, formDisableSelector)) {\n return disableFormElement(element);\n } else if (matches(element, formSubmitSelector)) {\n return disableFormElements(element);\n }\n};\n\nvar disableLinkElement = function(element) {\n if (getData(element, \"ujs:disabled\")) {\n return;\n }\n const replacement = element.getAttribute(\"data-disable-with\");\n if (replacement != null) {\n setData(element, \"ujs:enable-with\", element.innerHTML);\n element.innerHTML = replacement;\n }\n element.addEventListener(\"click\", stopEverything);\n return setData(element, \"ujs:disabled\", true);\n};\n\nvar enableLinkElement = function(element) {\n const originalText = getData(element, \"ujs:enable-with\");\n if (originalText != null) {\n element.innerHTML = originalText;\n setData(element, \"ujs:enable-with\", null);\n }\n element.removeEventListener(\"click\", stopEverything);\n return setData(element, \"ujs:disabled\", null);\n};\n\nvar disableFormElements = form => formElements(form, formDisableSelector).forEach(disableFormElement);\n\nvar disableFormElement = function(element) {\n if (getData(element, \"ujs:disabled\")) {\n return;\n }\n const replacement = element.getAttribute(\"data-disable-with\");\n if (replacement != null) {\n if (matches(element, \"button\")) {\n setData(element, \"ujs:enable-with\", element.innerHTML);\n element.innerHTML = replacement;\n } else {\n setData(element, \"ujs:enable-with\", element.value);\n element.value = replacement;\n }\n }\n element.disabled = true;\n return setData(element, \"ujs:disabled\", true);\n};\n\nvar enableFormElements = form => formElements(form, formEnableSelector).forEach((element => enableFormElement(element)));\n\nvar enableFormElement = function(element) {\n const originalText = getData(element, \"ujs:enable-with\");\n if (originalText != null) {\n if (matches(element, \"button\")) {\n element.innerHTML = originalText;\n } else {\n element.value = originalText;\n }\n setData(element, \"ujs:enable-with\", null);\n }\n element.disabled = false;\n return setData(element, \"ujs:disabled\", null);\n};\n\nvar isXhrRedirect = function(event) {\n const xhr = event.detail ? event.detail[0] : undefined;\n return xhr && xhr.getResponseHeader(\"X-Xhr-Redirect\");\n};\n\nconst handleMethodWithRails = rails => function(e) {\n const link = this;\n const method = link.getAttribute(\"data-method\");\n if (!method) {\n return;\n }\n if (isContentEditable(this)) {\n return;\n }\n const href = rails.href(link);\n const csrfToken$1 = csrfToken();\n const csrfParam$1 = csrfParam();\n const form = document.createElement(\"form\");\n let formContent = ``;\n if (csrfParam$1 && csrfToken$1 && !isCrossDomain(href)) {\n formContent += ``;\n }\n formContent += '';\n form.method = \"post\";\n form.action = href;\n form.target = link.target;\n form.innerHTML = formContent;\n form.style.display = \"none\";\n document.body.appendChild(form);\n form.querySelector('[type=\"submit\"]').click();\n stopEverything(e);\n};\n\nconst isRemote = function(element) {\n const value = element.getAttribute(\"data-remote\");\n return value != null && value !== \"false\";\n};\n\nconst handleRemoteWithRails = rails => function(e) {\n let data, method, url;\n const element = this;\n if (!isRemote(element)) {\n return true;\n }\n if (!fire(element, \"ajax:before\")) {\n fire(element, \"ajax:stopped\");\n return false;\n }\n if (isContentEditable(element)) {\n fire(element, \"ajax:stopped\");\n return false;\n }\n const withCredentials = element.getAttribute(\"data-with-credentials\");\n const dataType = element.getAttribute(\"data-type\") || \"script\";\n if (matches(element, formSubmitSelector)) {\n const button = getData(element, \"ujs:submit-button\");\n method = getData(element, \"ujs:submit-button-formmethod\") || element.getAttribute(\"method\") || \"get\";\n url = getData(element, \"ujs:submit-button-formaction\") || element.getAttribute(\"action\") || location.href;\n if (method.toUpperCase() === \"GET\") {\n url = url.replace(/\\?.*$/, \"\");\n }\n if (element.enctype === \"multipart/form-data\") {\n data = new FormData(element);\n if (button != null) {\n data.append(button.name, button.value);\n }\n } else {\n data = serializeElement(element, button);\n }\n setData(element, \"ujs:submit-button\", null);\n setData(element, \"ujs:submit-button-formmethod\", null);\n setData(element, \"ujs:submit-button-formaction\", null);\n } else if (matches(element, buttonClickSelector) || matches(element, inputChangeSelector)) {\n method = element.getAttribute(\"data-method\");\n url = element.getAttribute(\"data-url\");\n data = serializeElement(element, element.getAttribute(\"data-params\"));\n } else {\n method = element.getAttribute(\"data-method\");\n url = rails.href(element);\n data = element.getAttribute(\"data-params\");\n }\n ajax({\n type: method || \"GET\",\n url: url,\n data: data,\n dataType: dataType,\n beforeSend(xhr, options) {\n if (fire(element, \"ajax:beforeSend\", [ xhr, options ])) {\n return fire(element, \"ajax:send\", [ xhr ]);\n } else {\n fire(element, \"ajax:stopped\");\n return false;\n }\n },\n success(...args) {\n return fire(element, \"ajax:success\", args);\n },\n error(...args) {\n return fire(element, \"ajax:error\", args);\n },\n complete(...args) {\n return fire(element, \"ajax:complete\", args);\n },\n crossDomain: isCrossDomain(url),\n withCredentials: withCredentials != null && withCredentials !== \"false\"\n });\n stopEverything(e);\n};\n\nconst formSubmitButtonClick = function(e) {\n const button = this;\n const {form: form} = button;\n if (!form) {\n return;\n }\n if (button.name) {\n setData(form, \"ujs:submit-button\", {\n name: button.name,\n value: button.value\n });\n }\n setData(form, \"ujs:formnovalidate-button\", button.formNoValidate);\n setData(form, \"ujs:submit-button-formaction\", button.getAttribute(\"formaction\"));\n return setData(form, \"ujs:submit-button-formmethod\", button.getAttribute(\"formmethod\"));\n};\n\nconst preventInsignificantClick = function(e) {\n const link = this;\n const method = (link.getAttribute(\"data-method\") || \"GET\").toUpperCase();\n const data = link.getAttribute(\"data-params\");\n const metaClick = e.metaKey || e.ctrlKey;\n const insignificantMetaClick = metaClick && method === \"GET\" && !data;\n const nonPrimaryMouseClick = e.button != null && e.button !== 0;\n if (nonPrimaryMouseClick || insignificantMetaClick) {\n e.stopImmediatePropagation();\n }\n};\n\nconst Rails = {\n $: $,\n ajax: ajax,\n buttonClickSelector: buttonClickSelector,\n buttonDisableSelector: buttonDisableSelector,\n confirm: confirm,\n cspNonce: cspNonce,\n csrfToken: csrfToken,\n csrfParam: csrfParam,\n CSRFProtection: CSRFProtection,\n delegate: delegate,\n disableElement: disableElement,\n enableElement: enableElement,\n fileInputSelector: fileInputSelector,\n fire: fire,\n formElements: formElements,\n formEnableSelector: formEnableSelector,\n formDisableSelector: formDisableSelector,\n formInputClickSelector: formInputClickSelector,\n formSubmitButtonClick: formSubmitButtonClick,\n formSubmitSelector: formSubmitSelector,\n getData: getData,\n handleDisabledElement: handleDisabledElement,\n href: href,\n inputChangeSelector: inputChangeSelector,\n isCrossDomain: isCrossDomain,\n linkClickSelector: linkClickSelector,\n linkDisableSelector: linkDisableSelector,\n loadCSPNonce: loadCSPNonce,\n matches: matches,\n preventInsignificantClick: preventInsignificantClick,\n refreshCSRFTokens: refreshCSRFTokens,\n serializeElement: serializeElement,\n setData: setData,\n stopEverything: stopEverything\n};\n\nconst handleConfirm = handleConfirmWithRails(Rails);\n\nRails.handleConfirm = handleConfirm;\n\nconst handleMethod = handleMethodWithRails(Rails);\n\nRails.handleMethod = handleMethod;\n\nconst handleRemote = handleRemoteWithRails(Rails);\n\nRails.handleRemote = handleRemote;\n\nconst start = function() {\n if (window._rails_loaded) {\n throw new Error(\"rails-ujs has already been loaded!\");\n }\n window.addEventListener(\"pageshow\", (function() {\n $(formEnableSelector).forEach((function(el) {\n if (getData(el, \"ujs:disabled\")) {\n enableElement(el);\n }\n }));\n $(linkDisableSelector).forEach((function(el) {\n if (getData(el, \"ujs:disabled\")) {\n enableElement(el);\n }\n }));\n }));\n delegate(document, linkDisableSelector, \"ajax:complete\", enableElement);\n delegate(document, linkDisableSelector, \"ajax:stopped\", enableElement);\n delegate(document, buttonDisableSelector, \"ajax:complete\", enableElement);\n delegate(document, buttonDisableSelector, \"ajax:stopped\", enableElement);\n delegate(document, linkClickSelector, \"click\", preventInsignificantClick);\n delegate(document, linkClickSelector, \"click\", handleDisabledElement);\n delegate(document, linkClickSelector, \"click\", handleConfirm);\n delegate(document, linkClickSelector, \"click\", disableElement);\n delegate(document, linkClickSelector, \"click\", handleRemote);\n delegate(document, linkClickSelector, \"click\", handleMethod);\n delegate(document, buttonClickSelector, \"click\", preventInsignificantClick);\n delegate(document, buttonClickSelector, \"click\", handleDisabledElement);\n delegate(document, buttonClickSelector, \"click\", handleConfirm);\n delegate(document, buttonClickSelector, \"click\", disableElement);\n delegate(document, buttonClickSelector, \"click\", handleRemote);\n delegate(document, inputChangeSelector, \"change\", handleDisabledElement);\n delegate(document, inputChangeSelector, \"change\", handleConfirm);\n delegate(document, inputChangeSelector, \"change\", handleRemote);\n delegate(document, formSubmitSelector, \"submit\", handleDisabledElement);\n delegate(document, formSubmitSelector, \"submit\", handleConfirm);\n delegate(document, formSubmitSelector, \"submit\", handleRemote);\n delegate(document, formSubmitSelector, \"submit\", (e => setTimeout((() => disableElement(e)), 13)));\n delegate(document, formSubmitSelector, \"ajax:send\", disableElement);\n delegate(document, formSubmitSelector, \"ajax:complete\", enableElement);\n delegate(document, formInputClickSelector, \"click\", preventInsignificantClick);\n delegate(document, formInputClickSelector, \"click\", handleDisabledElement);\n delegate(document, formInputClickSelector, \"click\", handleConfirm);\n delegate(document, formInputClickSelector, \"click\", formSubmitButtonClick);\n document.addEventListener(\"DOMContentLoaded\", refreshCSRFTokens);\n document.addEventListener(\"DOMContentLoaded\", loadCSPNonce);\n return window._rails_loaded = true;\n};\n\nRails.start = start;\n\nif (typeof jQuery !== \"undefined\" && jQuery && jQuery.ajax) {\n if (jQuery.rails) {\n throw new Error(\"If you load both jquery_ujs and rails-ujs, use rails-ujs only.\");\n }\n jQuery.rails = Rails;\n jQuery.ajaxPrefilter((function(options, originalOptions, xhr) {\n if (!options.crossDomain) {\n return CSRFProtection(xhr);\n }\n }));\n}\n\nexport { Rails as default };\n", "/*!\nTurbo 8.0.13\nCopyright \u00A9 2025 37signals LLC\n */\n/**\n * The MIT License (MIT)\n *\n * Copyright (c) 2019 Javan Makhmali\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n(function (prototype) {\n if (typeof prototype.requestSubmit == \"function\") return\n\n prototype.requestSubmit = function (submitter) {\n if (submitter) {\n validateSubmitter(submitter, this);\n submitter.click();\n } else {\n submitter = document.createElement(\"input\");\n submitter.type = \"submit\";\n submitter.hidden = true;\n this.appendChild(submitter);\n submitter.click();\n this.removeChild(submitter);\n }\n };\n\n function validateSubmitter(submitter, form) {\n submitter instanceof HTMLElement || raise(TypeError, \"parameter 1 is not of type 'HTMLElement'\");\n submitter.type == \"submit\" || raise(TypeError, \"The specified element is not a submit button\");\n submitter.form == form ||\n raise(DOMException, \"The specified element is not owned by this form element\", \"NotFoundError\");\n }\n\n function raise(errorConstructor, message, name) {\n throw new errorConstructor(\"Failed to execute 'requestSubmit' on 'HTMLFormElement': \" + message + \".\", name)\n }\n})(HTMLFormElement.prototype);\n\nconst submittersByForm = new WeakMap();\n\nfunction findSubmitterFromClickTarget(target) {\n const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;\n const candidate = element ? element.closest(\"input, button\") : null;\n return candidate?.type == \"submit\" ? candidate : null\n}\n\nfunction clickCaptured(event) {\n const submitter = findSubmitterFromClickTarget(event.target);\n\n if (submitter && submitter.form) {\n submittersByForm.set(submitter.form, submitter);\n }\n}\n\n(function () {\n if (\"submitter\" in Event.prototype) return\n\n let prototype = window.Event.prototype;\n // Certain versions of Safari 15 have a bug where they won't\n // populate the submitter. This hurts TurboDrive's enable/disable detection.\n // See https://bugs.webkit.org/show_bug.cgi?id=229660\n if (\"SubmitEvent\" in window) {\n const prototypeOfSubmitEvent = window.SubmitEvent.prototype;\n\n if (/Apple Computer/.test(navigator.vendor) && !(\"submitter\" in prototypeOfSubmitEvent)) {\n prototype = prototypeOfSubmitEvent;\n } else {\n return // polyfill not needed\n }\n }\n\n addEventListener(\"click\", clickCaptured, true);\n\n Object.defineProperty(prototype, \"submitter\", {\n get() {\n if (this.type == \"submit\" && this.target instanceof HTMLFormElement) {\n return submittersByForm.get(this.target)\n }\n }\n });\n})();\n\nconst FrameLoadingStyle = {\n eager: \"eager\",\n lazy: \"lazy\"\n};\n\n/**\n * Contains a fragment of HTML which is updated based on navigation within\n * it (e.g. via links or form submissions).\n *\n * @customElement turbo-frame\n * @example\n * \n * \n * Show all expanded messages in this frame.\n * \n *\n *
\n * Show response from this form within this frame.\n *
\n *
\n */\nclass FrameElement extends HTMLElement {\n static delegateConstructor = undefined\n\n loaded = Promise.resolve()\n\n static get observedAttributes() {\n return [\"disabled\", \"loading\", \"src\"]\n }\n\n constructor() {\n super();\n this.delegate = new FrameElement.delegateConstructor(this);\n }\n\n connectedCallback() {\n this.delegate.connect();\n }\n\n disconnectedCallback() {\n this.delegate.disconnect();\n }\n\n reload() {\n return this.delegate.sourceURLReloaded()\n }\n\n attributeChangedCallback(name) {\n if (name == \"loading\") {\n this.delegate.loadingStyleChanged();\n } else if (name == \"src\") {\n this.delegate.sourceURLChanged();\n } else if (name == \"disabled\") {\n this.delegate.disabledChanged();\n }\n }\n\n /**\n * Gets the URL to lazily load source HTML from\n */\n get src() {\n return this.getAttribute(\"src\")\n }\n\n /**\n * Sets the URL to lazily load source HTML from\n */\n set src(value) {\n if (value) {\n this.setAttribute(\"src\", value);\n } else {\n this.removeAttribute(\"src\");\n }\n }\n\n /**\n * Gets the refresh mode for the frame.\n */\n get refresh() {\n return this.getAttribute(\"refresh\")\n }\n\n /**\n * Sets the refresh mode for the frame.\n */\n set refresh(value) {\n if (value) {\n this.setAttribute(\"refresh\", value);\n } else {\n this.removeAttribute(\"refresh\");\n }\n }\n\n get shouldReloadWithMorph() {\n return this.src && this.refresh === \"morph\"\n }\n\n /**\n * Determines if the element is loading\n */\n get loading() {\n return frameLoadingStyleFromString(this.getAttribute(\"loading\") || \"\")\n }\n\n /**\n * Sets the value of if the element is loading\n */\n set loading(value) {\n if (value) {\n this.setAttribute(\"loading\", value);\n } else {\n this.removeAttribute(\"loading\");\n }\n }\n\n /**\n * Gets the disabled state of the frame.\n *\n * If disabled, no requests will be intercepted by the frame.\n */\n get disabled() {\n return this.hasAttribute(\"disabled\")\n }\n\n /**\n * Sets the disabled state of the frame.\n *\n * If disabled, no requests will be intercepted by the frame.\n */\n set disabled(value) {\n if (value) {\n this.setAttribute(\"disabled\", \"\");\n } else {\n this.removeAttribute(\"disabled\");\n }\n }\n\n /**\n * Gets the autoscroll state of the frame.\n *\n * If true, the frame will be scrolled into view automatically on update.\n */\n get autoscroll() {\n return this.hasAttribute(\"autoscroll\")\n }\n\n /**\n * Sets the autoscroll state of the frame.\n *\n * If true, the frame will be scrolled into view automatically on update.\n */\n set autoscroll(value) {\n if (value) {\n this.setAttribute(\"autoscroll\", \"\");\n } else {\n this.removeAttribute(\"autoscroll\");\n }\n }\n\n /**\n * Determines if the element has finished loading\n */\n get complete() {\n return !this.delegate.isLoading\n }\n\n /**\n * Gets the active state of the frame.\n *\n * If inactive, source changes will not be observed.\n */\n get isActive() {\n return this.ownerDocument === document && !this.isPreview\n }\n\n /**\n * Sets the active state of the frame.\n *\n * If inactive, source changes will not be observed.\n */\n get isPreview() {\n return this.ownerDocument?.documentElement?.hasAttribute(\"data-turbo-preview\")\n }\n}\n\nfunction frameLoadingStyleFromString(style) {\n switch (style.toLowerCase()) {\n case \"lazy\":\n return FrameLoadingStyle.lazy\n default:\n return FrameLoadingStyle.eager\n }\n}\n\nconst drive = {\n enabled: true,\n progressBarDelay: 500,\n unvisitableExtensions: new Set(\n [\n \".7z\", \".aac\", \".apk\", \".avi\", \".bmp\", \".bz2\", \".css\", \".csv\", \".deb\", \".dmg\", \".doc\",\n \".docx\", \".exe\", \".gif\", \".gz\", \".heic\", \".heif\", \".ico\", \".iso\", \".jpeg\", \".jpg\",\n \".js\", \".json\", \".m4a\", \".mkv\", \".mov\", \".mp3\", \".mp4\", \".mpeg\", \".mpg\", \".msi\",\n \".ogg\", \".ogv\", \".pdf\", \".pkg\", \".png\", \".ppt\", \".pptx\", \".rar\", \".rtf\",\n \".svg\", \".tar\", \".tif\", \".tiff\", \".txt\", \".wav\", \".webm\", \".webp\", \".wma\", \".wmv\",\n \".xls\", \".xlsx\", \".xml\", \".zip\"\n ]\n )\n};\n\nfunction activateScriptElement(element) {\n if (element.getAttribute(\"data-turbo-eval\") == \"false\") {\n return element\n } else {\n const createdScriptElement = document.createElement(\"script\");\n const cspNonce = getCspNonce();\n if (cspNonce) {\n createdScriptElement.nonce = cspNonce;\n }\n createdScriptElement.textContent = element.textContent;\n createdScriptElement.async = false;\n copyElementAttributes(createdScriptElement, element);\n return createdScriptElement\n }\n}\n\nfunction copyElementAttributes(destinationElement, sourceElement) {\n for (const { name, value } of sourceElement.attributes) {\n destinationElement.setAttribute(name, value);\n }\n}\n\nfunction createDocumentFragment(html) {\n const template = document.createElement(\"template\");\n template.innerHTML = html;\n return template.content\n}\n\nfunction dispatch(eventName, { target, cancelable, detail } = {}) {\n const event = new CustomEvent(eventName, {\n cancelable,\n bubbles: true,\n composed: true,\n detail\n });\n\n if (target && target.isConnected) {\n target.dispatchEvent(event);\n } else {\n document.documentElement.dispatchEvent(event);\n }\n\n return event\n}\n\nfunction cancelEvent(event) {\n event.preventDefault();\n event.stopImmediatePropagation();\n}\n\nfunction nextRepaint() {\n if (document.visibilityState === \"hidden\") {\n return nextEventLoopTick()\n } else {\n return nextAnimationFrame()\n }\n}\n\nfunction nextAnimationFrame() {\n return new Promise((resolve) => requestAnimationFrame(() => resolve()))\n}\n\nfunction nextEventLoopTick() {\n return new Promise((resolve) => setTimeout(() => resolve(), 0))\n}\n\nfunction nextMicrotask() {\n return Promise.resolve()\n}\n\nfunction parseHTMLDocument(html = \"\") {\n return new DOMParser().parseFromString(html, \"text/html\")\n}\n\nfunction unindent(strings, ...values) {\n const lines = interpolate(strings, values).replace(/^\\n/, \"\").split(\"\\n\");\n const match = lines[0].match(/^\\s+/);\n const indent = match ? match[0].length : 0;\n return lines.map((line) => line.slice(indent)).join(\"\\n\")\n}\n\nfunction interpolate(strings, values) {\n return strings.reduce((result, string, i) => {\n const value = values[i] == undefined ? \"\" : values[i];\n return result + string + value\n }, \"\")\n}\n\nfunction uuid() {\n return Array.from({ length: 36 })\n .map((_, i) => {\n if (i == 8 || i == 13 || i == 18 || i == 23) {\n return \"-\"\n } else if (i == 14) {\n return \"4\"\n } else if (i == 19) {\n return (Math.floor(Math.random() * 4) + 8).toString(16)\n } else {\n return Math.floor(Math.random() * 15).toString(16)\n }\n })\n .join(\"\")\n}\n\nfunction getAttribute(attributeName, ...elements) {\n for (const value of elements.map((element) => element?.getAttribute(attributeName))) {\n if (typeof value == \"string\") return value\n }\n\n return null\n}\n\nfunction hasAttribute(attributeName, ...elements) {\n return elements.some((element) => element && element.hasAttribute(attributeName))\n}\n\nfunction markAsBusy(...elements) {\n for (const element of elements) {\n if (element.localName == \"turbo-frame\") {\n element.setAttribute(\"busy\", \"\");\n }\n element.setAttribute(\"aria-busy\", \"true\");\n }\n}\n\nfunction clearBusyState(...elements) {\n for (const element of elements) {\n if (element.localName == \"turbo-frame\") {\n element.removeAttribute(\"busy\");\n }\n\n element.removeAttribute(\"aria-busy\");\n }\n}\n\nfunction waitForLoad(element, timeoutInMilliseconds = 2000) {\n return new Promise((resolve) => {\n const onComplete = () => {\n element.removeEventListener(\"error\", onComplete);\n element.removeEventListener(\"load\", onComplete);\n resolve();\n };\n\n element.addEventListener(\"load\", onComplete, { once: true });\n element.addEventListener(\"error\", onComplete, { once: true });\n setTimeout(resolve, timeoutInMilliseconds);\n })\n}\n\nfunction getHistoryMethodForAction(action) {\n switch (action) {\n case \"replace\":\n return history.replaceState\n case \"advance\":\n case \"restore\":\n return history.pushState\n }\n}\n\nfunction isAction(action) {\n return action == \"advance\" || action == \"replace\" || action == \"restore\"\n}\n\nfunction getVisitAction(...elements) {\n const action = getAttribute(\"data-turbo-action\", ...elements);\n\n return isAction(action) ? action : null\n}\n\nfunction getMetaElement(name) {\n return document.querySelector(`meta[name=\"${name}\"]`)\n}\n\nfunction getMetaContent(name) {\n const element = getMetaElement(name);\n return element && element.content\n}\n\nfunction getCspNonce() {\n const element = getMetaElement(\"csp-nonce\");\n\n if (element) {\n const { nonce, content } = element;\n return nonce == \"\" ? content : nonce\n }\n}\n\nfunction setMetaContent(name, content) {\n let element = getMetaElement(name);\n\n if (!element) {\n element = document.createElement(\"meta\");\n element.setAttribute(\"name\", name);\n\n document.head.appendChild(element);\n }\n\n element.setAttribute(\"content\", content);\n\n return element\n}\n\nfunction findClosestRecursively(element, selector) {\n if (element instanceof Element) {\n return (\n element.closest(selector) || findClosestRecursively(element.assignedSlot || element.getRootNode()?.host, selector)\n )\n }\n}\n\nfunction elementIsFocusable(element) {\n const inertDisabledOrHidden = \"[inert], :disabled, [hidden], details:not([open]), dialog:not([open])\";\n\n return !!element && element.closest(inertDisabledOrHidden) == null && typeof element.focus == \"function\"\n}\n\nfunction queryAutofocusableElement(elementOrDocumentFragment) {\n return Array.from(elementOrDocumentFragment.querySelectorAll(\"[autofocus]\")).find(elementIsFocusable)\n}\n\nasync function around(callback, reader) {\n const before = reader();\n\n callback();\n\n await nextAnimationFrame();\n\n const after = reader();\n\n return [before, after]\n}\n\nfunction doesNotTargetIFrame(name) {\n if (name === \"_blank\") {\n return false\n } else if (name) {\n for (const element of document.getElementsByName(name)) {\n if (element instanceof HTMLIFrameElement) return false\n }\n\n return true\n } else {\n return true\n }\n}\n\nfunction findLinkFromClickTarget(target) {\n return findClosestRecursively(target, \"a[href]:not([target^=_]):not([download])\")\n}\n\nfunction getLocationForLink(link) {\n return expandURL(link.getAttribute(\"href\") || \"\")\n}\n\nfunction debounce(fn, delay) {\n let timeoutId = null;\n\n return (...args) => {\n const callback = () => fn.apply(this, args);\n clearTimeout(timeoutId);\n timeoutId = setTimeout(callback, delay);\n }\n}\n\nconst submitter = {\n \"aria-disabled\": {\n beforeSubmit: submitter => {\n submitter.setAttribute(\"aria-disabled\", \"true\");\n submitter.addEventListener(\"click\", cancelEvent);\n },\n\n afterSubmit: submitter => {\n submitter.removeAttribute(\"aria-disabled\");\n submitter.removeEventListener(\"click\", cancelEvent);\n }\n },\n\n \"disabled\": {\n beforeSubmit: submitter => submitter.disabled = true,\n afterSubmit: submitter => submitter.disabled = false\n }\n};\n\nclass Config {\n #submitter = null\n\n constructor(config) {\n Object.assign(this, config);\n }\n\n get submitter() {\n return this.#submitter\n }\n\n set submitter(value) {\n this.#submitter = submitter[value] || value;\n }\n}\n\nconst forms = new Config({\n mode: \"on\",\n submitter: \"disabled\"\n});\n\nconst config = {\n drive,\n forms\n};\n\nfunction expandURL(locatable) {\n return new URL(locatable.toString(), document.baseURI)\n}\n\nfunction getAnchor(url) {\n let anchorMatch;\n if (url.hash) {\n return url.hash.slice(1)\n // eslint-disable-next-line no-cond-assign\n } else if ((anchorMatch = url.href.match(/#(.*)$/))) {\n return anchorMatch[1]\n }\n}\n\nfunction getAction$1(form, submitter) {\n const action = submitter?.getAttribute(\"formaction\") || form.getAttribute(\"action\") || form.action;\n\n return expandURL(action)\n}\n\nfunction getExtension(url) {\n return (getLastPathComponent(url).match(/\\.[^.]*$/) || [])[0] || \"\"\n}\n\nfunction isPrefixedBy(baseURL, url) {\n const prefix = getPrefix(url);\n return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix)\n}\n\nfunction locationIsVisitable(location, rootLocation) {\n return isPrefixedBy(location, rootLocation) && !config.drive.unvisitableExtensions.has(getExtension(location))\n}\n\nfunction getRequestURL(url) {\n const anchor = getAnchor(url);\n return anchor != null ? url.href.slice(0, -(anchor.length + 1)) : url.href\n}\n\nfunction toCacheKey(url) {\n return getRequestURL(url)\n}\n\nfunction urlsAreEqual(left, right) {\n return expandURL(left).href == expandURL(right).href\n}\n\nfunction getPathComponents(url) {\n return url.pathname.split(\"/\").slice(1)\n}\n\nfunction getLastPathComponent(url) {\n return getPathComponents(url).slice(-1)[0]\n}\n\nfunction getPrefix(url) {\n return addTrailingSlash(url.origin + url.pathname)\n}\n\nfunction addTrailingSlash(value) {\n return value.endsWith(\"/\") ? value : value + \"/\"\n}\n\nclass FetchResponse {\n constructor(response) {\n this.response = response;\n }\n\n get succeeded() {\n return this.response.ok\n }\n\n get failed() {\n return !this.succeeded\n }\n\n get clientError() {\n return this.statusCode >= 400 && this.statusCode <= 499\n }\n\n get serverError() {\n return this.statusCode >= 500 && this.statusCode <= 599\n }\n\n get redirected() {\n return this.response.redirected\n }\n\n get location() {\n return expandURL(this.response.url)\n }\n\n get isHTML() {\n return this.contentType && this.contentType.match(/^(?:text\\/([^\\s;,]+\\b)?html|application\\/xhtml\\+xml)\\b/)\n }\n\n get statusCode() {\n return this.response.status\n }\n\n get contentType() {\n return this.header(\"Content-Type\")\n }\n\n get responseText() {\n return this.response.clone().text()\n }\n\n get responseHTML() {\n if (this.isHTML) {\n return this.response.clone().text()\n } else {\n return Promise.resolve(undefined)\n }\n }\n\n header(name) {\n return this.response.headers.get(name)\n }\n}\n\nclass LimitedSet extends Set {\n constructor(maxSize) {\n super();\n this.maxSize = maxSize;\n }\n\n add(value) {\n if (this.size >= this.maxSize) {\n const iterator = this.values();\n const oldestValue = iterator.next().value;\n this.delete(oldestValue);\n }\n super.add(value);\n }\n}\n\nconst recentRequests = new LimitedSet(20);\n\nconst nativeFetch = window.fetch;\n\nfunction fetchWithTurboHeaders(url, options = {}) {\n const modifiedHeaders = new Headers(options.headers || {});\n const requestUID = uuid();\n recentRequests.add(requestUID);\n modifiedHeaders.append(\"X-Turbo-Request-Id\", requestUID);\n\n return nativeFetch(url, {\n ...options,\n headers: modifiedHeaders\n })\n}\n\nfunction fetchMethodFromString(method) {\n switch (method.toLowerCase()) {\n case \"get\":\n return FetchMethod.get\n case \"post\":\n return FetchMethod.post\n case \"put\":\n return FetchMethod.put\n case \"patch\":\n return FetchMethod.patch\n case \"delete\":\n return FetchMethod.delete\n }\n}\n\nconst FetchMethod = {\n get: \"get\",\n post: \"post\",\n put: \"put\",\n patch: \"patch\",\n delete: \"delete\"\n};\n\nfunction fetchEnctypeFromString(encoding) {\n switch (encoding.toLowerCase()) {\n case FetchEnctype.multipart:\n return FetchEnctype.multipart\n case FetchEnctype.plain:\n return FetchEnctype.plain\n default:\n return FetchEnctype.urlEncoded\n }\n}\n\nconst FetchEnctype = {\n urlEncoded: \"application/x-www-form-urlencoded\",\n multipart: \"multipart/form-data\",\n plain: \"text/plain\"\n};\n\nclass FetchRequest {\n abortController = new AbortController()\n #resolveRequestPromise = (_value) => {}\n\n constructor(delegate, method, location, requestBody = new URLSearchParams(), target = null, enctype = FetchEnctype.urlEncoded) {\n const [url, body] = buildResourceAndBody(expandURL(location), method, requestBody, enctype);\n\n this.delegate = delegate;\n this.url = url;\n this.target = target;\n this.fetchOptions = {\n credentials: \"same-origin\",\n redirect: \"follow\",\n method: method.toUpperCase(),\n headers: { ...this.defaultHeaders },\n body: body,\n signal: this.abortSignal,\n referrer: this.delegate.referrer?.href\n };\n this.enctype = enctype;\n }\n\n get method() {\n return this.fetchOptions.method\n }\n\n set method(value) {\n const fetchBody = this.isSafe ? this.url.searchParams : this.fetchOptions.body || new FormData();\n const fetchMethod = fetchMethodFromString(value) || FetchMethod.get;\n\n this.url.search = \"\";\n\n const [url, body] = buildResourceAndBody(this.url, fetchMethod, fetchBody, this.enctype);\n\n this.url = url;\n this.fetchOptions.body = body;\n this.fetchOptions.method = fetchMethod.toUpperCase();\n }\n\n get headers() {\n return this.fetchOptions.headers\n }\n\n set headers(value) {\n this.fetchOptions.headers = value;\n }\n\n get body() {\n if (this.isSafe) {\n return this.url.searchParams\n } else {\n return this.fetchOptions.body\n }\n }\n\n set body(value) {\n this.fetchOptions.body = value;\n }\n\n get location() {\n return this.url\n }\n\n get params() {\n return this.url.searchParams\n }\n\n get entries() {\n return this.body ? Array.from(this.body.entries()) : []\n }\n\n cancel() {\n this.abortController.abort();\n }\n\n async perform() {\n const { fetchOptions } = this;\n this.delegate.prepareRequest(this);\n const event = await this.#allowRequestToBeIntercepted(fetchOptions);\n try {\n this.delegate.requestStarted(this);\n\n if (event.detail.fetchRequest) {\n this.response = event.detail.fetchRequest.response;\n } else {\n this.response = fetchWithTurboHeaders(this.url.href, fetchOptions);\n }\n\n const response = await this.response;\n return await this.receive(response)\n } catch (error) {\n if (error.name !== \"AbortError\") {\n if (this.#willDelegateErrorHandling(error)) {\n this.delegate.requestErrored(this, error);\n }\n throw error\n }\n } finally {\n this.delegate.requestFinished(this);\n }\n }\n\n async receive(response) {\n const fetchResponse = new FetchResponse(response);\n const event = dispatch(\"turbo:before-fetch-response\", {\n cancelable: true,\n detail: { fetchResponse },\n target: this.target\n });\n if (event.defaultPrevented) {\n this.delegate.requestPreventedHandlingResponse(this, fetchResponse);\n } else if (fetchResponse.succeeded) {\n this.delegate.requestSucceededWithResponse(this, fetchResponse);\n } else {\n this.delegate.requestFailedWithResponse(this, fetchResponse);\n }\n return fetchResponse\n }\n\n get defaultHeaders() {\n return {\n Accept: \"text/html, application/xhtml+xml\"\n }\n }\n\n get isSafe() {\n return isSafe(this.method)\n }\n\n get abortSignal() {\n return this.abortController.signal\n }\n\n acceptResponseType(mimeType) {\n this.headers[\"Accept\"] = [mimeType, this.headers[\"Accept\"]].join(\", \");\n }\n\n async #allowRequestToBeIntercepted(fetchOptions) {\n const requestInterception = new Promise((resolve) => (this.#resolveRequestPromise = resolve));\n const event = dispatch(\"turbo:before-fetch-request\", {\n cancelable: true,\n detail: {\n fetchOptions,\n url: this.url,\n resume: this.#resolveRequestPromise\n },\n target: this.target\n });\n this.url = event.detail.url;\n if (event.defaultPrevented) await requestInterception;\n\n return event\n }\n\n #willDelegateErrorHandling(error) {\n const event = dispatch(\"turbo:fetch-request-error\", {\n target: this.target,\n cancelable: true,\n detail: { request: this, error: error }\n });\n\n return !event.defaultPrevented\n }\n}\n\nfunction isSafe(fetchMethod) {\n return fetchMethodFromString(fetchMethod) == FetchMethod.get\n}\n\nfunction buildResourceAndBody(resource, method, requestBody, enctype) {\n const searchParams =\n Array.from(requestBody).length > 0 ? new URLSearchParams(entriesExcludingFiles(requestBody)) : resource.searchParams;\n\n if (isSafe(method)) {\n return [mergeIntoURLSearchParams(resource, searchParams), null]\n } else if (enctype == FetchEnctype.urlEncoded) {\n return [resource, searchParams]\n } else {\n return [resource, requestBody]\n }\n}\n\nfunction entriesExcludingFiles(requestBody) {\n const entries = [];\n\n for (const [name, value] of requestBody) {\n if (value instanceof File) continue\n else entries.push([name, value]);\n }\n\n return entries\n}\n\nfunction mergeIntoURLSearchParams(url, requestBody) {\n const searchParams = new URLSearchParams(entriesExcludingFiles(requestBody));\n\n url.search = searchParams.toString();\n\n return url\n}\n\nclass AppearanceObserver {\n started = false\n\n constructor(delegate, element) {\n this.delegate = delegate;\n this.element = element;\n this.intersectionObserver = new IntersectionObserver(this.intersect);\n }\n\n start() {\n if (!this.started) {\n this.started = true;\n this.intersectionObserver.observe(this.element);\n }\n }\n\n stop() {\n if (this.started) {\n this.started = false;\n this.intersectionObserver.unobserve(this.element);\n }\n }\n\n intersect = (entries) => {\n const lastEntry = entries.slice(-1)[0];\n if (lastEntry?.isIntersecting) {\n this.delegate.elementAppearedInViewport(this.element);\n }\n }\n}\n\nclass StreamMessage {\n static contentType = \"text/vnd.turbo-stream.html\"\n\n static wrap(message) {\n if (typeof message == \"string\") {\n return new this(createDocumentFragment(message))\n } else {\n return message\n }\n }\n\n constructor(fragment) {\n this.fragment = importStreamElements(fragment);\n }\n}\n\nfunction importStreamElements(fragment) {\n for (const element of fragment.querySelectorAll(\"turbo-stream\")) {\n const streamElement = document.importNode(element, true);\n\n for (const inertScriptElement of streamElement.templateElement.content.querySelectorAll(\"script\")) {\n inertScriptElement.replaceWith(activateScriptElement(inertScriptElement));\n }\n\n element.replaceWith(streamElement);\n }\n\n return fragment\n}\n\nconst PREFETCH_DELAY = 100;\n\nclass PrefetchCache {\n #prefetchTimeout = null\n #prefetched = null\n\n get(url) {\n if (this.#prefetched && this.#prefetched.url === url && this.#prefetched.expire > Date.now()) {\n return this.#prefetched.request\n }\n }\n\n setLater(url, request, ttl) {\n this.clear();\n\n this.#prefetchTimeout = setTimeout(() => {\n request.perform();\n this.set(url, request, ttl);\n this.#prefetchTimeout = null;\n }, PREFETCH_DELAY);\n }\n\n set(url, request, ttl) {\n this.#prefetched = { url, request, expire: new Date(new Date().getTime() + ttl) };\n }\n\n clear() {\n if (this.#prefetchTimeout) clearTimeout(this.#prefetchTimeout);\n this.#prefetched = null;\n }\n}\n\nconst cacheTtl = 10 * 1000;\nconst prefetchCache = new PrefetchCache();\n\nconst FormSubmissionState = {\n initialized: \"initialized\",\n requesting: \"requesting\",\n waiting: \"waiting\",\n receiving: \"receiving\",\n stopping: \"stopping\",\n stopped: \"stopped\"\n};\n\nclass FormSubmission {\n state = FormSubmissionState.initialized\n\n static confirmMethod(message) {\n return Promise.resolve(confirm(message))\n }\n\n constructor(delegate, formElement, submitter, mustRedirect = false) {\n const method = getMethod(formElement, submitter);\n const action = getAction(getFormAction(formElement, submitter), method);\n const body = buildFormData(formElement, submitter);\n const enctype = getEnctype(formElement, submitter);\n\n this.delegate = delegate;\n this.formElement = formElement;\n this.submitter = submitter;\n this.fetchRequest = new FetchRequest(this, method, action, body, formElement, enctype);\n this.mustRedirect = mustRedirect;\n }\n\n get method() {\n return this.fetchRequest.method\n }\n\n set method(value) {\n this.fetchRequest.method = value;\n }\n\n get action() {\n return this.fetchRequest.url.toString()\n }\n\n set action(value) {\n this.fetchRequest.url = expandURL(value);\n }\n\n get body() {\n return this.fetchRequest.body\n }\n\n get enctype() {\n return this.fetchRequest.enctype\n }\n\n get isSafe() {\n return this.fetchRequest.isSafe\n }\n\n get location() {\n return this.fetchRequest.url\n }\n\n // The submission process\n\n async start() {\n const { initialized, requesting } = FormSubmissionState;\n const confirmationMessage = getAttribute(\"data-turbo-confirm\", this.submitter, this.formElement);\n\n if (typeof confirmationMessage === \"string\") {\n const confirmMethod = typeof config.forms.confirm === \"function\" ?\n config.forms.confirm :\n FormSubmission.confirmMethod;\n\n const answer = await confirmMethod(confirmationMessage, this.formElement, this.submitter);\n if (!answer) {\n return\n }\n }\n\n if (this.state == initialized) {\n this.state = requesting;\n return this.fetchRequest.perform()\n }\n }\n\n stop() {\n const { stopping, stopped } = FormSubmissionState;\n if (this.state != stopping && this.state != stopped) {\n this.state = stopping;\n this.fetchRequest.cancel();\n return true\n }\n }\n\n // Fetch request delegate\n\n prepareRequest(request) {\n if (!request.isSafe) {\n const token = getCookieValue(getMetaContent(\"csrf-param\")) || getMetaContent(\"csrf-token\");\n if (token) {\n request.headers[\"X-CSRF-Token\"] = token;\n }\n }\n\n if (this.requestAcceptsTurboStreamResponse(request)) {\n request.acceptResponseType(StreamMessage.contentType);\n }\n }\n\n requestStarted(_request) {\n this.state = FormSubmissionState.waiting;\n if (this.submitter) config.forms.submitter.beforeSubmit(this.submitter);\n this.setSubmitsWith();\n markAsBusy(this.formElement);\n dispatch(\"turbo:submit-start\", {\n target: this.formElement,\n detail: { formSubmission: this }\n });\n this.delegate.formSubmissionStarted(this);\n }\n\n requestPreventedHandlingResponse(request, response) {\n prefetchCache.clear();\n\n this.result = { success: response.succeeded, fetchResponse: response };\n }\n\n requestSucceededWithResponse(request, response) {\n if (response.clientError || response.serverError) {\n this.delegate.formSubmissionFailedWithResponse(this, response);\n return\n }\n\n prefetchCache.clear();\n\n if (this.requestMustRedirect(request) && responseSucceededWithoutRedirect(response)) {\n const error = new Error(\"Form responses must redirect to another location\");\n this.delegate.formSubmissionErrored(this, error);\n } else {\n this.state = FormSubmissionState.receiving;\n this.result = { success: true, fetchResponse: response };\n this.delegate.formSubmissionSucceededWithResponse(this, response);\n }\n }\n\n requestFailedWithResponse(request, response) {\n this.result = { success: false, fetchResponse: response };\n this.delegate.formSubmissionFailedWithResponse(this, response);\n }\n\n requestErrored(request, error) {\n this.result = { success: false, error };\n this.delegate.formSubmissionErrored(this, error);\n }\n\n requestFinished(_request) {\n this.state = FormSubmissionState.stopped;\n if (this.submitter) config.forms.submitter.afterSubmit(this.submitter);\n this.resetSubmitterText();\n clearBusyState(this.formElement);\n dispatch(\"turbo:submit-end\", {\n target: this.formElement,\n detail: { formSubmission: this, ...this.result }\n });\n this.delegate.formSubmissionFinished(this);\n }\n\n // Private\n\n setSubmitsWith() {\n if (!this.submitter || !this.submitsWith) return\n\n if (this.submitter.matches(\"button\")) {\n this.originalSubmitText = this.submitter.innerHTML;\n this.submitter.innerHTML = this.submitsWith;\n } else if (this.submitter.matches(\"input\")) {\n const input = this.submitter;\n this.originalSubmitText = input.value;\n input.value = this.submitsWith;\n }\n }\n\n resetSubmitterText() {\n if (!this.submitter || !this.originalSubmitText) return\n\n if (this.submitter.matches(\"button\")) {\n this.submitter.innerHTML = this.originalSubmitText;\n } else if (this.submitter.matches(\"input\")) {\n const input = this.submitter;\n input.value = this.originalSubmitText;\n }\n }\n\n requestMustRedirect(request) {\n return !request.isSafe && this.mustRedirect\n }\n\n requestAcceptsTurboStreamResponse(request) {\n return !request.isSafe || hasAttribute(\"data-turbo-stream\", this.submitter, this.formElement)\n }\n\n get submitsWith() {\n return this.submitter?.getAttribute(\"data-turbo-submits-with\")\n }\n}\n\nfunction buildFormData(formElement, submitter) {\n const formData = new FormData(formElement);\n const name = submitter?.getAttribute(\"name\");\n const value = submitter?.getAttribute(\"value\");\n\n if (name) {\n formData.append(name, value || \"\");\n }\n\n return formData\n}\n\nfunction getCookieValue(cookieName) {\n if (cookieName != null) {\n const cookies = document.cookie ? document.cookie.split(\"; \") : [];\n const cookie = cookies.find((cookie) => cookie.startsWith(cookieName));\n if (cookie) {\n const value = cookie.split(\"=\").slice(1).join(\"=\");\n return value ? decodeURIComponent(value) : undefined\n }\n }\n}\n\nfunction responseSucceededWithoutRedirect(response) {\n return response.statusCode == 200 && !response.redirected\n}\n\nfunction getFormAction(formElement, submitter) {\n const formElementAction = typeof formElement.action === \"string\" ? formElement.action : null;\n\n if (submitter?.hasAttribute(\"formaction\")) {\n return submitter.getAttribute(\"formaction\") || \"\"\n } else {\n return formElement.getAttribute(\"action\") || formElementAction || \"\"\n }\n}\n\nfunction getAction(formAction, fetchMethod) {\n const action = expandURL(formAction);\n\n if (isSafe(fetchMethod)) {\n action.search = \"\";\n }\n\n return action\n}\n\nfunction getMethod(formElement, submitter) {\n const method = submitter?.getAttribute(\"formmethod\") || formElement.getAttribute(\"method\") || \"\";\n return fetchMethodFromString(method.toLowerCase()) || FetchMethod.get\n}\n\nfunction getEnctype(formElement, submitter) {\n return fetchEnctypeFromString(submitter?.getAttribute(\"formenctype\") || formElement.enctype)\n}\n\nclass Snapshot {\n constructor(element) {\n this.element = element;\n }\n\n get activeElement() {\n return this.element.ownerDocument.activeElement\n }\n\n get children() {\n return [...this.element.children]\n }\n\n hasAnchor(anchor) {\n return this.getElementForAnchor(anchor) != null\n }\n\n getElementForAnchor(anchor) {\n return anchor ? this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`) : null\n }\n\n get isConnected() {\n return this.element.isConnected\n }\n\n get firstAutofocusableElement() {\n return queryAutofocusableElement(this.element)\n }\n\n get permanentElements() {\n return queryPermanentElementsAll(this.element)\n }\n\n getPermanentElementById(id) {\n return getPermanentElementById(this.element, id)\n }\n\n getPermanentElementMapForSnapshot(snapshot) {\n const permanentElementMap = {};\n\n for (const currentPermanentElement of this.permanentElements) {\n const { id } = currentPermanentElement;\n const newPermanentElement = snapshot.getPermanentElementById(id);\n if (newPermanentElement) {\n permanentElementMap[id] = [currentPermanentElement, newPermanentElement];\n }\n }\n\n return permanentElementMap\n }\n}\n\nfunction getPermanentElementById(node, id) {\n return node.querySelector(`#${id}[data-turbo-permanent]`)\n}\n\nfunction queryPermanentElementsAll(node) {\n return node.querySelectorAll(\"[id][data-turbo-permanent]\")\n}\n\nclass FormSubmitObserver {\n started = false\n\n constructor(delegate, eventTarget) {\n this.delegate = delegate;\n this.eventTarget = eventTarget;\n }\n\n start() {\n if (!this.started) {\n this.eventTarget.addEventListener(\"submit\", this.submitCaptured, true);\n this.started = true;\n }\n }\n\n stop() {\n if (this.started) {\n this.eventTarget.removeEventListener(\"submit\", this.submitCaptured, true);\n this.started = false;\n }\n }\n\n submitCaptured = () => {\n this.eventTarget.removeEventListener(\"submit\", this.submitBubbled, false);\n this.eventTarget.addEventListener(\"submit\", this.submitBubbled, false);\n }\n\n submitBubbled = (event) => {\n if (!event.defaultPrevented) {\n const form = event.target instanceof HTMLFormElement ? event.target : undefined;\n const submitter = event.submitter || undefined;\n\n if (\n form &&\n submissionDoesNotDismissDialog(form, submitter) &&\n submissionDoesNotTargetIFrame(form, submitter) &&\n this.delegate.willSubmitForm(form, submitter)\n ) {\n event.preventDefault();\n event.stopImmediatePropagation();\n this.delegate.formSubmitted(form, submitter);\n }\n }\n }\n}\n\nfunction submissionDoesNotDismissDialog(form, submitter) {\n const method = submitter?.getAttribute(\"formmethod\") || form.getAttribute(\"method\");\n\n return method != \"dialog\"\n}\n\nfunction submissionDoesNotTargetIFrame(form, submitter) {\n const target = submitter?.getAttribute(\"formtarget\") || form.getAttribute(\"target\");\n\n return doesNotTargetIFrame(target)\n}\n\nclass View {\n #resolveRenderPromise = (_value) => {}\n #resolveInterceptionPromise = (_value) => {}\n\n constructor(delegate, element) {\n this.delegate = delegate;\n this.element = element;\n }\n\n // Scrolling\n\n scrollToAnchor(anchor) {\n const element = this.snapshot.getElementForAnchor(anchor);\n if (element) {\n this.scrollToElement(element);\n this.focusElement(element);\n } else {\n this.scrollToPosition({ x: 0, y: 0 });\n }\n }\n\n scrollToAnchorFromLocation(location) {\n this.scrollToAnchor(getAnchor(location));\n }\n\n scrollToElement(element) {\n element.scrollIntoView();\n }\n\n focusElement(element) {\n if (element instanceof HTMLElement) {\n if (element.hasAttribute(\"tabindex\")) {\n element.focus();\n } else {\n element.setAttribute(\"tabindex\", \"-1\");\n element.focus();\n element.removeAttribute(\"tabindex\");\n }\n }\n }\n\n scrollToPosition({ x, y }) {\n this.scrollRoot.scrollTo(x, y);\n }\n\n scrollToTop() {\n this.scrollToPosition({ x: 0, y: 0 });\n }\n\n get scrollRoot() {\n return window\n }\n\n // Rendering\n\n async render(renderer) {\n const { isPreview, shouldRender, willRender, newSnapshot: snapshot } = renderer;\n\n // A workaround to ignore tracked element mismatch reloads when performing\n // a promoted Visit from a frame navigation\n const shouldInvalidate = willRender;\n\n if (shouldRender) {\n try {\n this.renderPromise = new Promise((resolve) => (this.#resolveRenderPromise = resolve));\n this.renderer = renderer;\n await this.prepareToRenderSnapshot(renderer);\n\n const renderInterception = new Promise((resolve) => (this.#resolveInterceptionPromise = resolve));\n const options = { resume: this.#resolveInterceptionPromise, render: this.renderer.renderElement, renderMethod: this.renderer.renderMethod };\n const immediateRender = this.delegate.allowsImmediateRender(snapshot, options);\n if (!immediateRender) await renderInterception;\n\n await this.renderSnapshot(renderer);\n this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod);\n this.delegate.preloadOnLoadLinksForView(this.element);\n this.finishRenderingSnapshot(renderer);\n } finally {\n delete this.renderer;\n this.#resolveRenderPromise(undefined);\n delete this.renderPromise;\n }\n } else if (shouldInvalidate) {\n this.invalidate(renderer.reloadReason);\n }\n }\n\n invalidate(reason) {\n this.delegate.viewInvalidated(reason);\n }\n\n async prepareToRenderSnapshot(renderer) {\n this.markAsPreview(renderer.isPreview);\n await renderer.prepareToRender();\n }\n\n markAsPreview(isPreview) {\n if (isPreview) {\n this.element.setAttribute(\"data-turbo-preview\", \"\");\n } else {\n this.element.removeAttribute(\"data-turbo-preview\");\n }\n }\n\n markVisitDirection(direction) {\n this.element.setAttribute(\"data-turbo-visit-direction\", direction);\n }\n\n unmarkVisitDirection() {\n this.element.removeAttribute(\"data-turbo-visit-direction\");\n }\n\n async renderSnapshot(renderer) {\n await renderer.render();\n }\n\n finishRenderingSnapshot(renderer) {\n renderer.finishRendering();\n }\n}\n\nclass FrameView extends View {\n missing() {\n this.element.innerHTML = `Content missing`;\n }\n\n get snapshot() {\n return new Snapshot(this.element)\n }\n}\n\nclass LinkInterceptor {\n constructor(delegate, element) {\n this.delegate = delegate;\n this.element = element;\n }\n\n start() {\n this.element.addEventListener(\"click\", this.clickBubbled);\n document.addEventListener(\"turbo:click\", this.linkClicked);\n document.addEventListener(\"turbo:before-visit\", this.willVisit);\n }\n\n stop() {\n this.element.removeEventListener(\"click\", this.clickBubbled);\n document.removeEventListener(\"turbo:click\", this.linkClicked);\n document.removeEventListener(\"turbo:before-visit\", this.willVisit);\n }\n\n clickBubbled = (event) => {\n if (this.clickEventIsSignificant(event)) {\n this.clickEvent = event;\n } else {\n delete this.clickEvent;\n }\n }\n\n linkClicked = (event) => {\n if (this.clickEvent && this.clickEventIsSignificant(event)) {\n if (this.delegate.shouldInterceptLinkClick(event.target, event.detail.url, event.detail.originalEvent)) {\n this.clickEvent.preventDefault();\n event.preventDefault();\n this.delegate.linkClickIntercepted(event.target, event.detail.url, event.detail.originalEvent);\n }\n }\n delete this.clickEvent;\n }\n\n willVisit = (_event) => {\n delete this.clickEvent;\n }\n\n clickEventIsSignificant(event) {\n const target = event.composed ? event.target?.parentElement : event.target;\n const element = findLinkFromClickTarget(target) || target;\n\n return element instanceof Element && element.closest(\"turbo-frame, html\") == this.element\n }\n}\n\nclass LinkClickObserver {\n started = false\n\n constructor(delegate, eventTarget) {\n this.delegate = delegate;\n this.eventTarget = eventTarget;\n }\n\n start() {\n if (!this.started) {\n this.eventTarget.addEventListener(\"click\", this.clickCaptured, true);\n this.started = true;\n }\n }\n\n stop() {\n if (this.started) {\n this.eventTarget.removeEventListener(\"click\", this.clickCaptured, true);\n this.started = false;\n }\n }\n\n clickCaptured = () => {\n this.eventTarget.removeEventListener(\"click\", this.clickBubbled, false);\n this.eventTarget.addEventListener(\"click\", this.clickBubbled, false);\n }\n\n clickBubbled = (event) => {\n if (event instanceof MouseEvent && this.clickEventIsSignificant(event)) {\n const target = (event.composedPath && event.composedPath()[0]) || event.target;\n const link = findLinkFromClickTarget(target);\n if (link && doesNotTargetIFrame(link.target)) {\n const location = getLocationForLink(link);\n if (this.delegate.willFollowLinkToLocation(link, location, event)) {\n event.preventDefault();\n this.delegate.followedLinkToLocation(link, location);\n }\n }\n }\n }\n\n clickEventIsSignificant(event) {\n return !(\n (event.target && event.target.isContentEditable) ||\n event.defaultPrevented ||\n event.which > 1 ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey\n )\n }\n}\n\nclass FormLinkClickObserver {\n constructor(delegate, element) {\n this.delegate = delegate;\n this.linkInterceptor = new LinkClickObserver(this, element);\n }\n\n start() {\n this.linkInterceptor.start();\n }\n\n stop() {\n this.linkInterceptor.stop();\n }\n\n // Link hover observer delegate\n\n canPrefetchRequestToLocation(link, location) {\n return false\n }\n\n prefetchAndCacheRequestToLocation(link, location) {\n return\n }\n\n // Link click observer delegate\n\n willFollowLinkToLocation(link, location, originalEvent) {\n return (\n this.delegate.willSubmitFormLinkToLocation(link, location, originalEvent) &&\n (link.hasAttribute(\"data-turbo-method\") || link.hasAttribute(\"data-turbo-stream\"))\n )\n }\n\n followedLinkToLocation(link, location) {\n const form = document.createElement(\"form\");\n\n const type = \"hidden\";\n for (const [name, value] of location.searchParams) {\n form.append(Object.assign(document.createElement(\"input\"), { type, name, value }));\n }\n\n const action = Object.assign(location, { search: \"\" });\n form.setAttribute(\"data-turbo\", \"true\");\n form.setAttribute(\"action\", action.href);\n form.setAttribute(\"hidden\", \"\");\n\n const method = link.getAttribute(\"data-turbo-method\");\n if (method) form.setAttribute(\"method\", method);\n\n const turboFrame = link.getAttribute(\"data-turbo-frame\");\n if (turboFrame) form.setAttribute(\"data-turbo-frame\", turboFrame);\n\n const turboAction = getVisitAction(link);\n if (turboAction) form.setAttribute(\"data-turbo-action\", turboAction);\n\n const turboConfirm = link.getAttribute(\"data-turbo-confirm\");\n if (turboConfirm) form.setAttribute(\"data-turbo-confirm\", turboConfirm);\n\n const turboStream = link.hasAttribute(\"data-turbo-stream\");\n if (turboStream) form.setAttribute(\"data-turbo-stream\", \"\");\n\n this.delegate.submittedFormLinkToLocation(link, location, form);\n\n document.body.appendChild(form);\n form.addEventListener(\"turbo:submit-end\", () => form.remove(), { once: true });\n requestAnimationFrame(() => form.requestSubmit());\n }\n}\n\nclass Bardo {\n static async preservingPermanentElements(delegate, permanentElementMap, callback) {\n const bardo = new this(delegate, permanentElementMap);\n bardo.enter();\n await callback();\n bardo.leave();\n }\n\n constructor(delegate, permanentElementMap) {\n this.delegate = delegate;\n this.permanentElementMap = permanentElementMap;\n }\n\n enter() {\n for (const id in this.permanentElementMap) {\n const [currentPermanentElement, newPermanentElement] = this.permanentElementMap[id];\n this.delegate.enteringBardo(currentPermanentElement, newPermanentElement);\n this.replaceNewPermanentElementWithPlaceholder(newPermanentElement);\n }\n }\n\n leave() {\n for (const id in this.permanentElementMap) {\n const [currentPermanentElement] = this.permanentElementMap[id];\n this.replaceCurrentPermanentElementWithClone(currentPermanentElement);\n this.replacePlaceholderWithPermanentElement(currentPermanentElement);\n this.delegate.leavingBardo(currentPermanentElement);\n }\n }\n\n replaceNewPermanentElementWithPlaceholder(permanentElement) {\n const placeholder = createPlaceholderForPermanentElement(permanentElement);\n permanentElement.replaceWith(placeholder);\n }\n\n replaceCurrentPermanentElementWithClone(permanentElement) {\n const clone = permanentElement.cloneNode(true);\n permanentElement.replaceWith(clone);\n }\n\n replacePlaceholderWithPermanentElement(permanentElement) {\n const placeholder = this.getPlaceholderById(permanentElement.id);\n placeholder?.replaceWith(permanentElement);\n }\n\n getPlaceholderById(id) {\n return this.placeholders.find((element) => element.content == id)\n }\n\n get placeholders() {\n return [...document.querySelectorAll(\"meta[name=turbo-permanent-placeholder][content]\")]\n }\n}\n\nfunction createPlaceholderForPermanentElement(permanentElement) {\n const element = document.createElement(\"meta\");\n element.setAttribute(\"name\", \"turbo-permanent-placeholder\");\n element.setAttribute(\"content\", permanentElement.id);\n return element\n}\n\nclass Renderer {\n #activeElement = null\n\n static renderElement(currentElement, newElement) {\n // Abstract method\n }\n\n constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {\n this.currentSnapshot = currentSnapshot;\n this.newSnapshot = newSnapshot;\n this.isPreview = isPreview;\n this.willRender = willRender;\n this.renderElement = this.constructor.renderElement;\n this.promise = new Promise((resolve, reject) => (this.resolvingFunctions = { resolve, reject }));\n }\n\n get shouldRender() {\n return true\n }\n\n get shouldAutofocus() {\n return true\n }\n\n get reloadReason() {\n return\n }\n\n prepareToRender() {\n return\n }\n\n render() {\n // Abstract method\n }\n\n finishRendering() {\n if (this.resolvingFunctions) {\n this.resolvingFunctions.resolve();\n delete this.resolvingFunctions;\n }\n }\n\n async preservingPermanentElements(callback) {\n await Bardo.preservingPermanentElements(this, this.permanentElementMap, callback);\n }\n\n focusFirstAutofocusableElement() {\n if (this.shouldAutofocus) {\n const element = this.connectedSnapshot.firstAutofocusableElement;\n if (element) {\n element.focus();\n }\n }\n }\n\n // Bardo delegate\n\n enteringBardo(currentPermanentElement) {\n if (this.#activeElement) return\n\n if (currentPermanentElement.contains(this.currentSnapshot.activeElement)) {\n this.#activeElement = this.currentSnapshot.activeElement;\n }\n }\n\n leavingBardo(currentPermanentElement) {\n if (currentPermanentElement.contains(this.#activeElement) && this.#activeElement instanceof HTMLElement) {\n this.#activeElement.focus();\n\n this.#activeElement = null;\n }\n }\n\n get connectedSnapshot() {\n return this.newSnapshot.isConnected ? this.newSnapshot : this.currentSnapshot\n }\n\n get currentElement() {\n return this.currentSnapshot.element\n }\n\n get newElement() {\n return this.newSnapshot.element\n }\n\n get permanentElementMap() {\n return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot)\n }\n\n get renderMethod() {\n return \"replace\"\n }\n}\n\nclass FrameRenderer extends Renderer {\n static renderElement(currentElement, newElement) {\n const destinationRange = document.createRange();\n destinationRange.selectNodeContents(currentElement);\n destinationRange.deleteContents();\n\n const frameElement = newElement;\n const sourceRange = frameElement.ownerDocument?.createRange();\n if (sourceRange) {\n sourceRange.selectNodeContents(frameElement);\n currentElement.appendChild(sourceRange.extractContents());\n }\n }\n\n constructor(delegate, currentSnapshot, newSnapshot, renderElement, isPreview, willRender = true) {\n super(currentSnapshot, newSnapshot, renderElement, isPreview, willRender);\n this.delegate = delegate;\n }\n\n get shouldRender() {\n return true\n }\n\n async render() {\n await nextRepaint();\n this.preservingPermanentElements(() => {\n this.loadFrameElement();\n });\n this.scrollFrameIntoView();\n await nextRepaint();\n this.focusFirstAutofocusableElement();\n await nextRepaint();\n this.activateScriptElements();\n }\n\n loadFrameElement() {\n this.delegate.willRenderFrame(this.currentElement, this.newElement);\n this.renderElement(this.currentElement, this.newElement);\n }\n\n scrollFrameIntoView() {\n if (this.currentElement.autoscroll || this.newElement.autoscroll) {\n const element = this.currentElement.firstElementChild;\n const block = readScrollLogicalPosition(this.currentElement.getAttribute(\"data-autoscroll-block\"), \"end\");\n const behavior = readScrollBehavior(this.currentElement.getAttribute(\"data-autoscroll-behavior\"), \"auto\");\n\n if (element) {\n element.scrollIntoView({ block, behavior });\n return true\n }\n }\n return false\n }\n\n activateScriptElements() {\n for (const inertScriptElement of this.newScriptElements) {\n const activatedScriptElement = activateScriptElement(inertScriptElement);\n inertScriptElement.replaceWith(activatedScriptElement);\n }\n }\n\n get newScriptElements() {\n return this.currentElement.querySelectorAll(\"script\")\n }\n}\n\nfunction readScrollLogicalPosition(value, defaultValue) {\n if (value == \"end\" || value == \"start\" || value == \"center\" || value == \"nearest\") {\n return value\n } else {\n return defaultValue\n }\n}\n\nfunction readScrollBehavior(value, defaultValue) {\n if (value == \"auto\" || value == \"smooth\") {\n return value\n } else {\n return defaultValue\n }\n}\n\n/**\n * @typedef {object} ConfigHead\n *\n * @property {'merge' | 'append' | 'morph' | 'none'} [style]\n * @property {boolean} [block]\n * @property {boolean} [ignore]\n * @property {function(Element): boolean} [shouldPreserve]\n * @property {function(Element): boolean} [shouldReAppend]\n * @property {function(Element): boolean} [shouldRemove]\n * @property {function(Element, {added: Node[], kept: Element[], removed: Element[]}): void} [afterHeadMorphed]\n */\n\n/**\n * @typedef {object} ConfigCallbacks\n *\n * @property {function(Node): boolean} [beforeNodeAdded]\n * @property {function(Node): void} [afterNodeAdded]\n * @property {function(Element, Node): boolean} [beforeNodeMorphed]\n * @property {function(Element, Node): void} [afterNodeMorphed]\n * @property {function(Element): boolean} [beforeNodeRemoved]\n * @property {function(Element): void} [afterNodeRemoved]\n * @property {function(string, Element, \"update\" | \"remove\"): boolean} [beforeAttributeUpdated]\n */\n\n/**\n * @typedef {object} Config\n *\n * @property {'outerHTML' | 'innerHTML'} [morphStyle]\n * @property {boolean} [ignoreActive]\n * @property {boolean} [ignoreActiveValue]\n * @property {boolean} [restoreFocus]\n * @property {ConfigCallbacks} [callbacks]\n * @property {ConfigHead} [head]\n */\n\n/**\n * @typedef {function} NoOp\n *\n * @returns {void}\n */\n\n/**\n * @typedef {object} ConfigHeadInternal\n *\n * @property {'merge' | 'append' | 'morph' | 'none'} style\n * @property {boolean} [block]\n * @property {boolean} [ignore]\n * @property {(function(Element): boolean) | NoOp} shouldPreserve\n * @property {(function(Element): boolean) | NoOp} shouldReAppend\n * @property {(function(Element): boolean) | NoOp} shouldRemove\n * @property {(function(Element, {added: Node[], kept: Element[], removed: Element[]}): void) | NoOp} afterHeadMorphed\n */\n\n/**\n * @typedef {object} ConfigCallbacksInternal\n *\n * @property {(function(Node): boolean) | NoOp} beforeNodeAdded\n * @property {(function(Node): void) | NoOp} afterNodeAdded\n * @property {(function(Node, Node): boolean) | NoOp} beforeNodeMorphed\n * @property {(function(Node, Node): void) | NoOp} afterNodeMorphed\n * @property {(function(Node): boolean) | NoOp} beforeNodeRemoved\n * @property {(function(Node): void) | NoOp} afterNodeRemoved\n * @property {(function(string, Element, \"update\" | \"remove\"): boolean) | NoOp} beforeAttributeUpdated\n */\n\n/**\n * @typedef {object} ConfigInternal\n *\n * @property {'outerHTML' | 'innerHTML'} morphStyle\n * @property {boolean} [ignoreActive]\n * @property {boolean} [ignoreActiveValue]\n * @property {boolean} [restoreFocus]\n * @property {ConfigCallbacksInternal} callbacks\n * @property {ConfigHeadInternal} head\n */\n\n/**\n * @typedef {Object} IdSets\n * @property {Set} persistentIds\n * @property {Map>} idMap\n */\n\n/**\n * @typedef {Function} Morph\n *\n * @param {Element | Document} oldNode\n * @param {Element | Node | HTMLCollection | Node[] | string | null} newContent\n * @param {Config} [config]\n * @returns {undefined | Node[]}\n */\n\n// base IIFE to define idiomorph\n/**\n *\n * @type {{defaults: ConfigInternal, morph: Morph}}\n */\nvar Idiomorph = (function () {\n\n /**\n * @typedef {object} MorphContext\n *\n * @property {Element} target\n * @property {Element} newContent\n * @property {ConfigInternal} config\n * @property {ConfigInternal['morphStyle']} morphStyle\n * @property {ConfigInternal['ignoreActive']} ignoreActive\n * @property {ConfigInternal['ignoreActiveValue']} ignoreActiveValue\n * @property {ConfigInternal['restoreFocus']} restoreFocus\n * @property {Map>} idMap\n * @property {Set} persistentIds\n * @property {ConfigInternal['callbacks']} callbacks\n * @property {ConfigInternal['head']} head\n * @property {HTMLDivElement} pantry\n */\n\n //=============================================================================\n // AND NOW IT BEGINS...\n //=============================================================================\n\n const noOp = () => {};\n /**\n * Default configuration values, updatable by users now\n * @type {ConfigInternal}\n */\n const defaults = {\n morphStyle: \"outerHTML\",\n callbacks: {\n beforeNodeAdded: noOp,\n afterNodeAdded: noOp,\n beforeNodeMorphed: noOp,\n afterNodeMorphed: noOp,\n beforeNodeRemoved: noOp,\n afterNodeRemoved: noOp,\n beforeAttributeUpdated: noOp,\n },\n head: {\n style: \"merge\",\n shouldPreserve: (elt) => elt.getAttribute(\"im-preserve\") === \"true\",\n shouldReAppend: (elt) => elt.getAttribute(\"im-re-append\") === \"true\",\n shouldRemove: noOp,\n afterHeadMorphed: noOp,\n },\n restoreFocus: true,\n };\n\n /**\n * Core idiomorph function for morphing one DOM tree to another\n *\n * @param {Element | Document} oldNode\n * @param {Element | Node | HTMLCollection | Node[] | string | null} newContent\n * @param {Config} [config]\n * @returns {Promise | Node[]}\n */\n function morph(oldNode, newContent, config = {}) {\n oldNode = normalizeElement(oldNode);\n const newNode = normalizeParent(newContent);\n const ctx = createMorphContext(oldNode, newNode, config);\n\n const morphedNodes = saveAndRestoreFocus(ctx, () => {\n return withHeadBlocking(\n ctx,\n oldNode,\n newNode,\n /** @param {MorphContext} ctx */ (ctx) => {\n if (ctx.morphStyle === \"innerHTML\") {\n morphChildren(ctx, oldNode, newNode);\n return Array.from(oldNode.childNodes);\n } else {\n return morphOuterHTML(ctx, oldNode, newNode);\n }\n },\n );\n });\n\n ctx.pantry.remove();\n return morphedNodes;\n }\n\n /**\n * Morph just the outerHTML of the oldNode to the newContent\n * We have to be careful because the oldNode could have siblings which need to be untouched\n * @param {MorphContext} ctx\n * @param {Element} oldNode\n * @param {Element} newNode\n * @returns {Node[]}\n */\n function morphOuterHTML(ctx, oldNode, newNode) {\n const oldParent = normalizeParent(oldNode);\n\n // basis for calulating which nodes were morphed\n // since there may be unmorphed sibling nodes\n let childNodes = Array.from(oldParent.childNodes);\n const index = childNodes.indexOf(oldNode);\n // how many elements are to the right of the oldNode\n const rightMargin = childNodes.length - (index + 1);\n\n morphChildren(\n ctx,\n oldParent,\n newNode,\n // these two optional params are the secret sauce\n oldNode, // start point for iteration\n oldNode.nextSibling, // end point for iteration\n );\n\n // return just the morphed nodes\n childNodes = Array.from(oldParent.childNodes);\n return childNodes.slice(index, childNodes.length - rightMargin);\n }\n\n /**\n * @param {MorphContext} ctx\n * @param {Function} fn\n * @returns {Promise | Node[]}\n */\n function saveAndRestoreFocus(ctx, fn) {\n if (!ctx.config.restoreFocus) return fn();\n let activeElement =\n /** @type {HTMLInputElement|HTMLTextAreaElement|null} */ (\n document.activeElement\n );\n\n // don't bother if the active element is not an input or textarea\n if (\n !(\n activeElement instanceof HTMLInputElement ||\n activeElement instanceof HTMLTextAreaElement\n )\n ) {\n return fn();\n }\n\n const { id: activeElementId, selectionStart, selectionEnd } = activeElement;\n\n const results = fn();\n\n if (activeElementId && activeElementId !== document.activeElement?.id) {\n activeElement = ctx.target.querySelector(`#${activeElementId}`);\n activeElement?.focus();\n }\n if (activeElement && !activeElement.selectionEnd && selectionEnd) {\n activeElement.setSelectionRange(selectionStart, selectionEnd);\n }\n\n return results;\n }\n\n const morphChildren = (function () {\n /**\n * This is the core algorithm for matching up children. The idea is to use id sets to try to match up\n * nodes as faithfully as possible. We greedily match, which allows us to keep the algorithm fast, but\n * by using id sets, we are able to better match up with content deeper in the DOM.\n *\n * Basic algorithm:\n * - for each node in the new content:\n * - search self and siblings for an id set match, falling back to a soft match\n * - if match found\n * - remove any nodes up to the match:\n * - pantry persistent nodes\n * - delete the rest\n * - morph the match\n * - elsif no match found, and node is persistent\n * - find its match by querying the old root (future) and pantry (past)\n * - move it and its children here\n * - morph it\n * - else\n * - create a new node from scratch as a last result\n *\n * @param {MorphContext} ctx the merge context\n * @param {Element} oldParent the old content that we are merging the new content into\n * @param {Element} newParent the parent element of the new content\n * @param {Node|null} [insertionPoint] the point in the DOM we start morphing at (defaults to first child)\n * @param {Node|null} [endPoint] the point in the DOM we stop morphing at (defaults to after last child)\n */\n function morphChildren(\n ctx,\n oldParent,\n newParent,\n insertionPoint = null,\n endPoint = null,\n ) {\n // normalize\n if (\n oldParent instanceof HTMLTemplateElement &&\n newParent instanceof HTMLTemplateElement\n ) {\n // @ts-ignore we can pretend the DocumentFragment is an Element\n oldParent = oldParent.content;\n // @ts-ignore ditto\n newParent = newParent.content;\n }\n insertionPoint ||= oldParent.firstChild;\n\n // run through all the new content\n for (const newChild of newParent.childNodes) {\n // once we reach the end of the old parent content skip to the end and insert the rest\n if (insertionPoint && insertionPoint != endPoint) {\n const bestMatch = findBestMatch(\n ctx,\n newChild,\n insertionPoint,\n endPoint,\n );\n if (bestMatch) {\n // if the node to morph is not at the insertion point then remove/move up to it\n if (bestMatch !== insertionPoint) {\n removeNodesBetween(ctx, insertionPoint, bestMatch);\n }\n morphNode(bestMatch, newChild, ctx);\n insertionPoint = bestMatch.nextSibling;\n continue;\n }\n }\n\n // if the matching node is elsewhere in the original content\n if (newChild instanceof Element && ctx.persistentIds.has(newChild.id)) {\n // move it and all its children here and morph\n const movedChild = moveBeforeById(\n oldParent,\n newChild.id,\n insertionPoint,\n ctx,\n );\n morphNode(movedChild, newChild, ctx);\n insertionPoint = movedChild.nextSibling;\n continue;\n }\n\n // last resort: insert the new node from scratch\n const insertedNode = createNode(\n oldParent,\n newChild,\n insertionPoint,\n ctx,\n );\n // could be null if beforeNodeAdded prevented insertion\n if (insertedNode) {\n insertionPoint = insertedNode.nextSibling;\n }\n }\n\n // remove any remaining old nodes that didn't match up with new content\n while (insertionPoint && insertionPoint != endPoint) {\n const tempNode = insertionPoint;\n insertionPoint = insertionPoint.nextSibling;\n removeNode(ctx, tempNode);\n }\n }\n\n /**\n * This performs the action of inserting a new node while handling situations where the node contains\n * elements with persistent ids and possible state info we can still preserve by moving in and then morphing\n *\n * @param {Element} oldParent\n * @param {Node} newChild\n * @param {Node|null} insertionPoint\n * @param {MorphContext} ctx\n * @returns {Node|null}\n */\n function createNode(oldParent, newChild, insertionPoint, ctx) {\n if (ctx.callbacks.beforeNodeAdded(newChild) === false) return null;\n if (ctx.idMap.has(newChild)) {\n // node has children with ids with possible state so create a dummy elt of same type and apply full morph algorithm\n const newEmptyChild = document.createElement(\n /** @type {Element} */ (newChild).tagName,\n );\n oldParent.insertBefore(newEmptyChild, insertionPoint);\n morphNode(newEmptyChild, newChild, ctx);\n ctx.callbacks.afterNodeAdded(newEmptyChild);\n return newEmptyChild;\n } else {\n // optimisation: no id state to preserve so we can just insert a clone of the newChild and its descendants\n const newClonedChild = document.importNode(newChild, true); // importNode to not mutate newParent\n oldParent.insertBefore(newClonedChild, insertionPoint);\n ctx.callbacks.afterNodeAdded(newClonedChild);\n return newClonedChild;\n }\n }\n\n //=============================================================================\n // Matching Functions\n //=============================================================================\n const findBestMatch = (function () {\n /**\n * Scans forward from the startPoint to the endPoint looking for a match\n * for the node. It looks for an id set match first, then a soft match.\n * We abort softmatching if we find two future soft matches, to reduce churn.\n * @param {Node} node\n * @param {MorphContext} ctx\n * @param {Node | null} startPoint\n * @param {Node | null} endPoint\n * @returns {Node | null}\n */\n function findBestMatch(ctx, node, startPoint, endPoint) {\n let softMatch = null;\n let nextSibling = node.nextSibling;\n let siblingSoftMatchCount = 0;\n\n let cursor = startPoint;\n while (cursor && cursor != endPoint) {\n // soft matching is a prerequisite for id set matching\n if (isSoftMatch(cursor, node)) {\n if (isIdSetMatch(ctx, cursor, node)) {\n return cursor; // found an id set match, we're done!\n }\n\n // we haven't yet saved a soft match fallback\n if (softMatch === null) {\n // the current soft match will hard match something else in the future, leave it\n if (!ctx.idMap.has(cursor)) {\n // save this as the fallback if we get through the loop without finding a hard match\n softMatch = cursor;\n }\n }\n }\n if (\n softMatch === null &&\n nextSibling &&\n isSoftMatch(cursor, nextSibling)\n ) {\n // The next new node has a soft match with this node, so\n // increment the count of future soft matches\n siblingSoftMatchCount++;\n nextSibling = nextSibling.nextSibling;\n\n // If there are two future soft matches, block soft matching for this node to allow\n // future siblings to soft match. This is to reduce churn in the DOM when an element\n // is prepended.\n if (siblingSoftMatchCount >= 2) {\n softMatch = undefined;\n }\n }\n\n // if the current node contains active element, stop looking for better future matches,\n // because if one is found, this node will be moved to the pantry, reparenting it and thus losing focus\n if (cursor.contains(document.activeElement)) break;\n\n cursor = cursor.nextSibling;\n }\n\n return softMatch || null;\n }\n\n /**\n *\n * @param {MorphContext} ctx\n * @param {Node} oldNode\n * @param {Node} newNode\n * @returns {boolean}\n */\n function isIdSetMatch(ctx, oldNode, newNode) {\n let oldSet = ctx.idMap.get(oldNode);\n let newSet = ctx.idMap.get(newNode);\n\n if (!newSet || !oldSet) return false;\n\n for (const id of oldSet) {\n // a potential match is an id in the new and old nodes that\n // has not already been merged into the DOM\n // But the newNode content we call this on has not been\n // merged yet and we don't allow duplicate IDs so it is simple\n if (newSet.has(id)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n *\n * @param {Node} oldNode\n * @param {Node} newNode\n * @returns {boolean}\n */\n function isSoftMatch(oldNode, newNode) {\n // ok to cast: if one is not element, `id` and `tagName` will be undefined and we'll just compare that.\n const oldElt = /** @type {Element} */ (oldNode);\n const newElt = /** @type {Element} */ (newNode);\n\n return (\n oldElt.nodeType === newElt.nodeType &&\n oldElt.tagName === newElt.tagName &&\n // If oldElt has an `id` with possible state and it doesn't match newElt.id then avoid morphing.\n // We'll still match an anonymous node with an IDed newElt, though, because if it got this far,\n // its not persistent, and new nodes can't have any hidden state.\n (!oldElt.id || oldElt.id === newElt.id)\n );\n }\n\n return findBestMatch;\n })();\n\n //=============================================================================\n // DOM Manipulation Functions\n //=============================================================================\n\n /**\n * Gets rid of an unwanted DOM node; strategy depends on nature of its reuse:\n * - Persistent nodes will be moved to the pantry for later reuse\n * - Other nodes will have their hooks called, and then are removed\n * @param {MorphContext} ctx\n * @param {Node} node\n */\n function removeNode(ctx, node) {\n // are we going to id set match this later?\n if (ctx.idMap.has(node)) {\n // skip callbacks and move to pantry\n moveBefore(ctx.pantry, node, null);\n } else {\n // remove for realsies\n if (ctx.callbacks.beforeNodeRemoved(node) === false) return;\n node.parentNode?.removeChild(node);\n ctx.callbacks.afterNodeRemoved(node);\n }\n }\n\n /**\n * Remove nodes between the start and end nodes\n * @param {MorphContext} ctx\n * @param {Node} startInclusive\n * @param {Node} endExclusive\n * @returns {Node|null}\n */\n function removeNodesBetween(ctx, startInclusive, endExclusive) {\n /** @type {Node | null} */\n let cursor = startInclusive;\n // remove nodes until the endExclusive node\n while (cursor && cursor !== endExclusive) {\n let tempNode = /** @type {Node} */ (cursor);\n cursor = cursor.nextSibling;\n removeNode(ctx, tempNode);\n }\n return cursor;\n }\n\n /**\n * Search for an element by id within the document and pantry, and move it using moveBefore.\n *\n * @param {Element} parentNode - The parent node to which the element will be moved.\n * @param {string} id - The ID of the element to be moved.\n * @param {Node | null} after - The reference node to insert the element before.\n * If `null`, the element is appended as the last child.\n * @param {MorphContext} ctx\n * @returns {Element} The found element\n */\n function moveBeforeById(parentNode, id, after, ctx) {\n const target =\n /** @type {Element} - will always be found */\n (\n ctx.target.querySelector(`#${id}`) ||\n ctx.pantry.querySelector(`#${id}`)\n );\n removeElementFromAncestorsIdMaps(target, ctx);\n moveBefore(parentNode, target, after);\n return target;\n }\n\n /**\n * Removes an element from its ancestors' id maps. This is needed when an element is moved from the\n * \"future\" via `moveBeforeId`. Otherwise, its erstwhile ancestors could be mistakenly moved to the\n * pantry rather than being deleted, preventing their removal hooks from being called.\n *\n * @param {Element} element - element to remove from its ancestors' id maps\n * @param {MorphContext} ctx\n */\n function removeElementFromAncestorsIdMaps(element, ctx) {\n const id = element.id;\n /** @ts-ignore - safe to loop in this way **/\n while ((element = element.parentNode)) {\n let idSet = ctx.idMap.get(element);\n if (idSet) {\n idSet.delete(id);\n if (!idSet.size) {\n ctx.idMap.delete(element);\n }\n }\n }\n }\n\n /**\n * Moves an element before another element within the same parent.\n * Uses the proposed `moveBefore` API if available (and working), otherwise falls back to `insertBefore`.\n * This is essentialy a forward-compat wrapper.\n *\n * @param {Element} parentNode - The parent node containing the after element.\n * @param {Node} element - The element to be moved.\n * @param {Node | null} after - The reference node to insert `element` before.\n * If `null`, `element` is appended as the last child.\n */\n function moveBefore(parentNode, element, after) {\n // @ts-ignore - use proposed moveBefore feature\n if (parentNode.moveBefore) {\n try {\n // @ts-ignore - use proposed moveBefore feature\n parentNode.moveBefore(element, after);\n } catch (e) {\n // fall back to insertBefore as some browsers may fail on moveBefore when trying to move Dom disconnected nodes to pantry\n parentNode.insertBefore(element, after);\n }\n } else {\n parentNode.insertBefore(element, after);\n }\n }\n\n return morphChildren;\n })();\n\n //=============================================================================\n // Single Node Morphing Code\n //=============================================================================\n const morphNode = (function () {\n /**\n * @param {Node} oldNode root node to merge content into\n * @param {Node} newContent new content to merge\n * @param {MorphContext} ctx the merge context\n * @returns {Node | null} the element that ended up in the DOM\n */\n function morphNode(oldNode, newContent, ctx) {\n if (ctx.ignoreActive && oldNode === document.activeElement) {\n // don't morph focused element\n return null;\n }\n\n if (ctx.callbacks.beforeNodeMorphed(oldNode, newContent) === false) {\n return oldNode;\n }\n\n if (oldNode instanceof HTMLHeadElement && ctx.head.ignore) ; else if (\n oldNode instanceof HTMLHeadElement &&\n ctx.head.style !== \"morph\"\n ) {\n // ok to cast: if newContent wasn't also a , it would've got caught in the `!isSoftMatch` branch above\n handleHeadElement(\n oldNode,\n /** @type {HTMLHeadElement} */ (newContent),\n ctx,\n );\n } else {\n morphAttributes(oldNode, newContent, ctx);\n if (!ignoreValueOfActiveElement(oldNode, ctx)) {\n // @ts-ignore newContent can be a node here because .firstChild will be null\n morphChildren(ctx, oldNode, newContent);\n }\n }\n ctx.callbacks.afterNodeMorphed(oldNode, newContent);\n return oldNode;\n }\n\n /**\n * syncs the oldNode to the newNode, copying over all attributes and\n * inner element state from the newNode to the oldNode\n *\n * @param {Node} oldNode the node to copy attributes & state to\n * @param {Node} newNode the node to copy attributes & state from\n * @param {MorphContext} ctx the merge context\n */\n function morphAttributes(oldNode, newNode, ctx) {\n let type = newNode.nodeType;\n\n // if is an element type, sync the attributes from the\n // new node into the new node\n if (type === 1 /* element type */) {\n const oldElt = /** @type {Element} */ (oldNode);\n const newElt = /** @type {Element} */ (newNode);\n\n const oldAttributes = oldElt.attributes;\n const newAttributes = newElt.attributes;\n for (const newAttribute of newAttributes) {\n if (ignoreAttribute(newAttribute.name, oldElt, \"update\", ctx)) {\n continue;\n }\n if (oldElt.getAttribute(newAttribute.name) !== newAttribute.value) {\n oldElt.setAttribute(newAttribute.name, newAttribute.value);\n }\n }\n // iterate backwards to avoid skipping over items when a delete occurs\n for (let i = oldAttributes.length - 1; 0 <= i; i--) {\n const oldAttribute = oldAttributes[i];\n\n // toAttributes is a live NamedNodeMap, so iteration+mutation is unsafe\n // e.g. custom element attribute callbacks can remove other attributes\n if (!oldAttribute) continue;\n\n if (!newElt.hasAttribute(oldAttribute.name)) {\n if (ignoreAttribute(oldAttribute.name, oldElt, \"remove\", ctx)) {\n continue;\n }\n oldElt.removeAttribute(oldAttribute.name);\n }\n }\n\n if (!ignoreValueOfActiveElement(oldElt, ctx)) {\n syncInputValue(oldElt, newElt, ctx);\n }\n }\n\n // sync text nodes\n if (type === 8 /* comment */ || type === 3 /* text */) {\n if (oldNode.nodeValue !== newNode.nodeValue) {\n oldNode.nodeValue = newNode.nodeValue;\n }\n }\n }\n\n /**\n * NB: many bothans died to bring us information:\n *\n * https://github.com/patrick-steele-idem/morphdom/blob/master/src/specialElHandlers.js\n * https://github.com/choojs/nanomorph/blob/master/lib/morph.jsL113\n *\n * @param {Element} oldElement the element to sync the input value to\n * @param {Element} newElement the element to sync the input value from\n * @param {MorphContext} ctx the merge context\n */\n function syncInputValue(oldElement, newElement, ctx) {\n if (\n oldElement instanceof HTMLInputElement &&\n newElement instanceof HTMLInputElement &&\n newElement.type !== \"file\"\n ) {\n let newValue = newElement.value;\n let oldValue = oldElement.value;\n\n // sync boolean attributes\n syncBooleanAttribute(oldElement, newElement, \"checked\", ctx);\n syncBooleanAttribute(oldElement, newElement, \"disabled\", ctx);\n\n if (!newElement.hasAttribute(\"value\")) {\n if (!ignoreAttribute(\"value\", oldElement, \"remove\", ctx)) {\n oldElement.value = \"\";\n oldElement.removeAttribute(\"value\");\n }\n } else if (oldValue !== newValue) {\n if (!ignoreAttribute(\"value\", oldElement, \"update\", ctx)) {\n oldElement.setAttribute(\"value\", newValue);\n oldElement.value = newValue;\n }\n }\n // TODO: QUESTION(1cg): this used to only check `newElement` unlike the other branches -- why?\n // did I break something?\n } else if (\n oldElement instanceof HTMLOptionElement &&\n newElement instanceof HTMLOptionElement\n ) {\n syncBooleanAttribute(oldElement, newElement, \"selected\", ctx);\n } else if (\n oldElement instanceof HTMLTextAreaElement &&\n newElement instanceof HTMLTextAreaElement\n ) {\n let newValue = newElement.value;\n let oldValue = oldElement.value;\n if (ignoreAttribute(\"value\", oldElement, \"update\", ctx)) {\n return;\n }\n if (newValue !== oldValue) {\n oldElement.value = newValue;\n }\n if (\n oldElement.firstChild &&\n oldElement.firstChild.nodeValue !== newValue\n ) {\n oldElement.firstChild.nodeValue = newValue;\n }\n }\n }\n\n /**\n * @param {Element} oldElement element to write the value to\n * @param {Element} newElement element to read the value from\n * @param {string} attributeName the attribute name\n * @param {MorphContext} ctx the merge context\n */\n function syncBooleanAttribute(oldElement, newElement, attributeName, ctx) {\n // @ts-ignore this function is only used on boolean attrs that are reflected as dom properties\n const newLiveValue = newElement[attributeName],\n // @ts-ignore ditto\n oldLiveValue = oldElement[attributeName];\n if (newLiveValue !== oldLiveValue) {\n const ignoreUpdate = ignoreAttribute(\n attributeName,\n oldElement,\n \"update\",\n ctx,\n );\n if (!ignoreUpdate) {\n // update attribute's associated DOM property\n // @ts-ignore this function is only used on boolean attrs that are reflected as dom properties\n oldElement[attributeName] = newElement[attributeName];\n }\n if (newLiveValue) {\n if (!ignoreUpdate) {\n // https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML\n // this is the correct way to set a boolean attribute to \"true\"\n oldElement.setAttribute(attributeName, \"\");\n }\n } else {\n if (!ignoreAttribute(attributeName, oldElement, \"remove\", ctx)) {\n oldElement.removeAttribute(attributeName);\n }\n }\n }\n }\n\n /**\n * @param {string} attr the attribute to be mutated\n * @param {Element} element the element that is going to be updated\n * @param {\"update\" | \"remove\"} updateType\n * @param {MorphContext} ctx the merge context\n * @returns {boolean} true if the attribute should be ignored, false otherwise\n */\n function ignoreAttribute(attr, element, updateType, ctx) {\n if (\n attr === \"value\" &&\n ctx.ignoreActiveValue &&\n element === document.activeElement\n ) {\n return true;\n }\n return (\n ctx.callbacks.beforeAttributeUpdated(attr, element, updateType) ===\n false\n );\n }\n\n /**\n * @param {Node} possibleActiveElement\n * @param {MorphContext} ctx\n * @returns {boolean}\n */\n function ignoreValueOfActiveElement(possibleActiveElement, ctx) {\n return (\n !!ctx.ignoreActiveValue &&\n possibleActiveElement === document.activeElement &&\n possibleActiveElement !== document.body\n );\n }\n\n return morphNode;\n })();\n\n //=============================================================================\n // Head Management Functions\n //=============================================================================\n /**\n * @param {MorphContext} ctx\n * @param {Element} oldNode\n * @param {Element} newNode\n * @param {function} callback\n * @returns {Node[] | Promise}\n */\n function withHeadBlocking(ctx, oldNode, newNode, callback) {\n if (ctx.head.block) {\n const oldHead = oldNode.querySelector(\"head\");\n const newHead = newNode.querySelector(\"head\");\n if (oldHead && newHead) {\n const promises = handleHeadElement(oldHead, newHead, ctx);\n // when head promises resolve, proceed ignoring the head tag\n return Promise.all(promises).then(() => {\n const newCtx = Object.assign(ctx, {\n head: {\n block: false,\n ignore: true,\n },\n });\n return callback(newCtx);\n });\n }\n }\n // just proceed if we not head blocking\n return callback(ctx);\n }\n\n /**\n * The HEAD tag can be handled specially, either w/ a 'merge' or 'append' style\n *\n * @param {Element} oldHead\n * @param {Element} newHead\n * @param {MorphContext} ctx\n * @returns {Promise[]}\n */\n function handleHeadElement(oldHead, newHead, ctx) {\n let added = [];\n let removed = [];\n let preserved = [];\n let nodesToAppend = [];\n\n // put all new head elements into a Map, by their outerHTML\n let srcToNewHeadNodes = new Map();\n for (const newHeadChild of newHead.children) {\n srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);\n }\n\n // for each elt in the current head\n for (const currentHeadElt of oldHead.children) {\n // If the current head element is in the map\n let inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);\n let isReAppended = ctx.head.shouldReAppend(currentHeadElt);\n let isPreserved = ctx.head.shouldPreserve(currentHeadElt);\n if (inNewContent || isPreserved) {\n if (isReAppended) {\n // remove the current version and let the new version replace it and re-execute\n removed.push(currentHeadElt);\n } else {\n // this element already exists and should not be re-appended, so remove it from\n // the new content map, preserving it in the DOM\n srcToNewHeadNodes.delete(currentHeadElt.outerHTML);\n preserved.push(currentHeadElt);\n }\n } else {\n if (ctx.head.style === \"append\") {\n // we are appending and this existing element is not new content\n // so if and only if it is marked for re-append do we do anything\n if (isReAppended) {\n removed.push(currentHeadElt);\n nodesToAppend.push(currentHeadElt);\n }\n } else {\n // if this is a merge, we remove this content since it is not in the new head\n if (ctx.head.shouldRemove(currentHeadElt) !== false) {\n removed.push(currentHeadElt);\n }\n }\n }\n }\n\n // Push the remaining new head elements in the Map into the\n // nodes to append to the head tag\n nodesToAppend.push(...srcToNewHeadNodes.values());\n\n let promises = [];\n for (const newNode of nodesToAppend) {\n // TODO: This could theoretically be null, based on type\n let newElt = /** @type {ChildNode} */ (\n document.createRange().createContextualFragment(newNode.outerHTML)\n .firstChild\n );\n if (ctx.callbacks.beforeNodeAdded(newElt) !== false) {\n if (\n (\"href\" in newElt && newElt.href) ||\n (\"src\" in newElt && newElt.src)\n ) {\n /** @type {(result?: any) => void} */ let resolve;\n let promise = new Promise(function (_resolve) {\n resolve = _resolve;\n });\n newElt.addEventListener(\"load\", function () {\n resolve();\n });\n promises.push(promise);\n }\n oldHead.appendChild(newElt);\n ctx.callbacks.afterNodeAdded(newElt);\n added.push(newElt);\n }\n }\n\n // remove all removed elements, after we have appended the new elements to avoid\n // additional network requests for things like style sheets\n for (const removedElement of removed) {\n if (ctx.callbacks.beforeNodeRemoved(removedElement) !== false) {\n oldHead.removeChild(removedElement);\n ctx.callbacks.afterNodeRemoved(removedElement);\n }\n }\n\n ctx.head.afterHeadMorphed(oldHead, {\n added: added,\n kept: preserved,\n removed: removed,\n });\n return promises;\n }\n\n //=============================================================================\n // Create Morph Context Functions\n //=============================================================================\n const createMorphContext = (function () {\n /**\n *\n * @param {Element} oldNode\n * @param {Element} newContent\n * @param {Config} config\n * @returns {MorphContext}\n */\n function createMorphContext(oldNode, newContent, config) {\n const { persistentIds, idMap } = createIdMaps(oldNode, newContent);\n\n const mergedConfig = mergeDefaults(config);\n const morphStyle = mergedConfig.morphStyle || \"outerHTML\";\n if (![\"innerHTML\", \"outerHTML\"].includes(morphStyle)) {\n throw `Do not understand how to morph style ${morphStyle}`;\n }\n\n return {\n target: oldNode,\n newContent: newContent,\n config: mergedConfig,\n morphStyle: morphStyle,\n ignoreActive: mergedConfig.ignoreActive,\n ignoreActiveValue: mergedConfig.ignoreActiveValue,\n restoreFocus: mergedConfig.restoreFocus,\n idMap: idMap,\n persistentIds: persistentIds,\n pantry: createPantry(),\n callbacks: mergedConfig.callbacks,\n head: mergedConfig.head,\n };\n }\n\n /**\n * Deep merges the config object and the Idiomorph.defaults object to\n * produce a final configuration object\n * @param {Config} config\n * @returns {ConfigInternal}\n */\n function mergeDefaults(config) {\n let finalConfig = Object.assign({}, defaults);\n\n // copy top level stuff into final config\n Object.assign(finalConfig, config);\n\n // copy callbacks into final config (do this to deep merge the callbacks)\n finalConfig.callbacks = Object.assign(\n {},\n defaults.callbacks,\n config.callbacks,\n );\n\n // copy head config into final config (do this to deep merge the head)\n finalConfig.head = Object.assign({}, defaults.head, config.head);\n\n return finalConfig;\n }\n\n /**\n * @returns {HTMLDivElement}\n */\n function createPantry() {\n const pantry = document.createElement(\"div\");\n pantry.hidden = true;\n document.body.insertAdjacentElement(\"afterend\", pantry);\n return pantry;\n }\n\n /**\n * Returns all elements with an ID contained within the root element and its descendants\n *\n * @param {Element} root\n * @returns {Element[]}\n */\n function findIdElements(root) {\n let elements = Array.from(root.querySelectorAll(\"[id]\"));\n if (root.id) {\n elements.push(root);\n }\n return elements;\n }\n\n /**\n * A bottom-up algorithm that populates a map of Element -> IdSet.\n * The idSet for a given element is the set of all IDs contained within its subtree.\n * As an optimzation, we filter these IDs through the given list of persistent IDs,\n * because we don't need to bother considering IDed elements that won't be in the new content.\n *\n * @param {Map>} idMap\n * @param {Set} persistentIds\n * @param {Element} root\n * @param {Element[]} elements\n */\n function populateIdMapWithTree(idMap, persistentIds, root, elements) {\n for (const elt of elements) {\n if (persistentIds.has(elt.id)) {\n /** @type {Element|null} */\n let current = elt;\n // walk up the parent hierarchy of that element, adding the id\n // of element to the parent's id set\n while (current) {\n let idSet = idMap.get(current);\n // if the id set doesn't exist, create it and insert it in the map\n if (idSet == null) {\n idSet = new Set();\n idMap.set(current, idSet);\n }\n idSet.add(elt.id);\n\n if (current === root) break;\n current = current.parentElement;\n }\n }\n }\n }\n\n /**\n * This function computes a map of nodes to all ids contained within that node (inclusive of the\n * node). This map can be used to ask if two nodes have intersecting sets of ids, which allows\n * for a looser definition of \"matching\" than tradition id matching, and allows child nodes\n * to contribute to a parent nodes matching.\n *\n * @param {Element} oldContent the old content that will be morphed\n * @param {Element} newContent the new content to morph to\n * @returns {IdSets}\n */\n function createIdMaps(oldContent, newContent) {\n const oldIdElements = findIdElements(oldContent);\n const newIdElements = findIdElements(newContent);\n\n const persistentIds = createPersistentIds(oldIdElements, newIdElements);\n\n /** @type {Map>} */\n let idMap = new Map();\n populateIdMapWithTree(idMap, persistentIds, oldContent, oldIdElements);\n\n /** @ts-ignore - if newContent is a duck-typed parent, pass its single child node as the root to halt upwards iteration */\n const newRoot = newContent.__idiomorphRoot || newContent;\n populateIdMapWithTree(idMap, persistentIds, newRoot, newIdElements);\n\n return { persistentIds, idMap };\n }\n\n /**\n * This function computes the set of ids that persist between the two contents excluding duplicates\n *\n * @param {Element[]} oldIdElements\n * @param {Element[]} newIdElements\n * @returns {Set}\n */\n function createPersistentIds(oldIdElements, newIdElements) {\n let duplicateIds = new Set();\n\n /** @type {Map} */\n let oldIdTagNameMap = new Map();\n for (const { id, tagName } of oldIdElements) {\n if (oldIdTagNameMap.has(id)) {\n duplicateIds.add(id);\n } else {\n oldIdTagNameMap.set(id, tagName);\n }\n }\n\n let persistentIds = new Set();\n for (const { id, tagName } of newIdElements) {\n if (persistentIds.has(id)) {\n duplicateIds.add(id);\n } else if (oldIdTagNameMap.get(id) === tagName) {\n persistentIds.add(id);\n }\n // skip if tag types mismatch because its not possible to morph one tag into another\n }\n\n for (const id of duplicateIds) {\n persistentIds.delete(id);\n }\n return persistentIds;\n }\n\n return createMorphContext;\n })();\n\n //=============================================================================\n // HTML Normalization Functions\n //=============================================================================\n const { normalizeElement, normalizeParent } = (function () {\n /** @type {WeakSet} */\n const generatedByIdiomorph = new WeakSet();\n\n /**\n *\n * @param {Element | Document} content\n * @returns {Element}\n */\n function normalizeElement(content) {\n if (content instanceof Document) {\n return content.documentElement;\n } else {\n return content;\n }\n }\n\n /**\n *\n * @param {null | string | Node | HTMLCollection | Node[] | Document & {generatedByIdiomorph:boolean}} newContent\n * @returns {Element}\n */\n function normalizeParent(newContent) {\n if (newContent == null) {\n return document.createElement(\"div\"); // dummy parent element\n } else if (typeof newContent === \"string\") {\n return normalizeParent(parseContent(newContent));\n } else if (\n generatedByIdiomorph.has(/** @type {Element} */ (newContent))\n ) {\n // the template tag created by idiomorph parsing can serve as a dummy parent\n return /** @type {Element} */ (newContent);\n } else if (newContent instanceof Node) {\n if (newContent.parentNode) {\n // we can't use the parent directly because newContent may have siblings\n // that we don't want in the morph, and reparenting might be expensive (TODO is it?),\n // so we create a duck-typed parent node instead.\n return createDuckTypedParent(newContent);\n } else {\n // a single node is added as a child to a dummy parent\n const dummyParent = document.createElement(\"div\");\n dummyParent.append(newContent);\n return dummyParent;\n }\n } else {\n // all nodes in the array or HTMLElement collection are consolidated under\n // a single dummy parent element\n const dummyParent = document.createElement(\"div\");\n for (const elt of [...newContent]) {\n dummyParent.append(elt);\n }\n return dummyParent;\n }\n }\n\n /**\n * Creates a fake duck-typed parent element to wrap a single node, without actually reparenting it.\n * \"If it walks like a duck, and quacks like a duck, then it must be a duck!\" -- James Whitcomb Riley (1849\u20131916)\n *\n * @param {Node} newContent\n * @returns {Element}\n */\n function createDuckTypedParent(newContent) {\n return /** @type {Element} */ (\n /** @type {unknown} */ ({\n childNodes: [newContent],\n /** @ts-ignore - cover your eyes for a minute, tsc */\n querySelectorAll: (s) => {\n /** @ts-ignore */\n const elements = newContent.querySelectorAll(s);\n /** @ts-ignore */\n return newContent.matches(s) ? [newContent, ...elements] : elements;\n },\n /** @ts-ignore */\n insertBefore: (n, r) => newContent.parentNode.insertBefore(n, r),\n /** @ts-ignore */\n moveBefore: (n, r) => newContent.parentNode.moveBefore(n, r),\n // for later use with populateIdMapWithTree to halt upwards iteration\n get __idiomorphRoot() {\n return newContent;\n },\n })\n );\n }\n\n /**\n *\n * @param {string} newContent\n * @returns {Node | null | DocumentFragment}\n */\n function parseContent(newContent) {\n let parser = new DOMParser();\n\n // remove svgs to avoid false-positive matches on head, etc.\n let contentWithSvgsRemoved = newContent.replace(\n /]*>|>)([\\s\\S]*?)<\\/svg>/gim,\n \"\",\n );\n\n // if the newContent contains a html, head or body tag, we can simply parse it w/o wrapping\n if (\n contentWithSvgsRemoved.match(/<\\/html>/) ||\n contentWithSvgsRemoved.match(/<\\/head>/) ||\n contentWithSvgsRemoved.match(/<\\/body>/)\n ) {\n let content = parser.parseFromString(newContent, \"text/html\");\n // if it is a full HTML document, return the document itself as the parent container\n if (contentWithSvgsRemoved.match(/<\\/html>/)) {\n generatedByIdiomorph.add(content);\n return content;\n } else {\n // otherwise return the html element as the parent container\n let htmlElement = content.firstChild;\n if (htmlElement) {\n generatedByIdiomorph.add(htmlElement);\n }\n return htmlElement;\n }\n } else {\n // if it is partial HTML, wrap it in a template tag to provide a parent element and also to help\n // deal with touchy tags like tr, tbody, etc.\n let responseDoc = parser.parseFromString(\n \"\",\n \"text/html\",\n );\n let content = /** @type {HTMLTemplateElement} */ (\n responseDoc.body.querySelector(\"template\")\n ).content;\n generatedByIdiomorph.add(content);\n return content;\n }\n }\n\n return { normalizeElement, normalizeParent };\n })();\n\n //=============================================================================\n // This is what ends up becoming the Idiomorph global object\n //=============================================================================\n return {\n morph,\n defaults,\n };\n})();\n\nfunction morphElements(currentElement, newElement, { callbacks, ...options } = {}) {\n Idiomorph.morph(currentElement, newElement, {\n ...options,\n callbacks: new DefaultIdiomorphCallbacks(callbacks)\n });\n}\n\nfunction morphChildren(currentElement, newElement) {\n morphElements(currentElement, newElement.childNodes, {\n morphStyle: \"innerHTML\"\n });\n}\n\nclass DefaultIdiomorphCallbacks {\n #beforeNodeMorphed\n\n constructor({ beforeNodeMorphed } = {}) {\n this.#beforeNodeMorphed = beforeNodeMorphed || (() => true);\n }\n\n beforeNodeAdded = (node) => {\n return !(node.id && node.hasAttribute(\"data-turbo-permanent\") && document.getElementById(node.id))\n }\n\n beforeNodeMorphed = (currentElement, newElement) => {\n if (currentElement instanceof Element) {\n if (!currentElement.hasAttribute(\"data-turbo-permanent\") && this.#beforeNodeMorphed(currentElement, newElement)) {\n const event = dispatch(\"turbo:before-morph-element\", {\n cancelable: true,\n target: currentElement,\n detail: { currentElement, newElement }\n });\n\n return !event.defaultPrevented\n } else {\n return false\n }\n }\n }\n\n beforeAttributeUpdated = (attributeName, target, mutationType) => {\n const event = dispatch(\"turbo:before-morph-attribute\", {\n cancelable: true,\n target,\n detail: { attributeName, mutationType }\n });\n\n return !event.defaultPrevented\n }\n\n beforeNodeRemoved = (node) => {\n return this.beforeNodeMorphed(node)\n }\n\n afterNodeMorphed = (currentElement, newElement) => {\n if (currentElement instanceof Element) {\n dispatch(\"turbo:morph-element\", {\n target: currentElement,\n detail: { currentElement, newElement }\n });\n }\n }\n}\n\nclass MorphingFrameRenderer extends FrameRenderer {\n static renderElement(currentElement, newElement) {\n dispatch(\"turbo:before-frame-morph\", {\n target: currentElement,\n detail: { currentElement, newElement }\n });\n\n morphChildren(currentElement, newElement);\n }\n\n async preservingPermanentElements(callback) {\n return await callback()\n }\n}\n\nclass ProgressBar {\n static animationDuration = 300 /*ms*/\n\n static get defaultCSS() {\n return unindent`\n .turbo-progress-bar {\n position: fixed;\n display: block;\n top: 0;\n left: 0;\n height: 3px;\n background: #0076ff;\n z-index: 2147483647;\n transition:\n width ${ProgressBar.animationDuration}ms ease-out,\n opacity ${ProgressBar.animationDuration / 2}ms ${ProgressBar.animationDuration / 2}ms ease-in;\n transform: translate3d(0, 0, 0);\n }\n `\n }\n\n hiding = false\n value = 0\n visible = false\n\n constructor() {\n this.stylesheetElement = this.createStylesheetElement();\n this.progressElement = this.createProgressElement();\n this.installStylesheetElement();\n this.setValue(0);\n }\n\n show() {\n if (!this.visible) {\n this.visible = true;\n this.installProgressElement();\n this.startTrickling();\n }\n }\n\n hide() {\n if (this.visible && !this.hiding) {\n this.hiding = true;\n this.fadeProgressElement(() => {\n this.uninstallProgressElement();\n this.stopTrickling();\n this.visible = false;\n this.hiding = false;\n });\n }\n }\n\n setValue(value) {\n this.value = value;\n this.refresh();\n }\n\n // Private\n\n installStylesheetElement() {\n document.head.insertBefore(this.stylesheetElement, document.head.firstChild);\n }\n\n installProgressElement() {\n this.progressElement.style.width = \"0\";\n this.progressElement.style.opacity = \"1\";\n document.documentElement.insertBefore(this.progressElement, document.body);\n this.refresh();\n }\n\n fadeProgressElement(callback) {\n this.progressElement.style.opacity = \"0\";\n setTimeout(callback, ProgressBar.animationDuration * 1.5);\n }\n\n uninstallProgressElement() {\n if (this.progressElement.parentNode) {\n document.documentElement.removeChild(this.progressElement);\n }\n }\n\n startTrickling() {\n if (!this.trickleInterval) {\n this.trickleInterval = window.setInterval(this.trickle, ProgressBar.animationDuration);\n }\n }\n\n stopTrickling() {\n window.clearInterval(this.trickleInterval);\n delete this.trickleInterval;\n }\n\n trickle = () => {\n this.setValue(this.value + Math.random() / 100);\n }\n\n refresh() {\n requestAnimationFrame(() => {\n this.progressElement.style.width = `${10 + this.value * 90}%`;\n });\n }\n\n createStylesheetElement() {\n const element = document.createElement(\"style\");\n element.type = \"text/css\";\n element.textContent = ProgressBar.defaultCSS;\n const cspNonce = getCspNonce();\n if (cspNonce) {\n element.nonce = cspNonce;\n }\n return element\n }\n\n createProgressElement() {\n const element = document.createElement(\"div\");\n element.className = \"turbo-progress-bar\";\n return element\n }\n}\n\nclass HeadSnapshot extends Snapshot {\n detailsByOuterHTML = this.children\n .filter((element) => !elementIsNoscript(element))\n .map((element) => elementWithoutNonce(element))\n .reduce((result, element) => {\n const { outerHTML } = element;\n const details =\n outerHTML in result\n ? result[outerHTML]\n : {\n type: elementType(element),\n tracked: elementIsTracked(element),\n elements: []\n };\n return {\n ...result,\n [outerHTML]: {\n ...details,\n elements: [...details.elements, element]\n }\n }\n }, {})\n\n get trackedElementSignature() {\n return Object.keys(this.detailsByOuterHTML)\n .filter((outerHTML) => this.detailsByOuterHTML[outerHTML].tracked)\n .join(\"\")\n }\n\n getScriptElementsNotInSnapshot(snapshot) {\n return this.getElementsMatchingTypeNotInSnapshot(\"script\", snapshot)\n }\n\n getStylesheetElementsNotInSnapshot(snapshot) {\n return this.getElementsMatchingTypeNotInSnapshot(\"stylesheet\", snapshot)\n }\n\n getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {\n return Object.keys(this.detailsByOuterHTML)\n .filter((outerHTML) => !(outerHTML in snapshot.detailsByOuterHTML))\n .map((outerHTML) => this.detailsByOuterHTML[outerHTML])\n .filter(({ type }) => type == matchedType)\n .map(({ elements: [element] }) => element)\n }\n\n get provisionalElements() {\n return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n const { type, tracked, elements } = this.detailsByOuterHTML[outerHTML];\n if (type == null && !tracked) {\n return [...result, ...elements]\n } else if (elements.length > 1) {\n return [...result, ...elements.slice(1)]\n } else {\n return result\n }\n }, [])\n }\n\n getMetaValue(name) {\n const element = this.findMetaElementByName(name);\n return element ? element.getAttribute(\"content\") : null\n }\n\n findMetaElementByName(name) {\n return Object.keys(this.detailsByOuterHTML).reduce((result, outerHTML) => {\n const {\n elements: [element]\n } = this.detailsByOuterHTML[outerHTML];\n return elementIsMetaElementWithName(element, name) ? element : result\n }, undefined | undefined)\n }\n}\n\nfunction elementType(element) {\n if (elementIsScript(element)) {\n return \"script\"\n } else if (elementIsStylesheet(element)) {\n return \"stylesheet\"\n }\n}\n\nfunction elementIsTracked(element) {\n return element.getAttribute(\"data-turbo-track\") == \"reload\"\n}\n\nfunction elementIsScript(element) {\n const tagName = element.localName;\n return tagName == \"script\"\n}\n\nfunction elementIsNoscript(element) {\n const tagName = element.localName;\n return tagName == \"noscript\"\n}\n\nfunction elementIsStylesheet(element) {\n const tagName = element.localName;\n return tagName == \"style\" || (tagName == \"link\" && element.getAttribute(\"rel\") == \"stylesheet\")\n}\n\nfunction elementIsMetaElementWithName(element, name) {\n const tagName = element.localName;\n return tagName == \"meta\" && element.getAttribute(\"name\") == name\n}\n\nfunction elementWithoutNonce(element) {\n if (element.hasAttribute(\"nonce\")) {\n element.setAttribute(\"nonce\", \"\");\n }\n\n return element\n}\n\nclass PageSnapshot extends Snapshot {\n static fromHTMLString(html = \"\") {\n return this.fromDocument(parseHTMLDocument(html))\n }\n\n static fromElement(element) {\n return this.fromDocument(element.ownerDocument)\n }\n\n static fromDocument({ documentElement, body, head }) {\n return new this(documentElement, body, new HeadSnapshot(head))\n }\n\n constructor(documentElement, body, headSnapshot) {\n super(body);\n this.documentElement = documentElement;\n this.headSnapshot = headSnapshot;\n }\n\n clone() {\n const clonedElement = this.element.cloneNode(true);\n\n const selectElements = this.element.querySelectorAll(\"select\");\n const clonedSelectElements = clonedElement.querySelectorAll(\"select\");\n\n for (const [index, source] of selectElements.entries()) {\n const clone = clonedSelectElements[index];\n for (const option of clone.selectedOptions) option.selected = false;\n for (const option of source.selectedOptions) clone.options[option.index].selected = true;\n }\n\n for (const clonedPasswordInput of clonedElement.querySelectorAll('input[type=\"password\"]')) {\n clonedPasswordInput.value = \"\";\n }\n\n return new PageSnapshot(this.documentElement, clonedElement, this.headSnapshot)\n }\n\n get lang() {\n return this.documentElement.getAttribute(\"lang\")\n }\n\n get headElement() {\n return this.headSnapshot.element\n }\n\n get rootLocation() {\n const root = this.getSetting(\"root\") ?? \"/\";\n return expandURL(root)\n }\n\n get cacheControlValue() {\n return this.getSetting(\"cache-control\")\n }\n\n get isPreviewable() {\n return this.cacheControlValue != \"no-preview\"\n }\n\n get isCacheable() {\n return this.cacheControlValue != \"no-cache\"\n }\n\n get isVisitable() {\n return this.getSetting(\"visit-control\") != \"reload\"\n }\n\n get prefersViewTransitions() {\n return this.headSnapshot.getMetaValue(\"view-transition\") === \"same-origin\"\n }\n\n get shouldMorphPage() {\n return this.getSetting(\"refresh-method\") === \"morph\"\n }\n\n get shouldPreserveScrollPosition() {\n return this.getSetting(\"refresh-scroll\") === \"preserve\"\n }\n\n // Private\n\n getSetting(name) {\n return this.headSnapshot.getMetaValue(`turbo-${name}`)\n }\n}\n\nclass ViewTransitioner {\n #viewTransitionStarted = false\n #lastOperation = Promise.resolve()\n\n renderChange(useViewTransition, render) {\n if (useViewTransition && this.viewTransitionsAvailable && !this.#viewTransitionStarted) {\n this.#viewTransitionStarted = true;\n this.#lastOperation = this.#lastOperation.then(async () => {\n await document.startViewTransition(render).finished;\n });\n } else {\n this.#lastOperation = this.#lastOperation.then(render);\n }\n\n return this.#lastOperation\n }\n\n get viewTransitionsAvailable() {\n return document.startViewTransition\n }\n}\n\nconst defaultOptions = {\n action: \"advance\",\n historyChanged: false,\n visitCachedSnapshot: () => {},\n willRender: true,\n updateHistory: true,\n shouldCacheSnapshot: true,\n acceptsStreamResponse: false\n};\n\nconst TimingMetric = {\n visitStart: \"visitStart\",\n requestStart: \"requestStart\",\n requestEnd: \"requestEnd\",\n visitEnd: \"visitEnd\"\n};\n\nconst VisitState = {\n initialized: \"initialized\",\n started: \"started\",\n canceled: \"canceled\",\n failed: \"failed\",\n completed: \"completed\"\n};\n\nconst SystemStatusCode = {\n networkFailure: 0,\n timeoutFailure: -1,\n contentTypeMismatch: -2\n};\n\nconst Direction = {\n advance: \"forward\",\n restore: \"back\",\n replace: \"none\"\n};\n\nclass Visit {\n identifier = uuid() // Required by turbo-ios\n timingMetrics = {}\n\n followedRedirect = false\n historyChanged = false\n scrolled = false\n shouldCacheSnapshot = true\n acceptsStreamResponse = false\n snapshotCached = false\n state = VisitState.initialized\n viewTransitioner = new ViewTransitioner()\n\n constructor(delegate, location, restorationIdentifier, options = {}) {\n this.delegate = delegate;\n this.location = location;\n this.restorationIdentifier = restorationIdentifier || uuid();\n\n const {\n action,\n historyChanged,\n referrer,\n snapshot,\n snapshotHTML,\n response,\n visitCachedSnapshot,\n willRender,\n updateHistory,\n shouldCacheSnapshot,\n acceptsStreamResponse,\n direction\n } = {\n ...defaultOptions,\n ...options\n };\n this.action = action;\n this.historyChanged = historyChanged;\n this.referrer = referrer;\n this.snapshot = snapshot;\n this.snapshotHTML = snapshotHTML;\n this.response = response;\n this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);\n this.isPageRefresh = this.view.isPageRefresh(this);\n this.visitCachedSnapshot = visitCachedSnapshot;\n this.willRender = willRender;\n this.updateHistory = updateHistory;\n this.scrolled = !willRender;\n this.shouldCacheSnapshot = shouldCacheSnapshot;\n this.acceptsStreamResponse = acceptsStreamResponse;\n this.direction = direction || Direction[action];\n }\n\n get adapter() {\n return this.delegate.adapter\n }\n\n get view() {\n return this.delegate.view\n }\n\n get history() {\n return this.delegate.history\n }\n\n get restorationData() {\n return this.history.getRestorationDataForIdentifier(this.restorationIdentifier)\n }\n\n get silent() {\n return this.isSamePage\n }\n\n start() {\n if (this.state == VisitState.initialized) {\n this.recordTimingMetric(TimingMetric.visitStart);\n this.state = VisitState.started;\n this.adapter.visitStarted(this);\n this.delegate.visitStarted(this);\n }\n }\n\n cancel() {\n if (this.state == VisitState.started) {\n if (this.request) {\n this.request.cancel();\n }\n this.cancelRender();\n this.state = VisitState.canceled;\n }\n }\n\n complete() {\n if (this.state == VisitState.started) {\n this.recordTimingMetric(TimingMetric.visitEnd);\n this.adapter.visitCompleted(this);\n this.state = VisitState.completed;\n this.followRedirect();\n\n if (!this.followedRedirect) {\n this.delegate.visitCompleted(this);\n }\n }\n }\n\n fail() {\n if (this.state == VisitState.started) {\n this.state = VisitState.failed;\n this.adapter.visitFailed(this);\n this.delegate.visitCompleted(this);\n }\n }\n\n changeHistory() {\n if (!this.historyChanged && this.updateHistory) {\n const actionForHistory = this.location.href === this.referrer?.href ? \"replace\" : this.action;\n const method = getHistoryMethodForAction(actionForHistory);\n this.history.update(method, this.location, this.restorationIdentifier);\n this.historyChanged = true;\n }\n }\n\n issueRequest() {\n if (this.hasPreloadedResponse()) {\n this.simulateRequest();\n } else if (this.shouldIssueRequest() && !this.request) {\n this.request = new FetchRequest(this, FetchMethod.get, this.location);\n this.request.perform();\n }\n }\n\n simulateRequest() {\n if (this.response) {\n this.startRequest();\n this.recordResponse();\n this.finishRequest();\n }\n }\n\n startRequest() {\n this.recordTimingMetric(TimingMetric.requestStart);\n this.adapter.visitRequestStarted(this);\n }\n\n recordResponse(response = this.response) {\n this.response = response;\n if (response) {\n const { statusCode } = response;\n if (isSuccessful(statusCode)) {\n this.adapter.visitRequestCompleted(this);\n } else {\n this.adapter.visitRequestFailedWithStatusCode(this, statusCode);\n }\n }\n }\n\n finishRequest() {\n this.recordTimingMetric(TimingMetric.requestEnd);\n this.adapter.visitRequestFinished(this);\n }\n\n loadResponse() {\n if (this.response) {\n const { statusCode, responseHTML } = this.response;\n this.render(async () => {\n if (this.shouldCacheSnapshot) this.cacheSnapshot();\n if (this.view.renderPromise) await this.view.renderPromise;\n\n if (isSuccessful(statusCode) && responseHTML != null) {\n const snapshot = PageSnapshot.fromHTMLString(responseHTML);\n await this.renderPageSnapshot(snapshot, false);\n\n this.adapter.visitRendered(this);\n this.complete();\n } else {\n await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML), this);\n this.adapter.visitRendered(this);\n this.fail();\n }\n });\n }\n }\n\n getCachedSnapshot() {\n const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();\n\n if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {\n if (this.action == \"restore\" || snapshot.isPreviewable) {\n return snapshot\n }\n }\n }\n\n getPreloadedSnapshot() {\n if (this.snapshotHTML) {\n return PageSnapshot.fromHTMLString(this.snapshotHTML)\n }\n }\n\n hasCachedSnapshot() {\n return this.getCachedSnapshot() != null\n }\n\n loadCachedSnapshot() {\n const snapshot = this.getCachedSnapshot();\n if (snapshot) {\n const isPreview = this.shouldIssueRequest();\n this.render(async () => {\n this.cacheSnapshot();\n if (this.isSamePage || this.isPageRefresh) {\n this.adapter.visitRendered(this);\n } else {\n if (this.view.renderPromise) await this.view.renderPromise;\n\n await this.renderPageSnapshot(snapshot, isPreview);\n\n this.adapter.visitRendered(this);\n if (!isPreview) {\n this.complete();\n }\n }\n });\n }\n }\n\n followRedirect() {\n if (this.redirectedToLocation && !this.followedRedirect && this.response?.redirected) {\n this.adapter.visitProposedToLocation(this.redirectedToLocation, {\n action: \"replace\",\n response: this.response,\n shouldCacheSnapshot: false,\n willRender: false\n });\n this.followedRedirect = true;\n }\n }\n\n goToSamePageAnchor() {\n if (this.isSamePage) {\n this.render(async () => {\n this.cacheSnapshot();\n this.performScroll();\n this.changeHistory();\n this.adapter.visitRendered(this);\n });\n }\n }\n\n // Fetch request delegate\n\n prepareRequest(request) {\n if (this.acceptsStreamResponse) {\n request.acceptResponseType(StreamMessage.contentType);\n }\n }\n\n requestStarted() {\n this.startRequest();\n }\n\n requestPreventedHandlingResponse(_request, _response) {}\n\n async requestSucceededWithResponse(request, response) {\n const responseHTML = await response.responseHTML;\n const { redirected, statusCode } = response;\n if (responseHTML == undefined) {\n this.recordResponse({\n statusCode: SystemStatusCode.contentTypeMismatch,\n redirected\n });\n } else {\n this.redirectedToLocation = response.redirected ? response.location : undefined;\n this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n }\n }\n\n async requestFailedWithResponse(request, response) {\n const responseHTML = await response.responseHTML;\n const { redirected, statusCode } = response;\n if (responseHTML == undefined) {\n this.recordResponse({\n statusCode: SystemStatusCode.contentTypeMismatch,\n redirected\n });\n } else {\n this.recordResponse({ statusCode: statusCode, responseHTML, redirected });\n }\n }\n\n requestErrored(_request, _error) {\n this.recordResponse({\n statusCode: SystemStatusCode.networkFailure,\n redirected: false\n });\n }\n\n requestFinished() {\n this.finishRequest();\n }\n\n // Scrolling\n\n performScroll() {\n if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {\n if (this.action == \"restore\") {\n this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop();\n } else {\n this.scrollToAnchor() || this.view.scrollToTop();\n }\n if (this.isSamePage) {\n this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation, this.location);\n }\n\n this.scrolled = true;\n }\n }\n\n scrollToRestoredPosition() {\n const { scrollPosition } = this.restorationData;\n if (scrollPosition) {\n this.view.scrollToPosition(scrollPosition);\n return true\n }\n }\n\n scrollToAnchor() {\n const anchor = getAnchor(this.location);\n if (anchor != null) {\n this.view.scrollToAnchor(anchor);\n return true\n }\n }\n\n // Instrumentation\n\n recordTimingMetric(metric) {\n this.timingMetrics[metric] = new Date().getTime();\n }\n\n getTimingMetrics() {\n return { ...this.timingMetrics }\n }\n\n // Private\n\n hasPreloadedResponse() {\n return typeof this.response == \"object\"\n }\n\n shouldIssueRequest() {\n if (this.isSamePage) {\n return false\n } else if (this.action == \"restore\") {\n return !this.hasCachedSnapshot()\n } else {\n return this.willRender\n }\n }\n\n cacheSnapshot() {\n if (!this.snapshotCached) {\n this.view.cacheSnapshot(this.snapshot).then((snapshot) => snapshot && this.visitCachedSnapshot(snapshot));\n this.snapshotCached = true;\n }\n }\n\n async render(callback) {\n this.cancelRender();\n await new Promise((resolve) => {\n this.frame =\n document.visibilityState === \"hidden\" ? setTimeout(() => resolve(), 0) : requestAnimationFrame(() => resolve());\n });\n await callback();\n delete this.frame;\n }\n\n async renderPageSnapshot(snapshot, isPreview) {\n await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(snapshot), async () => {\n await this.view.renderPage(snapshot, isPreview, this.willRender, this);\n this.performScroll();\n });\n }\n\n cancelRender() {\n if (this.frame) {\n cancelAnimationFrame(this.frame);\n delete this.frame;\n }\n }\n}\n\nfunction isSuccessful(statusCode) {\n return statusCode >= 200 && statusCode < 300\n}\n\nclass BrowserAdapter {\n progressBar = new ProgressBar()\n\n constructor(session) {\n this.session = session;\n }\n\n visitProposedToLocation(location, options) {\n if (locationIsVisitable(location, this.navigator.rootLocation)) {\n this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options);\n } else {\n window.location.href = location.toString();\n }\n }\n\n visitStarted(visit) {\n this.location = visit.location;\n visit.loadCachedSnapshot();\n visit.issueRequest();\n visit.goToSamePageAnchor();\n }\n\n visitRequestStarted(visit) {\n this.progressBar.setValue(0);\n if (visit.hasCachedSnapshot() || visit.action != \"restore\") {\n this.showVisitProgressBarAfterDelay();\n } else {\n this.showProgressBar();\n }\n }\n\n visitRequestCompleted(visit) {\n visit.loadResponse();\n }\n\n visitRequestFailedWithStatusCode(visit, statusCode) {\n switch (statusCode) {\n case SystemStatusCode.networkFailure:\n case SystemStatusCode.timeoutFailure:\n case SystemStatusCode.contentTypeMismatch:\n return this.reload({\n reason: \"request_failed\",\n context: {\n statusCode\n }\n })\n default:\n return visit.loadResponse()\n }\n }\n\n visitRequestFinished(_visit) {}\n\n visitCompleted(_visit) {\n this.progressBar.setValue(1);\n this.hideVisitProgressBar();\n }\n\n pageInvalidated(reason) {\n this.reload(reason);\n }\n\n visitFailed(_visit) {\n this.progressBar.setValue(1);\n this.hideVisitProgressBar();\n }\n\n visitRendered(_visit) {}\n\n // Link prefetching\n\n linkPrefetchingIsEnabledForLocation(location) {\n return true\n }\n\n // Form Submission Delegate\n\n formSubmissionStarted(_formSubmission) {\n this.progressBar.setValue(0);\n this.showFormProgressBarAfterDelay();\n }\n\n formSubmissionFinished(_formSubmission) {\n this.progressBar.setValue(1);\n this.hideFormProgressBar();\n }\n\n // Private\n\n showVisitProgressBarAfterDelay() {\n this.visitProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n }\n\n hideVisitProgressBar() {\n this.progressBar.hide();\n if (this.visitProgressBarTimeout != null) {\n window.clearTimeout(this.visitProgressBarTimeout);\n delete this.visitProgressBarTimeout;\n }\n }\n\n showFormProgressBarAfterDelay() {\n if (this.formProgressBarTimeout == null) {\n this.formProgressBarTimeout = window.setTimeout(this.showProgressBar, this.session.progressBarDelay);\n }\n }\n\n hideFormProgressBar() {\n this.progressBar.hide();\n if (this.formProgressBarTimeout != null) {\n window.clearTimeout(this.formProgressBarTimeout);\n delete this.formProgressBarTimeout;\n }\n }\n\n showProgressBar = () => {\n this.progressBar.show();\n }\n\n reload(reason) {\n dispatch(\"turbo:reload\", { detail: reason });\n\n window.location.href = this.location?.toString() || window.location.href;\n }\n\n get navigator() {\n return this.session.navigator\n }\n}\n\nclass CacheObserver {\n selector = \"[data-turbo-temporary]\"\n deprecatedSelector = \"[data-turbo-cache=false]\"\n\n started = false\n\n start() {\n if (!this.started) {\n this.started = true;\n addEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n }\n }\n\n stop() {\n if (this.started) {\n this.started = false;\n removeEventListener(\"turbo:before-cache\", this.removeTemporaryElements, false);\n }\n }\n\n removeTemporaryElements = (_event) => {\n for (const element of this.temporaryElements) {\n element.remove();\n }\n }\n\n get temporaryElements() {\n return [...document.querySelectorAll(this.selector), ...this.temporaryElementsWithDeprecation]\n }\n\n get temporaryElementsWithDeprecation() {\n const elements = document.querySelectorAll(this.deprecatedSelector);\n\n if (elements.length) {\n console.warn(\n `The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`\n );\n }\n\n return [...elements]\n }\n}\n\nclass FrameRedirector {\n constructor(session, element) {\n this.session = session;\n this.element = element;\n this.linkInterceptor = new LinkInterceptor(this, element);\n this.formSubmitObserver = new FormSubmitObserver(this, element);\n }\n\n start() {\n this.linkInterceptor.start();\n this.formSubmitObserver.start();\n }\n\n stop() {\n this.linkInterceptor.stop();\n this.formSubmitObserver.stop();\n }\n\n // Link interceptor delegate\n\n shouldInterceptLinkClick(element, _location, _event) {\n return this.#shouldRedirect(element)\n }\n\n linkClickIntercepted(element, url, event) {\n const frame = this.#findFrameElement(element);\n if (frame) {\n frame.delegate.linkClickIntercepted(element, url, event);\n }\n }\n\n // Form submit observer delegate\n\n willSubmitForm(element, submitter) {\n return (\n element.closest(\"turbo-frame\") == null &&\n this.#shouldSubmit(element, submitter) &&\n this.#shouldRedirect(element, submitter)\n )\n }\n\n formSubmitted(element, submitter) {\n const frame = this.#findFrameElement(element, submitter);\n if (frame) {\n frame.delegate.formSubmitted(element, submitter);\n }\n }\n\n #shouldSubmit(form, submitter) {\n const action = getAction$1(form, submitter);\n const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n const rootLocation = expandURL(meta?.content ?? \"/\");\n\n return this.#shouldRedirect(form, submitter) && locationIsVisitable(action, rootLocation)\n }\n\n #shouldRedirect(element, submitter) {\n const isNavigatable =\n element instanceof HTMLFormElement\n ? this.session.submissionIsNavigatable(element, submitter)\n : this.session.elementIsNavigatable(element);\n\n if (isNavigatable) {\n const frame = this.#findFrameElement(element, submitter);\n return frame ? frame != element.closest(\"turbo-frame\") : false\n } else {\n return false\n }\n }\n\n #findFrameElement(element, submitter) {\n const id = submitter?.getAttribute(\"data-turbo-frame\") || element.getAttribute(\"data-turbo-frame\");\n if (id && id != \"_top\") {\n const frame = this.element.querySelector(`#${id}:not([disabled])`);\n if (frame instanceof FrameElement) {\n return frame\n }\n }\n }\n}\n\nclass History {\n location\n restorationIdentifier = uuid()\n restorationData = {}\n started = false\n pageLoaded = false\n currentIndex = 0\n\n constructor(delegate) {\n this.delegate = delegate;\n }\n\n start() {\n if (!this.started) {\n addEventListener(\"popstate\", this.onPopState, false);\n addEventListener(\"load\", this.onPageLoad, false);\n this.currentIndex = history.state?.turbo?.restorationIndex || 0;\n this.started = true;\n this.replace(new URL(window.location.href));\n }\n }\n\n stop() {\n if (this.started) {\n removeEventListener(\"popstate\", this.onPopState, false);\n removeEventListener(\"load\", this.onPageLoad, false);\n this.started = false;\n }\n }\n\n push(location, restorationIdentifier) {\n this.update(history.pushState, location, restorationIdentifier);\n }\n\n replace(location, restorationIdentifier) {\n this.update(history.replaceState, location, restorationIdentifier);\n }\n\n update(method, location, restorationIdentifier = uuid()) {\n if (method === history.pushState) ++this.currentIndex;\n\n const state = { turbo: { restorationIdentifier, restorationIndex: this.currentIndex } };\n method.call(history, state, \"\", location.href);\n this.location = location;\n this.restorationIdentifier = restorationIdentifier;\n }\n\n // Restoration data\n\n getRestorationDataForIdentifier(restorationIdentifier) {\n return this.restorationData[restorationIdentifier] || {}\n }\n\n updateRestorationData(additionalData) {\n const { restorationIdentifier } = this;\n const restorationData = this.restorationData[restorationIdentifier];\n this.restorationData[restorationIdentifier] = {\n ...restorationData,\n ...additionalData\n };\n }\n\n // Scroll restoration\n\n assumeControlOfScrollRestoration() {\n if (!this.previousScrollRestoration) {\n this.previousScrollRestoration = history.scrollRestoration ?? \"auto\";\n history.scrollRestoration = \"manual\";\n }\n }\n\n relinquishControlOfScrollRestoration() {\n if (this.previousScrollRestoration) {\n history.scrollRestoration = this.previousScrollRestoration;\n delete this.previousScrollRestoration;\n }\n }\n\n // Event handlers\n\n onPopState = (event) => {\n if (this.shouldHandlePopState()) {\n const { turbo } = event.state || {};\n if (turbo) {\n this.location = new URL(window.location.href);\n const { restorationIdentifier, restorationIndex } = turbo;\n this.restorationIdentifier = restorationIdentifier;\n const direction = restorationIndex > this.currentIndex ? \"forward\" : \"back\";\n this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location, restorationIdentifier, direction);\n this.currentIndex = restorationIndex;\n }\n }\n }\n\n onPageLoad = async (_event) => {\n await nextMicrotask();\n this.pageLoaded = true;\n }\n\n // Private\n\n shouldHandlePopState() {\n // Safari dispatches a popstate event after window's load event, ignore it\n return this.pageIsLoaded()\n }\n\n pageIsLoaded() {\n return this.pageLoaded || document.readyState == \"complete\"\n }\n}\n\nclass LinkPrefetchObserver {\n started = false\n #prefetchedLink = null\n\n constructor(delegate, eventTarget) {\n this.delegate = delegate;\n this.eventTarget = eventTarget;\n }\n\n start() {\n if (this.started) return\n\n if (this.eventTarget.readyState === \"loading\") {\n this.eventTarget.addEventListener(\"DOMContentLoaded\", this.#enable, { once: true });\n } else {\n this.#enable();\n }\n }\n\n stop() {\n if (!this.started) return\n\n this.eventTarget.removeEventListener(\"mouseenter\", this.#tryToPrefetchRequest, {\n capture: true,\n passive: true\n });\n this.eventTarget.removeEventListener(\"mouseleave\", this.#cancelRequestIfObsolete, {\n capture: true,\n passive: true\n });\n\n this.eventTarget.removeEventListener(\"turbo:before-fetch-request\", this.#tryToUsePrefetchedRequest, true);\n this.started = false;\n }\n\n #enable = () => {\n this.eventTarget.addEventListener(\"mouseenter\", this.#tryToPrefetchRequest, {\n capture: true,\n passive: true\n });\n this.eventTarget.addEventListener(\"mouseleave\", this.#cancelRequestIfObsolete, {\n capture: true,\n passive: true\n });\n\n this.eventTarget.addEventListener(\"turbo:before-fetch-request\", this.#tryToUsePrefetchedRequest, true);\n this.started = true;\n }\n\n #tryToPrefetchRequest = (event) => {\n if (getMetaContent(\"turbo-prefetch\") === \"false\") return\n\n const target = event.target;\n const isLink = target.matches && target.matches(\"a[href]:not([target^=_]):not([download])\");\n\n if (isLink && this.#isPrefetchable(target)) {\n const link = target;\n const location = getLocationForLink(link);\n\n if (this.delegate.canPrefetchRequestToLocation(link, location)) {\n this.#prefetchedLink = link;\n\n const fetchRequest = new FetchRequest(\n this,\n FetchMethod.get,\n location,\n new URLSearchParams(),\n target\n );\n\n prefetchCache.setLater(location.toString(), fetchRequest, this.#cacheTtl);\n }\n }\n }\n\n #cancelRequestIfObsolete = (event) => {\n if (event.target === this.#prefetchedLink) this.#cancelPrefetchRequest();\n }\n\n #cancelPrefetchRequest = () => {\n prefetchCache.clear();\n this.#prefetchedLink = null;\n }\n\n #tryToUsePrefetchedRequest = (event) => {\n if (event.target.tagName !== \"FORM\" && event.detail.fetchOptions.method === \"GET\") {\n const cached = prefetchCache.get(event.detail.url.toString());\n\n if (cached) {\n // User clicked link, use cache response\n event.detail.fetchRequest = cached;\n }\n\n prefetchCache.clear();\n }\n }\n\n prepareRequest(request) {\n const link = request.target;\n\n request.headers[\"X-Sec-Purpose\"] = \"prefetch\";\n\n const turboFrame = link.closest(\"turbo-frame\");\n const turboFrameTarget = link.getAttribute(\"data-turbo-frame\") || turboFrame?.getAttribute(\"target\") || turboFrame?.id;\n\n if (turboFrameTarget && turboFrameTarget !== \"_top\") {\n request.headers[\"Turbo-Frame\"] = turboFrameTarget;\n }\n }\n\n // Fetch request interface\n\n requestSucceededWithResponse() {}\n\n requestStarted(fetchRequest) {}\n\n requestErrored(fetchRequest) {}\n\n requestFinished(fetchRequest) {}\n\n requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}\n\n requestFailedWithResponse(fetchRequest, fetchResponse) {}\n\n get #cacheTtl() {\n return Number(getMetaContent(\"turbo-prefetch-cache-time\")) || cacheTtl\n }\n\n #isPrefetchable(link) {\n const href = link.getAttribute(\"href\");\n\n if (!href) return false\n\n if (unfetchableLink(link)) return false\n if (linkToTheSamePage(link)) return false\n if (linkOptsOut(link)) return false\n if (nonSafeLink(link)) return false\n if (eventPrevented(link)) return false\n\n return true\n }\n}\n\nconst unfetchableLink = (link) => {\n return link.origin !== document.location.origin || ![\"http:\", \"https:\"].includes(link.protocol) || link.hasAttribute(\"target\")\n};\n\nconst linkToTheSamePage = (link) => {\n return (link.pathname + link.search === document.location.pathname + document.location.search) || link.href.startsWith(\"#\")\n};\n\nconst linkOptsOut = (link) => {\n if (link.getAttribute(\"data-turbo-prefetch\") === \"false\") return true\n if (link.getAttribute(\"data-turbo\") === \"false\") return true\n\n const turboPrefetchParent = findClosestRecursively(link, \"[data-turbo-prefetch]\");\n if (turboPrefetchParent && turboPrefetchParent.getAttribute(\"data-turbo-prefetch\") === \"false\") return true\n\n return false\n};\n\nconst nonSafeLink = (link) => {\n const turboMethod = link.getAttribute(\"data-turbo-method\");\n if (turboMethod && turboMethod.toLowerCase() !== \"get\") return true\n\n if (isUJS(link)) return true\n if (link.hasAttribute(\"data-turbo-confirm\")) return true\n if (link.hasAttribute(\"data-turbo-stream\")) return true\n\n return false\n};\n\nconst isUJS = (link) => {\n return link.hasAttribute(\"data-remote\") || link.hasAttribute(\"data-behavior\") || link.hasAttribute(\"data-confirm\") || link.hasAttribute(\"data-method\")\n};\n\nconst eventPrevented = (link) => {\n const event = dispatch(\"turbo:before-prefetch\", { target: link, cancelable: true });\n return event.defaultPrevented\n};\n\nclass Navigator {\n constructor(delegate) {\n this.delegate = delegate;\n }\n\n proposeVisit(location, options = {}) {\n if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) {\n this.delegate.visitProposedToLocation(location, options);\n }\n }\n\n startVisit(locatable, restorationIdentifier, options = {}) {\n this.stop();\n this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, {\n referrer: this.location,\n ...options\n });\n this.currentVisit.start();\n }\n\n submitForm(form, submitter) {\n this.stop();\n this.formSubmission = new FormSubmission(this, form, submitter, true);\n\n this.formSubmission.start();\n }\n\n stop() {\n if (this.formSubmission) {\n this.formSubmission.stop();\n delete this.formSubmission;\n }\n\n if (this.currentVisit) {\n this.currentVisit.cancel();\n delete this.currentVisit;\n }\n }\n\n get adapter() {\n return this.delegate.adapter\n }\n\n get view() {\n return this.delegate.view\n }\n\n get rootLocation() {\n return this.view.snapshot.rootLocation\n }\n\n get history() {\n return this.delegate.history\n }\n\n // Form submission delegate\n\n formSubmissionStarted(formSubmission) {\n // Not all adapters implement formSubmissionStarted\n if (typeof this.adapter.formSubmissionStarted === \"function\") {\n this.adapter.formSubmissionStarted(formSubmission);\n }\n }\n\n async formSubmissionSucceededWithResponse(formSubmission, fetchResponse) {\n if (formSubmission == this.formSubmission) {\n const responseHTML = await fetchResponse.responseHTML;\n if (responseHTML) {\n const shouldCacheSnapshot = formSubmission.isSafe;\n if (!shouldCacheSnapshot) {\n this.view.clearSnapshotCache();\n }\n\n const { statusCode, redirected } = fetchResponse;\n const action = this.#getActionForFormSubmission(formSubmission, fetchResponse);\n const visitOptions = {\n action,\n shouldCacheSnapshot,\n response: { statusCode, responseHTML, redirected }\n };\n this.proposeVisit(fetchResponse.location, visitOptions);\n }\n }\n }\n\n async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n const responseHTML = await fetchResponse.responseHTML;\n\n if (responseHTML) {\n const snapshot = PageSnapshot.fromHTMLString(responseHTML);\n if (fetchResponse.serverError) {\n await this.view.renderError(snapshot, this.currentVisit);\n } else {\n await this.view.renderPage(snapshot, false, true, this.currentVisit);\n }\n if(!snapshot.shouldPreserveScrollPosition) {\n this.view.scrollToTop();\n }\n this.view.clearSnapshotCache();\n }\n }\n\n formSubmissionErrored(formSubmission, error) {\n console.error(error);\n }\n\n formSubmissionFinished(formSubmission) {\n // Not all adapters implement formSubmissionFinished\n if (typeof this.adapter.formSubmissionFinished === \"function\") {\n this.adapter.formSubmissionFinished(formSubmission);\n }\n }\n\n // Link prefetching\n\n linkPrefetchingIsEnabledForLocation(location) {\n // Not all adapters implement linkPrefetchingIsEnabledForLocation\n if (typeof this.adapter.linkPrefetchingIsEnabledForLocation === \"function\") {\n return this.adapter.linkPrefetchingIsEnabledForLocation(location)\n }\n\n return true\n }\n\n // Visit delegate\n\n visitStarted(visit) {\n this.delegate.visitStarted(visit);\n }\n\n visitCompleted(visit) {\n this.delegate.visitCompleted(visit);\n delete this.currentVisit;\n }\n\n locationWithActionIsSamePage(location, action) {\n const anchor = getAnchor(location);\n const currentAnchor = getAnchor(this.view.lastRenderedLocation);\n const isRestorationToTop = action === \"restore\" && typeof anchor === \"undefined\";\n\n return (\n action !== \"replace\" &&\n getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation) &&\n (isRestorationToTop || (anchor != null && anchor !== currentAnchor))\n )\n }\n\n visitScrolledToSamePageLocation(oldURL, newURL) {\n this.delegate.visitScrolledToSamePageLocation(oldURL, newURL);\n }\n\n // Visits\n\n get location() {\n return this.history.location\n }\n\n get restorationIdentifier() {\n return this.history.restorationIdentifier\n }\n\n #getActionForFormSubmission(formSubmission, fetchResponse) {\n const { submitter, formElement } = formSubmission;\n return getVisitAction(submitter, formElement) || this.#getDefaultAction(fetchResponse)\n }\n\n #getDefaultAction(fetchResponse) {\n const sameLocationRedirect = fetchResponse.redirected && fetchResponse.location.href === this.location?.href;\n return sameLocationRedirect ? \"replace\" : \"advance\"\n }\n}\n\nconst PageStage = {\n initial: 0,\n loading: 1,\n interactive: 2,\n complete: 3\n};\n\nclass PageObserver {\n stage = PageStage.initial\n started = false\n\n constructor(delegate) {\n this.delegate = delegate;\n }\n\n start() {\n if (!this.started) {\n if (this.stage == PageStage.initial) {\n this.stage = PageStage.loading;\n }\n document.addEventListener(\"readystatechange\", this.interpretReadyState, false);\n addEventListener(\"pagehide\", this.pageWillUnload, false);\n this.started = true;\n }\n }\n\n stop() {\n if (this.started) {\n document.removeEventListener(\"readystatechange\", this.interpretReadyState, false);\n removeEventListener(\"pagehide\", this.pageWillUnload, false);\n this.started = false;\n }\n }\n\n interpretReadyState = () => {\n const { readyState } = this;\n if (readyState == \"interactive\") {\n this.pageIsInteractive();\n } else if (readyState == \"complete\") {\n this.pageIsComplete();\n }\n }\n\n pageIsInteractive() {\n if (this.stage == PageStage.loading) {\n this.stage = PageStage.interactive;\n this.delegate.pageBecameInteractive();\n }\n }\n\n pageIsComplete() {\n this.pageIsInteractive();\n if (this.stage == PageStage.interactive) {\n this.stage = PageStage.complete;\n this.delegate.pageLoaded();\n }\n }\n\n pageWillUnload = () => {\n this.delegate.pageWillUnload();\n }\n\n get readyState() {\n return document.readyState\n }\n}\n\nclass ScrollObserver {\n started = false\n\n constructor(delegate) {\n this.delegate = delegate;\n }\n\n start() {\n if (!this.started) {\n addEventListener(\"scroll\", this.onScroll, false);\n this.onScroll();\n this.started = true;\n }\n }\n\n stop() {\n if (this.started) {\n removeEventListener(\"scroll\", this.onScroll, false);\n this.started = false;\n }\n }\n\n onScroll = () => {\n this.updatePosition({ x: window.pageXOffset, y: window.pageYOffset });\n }\n\n // Private\n\n updatePosition(position) {\n this.delegate.scrollPositionChanged(position);\n }\n}\n\nclass StreamMessageRenderer {\n render({ fragment }) {\n Bardo.preservingPermanentElements(this, getPermanentElementMapForFragment(fragment), () => {\n withAutofocusFromFragment(fragment, () => {\n withPreservedFocus(() => {\n document.documentElement.appendChild(fragment);\n });\n });\n });\n }\n\n // Bardo delegate\n\n enteringBardo(currentPermanentElement, newPermanentElement) {\n newPermanentElement.replaceWith(currentPermanentElement.cloneNode(true));\n }\n\n leavingBardo() {}\n}\n\nfunction getPermanentElementMapForFragment(fragment) {\n const permanentElementsInDocument = queryPermanentElementsAll(document.documentElement);\n const permanentElementMap = {};\n for (const permanentElementInDocument of permanentElementsInDocument) {\n const { id } = permanentElementInDocument;\n\n for (const streamElement of fragment.querySelectorAll(\"turbo-stream\")) {\n const elementInStream = getPermanentElementById(streamElement.templateElement.content, id);\n\n if (elementInStream) {\n permanentElementMap[id] = [permanentElementInDocument, elementInStream];\n }\n }\n }\n\n return permanentElementMap\n}\n\nasync function withAutofocusFromFragment(fragment, callback) {\n const generatedID = `turbo-stream-autofocus-${uuid()}`;\n const turboStreams = fragment.querySelectorAll(\"turbo-stream\");\n const elementWithAutofocus = firstAutofocusableElementInStreams(turboStreams);\n let willAutofocusId = null;\n\n if (elementWithAutofocus) {\n if (elementWithAutofocus.id) {\n willAutofocusId = elementWithAutofocus.id;\n } else {\n willAutofocusId = generatedID;\n }\n\n elementWithAutofocus.id = willAutofocusId;\n }\n\n callback();\n await nextRepaint();\n\n const hasNoActiveElement = document.activeElement == null || document.activeElement == document.body;\n\n if (hasNoActiveElement && willAutofocusId) {\n const elementToAutofocus = document.getElementById(willAutofocusId);\n\n if (elementIsFocusable(elementToAutofocus)) {\n elementToAutofocus.focus();\n }\n if (elementToAutofocus && elementToAutofocus.id == generatedID) {\n elementToAutofocus.removeAttribute(\"id\");\n }\n }\n}\n\nasync function withPreservedFocus(callback) {\n const [activeElementBeforeRender, activeElementAfterRender] = await around(callback, () => document.activeElement);\n\n const restoreFocusTo = activeElementBeforeRender && activeElementBeforeRender.id;\n\n if (restoreFocusTo) {\n const elementToFocus = document.getElementById(restoreFocusTo);\n\n if (elementIsFocusable(elementToFocus) && elementToFocus != activeElementAfterRender) {\n elementToFocus.focus();\n }\n }\n}\n\nfunction firstAutofocusableElementInStreams(nodeListOfStreamElements) {\n for (const streamElement of nodeListOfStreamElements) {\n const elementWithAutofocus = queryAutofocusableElement(streamElement.templateElement.content);\n\n if (elementWithAutofocus) return elementWithAutofocus\n }\n\n return null\n}\n\nclass StreamObserver {\n sources = new Set()\n #started = false\n\n constructor(delegate) {\n this.delegate = delegate;\n }\n\n start() {\n if (!this.#started) {\n this.#started = true;\n addEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n }\n }\n\n stop() {\n if (this.#started) {\n this.#started = false;\n removeEventListener(\"turbo:before-fetch-response\", this.inspectFetchResponse, false);\n }\n }\n\n connectStreamSource(source) {\n if (!this.streamSourceIsConnected(source)) {\n this.sources.add(source);\n source.addEventListener(\"message\", this.receiveMessageEvent, false);\n }\n }\n\n disconnectStreamSource(source) {\n if (this.streamSourceIsConnected(source)) {\n this.sources.delete(source);\n source.removeEventListener(\"message\", this.receiveMessageEvent, false);\n }\n }\n\n streamSourceIsConnected(source) {\n return this.sources.has(source)\n }\n\n inspectFetchResponse = (event) => {\n const response = fetchResponseFromEvent(event);\n if (response && fetchResponseIsStream(response)) {\n event.preventDefault();\n this.receiveMessageResponse(response);\n }\n }\n\n receiveMessageEvent = (event) => {\n if (this.#started && typeof event.data == \"string\") {\n this.receiveMessageHTML(event.data);\n }\n }\n\n async receiveMessageResponse(response) {\n const html = await response.responseHTML;\n if (html) {\n this.receiveMessageHTML(html);\n }\n }\n\n receiveMessageHTML(html) {\n this.delegate.receivedMessageFromStream(StreamMessage.wrap(html));\n }\n}\n\nfunction fetchResponseFromEvent(event) {\n const fetchResponse = event.detail?.fetchResponse;\n if (fetchResponse instanceof FetchResponse) {\n return fetchResponse\n }\n}\n\nfunction fetchResponseIsStream(response) {\n const contentType = response.contentType ?? \"\";\n return contentType.startsWith(StreamMessage.contentType)\n}\n\nclass ErrorRenderer extends Renderer {\n static renderElement(currentElement, newElement) {\n const { documentElement, body } = document;\n\n documentElement.replaceChild(newElement, body);\n }\n\n async render() {\n this.replaceHeadAndBody();\n this.activateScriptElements();\n }\n\n replaceHeadAndBody() {\n const { documentElement, head } = document;\n documentElement.replaceChild(this.newHead, head);\n this.renderElement(this.currentElement, this.newElement);\n }\n\n activateScriptElements() {\n for (const replaceableElement of this.scriptElements) {\n const parentNode = replaceableElement.parentNode;\n if (parentNode) {\n const element = activateScriptElement(replaceableElement);\n parentNode.replaceChild(element, replaceableElement);\n }\n }\n }\n\n get newHead() {\n return this.newSnapshot.headSnapshot.element\n }\n\n get scriptElements() {\n return document.documentElement.querySelectorAll(\"script\")\n }\n}\n\nclass PageRenderer extends Renderer {\n static renderElement(currentElement, newElement) {\n if (document.body && newElement instanceof HTMLBodyElement) {\n document.body.replaceWith(newElement);\n } else {\n document.documentElement.appendChild(newElement);\n }\n }\n\n get shouldRender() {\n return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical\n }\n\n get reloadReason() {\n if (!this.newSnapshot.isVisitable) {\n return {\n reason: \"turbo_visit_control_is_reload\"\n }\n }\n\n if (!this.trackedElementsAreIdentical) {\n return {\n reason: \"tracked_element_mismatch\"\n }\n }\n }\n\n async prepareToRender() {\n this.#setLanguage();\n await this.mergeHead();\n }\n\n async render() {\n if (this.willRender) {\n await this.replaceBody();\n }\n }\n\n finishRendering() {\n super.finishRendering();\n if (!this.isPreview) {\n this.focusFirstAutofocusableElement();\n }\n }\n\n get currentHeadSnapshot() {\n return this.currentSnapshot.headSnapshot\n }\n\n get newHeadSnapshot() {\n return this.newSnapshot.headSnapshot\n }\n\n get newElement() {\n return this.newSnapshot.element\n }\n\n #setLanguage() {\n const { documentElement } = this.currentSnapshot;\n const { lang } = this.newSnapshot;\n\n if (lang) {\n documentElement.setAttribute(\"lang\", lang);\n } else {\n documentElement.removeAttribute(\"lang\");\n }\n }\n\n async mergeHead() {\n const mergedHeadElements = this.mergeProvisionalElements();\n const newStylesheetElements = this.copyNewHeadStylesheetElements();\n this.copyNewHeadScriptElements();\n\n await mergedHeadElements;\n await newStylesheetElements;\n\n if (this.willRender) {\n this.removeUnusedDynamicStylesheetElements();\n }\n }\n\n async replaceBody() {\n await this.preservingPermanentElements(async () => {\n this.activateNewBody();\n await this.assignNewBody();\n });\n }\n\n get trackedElementsAreIdentical() {\n return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature\n }\n\n async copyNewHeadStylesheetElements() {\n const loadingElements = [];\n\n for (const element of this.newHeadStylesheetElements) {\n loadingElements.push(waitForLoad(element));\n\n document.head.appendChild(element);\n }\n\n await Promise.all(loadingElements);\n }\n\n copyNewHeadScriptElements() {\n for (const element of this.newHeadScriptElements) {\n document.head.appendChild(activateScriptElement(element));\n }\n }\n\n removeUnusedDynamicStylesheetElements() {\n for (const element of this.unusedDynamicStylesheetElements) {\n document.head.removeChild(element);\n }\n }\n\n async mergeProvisionalElements() {\n const newHeadElements = [...this.newHeadProvisionalElements];\n\n for (const element of this.currentHeadProvisionalElements) {\n if (!this.isCurrentElementInElementList(element, newHeadElements)) {\n document.head.removeChild(element);\n }\n }\n\n for (const element of newHeadElements) {\n document.head.appendChild(element);\n }\n }\n\n isCurrentElementInElementList(element, elementList) {\n for (const [index, newElement] of elementList.entries()) {\n // if title element...\n if (element.tagName == \"TITLE\") {\n if (newElement.tagName != \"TITLE\") {\n continue\n }\n if (element.innerHTML == newElement.innerHTML) {\n elementList.splice(index, 1);\n return true\n }\n }\n\n // if any other element...\n if (newElement.isEqualNode(element)) {\n elementList.splice(index, 1);\n return true\n }\n }\n\n return false\n }\n\n removeCurrentHeadProvisionalElements() {\n for (const element of this.currentHeadProvisionalElements) {\n document.head.removeChild(element);\n }\n }\n\n copyNewHeadProvisionalElements() {\n for (const element of this.newHeadProvisionalElements) {\n document.head.appendChild(element);\n }\n }\n\n activateNewBody() {\n document.adoptNode(this.newElement);\n this.activateNewBodyScriptElements();\n }\n\n activateNewBodyScriptElements() {\n for (const inertScriptElement of this.newBodyScriptElements) {\n const activatedScriptElement = activateScriptElement(inertScriptElement);\n inertScriptElement.replaceWith(activatedScriptElement);\n }\n }\n\n async assignNewBody() {\n await this.renderElement(this.currentElement, this.newElement);\n }\n\n get unusedDynamicStylesheetElements() {\n return this.oldHeadStylesheetElements.filter((element) => {\n return element.getAttribute(\"data-turbo-track\") === \"dynamic\"\n })\n }\n\n get oldHeadStylesheetElements() {\n return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot)\n }\n\n get newHeadStylesheetElements() {\n return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot)\n }\n\n get newHeadScriptElements() {\n return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot)\n }\n\n get currentHeadProvisionalElements() {\n return this.currentHeadSnapshot.provisionalElements\n }\n\n get newHeadProvisionalElements() {\n return this.newHeadSnapshot.provisionalElements\n }\n\n get newBodyScriptElements() {\n return this.newElement.querySelectorAll(\"script\")\n }\n}\n\nclass MorphingPageRenderer extends PageRenderer {\n static renderElement(currentElement, newElement) {\n morphElements(currentElement, newElement, {\n callbacks: {\n beforeNodeMorphed: element => !canRefreshFrame(element)\n }\n });\n\n for (const frame of currentElement.querySelectorAll(\"turbo-frame\")) {\n if (canRefreshFrame(frame)) frame.reload();\n }\n\n dispatch(\"turbo:morph\", { detail: { currentElement, newElement } });\n }\n\n async preservingPermanentElements(callback) {\n return await callback()\n }\n\n get renderMethod() {\n return \"morph\"\n }\n\n get shouldAutofocus() {\n return false\n }\n}\n\nfunction canRefreshFrame(frame) {\n return frame instanceof FrameElement &&\n frame.src &&\n frame.refresh === \"morph\" &&\n !frame.closest(\"[data-turbo-permanent]\")\n}\n\nclass SnapshotCache {\n keys = []\n snapshots = {}\n\n constructor(size) {\n this.size = size;\n }\n\n has(location) {\n return toCacheKey(location) in this.snapshots\n }\n\n get(location) {\n if (this.has(location)) {\n const snapshot = this.read(location);\n this.touch(location);\n return snapshot\n }\n }\n\n put(location, snapshot) {\n this.write(location, snapshot);\n this.touch(location);\n return snapshot\n }\n\n clear() {\n this.snapshots = {};\n }\n\n // Private\n\n read(location) {\n return this.snapshots[toCacheKey(location)]\n }\n\n write(location, snapshot) {\n this.snapshots[toCacheKey(location)] = snapshot;\n }\n\n touch(location) {\n const key = toCacheKey(location);\n const index = this.keys.indexOf(key);\n if (index > -1) this.keys.splice(index, 1);\n this.keys.unshift(key);\n this.trim();\n }\n\n trim() {\n for (const key of this.keys.splice(this.size)) {\n delete this.snapshots[key];\n }\n }\n}\n\nclass PageView extends View {\n snapshotCache = new SnapshotCache(10)\n lastRenderedLocation = new URL(location.href)\n forceReloaded = false\n\n shouldTransitionTo(newSnapshot) {\n return this.snapshot.prefersViewTransitions && newSnapshot.prefersViewTransitions\n }\n\n renderPage(snapshot, isPreview = false, willRender = true, visit) {\n const shouldMorphPage = this.isPageRefresh(visit) && this.snapshot.shouldMorphPage;\n const rendererClass = shouldMorphPage ? MorphingPageRenderer : PageRenderer;\n\n const renderer = new rendererClass(this.snapshot, snapshot, isPreview, willRender);\n\n if (!renderer.shouldRender) {\n this.forceReloaded = true;\n } else {\n visit?.changeHistory();\n }\n\n return this.render(renderer)\n }\n\n renderError(snapshot, visit) {\n visit?.changeHistory();\n const renderer = new ErrorRenderer(this.snapshot, snapshot, false);\n return this.render(renderer)\n }\n\n clearSnapshotCache() {\n this.snapshotCache.clear();\n }\n\n async cacheSnapshot(snapshot = this.snapshot) {\n if (snapshot.isCacheable) {\n this.delegate.viewWillCacheSnapshot();\n const { lastRenderedLocation: location } = this;\n await nextEventLoopTick();\n const cachedSnapshot = snapshot.clone();\n this.snapshotCache.put(location, cachedSnapshot);\n return cachedSnapshot\n }\n }\n\n getCachedSnapshotForLocation(location) {\n return this.snapshotCache.get(location)\n }\n\n isPageRefresh(visit) {\n return !visit || (this.lastRenderedLocation.pathname === visit.location.pathname && visit.action === \"replace\")\n }\n\n shouldPreserveScrollPosition(visit) {\n return this.isPageRefresh(visit) && this.snapshot.shouldPreserveScrollPosition\n }\n\n get snapshot() {\n return PageSnapshot.fromElement(this.element)\n }\n}\n\nclass Preloader {\n selector = \"a[data-turbo-preload]\"\n\n constructor(delegate, snapshotCache) {\n this.delegate = delegate;\n this.snapshotCache = snapshotCache;\n }\n\n start() {\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", this.#preloadAll);\n } else {\n this.preloadOnLoadLinksForView(document.body);\n }\n }\n\n stop() {\n document.removeEventListener(\"DOMContentLoaded\", this.#preloadAll);\n }\n\n preloadOnLoadLinksForView(element) {\n for (const link of element.querySelectorAll(this.selector)) {\n if (this.delegate.shouldPreloadLink(link)) {\n this.preloadURL(link);\n }\n }\n }\n\n async preloadURL(link) {\n const location = new URL(link.href);\n\n if (this.snapshotCache.has(location)) {\n return\n }\n\n const fetchRequest = new FetchRequest(this, FetchMethod.get, location, new URLSearchParams(), link);\n await fetchRequest.perform();\n }\n\n // Fetch request delegate\n\n prepareRequest(fetchRequest) {\n fetchRequest.headers[\"X-Sec-Purpose\"] = \"prefetch\";\n }\n\n async requestSucceededWithResponse(fetchRequest, fetchResponse) {\n try {\n const responseHTML = await fetchResponse.responseHTML;\n const snapshot = PageSnapshot.fromHTMLString(responseHTML);\n\n this.snapshotCache.put(fetchRequest.url, snapshot);\n } catch (_) {\n // If we cannot preload that is ok!\n }\n }\n\n requestStarted(fetchRequest) {}\n\n requestErrored(fetchRequest) {}\n\n requestFinished(fetchRequest) {}\n\n requestPreventedHandlingResponse(fetchRequest, fetchResponse) {}\n\n requestFailedWithResponse(fetchRequest, fetchResponse) {}\n\n #preloadAll = () => {\n this.preloadOnLoadLinksForView(document.body);\n }\n}\n\nclass Cache {\n constructor(session) {\n this.session = session;\n }\n\n clear() {\n this.session.clearCache();\n }\n\n resetCacheControl() {\n this.#setCacheControl(\"\");\n }\n\n exemptPageFromCache() {\n this.#setCacheControl(\"no-cache\");\n }\n\n exemptPageFromPreview() {\n this.#setCacheControl(\"no-preview\");\n }\n\n #setCacheControl(value) {\n setMetaContent(\"turbo-cache-control\", value);\n }\n}\n\nclass Session {\n navigator = new Navigator(this)\n history = new History(this)\n view = new PageView(this, document.documentElement)\n adapter = new BrowserAdapter(this)\n\n pageObserver = new PageObserver(this)\n cacheObserver = new CacheObserver()\n linkPrefetchObserver = new LinkPrefetchObserver(this, document)\n linkClickObserver = new LinkClickObserver(this, window)\n formSubmitObserver = new FormSubmitObserver(this, document)\n scrollObserver = new ScrollObserver(this)\n streamObserver = new StreamObserver(this)\n formLinkClickObserver = new FormLinkClickObserver(this, document.documentElement)\n frameRedirector = new FrameRedirector(this, document.documentElement)\n streamMessageRenderer = new StreamMessageRenderer()\n cache = new Cache(this)\n\n enabled = true\n started = false\n #pageRefreshDebouncePeriod = 150\n\n constructor(recentRequests) {\n this.recentRequests = recentRequests;\n this.preloader = new Preloader(this, this.view.snapshotCache);\n this.debouncedRefresh = this.refresh;\n this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod;\n }\n\n start() {\n if (!this.started) {\n this.pageObserver.start();\n this.cacheObserver.start();\n this.linkPrefetchObserver.start();\n this.formLinkClickObserver.start();\n this.linkClickObserver.start();\n this.formSubmitObserver.start();\n this.scrollObserver.start();\n this.streamObserver.start();\n this.frameRedirector.start();\n this.history.start();\n this.preloader.start();\n this.started = true;\n this.enabled = true;\n }\n }\n\n disable() {\n this.enabled = false;\n }\n\n stop() {\n if (this.started) {\n this.pageObserver.stop();\n this.cacheObserver.stop();\n this.linkPrefetchObserver.stop();\n this.formLinkClickObserver.stop();\n this.linkClickObserver.stop();\n this.formSubmitObserver.stop();\n this.scrollObserver.stop();\n this.streamObserver.stop();\n this.frameRedirector.stop();\n this.history.stop();\n this.preloader.stop();\n this.started = false;\n }\n }\n\n registerAdapter(adapter) {\n this.adapter = adapter;\n }\n\n visit(location, options = {}) {\n const frameElement = options.frame ? document.getElementById(options.frame) : null;\n\n if (frameElement instanceof FrameElement) {\n const action = options.action || getVisitAction(frameElement);\n\n frameElement.delegate.proposeVisitIfNavigatedWithAction(frameElement, action);\n frameElement.src = location.toString();\n } else {\n this.navigator.proposeVisit(expandURL(location), options);\n }\n }\n\n refresh(url, requestId) {\n const isRecentRequest = requestId && this.recentRequests.has(requestId);\n const isCurrentUrl = url === document.baseURI;\n if (!isRecentRequest && !this.navigator.currentVisit && isCurrentUrl) {\n this.visit(url, { action: \"replace\", shouldCacheSnapshot: false });\n }\n }\n\n connectStreamSource(source) {\n this.streamObserver.connectStreamSource(source);\n }\n\n disconnectStreamSource(source) {\n this.streamObserver.disconnectStreamSource(source);\n }\n\n renderStreamMessage(message) {\n this.streamMessageRenderer.render(StreamMessage.wrap(message));\n }\n\n clearCache() {\n this.view.clearSnapshotCache();\n }\n\n setProgressBarDelay(delay) {\n console.warn(\n \"Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`\"\n );\n\n this.progressBarDelay = delay;\n }\n\n set progressBarDelay(delay) {\n config.drive.progressBarDelay = delay;\n }\n\n get progressBarDelay() {\n return config.drive.progressBarDelay\n }\n\n set drive(value) {\n config.drive.enabled = value;\n }\n\n get drive() {\n return config.drive.enabled\n }\n\n set formMode(value) {\n config.forms.mode = value;\n }\n\n get formMode() {\n return config.forms.mode\n }\n\n get location() {\n return this.history.location\n }\n\n get restorationIdentifier() {\n return this.history.restorationIdentifier\n }\n\n get pageRefreshDebouncePeriod() {\n return this.#pageRefreshDebouncePeriod\n }\n\n set pageRefreshDebouncePeriod(value) {\n this.refresh = debounce(this.debouncedRefresh.bind(this), value);\n this.#pageRefreshDebouncePeriod = value;\n }\n\n // Preloader delegate\n\n shouldPreloadLink(element) {\n const isUnsafe = element.hasAttribute(\"data-turbo-method\");\n const isStream = element.hasAttribute(\"data-turbo-stream\");\n const frameTarget = element.getAttribute(\"data-turbo-frame\");\n const frame = frameTarget == \"_top\" ?\n null :\n document.getElementById(frameTarget) || findClosestRecursively(element, \"turbo-frame:not([disabled])\");\n\n if (isUnsafe || isStream || frame instanceof FrameElement) {\n return false\n } else {\n const location = new URL(element.href);\n\n return this.elementIsNavigatable(element) && locationIsVisitable(location, this.snapshot.rootLocation)\n }\n }\n\n // History delegate\n\n historyPoppedToLocationWithRestorationIdentifierAndDirection(location, restorationIdentifier, direction) {\n if (this.enabled) {\n this.navigator.startVisit(location, restorationIdentifier, {\n action: \"restore\",\n historyChanged: true,\n direction\n });\n } else {\n this.adapter.pageInvalidated({\n reason: \"turbo_disabled\"\n });\n }\n }\n\n // Scroll observer delegate\n\n scrollPositionChanged(position) {\n this.history.updateRestorationData({ scrollPosition: position });\n }\n\n // Form click observer delegate\n\n willSubmitFormLinkToLocation(link, location) {\n return this.elementIsNavigatable(link) && locationIsVisitable(location, this.snapshot.rootLocation)\n }\n\n submittedFormLinkToLocation() {}\n\n // Link hover observer delegate\n\n canPrefetchRequestToLocation(link, location) {\n return (\n this.elementIsNavigatable(link) &&\n locationIsVisitable(location, this.snapshot.rootLocation) &&\n this.navigator.linkPrefetchingIsEnabledForLocation(location)\n )\n }\n\n // Link click observer delegate\n\n willFollowLinkToLocation(link, location, event) {\n return (\n this.elementIsNavigatable(link) &&\n locationIsVisitable(location, this.snapshot.rootLocation) &&\n this.applicationAllowsFollowingLinkToLocation(link, location, event)\n )\n }\n\n followedLinkToLocation(link, location) {\n const action = this.getActionForLink(link);\n const acceptsStreamResponse = link.hasAttribute(\"data-turbo-stream\");\n\n this.visit(location.href, { action, acceptsStreamResponse });\n }\n\n // Navigator delegate\n\n allowsVisitingLocationWithAction(location, action) {\n return this.locationWithActionIsSamePage(location, action) || this.applicationAllowsVisitingLocation(location)\n }\n\n visitProposedToLocation(location, options) {\n extendURLWithDeprecatedProperties(location);\n this.adapter.visitProposedToLocation(location, options);\n }\n\n // Visit delegate\n\n visitStarted(visit) {\n if (!visit.acceptsStreamResponse) {\n markAsBusy(document.documentElement);\n this.view.markVisitDirection(visit.direction);\n }\n extendURLWithDeprecatedProperties(visit.location);\n if (!visit.silent) {\n this.notifyApplicationAfterVisitingLocation(visit.location, visit.action);\n }\n }\n\n visitCompleted(visit) {\n this.view.unmarkVisitDirection();\n clearBusyState(document.documentElement);\n this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());\n }\n\n locationWithActionIsSamePage(location, action) {\n return this.navigator.locationWithActionIsSamePage(location, action)\n }\n\n visitScrolledToSamePageLocation(oldURL, newURL) {\n this.notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL);\n }\n\n // Form submit observer delegate\n\n willSubmitForm(form, submitter) {\n const action = getAction$1(form, submitter);\n\n return (\n this.submissionIsNavigatable(form, submitter) &&\n locationIsVisitable(expandURL(action), this.snapshot.rootLocation)\n )\n }\n\n formSubmitted(form, submitter) {\n this.navigator.submitForm(form, submitter);\n }\n\n // Page observer delegate\n\n pageBecameInteractive() {\n this.view.lastRenderedLocation = this.location;\n this.notifyApplicationAfterPageLoad();\n }\n\n pageLoaded() {\n this.history.assumeControlOfScrollRestoration();\n }\n\n pageWillUnload() {\n this.history.relinquishControlOfScrollRestoration();\n }\n\n // Stream observer delegate\n\n receivedMessageFromStream(message) {\n this.renderStreamMessage(message);\n }\n\n // Page view delegate\n\n viewWillCacheSnapshot() {\n if (!this.navigator.currentVisit?.silent) {\n this.notifyApplicationBeforeCachingSnapshot();\n }\n }\n\n allowsImmediateRender({ element }, options) {\n const event = this.notifyApplicationBeforeRender(element, options);\n const {\n defaultPrevented,\n detail: { render }\n } = event;\n\n if (this.view.renderer && render) {\n this.view.renderer.renderElement = render;\n }\n\n return !defaultPrevented\n }\n\n viewRenderedSnapshot(_snapshot, _isPreview, renderMethod) {\n this.view.lastRenderedLocation = this.history.location;\n this.notifyApplicationAfterRender(renderMethod);\n }\n\n preloadOnLoadLinksForView(element) {\n this.preloader.preloadOnLoadLinksForView(element);\n }\n\n viewInvalidated(reason) {\n this.adapter.pageInvalidated(reason);\n }\n\n // Frame element\n\n frameLoaded(frame) {\n this.notifyApplicationAfterFrameLoad(frame);\n }\n\n frameRendered(fetchResponse, frame) {\n this.notifyApplicationAfterFrameRender(fetchResponse, frame);\n }\n\n // Application events\n\n applicationAllowsFollowingLinkToLocation(link, location, ev) {\n const event = this.notifyApplicationAfterClickingLinkToLocation(link, location, ev);\n return !event.defaultPrevented\n }\n\n applicationAllowsVisitingLocation(location) {\n const event = this.notifyApplicationBeforeVisitingLocation(location);\n return !event.defaultPrevented\n }\n\n notifyApplicationAfterClickingLinkToLocation(link, location, event) {\n return dispatch(\"turbo:click\", {\n target: link,\n detail: { url: location.href, originalEvent: event },\n cancelable: true\n })\n }\n\n notifyApplicationBeforeVisitingLocation(location) {\n return dispatch(\"turbo:before-visit\", {\n detail: { url: location.href },\n cancelable: true\n })\n }\n\n notifyApplicationAfterVisitingLocation(location, action) {\n return dispatch(\"turbo:visit\", { detail: { url: location.href, action } })\n }\n\n notifyApplicationBeforeCachingSnapshot() {\n return dispatch(\"turbo:before-cache\")\n }\n\n notifyApplicationBeforeRender(newBody, options) {\n return dispatch(\"turbo:before-render\", {\n detail: { newBody, ...options },\n cancelable: true\n })\n }\n\n notifyApplicationAfterRender(renderMethod) {\n return dispatch(\"turbo:render\", { detail: { renderMethod } })\n }\n\n notifyApplicationAfterPageLoad(timing = {}) {\n return dispatch(\"turbo:load\", {\n detail: { url: this.location.href, timing }\n })\n }\n\n notifyApplicationAfterVisitingSamePageLocation(oldURL, newURL) {\n dispatchEvent(\n new HashChangeEvent(\"hashchange\", {\n oldURL: oldURL.toString(),\n newURL: newURL.toString()\n })\n );\n }\n\n notifyApplicationAfterFrameLoad(frame) {\n return dispatch(\"turbo:frame-load\", { target: frame })\n }\n\n notifyApplicationAfterFrameRender(fetchResponse, frame) {\n return dispatch(\"turbo:frame-render\", {\n detail: { fetchResponse },\n target: frame,\n cancelable: true\n })\n }\n\n // Helpers\n\n submissionIsNavigatable(form, submitter) {\n if (config.forms.mode == \"off\") {\n return false\n } else {\n const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true;\n\n if (config.forms.mode == \"optin\") {\n return submitterIsNavigatable && form.closest('[data-turbo=\"true\"]') != null\n } else {\n return submitterIsNavigatable && this.elementIsNavigatable(form)\n }\n }\n }\n\n elementIsNavigatable(element) {\n const container = findClosestRecursively(element, \"[data-turbo]\");\n const withinFrame = findClosestRecursively(element, \"turbo-frame\");\n\n // Check if Drive is enabled on the session or we're within a Frame.\n if (config.drive.enabled || withinFrame) {\n // Element is navigatable by default, unless `data-turbo=\"false\"`.\n if (container) {\n return container.getAttribute(\"data-turbo\") != \"false\"\n } else {\n return true\n }\n } else {\n // Element isn't navigatable by default, unless `data-turbo=\"true\"`.\n if (container) {\n return container.getAttribute(\"data-turbo\") == \"true\"\n } else {\n return false\n }\n }\n }\n\n // Private\n\n getActionForLink(link) {\n return getVisitAction(link) || \"advance\"\n }\n\n get snapshot() {\n return this.view.snapshot\n }\n}\n\n// Older versions of the Turbo Native adapters referenced the\n// `Location#absoluteURL` property in their implementations of\n// the `Adapter#visitProposedToLocation()` and `#visitStarted()`\n// methods. The Location class has since been removed in favor\n// of the DOM URL API, and accordingly all Adapter methods now\n// receive URL objects.\n//\n// We alias #absoluteURL to #toString() here to avoid crashing\n// older adapters which do not expect URL objects. We should\n// consider removing this support at some point in the future.\n\nfunction extendURLWithDeprecatedProperties(url) {\n Object.defineProperties(url, deprecatedLocationPropertyDescriptors);\n}\n\nconst deprecatedLocationPropertyDescriptors = {\n absoluteURL: {\n get() {\n return this.toString()\n }\n }\n};\n\nconst session = new Session(recentRequests);\nconst { cache, navigator: navigator$1 } = session;\n\n/**\n * Starts the main session.\n * This initialises any necessary observers such as those to monitor\n * link interactions.\n */\nfunction start() {\n session.start();\n}\n\n/**\n * Registers an adapter for the main session.\n *\n * @param adapter Adapter to register\n */\nfunction registerAdapter(adapter) {\n session.registerAdapter(adapter);\n}\n\n/**\n * Performs an application visit to the given location.\n *\n * @param location Location to visit (a URL or path)\n * @param options Options to apply\n * @param options.action Type of history navigation to apply (\"restore\",\n * \"replace\" or \"advance\")\n * @param options.historyChanged Specifies whether the browser history has\n * already been changed for this visit or not\n * @param options.referrer Specifies the referrer of this visit such that\n * navigations to the same page will not result in a new history entry.\n * @param options.snapshotHTML Cached snapshot to render\n * @param options.response Response of the specified location\n */\nfunction visit(location, options) {\n session.visit(location, options);\n}\n\n/**\n * Connects a stream source to the main session.\n *\n * @param source Stream source to connect\n */\nfunction connectStreamSource(source) {\n session.connectStreamSource(source);\n}\n\n/**\n * Disconnects a stream source from the main session.\n *\n * @param source Stream source to disconnect\n */\nfunction disconnectStreamSource(source) {\n session.disconnectStreamSource(source);\n}\n\n/**\n * Renders a stream message to the main session by appending it to the\n * current document.\n *\n * @param message Message to render\n */\nfunction renderStreamMessage(message) {\n session.renderStreamMessage(message);\n}\n\n/**\n * Removes all entries from the Turbo Drive page cache.\n * Call this when state has changed on the server that may affect cached pages.\n *\n * @deprecated since version 7.2.0 in favor of `Turbo.cache.clear()`\n */\nfunction clearCache() {\n console.warn(\n \"Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`\"\n );\n session.clearCache();\n}\n\n/**\n * Sets the delay after which the progress bar will appear during navigation.\n *\n * The progress bar appears after 500ms by default.\n *\n * Note that this method has no effect when used with the iOS or Android\n * adapters.\n *\n * @param delay Time to delay in milliseconds\n */\nfunction setProgressBarDelay(delay) {\n console.warn(\n \"Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`\"\n );\n config.drive.progressBarDelay = delay;\n}\n\nfunction setConfirmMethod(confirmMethod) {\n console.warn(\n \"Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`\"\n );\n config.forms.confirm = confirmMethod;\n}\n\nfunction setFormMode(mode) {\n console.warn(\n \"Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`\"\n );\n config.forms.mode = mode;\n}\n\nvar Turbo = /*#__PURE__*/Object.freeze({\n __proto__: null,\n navigator: navigator$1,\n session: session,\n cache: cache,\n PageRenderer: PageRenderer,\n PageSnapshot: PageSnapshot,\n FrameRenderer: FrameRenderer,\n fetch: fetchWithTurboHeaders,\n config: config,\n start: start,\n registerAdapter: registerAdapter,\n visit: visit,\n connectStreamSource: connectStreamSource,\n disconnectStreamSource: disconnectStreamSource,\n renderStreamMessage: renderStreamMessage,\n clearCache: clearCache,\n setProgressBarDelay: setProgressBarDelay,\n setConfirmMethod: setConfirmMethod,\n setFormMode: setFormMode\n});\n\nclass TurboFrameMissingError extends Error {}\n\nclass FrameController {\n fetchResponseLoaded = (_fetchResponse) => Promise.resolve()\n #currentFetchRequest = null\n #resolveVisitPromise = () => {}\n #connected = false\n #hasBeenLoaded = false\n #ignoredAttributes = new Set()\n #shouldMorphFrame = false\n action = null\n\n constructor(element) {\n this.element = element;\n this.view = new FrameView(this, this.element);\n this.appearanceObserver = new AppearanceObserver(this, this.element);\n this.formLinkClickObserver = new FormLinkClickObserver(this, this.element);\n this.linkInterceptor = new LinkInterceptor(this, this.element);\n this.restorationIdentifier = uuid();\n this.formSubmitObserver = new FormSubmitObserver(this, this.element);\n }\n\n // Frame delegate\n\n connect() {\n if (!this.#connected) {\n this.#connected = true;\n if (this.loadingStyle == FrameLoadingStyle.lazy) {\n this.appearanceObserver.start();\n } else {\n this.#loadSourceURL();\n }\n this.formLinkClickObserver.start();\n this.linkInterceptor.start();\n this.formSubmitObserver.start();\n }\n }\n\n disconnect() {\n if (this.#connected) {\n this.#connected = false;\n this.appearanceObserver.stop();\n this.formLinkClickObserver.stop();\n this.linkInterceptor.stop();\n this.formSubmitObserver.stop();\n }\n }\n\n disabledChanged() {\n if (this.loadingStyle == FrameLoadingStyle.eager) {\n this.#loadSourceURL();\n }\n }\n\n sourceURLChanged() {\n if (this.#isIgnoringChangesTo(\"src\")) return\n\n if (this.element.isConnected) {\n this.complete = false;\n }\n\n if (this.loadingStyle == FrameLoadingStyle.eager || this.#hasBeenLoaded) {\n this.#loadSourceURL();\n }\n }\n\n sourceURLReloaded() {\n const { refresh, src } = this.element;\n\n this.#shouldMorphFrame = src && refresh === \"morph\";\n\n this.element.removeAttribute(\"complete\");\n this.element.src = null;\n this.element.src = src;\n return this.element.loaded\n }\n\n loadingStyleChanged() {\n if (this.loadingStyle == FrameLoadingStyle.lazy) {\n this.appearanceObserver.start();\n } else {\n this.appearanceObserver.stop();\n this.#loadSourceURL();\n }\n }\n\n async #loadSourceURL() {\n if (this.enabled && this.isActive && !this.complete && this.sourceURL) {\n this.element.loaded = this.#visit(expandURL(this.sourceURL));\n this.appearanceObserver.stop();\n await this.element.loaded;\n this.#hasBeenLoaded = true;\n }\n }\n\n async loadResponse(fetchResponse) {\n if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {\n this.sourceURL = fetchResponse.response.url;\n }\n\n try {\n const html = await fetchResponse.responseHTML;\n if (html) {\n const document = parseHTMLDocument(html);\n const pageSnapshot = PageSnapshot.fromDocument(document);\n\n if (pageSnapshot.isVisitable) {\n await this.#loadFrameResponse(fetchResponse, document);\n } else {\n await this.#handleUnvisitableFrameResponse(fetchResponse);\n }\n }\n } finally {\n this.#shouldMorphFrame = false;\n this.fetchResponseLoaded = () => Promise.resolve();\n }\n }\n\n // Appearance observer delegate\n\n elementAppearedInViewport(element) {\n this.proposeVisitIfNavigatedWithAction(element, getVisitAction(element));\n this.#loadSourceURL();\n }\n\n // Form link click observer delegate\n\n willSubmitFormLinkToLocation(link) {\n return this.#shouldInterceptNavigation(link)\n }\n\n submittedFormLinkToLocation(link, _location, form) {\n const frame = this.#findFrameElement(link);\n if (frame) form.setAttribute(\"data-turbo-frame\", frame.id);\n }\n\n // Link interceptor delegate\n\n shouldInterceptLinkClick(element, _location, _event) {\n return this.#shouldInterceptNavigation(element)\n }\n\n linkClickIntercepted(element, location) {\n this.#navigateFrame(element, location);\n }\n\n // Form submit observer delegate\n\n willSubmitForm(element, submitter) {\n return element.closest(\"turbo-frame\") == this.element && this.#shouldInterceptNavigation(element, submitter)\n }\n\n formSubmitted(element, submitter) {\n if (this.formSubmission) {\n this.formSubmission.stop();\n }\n\n this.formSubmission = new FormSubmission(this, element, submitter);\n const { fetchRequest } = this.formSubmission;\n this.prepareRequest(fetchRequest);\n this.formSubmission.start();\n }\n\n // Fetch request delegate\n\n prepareRequest(request) {\n request.headers[\"Turbo-Frame\"] = this.id;\n\n if (this.currentNavigationElement?.hasAttribute(\"data-turbo-stream\")) {\n request.acceptResponseType(StreamMessage.contentType);\n }\n }\n\n requestStarted(_request) {\n markAsBusy(this.element);\n }\n\n requestPreventedHandlingResponse(_request, _response) {\n this.#resolveVisitPromise();\n }\n\n async requestSucceededWithResponse(request, response) {\n await this.loadResponse(response);\n this.#resolveVisitPromise();\n }\n\n async requestFailedWithResponse(request, response) {\n await this.loadResponse(response);\n this.#resolveVisitPromise();\n }\n\n requestErrored(request, error) {\n console.error(error);\n this.#resolveVisitPromise();\n }\n\n requestFinished(_request) {\n clearBusyState(this.element);\n }\n\n // Form submission delegate\n\n formSubmissionStarted({ formElement }) {\n markAsBusy(formElement, this.#findFrameElement(formElement));\n }\n\n formSubmissionSucceededWithResponse(formSubmission, response) {\n const frame = this.#findFrameElement(formSubmission.formElement, formSubmission.submitter);\n\n frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(formSubmission.submitter, formSubmission.formElement, frame));\n frame.delegate.loadResponse(response);\n\n if (!formSubmission.isSafe) {\n session.clearCache();\n }\n }\n\n formSubmissionFailedWithResponse(formSubmission, fetchResponse) {\n this.element.delegate.loadResponse(fetchResponse);\n session.clearCache();\n }\n\n formSubmissionErrored(formSubmission, error) {\n console.error(error);\n }\n\n formSubmissionFinished({ formElement }) {\n clearBusyState(formElement, this.#findFrameElement(formElement));\n }\n\n // View delegate\n\n allowsImmediateRender({ element: newFrame }, options) {\n const event = dispatch(\"turbo:before-frame-render\", {\n target: this.element,\n detail: { newFrame, ...options },\n cancelable: true\n });\n\n const {\n defaultPrevented,\n detail: { render }\n } = event;\n\n if (this.view.renderer && render) {\n this.view.renderer.renderElement = render;\n }\n\n return !defaultPrevented\n }\n\n viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {}\n\n preloadOnLoadLinksForView(element) {\n session.preloadOnLoadLinksForView(element);\n }\n\n viewInvalidated() {}\n\n // Frame renderer delegate\n\n willRenderFrame(currentElement, _newElement) {\n this.previousFrameElement = currentElement.cloneNode(true);\n }\n\n visitCachedSnapshot = ({ element }) => {\n const frame = element.querySelector(\"#\" + this.element.id);\n\n if (frame && this.previousFrameElement) {\n frame.replaceChildren(...this.previousFrameElement.children);\n }\n\n delete this.previousFrameElement;\n }\n\n // Private\n\n async #loadFrameResponse(fetchResponse, document) {\n const newFrameElement = await this.extractForeignFrameElement(document.body);\n const rendererClass = this.#shouldMorphFrame ? MorphingFrameRenderer : FrameRenderer;\n\n if (newFrameElement) {\n const snapshot = new Snapshot(newFrameElement);\n const renderer = new rendererClass(this, this.view.snapshot, snapshot, false, false);\n if (this.view.renderPromise) await this.view.renderPromise;\n this.changeHistory();\n\n await this.view.render(renderer);\n this.complete = true;\n session.frameRendered(fetchResponse, this.element);\n session.frameLoaded(this.element);\n await this.fetchResponseLoaded(fetchResponse);\n } else if (this.#willHandleFrameMissingFromResponse(fetchResponse)) {\n this.#handleFrameMissingFromResponse(fetchResponse);\n }\n }\n\n async #visit(url) {\n const request = new FetchRequest(this, FetchMethod.get, url, new URLSearchParams(), this.element);\n\n this.#currentFetchRequest?.cancel();\n this.#currentFetchRequest = request;\n\n return new Promise((resolve) => {\n this.#resolveVisitPromise = () => {\n this.#resolveVisitPromise = () => {};\n this.#currentFetchRequest = null;\n resolve();\n };\n request.perform();\n })\n }\n\n #navigateFrame(element, url, submitter) {\n const frame = this.#findFrameElement(element, submitter);\n\n frame.delegate.proposeVisitIfNavigatedWithAction(frame, getVisitAction(submitter, element, frame));\n\n this.#withCurrentNavigationElement(element, () => {\n frame.src = url;\n });\n }\n\n proposeVisitIfNavigatedWithAction(frame, action = null) {\n this.action = action;\n\n if (this.action) {\n const pageSnapshot = PageSnapshot.fromElement(frame).clone();\n const { visitCachedSnapshot } = frame.delegate;\n\n frame.delegate.fetchResponseLoaded = async (fetchResponse) => {\n if (frame.src) {\n const { statusCode, redirected } = fetchResponse;\n const responseHTML = await fetchResponse.responseHTML;\n const response = { statusCode, redirected, responseHTML };\n const options = {\n response,\n visitCachedSnapshot,\n willRender: false,\n updateHistory: false,\n restorationIdentifier: this.restorationIdentifier,\n snapshot: pageSnapshot\n };\n\n if (this.action) options.action = this.action;\n\n session.visit(frame.src, options);\n }\n };\n }\n }\n\n changeHistory() {\n if (this.action) {\n const method = getHistoryMethodForAction(this.action);\n session.history.update(method, expandURL(this.element.src || \"\"), this.restorationIdentifier);\n }\n }\n\n async #handleUnvisitableFrameResponse(fetchResponse) {\n console.warn(\n `The response (${fetchResponse.statusCode}) from is performing a full page visit due to turbo-visit-control.`\n );\n\n await this.#visitResponse(fetchResponse.response);\n }\n\n #willHandleFrameMissingFromResponse(fetchResponse) {\n this.element.setAttribute(\"complete\", \"\");\n\n const response = fetchResponse.response;\n const visit = async (url, options) => {\n if (url instanceof Response) {\n this.#visitResponse(url);\n } else {\n session.visit(url, options);\n }\n };\n\n const event = dispatch(\"turbo:frame-missing\", {\n target: this.element,\n detail: { response, visit },\n cancelable: true\n });\n\n return !event.defaultPrevented\n }\n\n #handleFrameMissingFromResponse(fetchResponse) {\n this.view.missing();\n this.#throwFrameMissingError(fetchResponse);\n }\n\n #throwFrameMissingError(fetchResponse) {\n const message = `The response (${fetchResponse.statusCode}) did not contain the expected and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;\n throw new TurboFrameMissingError(message)\n }\n\n async #visitResponse(response) {\n const wrapped = new FetchResponse(response);\n const responseHTML = await wrapped.responseHTML;\n const { location, redirected, statusCode } = wrapped;\n\n return session.visit(location, { response: { redirected, statusCode, responseHTML } })\n }\n\n #findFrameElement(element, submitter) {\n const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n return getFrameElementById(id) ?? this.element\n }\n\n async extractForeignFrameElement(container) {\n let element;\n const id = CSS.escape(this.id);\n\n try {\n element = activateElement(container.querySelector(`turbo-frame#${id}`), this.sourceURL);\n if (element) {\n return element\n }\n\n element = activateElement(container.querySelector(`turbo-frame[src][recurse~=${id}]`), this.sourceURL);\n if (element) {\n await element.loaded;\n return await this.extractForeignFrameElement(element)\n }\n } catch (error) {\n console.error(error);\n return new FrameElement()\n }\n\n return null\n }\n\n #formActionIsVisitable(form, submitter) {\n const action = getAction$1(form, submitter);\n\n return locationIsVisitable(expandURL(action), this.rootLocation)\n }\n\n #shouldInterceptNavigation(element, submitter) {\n const id = getAttribute(\"data-turbo-frame\", submitter, element) || this.element.getAttribute(\"target\");\n\n if (element instanceof HTMLFormElement && !this.#formActionIsVisitable(element, submitter)) {\n return false\n }\n\n if (!this.enabled || id == \"_top\") {\n return false\n }\n\n if (id) {\n const frameElement = getFrameElementById(id);\n if (frameElement) {\n return !frameElement.disabled\n }\n }\n\n if (!session.elementIsNavigatable(element)) {\n return false\n }\n\n if (submitter && !session.elementIsNavigatable(submitter)) {\n return false\n }\n\n return true\n }\n\n // Computed properties\n\n get id() {\n return this.element.id\n }\n\n get enabled() {\n return !this.element.disabled\n }\n\n get sourceURL() {\n if (this.element.src) {\n return this.element.src\n }\n }\n\n set sourceURL(sourceURL) {\n this.#ignoringChangesToAttribute(\"src\", () => {\n this.element.src = sourceURL ?? null;\n });\n }\n\n get loadingStyle() {\n return this.element.loading\n }\n\n get isLoading() {\n return this.formSubmission !== undefined || this.#resolveVisitPromise() !== undefined\n }\n\n get complete() {\n return this.element.hasAttribute(\"complete\")\n }\n\n set complete(value) {\n if (value) {\n this.element.setAttribute(\"complete\", \"\");\n } else {\n this.element.removeAttribute(\"complete\");\n }\n }\n\n get isActive() {\n return this.element.isActive && this.#connected\n }\n\n get rootLocation() {\n const meta = this.element.ownerDocument.querySelector(`meta[name=\"turbo-root\"]`);\n const root = meta?.content ?? \"/\";\n return expandURL(root)\n }\n\n #isIgnoringChangesTo(attributeName) {\n return this.#ignoredAttributes.has(attributeName)\n }\n\n #ignoringChangesToAttribute(attributeName, callback) {\n this.#ignoredAttributes.add(attributeName);\n callback();\n this.#ignoredAttributes.delete(attributeName);\n }\n\n #withCurrentNavigationElement(element, callback) {\n this.currentNavigationElement = element;\n callback();\n delete this.currentNavigationElement;\n }\n}\n\nfunction getFrameElementById(id) {\n if (id != null) {\n const element = document.getElementById(id);\n if (element instanceof FrameElement) {\n return element\n }\n }\n}\n\nfunction activateElement(element, currentURL) {\n if (element) {\n const src = element.getAttribute(\"src\");\n if (src != null && currentURL != null && urlsAreEqual(src, currentURL)) {\n throw new Error(`Matching element has a source URL which references itself`)\n }\n if (element.ownerDocument !== document) {\n element = document.importNode(element, true);\n }\n\n if (element instanceof FrameElement) {\n element.connectedCallback();\n element.disconnectedCallback();\n return element\n }\n }\n}\n\nconst StreamActions = {\n after() {\n this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e.nextSibling));\n },\n\n append() {\n this.removeDuplicateTargetChildren();\n this.targetElements.forEach((e) => e.append(this.templateContent));\n },\n\n before() {\n this.targetElements.forEach((e) => e.parentElement?.insertBefore(this.templateContent, e));\n },\n\n prepend() {\n this.removeDuplicateTargetChildren();\n this.targetElements.forEach((e) => e.prepend(this.templateContent));\n },\n\n remove() {\n this.targetElements.forEach((e) => e.remove());\n },\n\n replace() {\n const method = this.getAttribute(\"method\");\n\n this.targetElements.forEach((targetElement) => {\n if (method === \"morph\") {\n morphElements(targetElement, this.templateContent);\n } else {\n targetElement.replaceWith(this.templateContent);\n }\n });\n },\n\n update() {\n const method = this.getAttribute(\"method\");\n\n this.targetElements.forEach((targetElement) => {\n if (method === \"morph\") {\n morphChildren(targetElement, this.templateContent);\n } else {\n targetElement.innerHTML = \"\";\n targetElement.append(this.templateContent);\n }\n });\n },\n\n refresh() {\n session.refresh(this.baseURI, this.requestId);\n }\n};\n\n//