@abrar71/lib-jitsi-meet 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +26 -0
- package/dist/esm/JitsiConference.js +3692 -0
- package/dist/esm/JitsiConference.js.map +1 -0
- package/dist/esm/JitsiConferenceErrors.js +126 -0
- package/dist/esm/JitsiConferenceErrors.js.map +1 -0
- package/dist/esm/JitsiConferenceEventManager.js +424 -0
- package/dist/esm/JitsiConferenceEventManager.js.map +1 -0
- package/dist/esm/JitsiConferenceEvents.js +431 -0
- package/dist/esm/JitsiConferenceEvents.js.map +1 -0
- package/dist/esm/JitsiConnection.js +187 -0
- package/dist/esm/JitsiConnection.js.map +1 -0
- package/dist/esm/JitsiConnectionErrors.js +52 -0
- package/dist/esm/JitsiConnectionErrors.js.map +1 -0
- package/dist/esm/JitsiConnectionEvents.js +57 -0
- package/dist/esm/JitsiConnectionEvents.js.map +1 -0
- package/dist/esm/JitsiMediaDevices.js +221 -0
- package/dist/esm/JitsiMediaDevices.js.map +1 -0
- package/dist/esm/JitsiMediaDevicesEvents.js +29 -0
- package/dist/esm/JitsiMediaDevicesEvents.js.map +1 -0
- package/dist/esm/JitsiMeetJS.js +499 -0
- package/dist/esm/JitsiMeetJS.js.map +1 -0
- package/dist/esm/JitsiParticipant.js +323 -0
- package/dist/esm/JitsiParticipant.js.map +1 -0
- package/dist/esm/JitsiTrackError.js +122 -0
- package/dist/esm/JitsiTrackError.js.map +1 -0
- package/dist/esm/JitsiTrackErrors.js +91 -0
- package/dist/esm/JitsiTrackErrors.js.map +1 -0
- package/dist/esm/JitsiTrackEvents.js +60 -0
- package/dist/esm/JitsiTrackEvents.js.map +1 -0
- package/dist/esm/JitsiTranscriptionStatus.js +15 -0
- package/dist/esm/JitsiTranscriptionStatus.js.map +1 -0
- package/dist/esm/modules/RTC/BridgeChannel.js +398 -0
- package/dist/esm/modules/RTC/BridgeChannel.js.map +1 -0
- package/dist/esm/modules/RTC/JitsiLocalTrack.js +896 -0
- package/dist/esm/modules/RTC/JitsiLocalTrack.js.map +1 -0
- package/dist/esm/modules/RTC/JitsiRemoteTrack.js +427 -0
- package/dist/esm/modules/RTC/JitsiRemoteTrack.js.map +1 -0
- package/dist/esm/modules/RTC/JitsiTrack.js +453 -0
- package/dist/esm/modules/RTC/JitsiTrack.js.map +1 -0
- package/dist/esm/modules/RTC/MockClasses.js +388 -0
- package/dist/esm/modules/RTC/MockClasses.js.map +1 -0
- package/dist/esm/modules/RTC/RTC.js +658 -0
- package/dist/esm/modules/RTC/RTC.js.map +1 -0
- package/dist/esm/modules/RTC/RTCUtils.js +762 -0
- package/dist/esm/modules/RTC/RTCUtils.js.map +1 -0
- package/dist/esm/modules/RTC/ScreenObtainer.js +380 -0
- package/dist/esm/modules/RTC/ScreenObtainer.js.map +1 -0
- package/dist/esm/modules/RTC/TPCUtils.js +803 -0
- package/dist/esm/modules/RTC/TPCUtils.js.map +1 -0
- package/dist/esm/modules/RTC/TraceablePeerConnection.js +2223 -0
- package/dist/esm/modules/RTC/TraceablePeerConnection.js.map +1 -0
- package/dist/esm/modules/RTCStats/DefaulLogStorage.js +35 -0
- package/dist/esm/modules/RTCStats/DefaulLogStorage.js.map +1 -0
- package/dist/esm/modules/RTCStats/RTCStats.js +219 -0
- package/dist/esm/modules/RTCStats/RTCStats.js.map +1 -0
- package/dist/esm/modules/RTCStats/RTCStatsEvents.js +92 -0
- package/dist/esm/modules/RTCStats/RTCStatsEvents.js.map +1 -0
- package/dist/esm/modules/RTCStats/interfaces.js +2 -0
- package/dist/esm/modules/RTCStats/interfaces.js.map +1 -0
- package/dist/esm/modules/browser/BrowserCapabilities.js +345 -0
- package/dist/esm/modules/browser/BrowserCapabilities.js.map +1 -0
- package/dist/esm/modules/browser/index.js +3 -0
- package/dist/esm/modules/browser/index.js.map +1 -0
- package/dist/esm/modules/connectivity/ConnectionQuality.js +389 -0
- package/dist/esm/modules/connectivity/ConnectionQuality.js.map +1 -0
- package/dist/esm/modules/connectivity/IceFailedHandling.js +84 -0
- package/dist/esm/modules/connectivity/IceFailedHandling.js.map +1 -0
- package/dist/esm/modules/connectivity/NetworkInfo.js +49 -0
- package/dist/esm/modules/connectivity/NetworkInfo.js.map +1 -0
- package/dist/esm/modules/connectivity/TrackStreamingStatus.js +453 -0
- package/dist/esm/modules/connectivity/TrackStreamingStatus.js.map +1 -0
- package/dist/esm/modules/detection/ActiveDeviceDetector.js +79 -0
- package/dist/esm/modules/detection/ActiveDeviceDetector.js.map +1 -0
- package/dist/esm/modules/detection/DetectionEvents.js +58 -0
- package/dist/esm/modules/detection/DetectionEvents.js.map +1 -0
- package/dist/esm/modules/detection/NoAudioSignalDetection.js +127 -0
- package/dist/esm/modules/detection/NoAudioSignalDetection.js.map +1 -0
- package/dist/esm/modules/detection/P2PDominantSpeakerDetection.js +47 -0
- package/dist/esm/modules/detection/P2PDominantSpeakerDetection.js.map +1 -0
- package/dist/esm/modules/detection/TrackVADEmitter.js +190 -0
- package/dist/esm/modules/detection/TrackVADEmitter.js.map +1 -0
- package/dist/esm/modules/detection/VADAudioAnalyser.js +199 -0
- package/dist/esm/modules/detection/VADAudioAnalyser.js.map +1 -0
- package/dist/esm/modules/detection/VADNoiseDetection.js +168 -0
- package/dist/esm/modules/detection/VADNoiseDetection.js.map +1 -0
- package/dist/esm/modules/detection/VADReportingService.js +203 -0
- package/dist/esm/modules/detection/VADReportingService.js.map +1 -0
- package/dist/esm/modules/detection/VADTalkMutedDetection.js +131 -0
- package/dist/esm/modules/detection/VADTalkMutedDetection.js.map +1 -0
- package/dist/esm/modules/e2ee/Context.js +274 -0
- package/dist/esm/modules/e2ee/Context.js.map +1 -0
- package/dist/esm/modules/e2ee/E2EEContext.js +158 -0
- package/dist/esm/modules/e2ee/E2EEContext.js.map +1 -0
- package/dist/esm/modules/e2ee/E2EEErrors.js +10 -0
- package/dist/esm/modules/e2ee/E2EEErrors.js.map +1 -0
- package/dist/esm/modules/e2ee/E2EEncryption.js +87 -0
- package/dist/esm/modules/e2ee/E2EEncryption.js.map +1 -0
- package/dist/esm/modules/e2ee/ExternallyManagedKeyHandler.js +24 -0
- package/dist/esm/modules/e2ee/ExternallyManagedKeyHandler.js.map +1 -0
- package/dist/esm/modules/e2ee/KeyHandler.js +137 -0
- package/dist/esm/modules/e2ee/KeyHandler.js.map +1 -0
- package/dist/esm/modules/e2ee/ManagedKeyHandler.js +182 -0
- package/dist/esm/modules/e2ee/ManagedKeyHandler.js.map +1 -0
- package/dist/esm/modules/e2ee/OlmAdapter.js +860 -0
- package/dist/esm/modules/e2ee/OlmAdapter.js.map +1 -0
- package/dist/esm/modules/e2ee/SAS.js +128 -0
- package/dist/esm/modules/e2ee/SAS.js.map +1 -0
- package/dist/esm/modules/e2ee/Worker.js +102 -0
- package/dist/esm/modules/e2ee/Worker.js.map +1 -0
- package/dist/esm/modules/e2ee/crypto-utils.js +53 -0
- package/dist/esm/modules/e2ee/crypto-utils.js.map +1 -0
- package/dist/esm/modules/e2ee/utils.js +15 -0
- package/dist/esm/modules/e2ee/utils.js.map +1 -0
- package/dist/esm/modules/e2eping/e2eping.js +314 -0
- package/dist/esm/modules/e2eping/e2eping.js.map +1 -0
- package/dist/esm/modules/flags/FeatureFlags.js +36 -0
- package/dist/esm/modules/flags/FeatureFlags.js.map +1 -0
- package/dist/esm/modules/litemode/LiteModeContext.js +50 -0
- package/dist/esm/modules/litemode/LiteModeContext.js.map +1 -0
- package/dist/esm/modules/proxyconnection/CustomSignalingLayer.js +98 -0
- package/dist/esm/modules/proxyconnection/CustomSignalingLayer.js.map +1 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionPC.js +348 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionPC.js.map +1 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionService.js +279 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionService.js.map +1 -0
- package/dist/esm/modules/proxyconnection/constants.js +14 -0
- package/dist/esm/modules/proxyconnection/constants.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/CodecSelection.js +222 -0
- package/dist/esm/modules/qualitycontrol/CodecSelection.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/MockClasses.js +120 -0
- package/dist/esm/modules/qualitycontrol/MockClasses.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/QualityController.js +366 -0
- package/dist/esm/modules/qualitycontrol/QualityController.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/ReceiveAudioController.js +73 -0
- package/dist/esm/modules/qualitycontrol/ReceiveAudioController.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/ReceiveVideoController.js +216 -0
- package/dist/esm/modules/qualitycontrol/ReceiveVideoController.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/SendVideoController.js +133 -0
- package/dist/esm/modules/qualitycontrol/SendVideoController.js.map +1 -0
- package/dist/esm/modules/recording/JibriSession.js +279 -0
- package/dist/esm/modules/recording/JibriSession.js.map +1 -0
- package/dist/esm/modules/recording/RecordingManager.js +257 -0
- package/dist/esm/modules/recording/RecordingManager.js.map +1 -0
- package/dist/esm/modules/recording/recordingConstants.js +21 -0
- package/dist/esm/modules/recording/recordingConstants.js.map +1 -0
- package/dist/esm/modules/recording/recordingXMLUtils.js +69 -0
- package/dist/esm/modules/recording/recordingXMLUtils.js.map +1 -0
- package/dist/esm/modules/red/red.js +108 -0
- package/dist/esm/modules/red/red.js.map +1 -0
- package/dist/esm/modules/sdp/LocalSdpMunger.js +143 -0
- package/dist/esm/modules/sdp/LocalSdpMunger.js.map +1 -0
- package/dist/esm/modules/sdp/RtxModifier.js +179 -0
- package/dist/esm/modules/sdp/RtxModifier.js.map +1 -0
- package/dist/esm/modules/sdp/SDP.js +848 -0
- package/dist/esm/modules/sdp/SDP.js.map +1 -0
- package/dist/esm/modules/sdp/SDPDiffer.js +96 -0
- package/dist/esm/modules/sdp/SDPDiffer.js.map +1 -0
- package/dist/esm/modules/sdp/SDPUtil.js +798 -0
- package/dist/esm/modules/sdp/SDPUtil.js.map +1 -0
- package/dist/esm/modules/sdp/SampleSdpStrings.js +589 -0
- package/dist/esm/modules/sdp/SampleSdpStrings.js.map +1 -0
- package/dist/esm/modules/sdp/SdpSimulcast.js +196 -0
- package/dist/esm/modules/sdp/SdpSimulcast.js.map +1 -0
- package/dist/esm/modules/sdp/SdpTransformUtil.js +337 -0
- package/dist/esm/modules/sdp/SdpTransformUtil.js.map +1 -0
- package/dist/esm/modules/sdp/constants.js +2 -0
- package/dist/esm/modules/sdp/constants.js.map +1 -0
- package/dist/esm/modules/settings/Settings.js +95 -0
- package/dist/esm/modules/settings/Settings.js.map +1 -0
- package/dist/esm/modules/statistics/AnalyticsAdapter.js +277 -0
- package/dist/esm/modules/statistics/AnalyticsAdapter.js.map +1 -0
- package/dist/esm/modules/statistics/AvgRTPStatsReporter.js +817 -0
- package/dist/esm/modules/statistics/AvgRTPStatsReporter.js.map +1 -0
- package/dist/esm/modules/statistics/LocalStatsCollector.js +149 -0
- package/dist/esm/modules/statistics/LocalStatsCollector.js.map +1 -0
- package/dist/esm/modules/statistics/PreCallTest.js +15 -0
- package/dist/esm/modules/statistics/PreCallTest.js.map +1 -0
- package/dist/esm/modules/statistics/RTPStatsCollector.js +601 -0
- package/dist/esm/modules/statistics/RTPStatsCollector.js.map +1 -0
- package/dist/esm/modules/statistics/SpeakerStats.js +163 -0
- package/dist/esm/modules/statistics/SpeakerStats.js.map +1 -0
- package/dist/esm/modules/statistics/SpeakerStatsCollector.js +161 -0
- package/dist/esm/modules/statistics/SpeakerStatsCollector.js.map +1 -0
- package/dist/esm/modules/statistics/constants.js +7 -0
- package/dist/esm/modules/statistics/constants.js.map +1 -0
- package/dist/esm/modules/statistics/statistics.js +362 -0
- package/dist/esm/modules/statistics/statistics.js.map +1 -0
- package/dist/esm/modules/util/AsyncQueue.js +102 -0
- package/dist/esm/modules/util/AsyncQueue.js.map +1 -0
- package/dist/esm/modules/util/Deferred.js +41 -0
- package/dist/esm/modules/util/Deferred.js.map +1 -0
- package/dist/esm/modules/util/EventEmitter.js +17 -0
- package/dist/esm/modules/util/EventEmitter.js.map +1 -0
- package/dist/esm/modules/util/EventEmitterForwarder.js +52 -0
- package/dist/esm/modules/util/EventEmitterForwarder.js.map +1 -0
- package/dist/esm/modules/util/Listenable.js +106 -0
- package/dist/esm/modules/util/Listenable.js.map +1 -0
- package/dist/esm/modules/util/MathUtil.js +103 -0
- package/dist/esm/modules/util/MathUtil.js.map +1 -0
- package/dist/esm/modules/util/RandomUtil.js +66 -0
- package/dist/esm/modules/util/RandomUtil.js.map +1 -0
- package/dist/esm/modules/util/Retry.js +15 -0
- package/dist/esm/modules/util/Retry.js.map +1 -0
- package/dist/esm/modules/util/ScriptUtil.js +58 -0
- package/dist/esm/modules/util/ScriptUtil.js.map +1 -0
- package/dist/esm/modules/util/StringUtils.js +21 -0
- package/dist/esm/modules/util/StringUtils.js.map +1 -0
- package/dist/esm/modules/util/TestUtils.js +14 -0
- package/dist/esm/modules/util/TestUtils.js.map +1 -0
- package/dist/esm/modules/util/UsernameGenerator.js +436 -0
- package/dist/esm/modules/util/UsernameGenerator.js.map +1 -0
- package/dist/esm/modules/util/XMLUtils.js +135 -0
- package/dist/esm/modules/util/XMLUtils.js.map +1 -0
- package/dist/esm/modules/version/ComponentsVersions.js +52 -0
- package/dist/esm/modules/version/ComponentsVersions.js.map +1 -0
- package/dist/esm/modules/videosipgw/JitsiVideoSIPGWSession.js +137 -0
- package/dist/esm/modules/videosipgw/JitsiVideoSIPGWSession.js.map +1 -0
- package/dist/esm/modules/videosipgw/VideoSIPGW.js +102 -0
- package/dist/esm/modules/videosipgw/VideoSIPGW.js.map +1 -0
- package/dist/esm/modules/videosipgw/VideoSIPGWConstants.js +65 -0
- package/dist/esm/modules/videosipgw/VideoSIPGWConstants.js.map +1 -0
- package/dist/esm/modules/watchRTC/WatchRTC.js +69 -0
- package/dist/esm/modules/watchRTC/WatchRTC.js.map +1 -0
- package/dist/esm/modules/watchRTC/functions.js +31 -0
- package/dist/esm/modules/watchRTC/functions.js.map +1 -0
- package/dist/esm/modules/watchRTC/interfaces.js +2 -0
- package/dist/esm/modules/watchRTC/interfaces.js.map +1 -0
- package/dist/esm/modules/webaudio/AudioMixer.js +74 -0
- package/dist/esm/modules/webaudio/AudioMixer.js.map +1 -0
- package/dist/esm/modules/webaudio/WebAudioUtils.js +13 -0
- package/dist/esm/modules/webaudio/WebAudioUtils.js.map +1 -0
- package/dist/esm/modules/xmpp/AVModeration.js +156 -0
- package/dist/esm/modules/xmpp/AVModeration.js.map +1 -0
- package/dist/esm/modules/xmpp/BreakoutRooms.js +230 -0
- package/dist/esm/modules/xmpp/BreakoutRooms.js.map +1 -0
- package/dist/esm/modules/xmpp/Caps.js +223 -0
- package/dist/esm/modules/xmpp/Caps.js.map +1 -0
- package/dist/esm/modules/xmpp/ChatRoom.js +1877 -0
- package/dist/esm/modules/xmpp/ChatRoom.js.map +1 -0
- package/dist/esm/modules/xmpp/ConnectionPlugin.js +37 -0
- package/dist/esm/modules/xmpp/ConnectionPlugin.js.map +1 -0
- package/dist/esm/modules/xmpp/FileSharing.js +95 -0
- package/dist/esm/modules/xmpp/FileSharing.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleHelperFunctions.js +168 -0
- package/dist/esm/modules/xmpp/JingleHelperFunctions.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleSession.js +166 -0
- package/dist/esm/modules/xmpp/JingleSession.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleSessionPC.js +1969 -0
- package/dist/esm/modules/xmpp/JingleSessionPC.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleSessionState.js +23 -0
- package/dist/esm/modules/xmpp/JingleSessionState.js.map +1 -0
- package/dist/esm/modules/xmpp/Lobby.js +384 -0
- package/dist/esm/modules/xmpp/Lobby.js.map +1 -0
- package/dist/esm/modules/xmpp/MediaSessionEvents.js +12 -0
- package/dist/esm/modules/xmpp/MediaSessionEvents.js.map +1 -0
- package/dist/esm/modules/xmpp/MockClasses.js +77 -0
- package/dist/esm/modules/xmpp/MockClasses.js.map +1 -0
- package/dist/esm/modules/xmpp/Polls.js +87 -0
- package/dist/esm/modules/xmpp/Polls.js.map +1 -0
- package/dist/esm/modules/xmpp/ResumeTask.js +149 -0
- package/dist/esm/modules/xmpp/ResumeTask.js.map +1 -0
- package/dist/esm/modules/xmpp/RoomMetadata.js +96 -0
- package/dist/esm/modules/xmpp/RoomMetadata.js.map +1 -0
- package/dist/esm/modules/xmpp/SignalingLayerImpl.js +313 -0
- package/dist/esm/modules/xmpp/SignalingLayerImpl.js.map +1 -0
- package/dist/esm/modules/xmpp/StropheErrorHandler.js +53 -0
- package/dist/esm/modules/xmpp/StropheErrorHandler.js.map +1 -0
- package/dist/esm/modules/xmpp/StropheLastSuccess.js +52 -0
- package/dist/esm/modules/xmpp/StropheLastSuccess.js.map +1 -0
- package/dist/esm/modules/xmpp/XmppConnection.js +579 -0
- package/dist/esm/modules/xmpp/XmppConnection.js.map +1 -0
- package/dist/esm/modules/xmpp/moderator.js +524 -0
- package/dist/esm/modules/xmpp/moderator.js.map +1 -0
- package/dist/esm/modules/xmpp/sha1.js +165 -0
- package/dist/esm/modules/xmpp/sha1.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.disco.js +222 -0
- package/dist/esm/modules/xmpp/strophe.disco.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.emuc.js +206 -0
- package/dist/esm/modules/xmpp/strophe.emuc.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.jingle.js +404 -0
- package/dist/esm/modules/xmpp/strophe.jingle.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.logger.js +44 -0
- package/dist/esm/modules/xmpp/strophe.logger.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.ping.js +170 -0
- package/dist/esm/modules/xmpp/strophe.ping.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.rayo.js +117 -0
- package/dist/esm/modules/xmpp/strophe.rayo.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.stream-management.js +365 -0
- package/dist/esm/modules/xmpp/strophe.stream-management.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.util.js +116 -0
- package/dist/esm/modules/xmpp/strophe.util.js.map +1 -0
- package/dist/esm/modules/xmpp/xmpp.js +973 -0
- package/dist/esm/modules/xmpp/xmpp.js.map +1 -0
- package/dist/esm/service/RTC/BridgeVideoType.js +24 -0
- package/dist/esm/service/RTC/BridgeVideoType.js.map +1 -0
- package/dist/esm/service/RTC/CameraFacingMode.js +21 -0
- package/dist/esm/service/RTC/CameraFacingMode.js.map +1 -0
- package/dist/esm/service/RTC/CodecMimeType.js +36 -0
- package/dist/esm/service/RTC/CodecMimeType.js.map +1 -0
- package/dist/esm/service/RTC/MediaDirection.js +23 -0
- package/dist/esm/service/RTC/MediaDirection.js.map +1 -0
- package/dist/esm/service/RTC/MediaType.js +20 -0
- package/dist/esm/service/RTC/MediaType.js.map +1 -0
- package/dist/esm/service/RTC/RTCEvents.js +111 -0
- package/dist/esm/service/RTC/RTCEvents.js.map +1 -0
- package/dist/esm/service/RTC/ReceiverAudioSubscription.js +27 -0
- package/dist/esm/service/RTC/ReceiverAudioSubscription.js.map +1 -0
- package/dist/esm/service/RTC/Resolutions.js +56 -0
- package/dist/esm/service/RTC/Resolutions.js.map +1 -0
- package/dist/esm/service/RTC/SignalingEvents.js +42 -0
- package/dist/esm/service/RTC/SignalingEvents.js.map +1 -0
- package/dist/esm/service/RTC/SignalingLayer.js +153 -0
- package/dist/esm/service/RTC/SignalingLayer.js.map +1 -0
- package/dist/esm/service/RTC/StandardVideoQualitySettings.js +180 -0
- package/dist/esm/service/RTC/StandardVideoQualitySettings.js.map +1 -0
- package/dist/esm/service/RTC/VideoEncoderScalabilityMode.js +36 -0
- package/dist/esm/service/RTC/VideoEncoderScalabilityMode.js.map +1 -0
- package/dist/esm/service/RTC/VideoType.js +19 -0
- package/dist/esm/service/RTC/VideoType.js.map +1 -0
- package/dist/esm/service/authentication/AuthenticationEvents.js +13 -0
- package/dist/esm/service/authentication/AuthenticationEvents.js.map +1 -0
- package/dist/esm/service/connectivity/ConnectionQualityEvents.js +13 -0
- package/dist/esm/service/connectivity/ConnectionQualityEvents.js.map +1 -0
- package/dist/esm/service/connectivity/Constants.js +3 -0
- package/dist/esm/service/connectivity/Constants.js.map +1 -0
- package/dist/esm/service/e2eping/E2ePingEvents.js +8 -0
- package/dist/esm/service/e2eping/E2ePingEvents.js.map +1 -0
- package/dist/esm/service/statistics/AnalyticsEvents.js +521 -0
- package/dist/esm/service/statistics/AnalyticsEvents.js.map +1 -0
- package/dist/esm/service/statistics/Events.js +36 -0
- package/dist/esm/service/statistics/Events.js.map +1 -0
- package/dist/esm/service/statistics/constants.js +2 -0
- package/dist/esm/service/statistics/constants.js.map +1 -0
- package/dist/esm/service/xmpp/XMPPEvents.js +359 -0
- package/dist/esm/service/xmpp/XMPPEvents.js.map +1 -0
- package/dist/esm/service/xmpp/XMPPExtensioProtocols.js +64 -0
- package/dist/esm/service/xmpp/XMPPExtensioProtocols.js.map +1 -0
- package/dist/esm/test-setup-polyfill.js +34 -0
- package/dist/esm/test-setup-polyfill.js.map +1 -0
- package/dist/esm/tools/gen-version.js +15 -0
- package/dist/esm/tools/gen-version.js.map +1 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/umd/lib-jitsi-meet.e2ee-worker.js +484 -0
- package/dist/umd/lib-jitsi-meet.min.js +3 -0
- package/dist/umd/lib-jitsi-meet.min.js.LICENSE.txt +18 -0
- package/dist/umd/lib-jitsi-meet.min.map +1 -0
- package/package.json +93 -0
- package/types/index.d.ts +16180 -0
|
@@ -0,0 +1,1877 @@
|
|
|
1
|
+
import { getLogger } from '@jitsi/logger';
|
|
2
|
+
import emojiRegex from 'emoji-regex';
|
|
3
|
+
import { isEqual } from 'lodash-es';
|
|
4
|
+
import { $iq, $msg, $pres, Strophe } from 'strophe.js';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import { AUTH_ERROR_TYPES } from '../../JitsiConferenceErrors';
|
|
7
|
+
import * as JitsiTranscriptionStatus from '../../JitsiTranscriptionStatus';
|
|
8
|
+
import { MediaType } from '../../service/RTC/MediaType';
|
|
9
|
+
import { VideoType } from '../../service/RTC/VideoType';
|
|
10
|
+
import { AuthenticationEvents } from '../../service/authentication/AuthenticationEvents';
|
|
11
|
+
import { XMPPEvents } from '../../service/xmpp/XMPPEvents';
|
|
12
|
+
import Settings from '../settings/Settings';
|
|
13
|
+
import EventEmitterForwarder from '../util/EventEmitterForwarder';
|
|
14
|
+
import Listenable from '../util/Listenable';
|
|
15
|
+
import { getJitterDelay } from '../util/Retry';
|
|
16
|
+
import { exists, findAll, findFirst, getAttribute, getText } from '../util/XMLUtils';
|
|
17
|
+
import AVModeration from './AVModeration';
|
|
18
|
+
import BreakoutRooms from './BreakoutRooms';
|
|
19
|
+
import FileSharing from './FileSharing';
|
|
20
|
+
import Lobby from './Lobby';
|
|
21
|
+
import Polls from './Polls';
|
|
22
|
+
import RoomMetadata from './RoomMetadata';
|
|
23
|
+
import { handleStropheError } from './StropheErrorHandler';
|
|
24
|
+
import XmppConnection from './XmppConnection';
|
|
25
|
+
import { FEATURE_TRANSCRIBER } from './xmpp';
|
|
26
|
+
const logger = getLogger('xmpp:ChatRoom');
|
|
27
|
+
/**
|
|
28
|
+
* Regex that matches all emojis.
|
|
29
|
+
*/
|
|
30
|
+
const EMOJI_REGEX = emojiRegex();
|
|
31
|
+
/**
|
|
32
|
+
* How long we're going to wait for IQ response, before timeout error is triggered.
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
const IQ_TIMEOUT = 10000;
|
|
36
|
+
export const parser = {
|
|
37
|
+
json2packet(nodes, packet) {
|
|
38
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
39
|
+
const node = nodes[i];
|
|
40
|
+
if (node) {
|
|
41
|
+
packet.c(node.tagName, node.attributes);
|
|
42
|
+
if (node.value) {
|
|
43
|
+
packet.t(node.value);
|
|
44
|
+
}
|
|
45
|
+
if (node.children) {
|
|
46
|
+
this.json2packet(node.children, packet);
|
|
47
|
+
}
|
|
48
|
+
packet.up();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// packet.up();
|
|
52
|
+
},
|
|
53
|
+
packet2JSON(xmlElement, nodes) {
|
|
54
|
+
for (const child of Array.from(xmlElement.children)) {
|
|
55
|
+
const node = {
|
|
56
|
+
attributes: {},
|
|
57
|
+
children: [],
|
|
58
|
+
tagName: child.tagName
|
|
59
|
+
};
|
|
60
|
+
for (const attr of Array.from(child.attributes)) {
|
|
61
|
+
node.attributes[attr.name] = attr.value;
|
|
62
|
+
}
|
|
63
|
+
const text = Strophe.getText(child);
|
|
64
|
+
if (text) {
|
|
65
|
+
// Using Strophe.getText will do work for traversing all direct
|
|
66
|
+
// child text nodes but returns an escaped value, which is not
|
|
67
|
+
// desirable at this point.
|
|
68
|
+
node.value = Strophe.xmlunescape(text);
|
|
69
|
+
}
|
|
70
|
+
nodes.push(node);
|
|
71
|
+
this.packet2JSON(child, node.children);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Returns array of JS objects from the presence JSON associated with the passed
|
|
77
|
+
/ nodeName
|
|
78
|
+
* @param pres the presence JSON
|
|
79
|
+
* @param nodeName the name of the node (videomuted, audiomuted, etc)
|
|
80
|
+
*/
|
|
81
|
+
export function filterNodeFromPresenceJSON(pres, nodeName) {
|
|
82
|
+
const res = [];
|
|
83
|
+
for (let i = 0; i < pres.length; i++) {
|
|
84
|
+
if (pres[i].tagName === nodeName) {
|
|
85
|
+
res.push(pres[i]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return res;
|
|
89
|
+
}
|
|
90
|
+
// XXX As ChatRoom constructs XMPP stanzas and Strophe is build around the idea
|
|
91
|
+
// of chaining function calls, allow long function call chains.
|
|
92
|
+
/* eslint-disable newline-per-chained-call */
|
|
93
|
+
/**
|
|
94
|
+
* Array of affiliations that are allowed in members only room.
|
|
95
|
+
* @type {string[]}
|
|
96
|
+
*/
|
|
97
|
+
const MEMBERS_AFFILIATIONS = ['owner', 'admin', 'member'];
|
|
98
|
+
/**
|
|
99
|
+
* Process nodes to extract data needed for MUC_JOINED and MUC_MEMBER_JOINED events.
|
|
100
|
+
*
|
|
101
|
+
*/
|
|
102
|
+
function extractIdentityInformation(node, hiddenFromRecorderFeatureEnabled) {
|
|
103
|
+
const identity = {};
|
|
104
|
+
const userInfo = node.children.find(c => c.tagName === 'user');
|
|
105
|
+
if (userInfo) {
|
|
106
|
+
identity.user = {};
|
|
107
|
+
const tags = ['id', 'name', 'avatar'];
|
|
108
|
+
if (hiddenFromRecorderFeatureEnabled) {
|
|
109
|
+
tags.push('hidden-from-recorder');
|
|
110
|
+
}
|
|
111
|
+
for (const tag of tags) {
|
|
112
|
+
const child = userInfo.children.find(c => c.tagName === tag);
|
|
113
|
+
if (child) {
|
|
114
|
+
identity.user[tag] = child.value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const groupInfo = node.children.find(c => c.tagName === 'group');
|
|
119
|
+
if (groupInfo) {
|
|
120
|
+
identity.group = groupInfo.value;
|
|
121
|
+
}
|
|
122
|
+
return identity;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
*
|
|
126
|
+
*/
|
|
127
|
+
export default class ChatRoom extends Listenable {
|
|
128
|
+
/* eslint-disable max-params */
|
|
129
|
+
/**
|
|
130
|
+
*
|
|
131
|
+
* @param {XmppConnection} connection - The XMPP connection instance.
|
|
132
|
+
* @param jid
|
|
133
|
+
* @param password
|
|
134
|
+
* @param XMPP
|
|
135
|
+
* @param options
|
|
136
|
+
* @param {boolean} options.disableFocus - when set to {@code false} will
|
|
137
|
+
* not invite Jicofo into the room.
|
|
138
|
+
* @param {boolean} options.disableDiscoInfo - when set to {@code false} will skip disco info.
|
|
139
|
+
* This is intended to be used only for lobby rooms.
|
|
140
|
+
* @param {boolean} options.enableLobby - when set to {@code false} will skip creating lobby room.
|
|
141
|
+
* @param {boolean} options.hiddenFromRecorderFeatureEnabled - when set to {@code true} we will check identity tag
|
|
142
|
+
* for node presence.
|
|
143
|
+
*/
|
|
144
|
+
constructor(connection, jid, password, xmpp, options) {
|
|
145
|
+
super();
|
|
146
|
+
this.xmpp = xmpp;
|
|
147
|
+
this.connection = connection;
|
|
148
|
+
this.roomjid = Strophe.getBareJidFromJid(jid);
|
|
149
|
+
this.myroomjid = jid;
|
|
150
|
+
this.password = password;
|
|
151
|
+
this.replaceParticipant = false;
|
|
152
|
+
logger.info(`Joining MUC as ${this.myroomjid}`);
|
|
153
|
+
this.members = {};
|
|
154
|
+
this.presMap = { nodes: [] };
|
|
155
|
+
this.presHandlers = {};
|
|
156
|
+
this._removeConnListeners = [];
|
|
157
|
+
this.joined = false;
|
|
158
|
+
this.inProgressEmitted = false;
|
|
159
|
+
this.role = null;
|
|
160
|
+
this.focusMucJid = null;
|
|
161
|
+
this.noBridgeAvailable = false;
|
|
162
|
+
this.options = options || {};
|
|
163
|
+
this.eventsForwarder = new EventEmitterForwarder(this.xmpp.moderator, this.eventEmitter);
|
|
164
|
+
this.eventsForwarder.forward(AuthenticationEvents.IDENTITY_UPDATED, AuthenticationEvents.IDENTITY_UPDATED);
|
|
165
|
+
this.eventsForwarder.forward(XMPPEvents.AUTHENTICATION_REQUIRED, XMPPEvents.AUTHENTICATION_REQUIRED);
|
|
166
|
+
this.eventsForwarder.forward(XMPPEvents.FOCUS_DISCONNECTED, XMPPEvents.FOCUS_DISCONNECTED);
|
|
167
|
+
this.eventsForwarder.forward(XMPPEvents.RESERVATION_ERROR, XMPPEvents.RESERVATION_ERROR);
|
|
168
|
+
if (typeof this.options.enableLobby === 'undefined' || this.options.enableLobby) {
|
|
169
|
+
this.lobby = new Lobby(this);
|
|
170
|
+
}
|
|
171
|
+
this.avModeration = new AVModeration(this);
|
|
172
|
+
this.breakoutRooms = new BreakoutRooms(this);
|
|
173
|
+
this.fileSharing = new FileSharing(this);
|
|
174
|
+
this.polls = new Polls(this);
|
|
175
|
+
this.roomMetadata = new RoomMetadata(this);
|
|
176
|
+
this.initPresenceMap(options);
|
|
177
|
+
this.lastPresences = {};
|
|
178
|
+
this.phoneNumber = null;
|
|
179
|
+
this.phonePin = null;
|
|
180
|
+
this.connectionTimes = {};
|
|
181
|
+
this.participantPropertyListener = null;
|
|
182
|
+
this.locked = false;
|
|
183
|
+
this.transcriptionStatus = JitsiTranscriptionStatus.OFF;
|
|
184
|
+
this.initialDiscoRoomInfoReceived = false;
|
|
185
|
+
}
|
|
186
|
+
/* eslint-enable max-params */
|
|
187
|
+
/**
|
|
188
|
+
*
|
|
189
|
+
*/
|
|
190
|
+
initPresenceMap(options = {}) {
|
|
191
|
+
this.presMap.to = this.myroomjid;
|
|
192
|
+
this.presMap.xns = 'http://jabber.org/protocol/muc';
|
|
193
|
+
this.presMap.nodes = [];
|
|
194
|
+
if (options.statsId) {
|
|
195
|
+
this.presMap.nodes.push({
|
|
196
|
+
'tagName': 'stats-id',
|
|
197
|
+
'value': options.statsId
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
this.presenceUpdateTime = Date.now();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Extracts the features from the presence.
|
|
204
|
+
* @param node the node to process.
|
|
205
|
+
* @return features the Set of features where extracted data is added.
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
_extractFeatures(node) {
|
|
209
|
+
const features = new Set();
|
|
210
|
+
for (let j = 0; j < node.children.length; j++) {
|
|
211
|
+
const { attributes } = node.children[j];
|
|
212
|
+
if (attributes?.var) {
|
|
213
|
+
features.add(attributes.var);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return features;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Initialize some properties when the focus participant is verified.
|
|
220
|
+
* @param from jid of the focus
|
|
221
|
+
* @param features the features reported in jicofo presence
|
|
222
|
+
*/
|
|
223
|
+
_initFocus(from, features) {
|
|
224
|
+
this.focusMucJid = from;
|
|
225
|
+
this.focusFeatures = features;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Handles Xmpp Connection status updates.
|
|
229
|
+
*
|
|
230
|
+
* @param {Strophe.Status} status - The Strophe connection status.
|
|
231
|
+
*/
|
|
232
|
+
_onConnStatusChanged(status) {
|
|
233
|
+
// Send cached presence when the XMPP connection is re-established, only if needed
|
|
234
|
+
if (status === XmppConnection.Status.CONNECTED && this.presenceUpdateTime > this.presenceSyncTime) {
|
|
235
|
+
this.sendPresence();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
*
|
|
240
|
+
* @param node
|
|
241
|
+
* @param from
|
|
242
|
+
*/
|
|
243
|
+
_processNode(node, from) {
|
|
244
|
+
// make sure we catch all errors coming from any handler
|
|
245
|
+
// otherwise we can remove the presence handler from strophe
|
|
246
|
+
try {
|
|
247
|
+
const tagHandlers = this.presHandlers[node.tagName] ?? [];
|
|
248
|
+
tagHandlers.forEach(handler => {
|
|
249
|
+
handler(node, Strophe.getResourceFromJid(from), from);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
logger.error(`Error processing:${node.tagName} node.`, e);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Clean any listeners or resources, executed on leaving.
|
|
258
|
+
*/
|
|
259
|
+
clean() {
|
|
260
|
+
this._removeConnListeners.forEach(remove => remove());
|
|
261
|
+
this._removeConnListeners = [];
|
|
262
|
+
this.eventsForwarder.removeListeners(AuthenticationEvents.IDENTITY_UPDATED, XMPPEvents.AUTHENTICATION_REQUIRED, XMPPEvents.FOCUS_DISCONNECTED, XMPPEvents.RESERVATION_ERROR);
|
|
263
|
+
this.joined = false;
|
|
264
|
+
this.inProgressEmitted = false;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Parses XEP-0461 reply message information from a message stanza.
|
|
268
|
+
* @param {Element} msg - The message stanza.
|
|
269
|
+
* @returns {string|null} The ID of the message being replied to, or null if not a reply.
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
_parseReplyMessage(msg) {
|
|
273
|
+
return getAttribute(findFirst(msg, ':scope>reply'), 'to');
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Joins the chat room.
|
|
277
|
+
* @param {string} password - Password to unlock room on joining.
|
|
278
|
+
* @returns {Promise} - resolved when join completes. At the time of this
|
|
279
|
+
* writing it's never rejected.
|
|
280
|
+
*/
|
|
281
|
+
join(password, replaceParticipant) {
|
|
282
|
+
this.password = password;
|
|
283
|
+
this.replaceParticipant = replaceParticipant;
|
|
284
|
+
return new Promise(resolve => {
|
|
285
|
+
this.options.disableFocus
|
|
286
|
+
&& logger.info(`Conference focus disabled for ${this.roomjid}`);
|
|
287
|
+
const preJoin = this.options.disableFocus
|
|
288
|
+
? Promise.resolve()
|
|
289
|
+
.finally(() => {
|
|
290
|
+
this.xmpp.connection._breakoutMovingToMain = undefined;
|
|
291
|
+
})
|
|
292
|
+
: this.xmpp.moderator.sendConferenceRequest(this.roomjid);
|
|
293
|
+
preJoin.then(() => {
|
|
294
|
+
this.sendPresence(true);
|
|
295
|
+
this._removeConnListeners.push(this.connection.addCancellableListener(XmppConnection.Events.CONN_STATUS_CHANGED, this._onConnStatusChanged.bind(this)));
|
|
296
|
+
resolve();
|
|
297
|
+
})
|
|
298
|
+
.catch(e => logger.trace('PreJoin rejected', e));
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
*
|
|
303
|
+
* @param fromJoin - Whether this is initial presence to join the room.
|
|
304
|
+
*/
|
|
305
|
+
sendPresence(fromJoin = false) {
|
|
306
|
+
const to = this.presMap.to;
|
|
307
|
+
if (!this.connection || !this.connection.connected || !to || (!this.joined && !fromJoin)) {
|
|
308
|
+
// Too early to send presence - not initialized
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const pres = $pres({ to });
|
|
312
|
+
// xep-0045 defines: "including in the initial presence stanza an empty
|
|
313
|
+
// <x/> element qualified by the 'http://jabber.org/protocol/muc'
|
|
314
|
+
// namespace" and subsequent presences should not include that or it can
|
|
315
|
+
// be considered as joining, and server can send us the message history
|
|
316
|
+
// for the room on every presence
|
|
317
|
+
if (fromJoin) {
|
|
318
|
+
if (this.replaceParticipant) {
|
|
319
|
+
pres.c('flip_device').up();
|
|
320
|
+
}
|
|
321
|
+
pres.c('x', { xmlns: this.presMap.xns });
|
|
322
|
+
if (this.password) {
|
|
323
|
+
pres.c('password').t(this.password).up();
|
|
324
|
+
}
|
|
325
|
+
// send the machineId with the initial presence
|
|
326
|
+
if (this.xmpp.moderator.targetUrl) {
|
|
327
|
+
pres.c('billingid').t(Settings.machineId).up();
|
|
328
|
+
}
|
|
329
|
+
pres.up();
|
|
330
|
+
}
|
|
331
|
+
parser.json2packet(this.presMap.nodes, pres);
|
|
332
|
+
// we store time we last synced presence state
|
|
333
|
+
this.presenceSyncTime = Date.now();
|
|
334
|
+
this.connection.send(pres);
|
|
335
|
+
if (fromJoin) {
|
|
336
|
+
// XXX We're pressed for time here because we're beginning a complex
|
|
337
|
+
// and/or lengthy conference-establishment process which supposedly
|
|
338
|
+
// involves multiple RTTs. We don't have the time to wait for
|
|
339
|
+
// Strophe to decide to send our IQ.
|
|
340
|
+
this.connection.flush();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Sends the presence unavailable, signaling the server
|
|
345
|
+
* we want to leave the room.
|
|
346
|
+
*/
|
|
347
|
+
doLeave(reason) {
|
|
348
|
+
logger.info('do leave', this.myroomjid);
|
|
349
|
+
const pres = $pres({
|
|
350
|
+
to: this.myroomjid,
|
|
351
|
+
type: 'unavailable'
|
|
352
|
+
});
|
|
353
|
+
if (reason) {
|
|
354
|
+
pres.c('status').t(reason).up();
|
|
355
|
+
}
|
|
356
|
+
this.presMap.length = 0;
|
|
357
|
+
// XXX Strophe is asynchronously sending by default. Unfortunately, that
|
|
358
|
+
// means that there may not be enough time to send the unavailable
|
|
359
|
+
// presence. Switching Strophe to synchronous sending is not much of an
|
|
360
|
+
// option because it may lead to a noticeable delay in navigating away
|
|
361
|
+
// from the current location. As a compromise, we will try to increase
|
|
362
|
+
// the chances of sending the unavailable presence within the short time
|
|
363
|
+
// span that we have upon unloading by invoking flush() on the
|
|
364
|
+
// connection. We flush() once before sending/queuing the unavailable
|
|
365
|
+
// presence in order to attemtp to have the unavailable presence at the
|
|
366
|
+
// top of the send queue. We flush() once more after sending/queuing the
|
|
367
|
+
// unavailable presence in order to attempt to have it sent as soon as
|
|
368
|
+
// possible.
|
|
369
|
+
// FIXME do not use Strophe.Connection in the ChatRoom directly
|
|
370
|
+
!this.connection.isUsingWebSocket && this.connection.flush();
|
|
371
|
+
this.connection.send(pres);
|
|
372
|
+
this.connection.flush();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
*
|
|
376
|
+
*/
|
|
377
|
+
discoRoomInfo() {
|
|
378
|
+
// https://xmpp.org/extensions/xep-0045.html#disco-roominfo
|
|
379
|
+
const getInfo = $iq({
|
|
380
|
+
to: this.roomjid,
|
|
381
|
+
type: 'get'
|
|
382
|
+
})
|
|
383
|
+
.c('query', { xmlns: Strophe.NS.DISCO_INFO });
|
|
384
|
+
this.connection.sendIQ(getInfo, result => {
|
|
385
|
+
const locked = findAll(result, ':scope>query>feature[var="muc_passwordprotected"]').length === 1;
|
|
386
|
+
if (locked !== this.locked) {
|
|
387
|
+
this.eventEmitter.emit(XMPPEvents.MUC_LOCK_CHANGED, locked);
|
|
388
|
+
this.locked = locked;
|
|
389
|
+
}
|
|
390
|
+
const meetingIdValEl = findFirst(result, ':scope>query>x[type="result"]>field[var="muc#roominfo_meetingId"]>value');
|
|
391
|
+
if (meetingIdValEl) {
|
|
392
|
+
this.setMeetingId(getText(meetingIdValEl));
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
logger.warn('No meeting ID from backend');
|
|
396
|
+
}
|
|
397
|
+
const meetingCreatedTSValEl = findFirst(result, ':scope>query>x[type="result"]>field[var="muc#roominfo_created_timestamp"]>value');
|
|
398
|
+
if (meetingCreatedTSValEl) {
|
|
399
|
+
this.eventEmitter.emit(XMPPEvents.CONFERENCE_TIMESTAMP_RECEIVED, getText(meetingCreatedTSValEl));
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
logger.warn('No conference duration from backend');
|
|
403
|
+
}
|
|
404
|
+
const membersOnly = findAll(result, ':scope>query>feature[var="muc_membersonly"]').length === 1;
|
|
405
|
+
const lobbyRoomField = findFirst(result, ':scope>query>x[type="result"]>field[var="muc#roominfo_lobbyroom"]>value');
|
|
406
|
+
if (this.lobby) {
|
|
407
|
+
this.lobby.setLobbyRoomJid(lobbyRoomField ? getText(lobbyRoomField) : undefined);
|
|
408
|
+
}
|
|
409
|
+
const isBreakoutField = findFirst(result, ':scope>query>x[type="result"]>field[var="muc#roominfo_isbreakout"]>value');
|
|
410
|
+
const isBreakoutRoom = Boolean(getText(isBreakoutField));
|
|
411
|
+
this.breakoutRooms._setIsBreakoutRoom(isBreakoutRoom);
|
|
412
|
+
const breakoutMainRoomField = findFirst(result, ':scope>query>x[type="result"]>field[var="muc#roominfo_breakout_main_room"]>value');
|
|
413
|
+
if (breakoutMainRoomField) {
|
|
414
|
+
this.breakoutRooms._setMainRoomJid(getText(breakoutMainRoomField));
|
|
415
|
+
}
|
|
416
|
+
if (membersOnly !== this.membersOnlyEnabled) {
|
|
417
|
+
this.membersOnlyEnabled = membersOnly;
|
|
418
|
+
this.eventEmitter.emit(XMPPEvents.MUC_MEMBERS_ONLY_CHANGED, membersOnly);
|
|
419
|
+
}
|
|
420
|
+
const visitorsEnabledEl = findFirst(result, ':scope>query>x[type="result"]>field[var="muc#roominfo_visitorsEnabled"]>value');
|
|
421
|
+
const visitorsSupported = getText(visitorsEnabledEl) === '1';
|
|
422
|
+
if (visitorsSupported !== this.visitorsSupported) {
|
|
423
|
+
this.visitorsSupported = visitorsSupported;
|
|
424
|
+
this.eventEmitter.emit(XMPPEvents.MUC_VISITORS_SUPPORTED_CHANGED, visitorsSupported);
|
|
425
|
+
}
|
|
426
|
+
this.initialDiscoRoomInfoReceived = true;
|
|
427
|
+
this.eventEmitter.emit(XMPPEvents.ROOM_DISCO_INFO_UPDATED);
|
|
428
|
+
}, error => {
|
|
429
|
+
handleStropheError(error, {
|
|
430
|
+
operation: 'get room disco info',
|
|
431
|
+
roomJid: this.roomjid,
|
|
432
|
+
userJid: this.connection.jid
|
|
433
|
+
});
|
|
434
|
+
this.eventEmitter.emit(XMPPEvents.ROOM_DISCO_INFO_FAILED, error);
|
|
435
|
+
}, IQ_TIMEOUT);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Sets the meeting unique Id (received from the backend).
|
|
439
|
+
*
|
|
440
|
+
* @param {string} meetingId - The new meetings id.
|
|
441
|
+
* @returns {void}
|
|
442
|
+
*/
|
|
443
|
+
setMeetingId(meetingId) {
|
|
444
|
+
if (this.meetingId !== meetingId) {
|
|
445
|
+
if (this.meetingId) {
|
|
446
|
+
logger.warn(`Meeting Id changed from:${this.meetingId} to:${meetingId}`);
|
|
447
|
+
}
|
|
448
|
+
this.meetingId = meetingId;
|
|
449
|
+
this.eventEmitter.emit(XMPPEvents.MEETING_ID_SET, meetingId);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
*
|
|
454
|
+
*/
|
|
455
|
+
createNonAnonymousRoom() {
|
|
456
|
+
// http://xmpp.org/extensions/xep-0045.html#createroom-reserved
|
|
457
|
+
if (this.options.disableDiscoInfo) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const getForm = $iq({ to: this.roomjid,
|
|
461
|
+
type: 'get' })
|
|
462
|
+
.c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' })
|
|
463
|
+
.c('x', { type: 'submit',
|
|
464
|
+
xmlns: 'jabber:x:data' });
|
|
465
|
+
this.connection.sendIQ(getForm, form => {
|
|
466
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
467
|
+
if (!exists(form, ':scope>query>x[*|xmlns="jabber:x:data"]>field[var="muc#roomconfig_whois"]')) {
|
|
468
|
+
logger.error('non-anonymous rooms not supported');
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const formSubmit = $iq({ to: this.roomjid,
|
|
472
|
+
type: 'set' })
|
|
473
|
+
.c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' });
|
|
474
|
+
formSubmit.c('x', { type: 'submit',
|
|
475
|
+
xmlns: 'jabber:x:data' });
|
|
476
|
+
formSubmit.c('field', { 'var': 'FORM_TYPE' })
|
|
477
|
+
.c('value')
|
|
478
|
+
.t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
|
479
|
+
formSubmit.c('field', { 'var': 'muc#roomconfig_whois' })
|
|
480
|
+
.c('value').t('anyone').up().up();
|
|
481
|
+
this.connection.sendIQ(formSubmit, undefined, error => {
|
|
482
|
+
handleStropheError(error, {
|
|
483
|
+
operation: 'submit room configuration form',
|
|
484
|
+
roomJid: this.roomjid,
|
|
485
|
+
userJid: this.connection.jid
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
}, error => {
|
|
489
|
+
handleStropheError(error, {
|
|
490
|
+
operation: 'get room configuration form',
|
|
491
|
+
roomJid: this.roomjid,
|
|
492
|
+
userJid: this.connection.jid
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
*
|
|
498
|
+
* @param pres
|
|
499
|
+
* @internal
|
|
500
|
+
*/
|
|
501
|
+
onPresence(pres) {
|
|
502
|
+
const from = pres.getAttribute('from');
|
|
503
|
+
const member = {};
|
|
504
|
+
const statusEl = pres.getElementsByTagName('status')[0];
|
|
505
|
+
if (statusEl) {
|
|
506
|
+
member.status = statusEl.textContent || '';
|
|
507
|
+
}
|
|
508
|
+
let hasStatusUpdate = false;
|
|
509
|
+
let hasVersionUpdate = false;
|
|
510
|
+
const xElement = pres.getElementsByTagNameNS('http://jabber.org/protocol/muc#user', 'x')[0];
|
|
511
|
+
const mucUserItem = xElement?.getElementsByTagName('item')[0];
|
|
512
|
+
member.isReplaceParticipant
|
|
513
|
+
= pres.getElementsByTagName('flip_device').length;
|
|
514
|
+
member.affiliation
|
|
515
|
+
= mucUserItem?.getAttribute('affiliation');
|
|
516
|
+
member.role = mucUserItem?.getAttribute('role');
|
|
517
|
+
// Focus recognition
|
|
518
|
+
const jid = mucUserItem?.getAttribute('jid');
|
|
519
|
+
member.jid = jid;
|
|
520
|
+
member.isFocus = this.xmpp.moderator.isFocusJid(jid);
|
|
521
|
+
member.isHiddenDomain
|
|
522
|
+
= jid?.indexOf('@') > 0
|
|
523
|
+
&& this.options.hiddenDomain
|
|
524
|
+
=== jid.substring(jid.indexOf('@') + 1, jid.indexOf('/'));
|
|
525
|
+
this.eventEmitter.emit(XMPPEvents.PRESENCE_RECEIVED, {
|
|
526
|
+
fromHiddenDomain: member.isHiddenDomain,
|
|
527
|
+
presence: pres
|
|
528
|
+
});
|
|
529
|
+
const xEl = pres.querySelector('x');
|
|
530
|
+
if (xEl) {
|
|
531
|
+
xEl.remove();
|
|
532
|
+
}
|
|
533
|
+
const nodes = [];
|
|
534
|
+
parser.packet2JSON(pres, nodes);
|
|
535
|
+
this.lastPresences[from] = nodes;
|
|
536
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
537
|
+
const node = nodes[i];
|
|
538
|
+
switch (node.tagName) {
|
|
539
|
+
case 'bot': {
|
|
540
|
+
const { attributes } = node;
|
|
541
|
+
if (!attributes) {
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
const { type } = attributes;
|
|
545
|
+
member.botType = type;
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case 'nick':
|
|
549
|
+
member.nick = node.value;
|
|
550
|
+
break;
|
|
551
|
+
case 'silent':
|
|
552
|
+
member.isSilent = Boolean(node.value);
|
|
553
|
+
break;
|
|
554
|
+
case 'userId':
|
|
555
|
+
member.id = node.value;
|
|
556
|
+
break;
|
|
557
|
+
case 'stats-id':
|
|
558
|
+
member.statsID = node.value;
|
|
559
|
+
break;
|
|
560
|
+
case 'identity':
|
|
561
|
+
member.identity = extractIdentityInformation(node, this.options.hiddenFromRecorderFeatureEnabled);
|
|
562
|
+
break;
|
|
563
|
+
case 'features': {
|
|
564
|
+
member.features = this._extractFeatures(node);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case 'jitsi_participant_region': {
|
|
568
|
+
member.region = node.value;
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
case 'stat': {
|
|
572
|
+
const { attributes } = node;
|
|
573
|
+
if (!attributes) {
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
const { name } = attributes;
|
|
577
|
+
if (name === 'version') {
|
|
578
|
+
member.version = attributes.value;
|
|
579
|
+
}
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (!this.joined && !this.inProgressEmitted) {
|
|
585
|
+
const now = this.connectionTimes['muc.join.started'] = window.performance.now();
|
|
586
|
+
logger.info('(TIME) MUC join started:\t', now);
|
|
587
|
+
this.eventEmitter.emit(XMPPEvents.MUC_JOIN_IN_PROGRESS);
|
|
588
|
+
this.inProgressEmitted = true;
|
|
589
|
+
}
|
|
590
|
+
if (from === this.myroomjid) {
|
|
591
|
+
const newRole = member.role || 'none';
|
|
592
|
+
if (!this.joined) {
|
|
593
|
+
this.joined = true;
|
|
594
|
+
const now = this.connectionTimes['muc.joined']
|
|
595
|
+
= window.performance.now();
|
|
596
|
+
logger.info('(TIME) MUC joined:\t', now);
|
|
597
|
+
// set correct initial state of locked
|
|
598
|
+
if (this.password) {
|
|
599
|
+
this.locked = true;
|
|
600
|
+
}
|
|
601
|
+
if (member.region && this.options?.deploymentInfo) {
|
|
602
|
+
this.options.deploymentInfo.userRegion = member.region;
|
|
603
|
+
}
|
|
604
|
+
// Re-send presence in case any presence updates were added,
|
|
605
|
+
// but blocked from sending, during the join process.
|
|
606
|
+
// send the presence only if there was a modification after we had synced it
|
|
607
|
+
if (this.presenceUpdateTime > this.presenceSyncTime) {
|
|
608
|
+
this.sendPresence();
|
|
609
|
+
}
|
|
610
|
+
// we need to reset it because of breakout rooms which will reuse connection but will invite jicofo
|
|
611
|
+
this.xmpp.moderator.conferenceRequestSent = false;
|
|
612
|
+
this.eventEmitter.emit(XMPPEvents.MUC_JOINED);
|
|
613
|
+
// Now let's check the disco-info to retrieve the
|
|
614
|
+
// meeting Id if any
|
|
615
|
+
!this.options.disableDiscoInfo && this.discoRoomInfo();
|
|
616
|
+
}
|
|
617
|
+
if (this.role !== newRole) {
|
|
618
|
+
this.role = newRole;
|
|
619
|
+
this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, this.role);
|
|
620
|
+
}
|
|
621
|
+
if (exists(xElement, ':scope>status[code="110"]')) {
|
|
622
|
+
// let's check for some backend forced permissions
|
|
623
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
624
|
+
const permissionEl = findFirst(pres, ':scope>permissions[*|xmlns="http://jitsi.org/jitmeet"]');
|
|
625
|
+
if (permissionEl) {
|
|
626
|
+
const permissionsMap = {};
|
|
627
|
+
findAll(permissionEl, 'p').forEach(p => {
|
|
628
|
+
permissionsMap[getAttribute(p, 'name')] = getAttribute(p, 'val');
|
|
629
|
+
});
|
|
630
|
+
this.eventEmitter.emit(XMPPEvents.PERMISSIONS_RECEIVED, permissionsMap);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
else if (jid === undefined) {
|
|
635
|
+
logger.info('Ignoring member with undefined JID');
|
|
636
|
+
}
|
|
637
|
+
else if (this.members[from] === undefined) {
|
|
638
|
+
// new participant
|
|
639
|
+
this.members[from] = member;
|
|
640
|
+
logger.info('entered', from);
|
|
641
|
+
hasStatusUpdate = member.status !== undefined;
|
|
642
|
+
hasVersionUpdate = member.version !== undefined;
|
|
643
|
+
if (member.isFocus) {
|
|
644
|
+
this._initFocus(from, member.features);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
// identity is being added to member joined, so external
|
|
648
|
+
// services can be notified for that (currently identity is
|
|
649
|
+
// not used inside library)
|
|
650
|
+
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, member.nick, member.role, member.isHiddenDomain, member.statsID, member.status, member.identity, member.botType, member.jid, member.features, member.isReplaceParticipant, member.isSilent);
|
|
651
|
+
// we are reporting the status with the join
|
|
652
|
+
// so we do not want a second event about status update
|
|
653
|
+
hasStatusUpdate = false;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
// Presence update for existing participant
|
|
658
|
+
// Watch role change:
|
|
659
|
+
const memberOfThis = this.members[from];
|
|
660
|
+
if (memberOfThis.role !== member.role) {
|
|
661
|
+
memberOfThis.role = member.role;
|
|
662
|
+
this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED, from, member.role);
|
|
663
|
+
}
|
|
664
|
+
// affiliation changed
|
|
665
|
+
if (memberOfThis.affiliation !== member.affiliation) {
|
|
666
|
+
memberOfThis.affiliation = member.affiliation;
|
|
667
|
+
}
|
|
668
|
+
// fire event that botType had changed
|
|
669
|
+
if (memberOfThis.botType !== member.botType) {
|
|
670
|
+
memberOfThis.botType = member.botType;
|
|
671
|
+
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_BOT_TYPE_CHANGED, from, member.botType);
|
|
672
|
+
}
|
|
673
|
+
if (member.isFocus) {
|
|
674
|
+
// From time to time first few presences of the focus are not
|
|
675
|
+
// containing it's jid. That way we can mark later the focus
|
|
676
|
+
// member instead of not marking it at all and not starting the
|
|
677
|
+
// conference.
|
|
678
|
+
// FIXME: Maybe there is a better way to handle this issue. It
|
|
679
|
+
// seems there is some period of time in prosody that the
|
|
680
|
+
// configuration form is received but not applied. And if any
|
|
681
|
+
// participant joins during that period of time the first
|
|
682
|
+
// presence from the focus won't contain
|
|
683
|
+
// <item jid="focus..." />.
|
|
684
|
+
// By default we are disabling the waiting for form submission in order to use the room
|
|
685
|
+
// and we had enabled by default that jids are public in the room ,
|
|
686
|
+
// so this case should not happen, if public jid is turned off we will receive the jid
|
|
687
|
+
// when we become moderator in the room
|
|
688
|
+
memberOfThis.isFocus = true;
|
|
689
|
+
this._initFocus(from, member.features);
|
|
690
|
+
}
|
|
691
|
+
// store the new display name
|
|
692
|
+
if (member.displayName) {
|
|
693
|
+
memberOfThis.displayName = member.displayName;
|
|
694
|
+
}
|
|
695
|
+
// join without audio
|
|
696
|
+
if (member.isSilent) {
|
|
697
|
+
memberOfThis.isSilent = member.isSilent;
|
|
698
|
+
}
|
|
699
|
+
// update stored status message to be able to detect changes
|
|
700
|
+
if (memberOfThis.status !== member.status) {
|
|
701
|
+
hasStatusUpdate = true;
|
|
702
|
+
memberOfThis.status = member.status;
|
|
703
|
+
}
|
|
704
|
+
if (memberOfThis.version !== member.version) {
|
|
705
|
+
hasVersionUpdate = true;
|
|
706
|
+
memberOfThis.version = member.version;
|
|
707
|
+
}
|
|
708
|
+
if (!isEqual(memberOfThis.features, member.features)) {
|
|
709
|
+
memberOfThis.features = member.features;
|
|
710
|
+
this.eventEmitter.emit(XMPPEvents.PARTICIPANT_FEATURES_CHANGED, from, member.features);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const participantProperties = new Map();
|
|
714
|
+
// after we had fired member or room joined events, lets fire events
|
|
715
|
+
// for the rest info we got in presence
|
|
716
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
717
|
+
const node = nodes[i];
|
|
718
|
+
switch (node.tagName) {
|
|
719
|
+
case 'nick':
|
|
720
|
+
if (!member.isFocus) {
|
|
721
|
+
const displayName
|
|
722
|
+
// @ts-ignore will fix after xmpp PR merge
|
|
723
|
+
= this.xmpp.options.displayJids
|
|
724
|
+
? Strophe.getResourceFromJid(from)
|
|
725
|
+
: member.nick;
|
|
726
|
+
this.eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
|
|
727
|
+
}
|
|
728
|
+
break;
|
|
729
|
+
case 'silent':
|
|
730
|
+
this.eventEmitter.emit(XMPPEvents.SILENT_STATUS_CHANGED, from, member.isSilent);
|
|
731
|
+
break;
|
|
732
|
+
case 'bridgeNotAvailable':
|
|
733
|
+
if (member.isFocus && !this.noBridgeAvailable) {
|
|
734
|
+
this.noBridgeAvailable = true;
|
|
735
|
+
this.eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
|
|
736
|
+
}
|
|
737
|
+
break;
|
|
738
|
+
case 'conference-properties':
|
|
739
|
+
if (member.isFocus) {
|
|
740
|
+
const properties = {};
|
|
741
|
+
for (let j = 0; j < node.children.length; j++) {
|
|
742
|
+
const { attributes } = node.children[j];
|
|
743
|
+
if (attributes?.key) {
|
|
744
|
+
properties[attributes.key] = attributes.value;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
this.eventEmitter.emit(XMPPEvents.CONFERENCE_PROPERTIES_CHANGED, properties);
|
|
748
|
+
}
|
|
749
|
+
break;
|
|
750
|
+
case 'transcription-status': {
|
|
751
|
+
const { attributes } = node;
|
|
752
|
+
if (!attributes) {
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
const { status } = attributes;
|
|
756
|
+
if (status && status !== this.transcriptionStatus
|
|
757
|
+
&& member.isHiddenDomain && member.features.has(FEATURE_TRANSCRIBER)) {
|
|
758
|
+
this.transcriptionStatus = status;
|
|
759
|
+
this.eventEmitter.emit(XMPPEvents.TRANSCRIPTION_STATUS_CHANGED, status, Strophe.getResourceFromJid(from));
|
|
760
|
+
}
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
case 'call-control': {
|
|
764
|
+
const att = node.attributes;
|
|
765
|
+
if (!att) {
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
this.phoneNumber = att.phone || null;
|
|
769
|
+
this.phonePin = att.pin || null;
|
|
770
|
+
this.eventEmitter.emit(XMPPEvents.PHONE_NUMBER_CHANGED);
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
default: {
|
|
774
|
+
if (node.tagName.startsWith('jitsi_participant_')) {
|
|
775
|
+
participantProperties
|
|
776
|
+
.set(node.tagName.substring('jitsi_participant_'.length), node.value);
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
this._processNode(node, from);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
// All participant properties are in `participantProperties`, call the event handlers now.
|
|
785
|
+
const participantId = Strophe.getResourceFromJid(from);
|
|
786
|
+
for (const [key, value] of participantProperties) {
|
|
787
|
+
this.participantPropertyListener(participantId, key, value);
|
|
788
|
+
}
|
|
789
|
+
// Trigger status message update if necessary
|
|
790
|
+
if (hasStatusUpdate) {
|
|
791
|
+
this.eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member.status);
|
|
792
|
+
}
|
|
793
|
+
if (hasVersionUpdate) {
|
|
794
|
+
logger.info(`Received version for ${jid}: ${member.version}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Sets the special listener to be used for "command"s whose name starts
|
|
799
|
+
* with "jitsi_participant_".
|
|
800
|
+
*/
|
|
801
|
+
setParticipantPropertyListener(listener) {
|
|
802
|
+
this.participantPropertyListener = listener;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Send text message to the other participants in the conference
|
|
806
|
+
* @param message
|
|
807
|
+
* @param elementName
|
|
808
|
+
* @param replyToId
|
|
809
|
+
*/
|
|
810
|
+
sendMessage(message, elementName, replyToId) {
|
|
811
|
+
const msg = $msg({
|
|
812
|
+
to: this.roomjid,
|
|
813
|
+
type: 'groupchat'
|
|
814
|
+
});
|
|
815
|
+
// We are adding the message in a packet extension. If this element
|
|
816
|
+
// is different from 'body', we add a custom namespace.
|
|
817
|
+
// e.g. for 'json-message' extension of message stanza.
|
|
818
|
+
if (elementName === 'body') {
|
|
819
|
+
msg.c(elementName, {}, message);
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
msg.c(elementName, { xmlns: 'http://jitsi.org/jitmeet' }, message);
|
|
823
|
+
}
|
|
824
|
+
if (replyToId) {
|
|
825
|
+
msg.up().c('reply', { to: replyToId });
|
|
826
|
+
}
|
|
827
|
+
this.connection.send(msg);
|
|
828
|
+
this.eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, message);
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Sends a reaction message to the other participants in the conference.
|
|
832
|
+
* @param {string} reaction - The reaction being sent.
|
|
833
|
+
* @param {string} messageId - The id of the message being sent.
|
|
834
|
+
* @param {string} receiverId - The receiver of the message if it is private.
|
|
835
|
+
*/
|
|
836
|
+
sendReaction(reaction, messageId, receiverId) {
|
|
837
|
+
const m = reaction.match(EMOJI_REGEX);
|
|
838
|
+
if (!m?.[0]) {
|
|
839
|
+
throw new Error(`Invalid reaction: ${reaction}`);
|
|
840
|
+
}
|
|
841
|
+
// Adds the 'to' attribute depending on if the message is private or not.
|
|
842
|
+
const msg = receiverId ? $msg({ to: `${this.roomjid}/${receiverId}`,
|
|
843
|
+
type: 'chat' }) : $msg({ to: this.roomjid,
|
|
844
|
+
type: 'groupchat' });
|
|
845
|
+
msg.c('reactions', { id: messageId,
|
|
846
|
+
xmlns: 'urn:xmpp:reactions:0' })
|
|
847
|
+
.c('reaction', {}, m[0])
|
|
848
|
+
.up().c('store', { xmlns: 'urn:xmpp:hints' });
|
|
849
|
+
this.connection.send(msg);
|
|
850
|
+
}
|
|
851
|
+
/* eslint-disable max-params */
|
|
852
|
+
/**
|
|
853
|
+
* Send private text message to another participant of the conference
|
|
854
|
+
* @param id id/muc resource of the receiver
|
|
855
|
+
* @param message
|
|
856
|
+
* @param elementName
|
|
857
|
+
* @param useDirectJid
|
|
858
|
+
* @param replyToId
|
|
859
|
+
*/
|
|
860
|
+
sendPrivateMessage(id, message, elementName, useDirectJid = false, replyToId) {
|
|
861
|
+
const targetJid = useDirectJid ? id : `${this.roomjid}/${id}`;
|
|
862
|
+
const msg = $msg({ to: targetJid,
|
|
863
|
+
type: 'chat' });
|
|
864
|
+
// We are adding the message in packet. If this element is different
|
|
865
|
+
// from 'body', we add our custom namespace for the same.
|
|
866
|
+
// e.g. for 'json-message' message extension.
|
|
867
|
+
if (elementName === 'body') {
|
|
868
|
+
msg.c(elementName, message).up();
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
msg.c(elementName, { xmlns: 'http://jitsi.org/jitmeet' }, message)
|
|
872
|
+
.up();
|
|
873
|
+
}
|
|
874
|
+
if (replyToId) {
|
|
875
|
+
msg.c('reply', { to: replyToId });
|
|
876
|
+
}
|
|
877
|
+
this.connection.send(msg);
|
|
878
|
+
this.eventEmitter.emit(XMPPEvents.SENDING_PRIVATE_CHAT_MESSAGE, message);
|
|
879
|
+
}
|
|
880
|
+
/* eslint-enable max-params */
|
|
881
|
+
/**
|
|
882
|
+
*
|
|
883
|
+
* @param subject
|
|
884
|
+
*/
|
|
885
|
+
setSubject(subject) {
|
|
886
|
+
const valueToProcess = subject ? subject.trim() : subject;
|
|
887
|
+
if (valueToProcess === this.subject) {
|
|
888
|
+
// subject already set to the new value
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const msg = $msg({ to: this.roomjid,
|
|
892
|
+
type: 'groupchat' });
|
|
893
|
+
msg.c('subject', valueToProcess);
|
|
894
|
+
this.connection.send(msg);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
*
|
|
898
|
+
* @param pres
|
|
899
|
+
* @param from
|
|
900
|
+
* @internal
|
|
901
|
+
*/
|
|
902
|
+
onPresenceUnavailable(pres, from) {
|
|
903
|
+
// ignore presence
|
|
904
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
905
|
+
if (exists(pres, ':scope>ignore[*|xmlns="http://jitsi.org/jitmeet/"]')) {
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
// room destroyed ?
|
|
909
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
910
|
+
const destroySelect = findFirst(pres, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>destroy');
|
|
911
|
+
if (destroySelect) {
|
|
912
|
+
let reason;
|
|
913
|
+
const reasonSelect = findFirst(pres, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>destroy>reason');
|
|
914
|
+
if (reasonSelect) {
|
|
915
|
+
reason = getText(reasonSelect);
|
|
916
|
+
}
|
|
917
|
+
this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason, getAttribute(destroySelect, 'jid'));
|
|
918
|
+
this.connection.emuc.doLeave(this.roomjid);
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
// Status code 110 indicates that this notification is "self-presence".
|
|
922
|
+
const isSelfPresence = exists(pres, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]');
|
|
923
|
+
const isKick = exists(pres, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]');
|
|
924
|
+
const membersKeys = Object.keys(this.members);
|
|
925
|
+
const isReplaceParticipant = exists(pres, 'flip_device');
|
|
926
|
+
if (isKick) {
|
|
927
|
+
const actorSelect = findFirst(pres, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>item>actor');
|
|
928
|
+
let actorNick;
|
|
929
|
+
if (actorSelect) {
|
|
930
|
+
actorNick = getAttribute(actorSelect, 'nick');
|
|
931
|
+
}
|
|
932
|
+
let reason;
|
|
933
|
+
const reasonSelect = findFirst(pres, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>item>reason');
|
|
934
|
+
if (reasonSelect) {
|
|
935
|
+
reason = getText(reasonSelect);
|
|
936
|
+
}
|
|
937
|
+
logger.info(`Kicked by ${from}`);
|
|
938
|
+
// we first fire the kicked so we can show the participant
|
|
939
|
+
// who kicked, before notifying that participant left
|
|
940
|
+
// we fire kicked for us and for any participant kicked
|
|
941
|
+
this.eventEmitter.emit(XMPPEvents.KICKED, isSelfPresence, actorNick, Strophe.getResourceFromJid(from), reason, isReplaceParticipant);
|
|
942
|
+
}
|
|
943
|
+
if (isSelfPresence) {
|
|
944
|
+
// If the status code is 110 this means we're leaving and we would
|
|
945
|
+
// like to remove everyone else from our view, so we trigger the
|
|
946
|
+
// event.
|
|
947
|
+
membersKeys.forEach(jid => {
|
|
948
|
+
const member = this.members[jid];
|
|
949
|
+
delete this.members[jid];
|
|
950
|
+
delete this.lastPresences[jid];
|
|
951
|
+
if (!member.isFocus) {
|
|
952
|
+
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
this.connection.emuc.doLeave(this.roomjid);
|
|
956
|
+
// we fire muc_left only if this is not a kick,
|
|
957
|
+
// kick has both statuses 110 and 307.
|
|
958
|
+
if (!isKick) {
|
|
959
|
+
this.eventEmitter.emit(XMPPEvents.MUC_LEFT);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
const reasonSelect = findFirst(pres, ':scope>status');
|
|
964
|
+
const member = this.members[from];
|
|
965
|
+
let reason;
|
|
966
|
+
if (reasonSelect) {
|
|
967
|
+
reason = getText(reasonSelect);
|
|
968
|
+
}
|
|
969
|
+
delete this.members[from];
|
|
970
|
+
delete this.lastPresences[from];
|
|
971
|
+
// In this case we *do* fire MUC_MEMBER_LEFT for the focus?
|
|
972
|
+
this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, from, reason);
|
|
973
|
+
if (member && member.isHiddenDomain && member.features.has(FEATURE_TRANSCRIBER)
|
|
974
|
+
&& this.transcriptionStatus !== JitsiTranscriptionStatus.OFF) {
|
|
975
|
+
this.transcriptionStatus = JitsiTranscriptionStatus.OFF;
|
|
976
|
+
this.eventEmitter.emit(XMPPEvents.TRANSCRIPTION_STATUS_CHANGED, this.transcriptionStatus, Strophe.getResourceFromJid(from), true /* exited abruptly */);
|
|
977
|
+
}
|
|
978
|
+
if (member?.isFocus) {
|
|
979
|
+
logger.info('Focus has left the room - leaving conference');
|
|
980
|
+
this.eventEmitter.emit(XMPPEvents.FOCUS_LEFT);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
*
|
|
986
|
+
* @param msg
|
|
987
|
+
* @param from
|
|
988
|
+
* @internal
|
|
989
|
+
*/
|
|
990
|
+
onMessage(msg, from) {
|
|
991
|
+
const type = msg.getAttribute('type');
|
|
992
|
+
if (type === 'error') {
|
|
993
|
+
const settingsErrorEl = findAll(msg, ':scope>settings-error>text');
|
|
994
|
+
const settingsErrorMsg = settingsErrorEl.length ? getText(settingsErrorEl[0]) : '';
|
|
995
|
+
if (settingsErrorMsg.length) {
|
|
996
|
+
this.eventEmitter.emit(XMPPEvents.SETTINGS_ERROR_RECEIVED, settingsErrorMsg);
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
const errorEl = findAll(msg, ':scope>error>text');
|
|
1000
|
+
const errorMsg = errorEl.length ? getText(errorEl[0]) : '';
|
|
1001
|
+
this.eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED, errorMsg);
|
|
1002
|
+
return true;
|
|
1003
|
+
}
|
|
1004
|
+
const reactions = findAll(msg, ':scope>[*|xmlns="urn:xmpp:reactions:0"]>reaction');
|
|
1005
|
+
if (reactions.length > 0) {
|
|
1006
|
+
const messageId = getAttribute(findFirst(msg, ':scope>[*|xmlns="urn:xmpp:reactions:0"]'), 'id');
|
|
1007
|
+
const reactionList = [];
|
|
1008
|
+
reactions.forEach(reactionElem => {
|
|
1009
|
+
const reaction = getText(reactionElem);
|
|
1010
|
+
const m = reaction.match(EMOJI_REGEX);
|
|
1011
|
+
// Only allow one reaction per <reaction> element.
|
|
1012
|
+
if (m?.[0]) {
|
|
1013
|
+
reactionList.push(m[0]);
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
if (reactionList.length > 0) {
|
|
1017
|
+
this.eventEmitter.emit(XMPPEvents.REACTION_RECEIVED, from, reactionList, messageId);
|
|
1018
|
+
}
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
const bodyEl = findFirst(msg, ':scope>body');
|
|
1022
|
+
const txt = bodyEl ? getText(bodyEl) : '';
|
|
1023
|
+
const subject = findFirst(msg, ':scope>subject');
|
|
1024
|
+
if (subject) {
|
|
1025
|
+
const subjectText = getText(subject);
|
|
1026
|
+
if (subjectText || subjectText === '') {
|
|
1027
|
+
this.subject = subjectText.trim();
|
|
1028
|
+
this.eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
|
|
1029
|
+
logger.info(`Subject is changed to ${subjectText}`);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
// xep-0203 delay
|
|
1033
|
+
const delayEl = findFirst(msg, ':scope>delay');
|
|
1034
|
+
let stamp = getAttribute(delayEl, 'stamp');
|
|
1035
|
+
if (!stamp) {
|
|
1036
|
+
// or xep-0091 delay, UTC timestamp
|
|
1037
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
1038
|
+
stamp = getAttribute(findFirst(msg, ':scope>[*|xmlns="jabber:x:delay"]'), 'stamp');
|
|
1039
|
+
if (stamp) {
|
|
1040
|
+
// the format is CCYYMMDDThh:mm:ss
|
|
1041
|
+
const dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
|
|
1042
|
+
stamp = `${dateParts[1]}-${dateParts[2]}-${dateParts[3]}Z`;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (from === this.roomjid) {
|
|
1046
|
+
let invite;
|
|
1047
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
1048
|
+
if (exists(msg, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>status[code="104"]')) {
|
|
1049
|
+
this.discoRoomInfo();
|
|
1050
|
+
}
|
|
1051
|
+
else if (invite = findFirst(msg, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>invite')) {
|
|
1052
|
+
const passwordSelect = findFirst(msg, ':scope>x[*|xmlns="http://jabber.org/protocol/muc#user"]>password');
|
|
1053
|
+
let password;
|
|
1054
|
+
if (passwordSelect) {
|
|
1055
|
+
password = getText(passwordSelect);
|
|
1056
|
+
}
|
|
1057
|
+
this.eventEmitter.emit(XMPPEvents.INVITE_MESSAGE_RECEIVED, from, getAttribute(invite, 'from'), txt, password);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
1061
|
+
const jsonMessage = getText(findFirst(msg, ':scope>json-message[*|xmlns="http://jitsi.org/jitmeet"]'));
|
|
1062
|
+
if (jsonMessage) {
|
|
1063
|
+
const parsedJson = this.xmpp.tryParseJSONAndVerify(jsonMessage);
|
|
1064
|
+
// We emit this event if the message is a valid json, and is not
|
|
1065
|
+
// delivered after a delay, i.e. stamp is null.
|
|
1066
|
+
// e.g. - subtitles should not be displayed if delayed.
|
|
1067
|
+
if (parsedJson && stamp === null) {
|
|
1068
|
+
this.eventEmitter.emit(XMPPEvents.JSON_MESSAGE_RECEIVED, from, parsedJson);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
if (txt) {
|
|
1073
|
+
const messageId = getAttribute(msg, 'id') || uuidv4();
|
|
1074
|
+
const replyToId = this._parseReplyMessage(msg);
|
|
1075
|
+
const displayNameEl = findFirst(msg, ':scope>display-name[*|xmlns="http://jitsi.org/protocol/display-name"]');
|
|
1076
|
+
const isVisitorMessage = getAttribute(displayNameEl, 'source') === 'visitor';
|
|
1077
|
+
if (type === 'chat') {
|
|
1078
|
+
let displayName;
|
|
1079
|
+
let originalFrom;
|
|
1080
|
+
if (isVisitorMessage) {
|
|
1081
|
+
displayName = getText(displayNameEl);
|
|
1082
|
+
// Check for original visitor JID in addresses element (XEP-0033)
|
|
1083
|
+
const addressesEl = findFirst(msg, ':scope>addresses[*|xmlns="http://jabber.org/protocol/address"]');
|
|
1084
|
+
if (addressesEl) {
|
|
1085
|
+
const ofromEl = findFirst(addressesEl, 'address[type="ofrom"]');
|
|
1086
|
+
if (ofromEl) {
|
|
1087
|
+
originalFrom = getAttribute(ofromEl, 'jid');
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
this.eventEmitter.emit(XMPPEvents.PRIVATE_MESSAGE_RECEIVED, from, txt, this.myroomjid, stamp, messageId, displayName, isVisitorMessage, originalFrom, replyToId);
|
|
1092
|
+
}
|
|
1093
|
+
else if (type === 'groupchat') {
|
|
1094
|
+
const displayName = displayNameEl ? getText(displayNameEl) : undefined;
|
|
1095
|
+
let sourceAttrValue = getAttribute(displayNameEl, 'source');
|
|
1096
|
+
if (sourceAttrValue === null) {
|
|
1097
|
+
// When displayNameEl[0] doesn't exist or there is no source attribute getAttribute returns null.
|
|
1098
|
+
// Lets unify the reported values and use undefined instead.
|
|
1099
|
+
sourceAttrValue = undefined;
|
|
1100
|
+
}
|
|
1101
|
+
const source = isVisitorMessage ? undefined : sourceAttrValue;
|
|
1102
|
+
// we will fire explicitly that this is a visitor(isVisitor:true) to the conference
|
|
1103
|
+
// a message with explicit name set
|
|
1104
|
+
this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED, from, txt, this.myroomjid, stamp, displayName, isVisitorMessage, messageId, source, replyToId);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
*
|
|
1110
|
+
* @param pres
|
|
1111
|
+
* @param from
|
|
1112
|
+
* @internal
|
|
1113
|
+
*/
|
|
1114
|
+
onPresenceError(pres, from) {
|
|
1115
|
+
let errorDescriptionNode;
|
|
1116
|
+
if (from === this.myroomjid) {
|
|
1117
|
+
// we have tried to join, and we received an error, let's send again conference-iq on next attempt
|
|
1118
|
+
// as it may turn out that jicofo left the room if we were the first to try,
|
|
1119
|
+
// and the user delayed the attempt for entering the password or such
|
|
1120
|
+
this.xmpp.moderator.conferenceRequestSent = false;
|
|
1121
|
+
}
|
|
1122
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
1123
|
+
if (exists(pres, ':scope>error[type="auth"]>not-authorized[*|xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]')) {
|
|
1124
|
+
logger.debug('on password required', from);
|
|
1125
|
+
this.eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED);
|
|
1126
|
+
}
|
|
1127
|
+
else if (exists(pres, ':scope>error[type="cancel"]>not-allowed[*|xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]')) {
|
|
1128
|
+
const toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
|
|
1129
|
+
// @ts-ignore will fix after xmpp PR merge
|
|
1130
|
+
if (toDomain === this.xmpp.options.hosts.anonymousdomain) {
|
|
1131
|
+
// enter the room by replying with 'not-authorized'. This would
|
|
1132
|
+
// result in reconnection from authorized domain.
|
|
1133
|
+
// We're either missing Jicofo/Prosody config for anonymous
|
|
1134
|
+
// domains or something is wrong.
|
|
1135
|
+
this.eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR);
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
logger.warn('onPresError ', pres);
|
|
1139
|
+
const txtNode = findFirst(pres, ':scope>error[type="cancel"]>text[*|xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]');
|
|
1140
|
+
const txt = getText(txtNode);
|
|
1141
|
+
let type = AUTH_ERROR_TYPES.GENERAL;
|
|
1142
|
+
// a race where we have sent a conference request to jicofo and jicofo was about to leave or just left
|
|
1143
|
+
// because of no participants in the room, and we tried to create the room, without having
|
|
1144
|
+
// permissions for that (only jicofo creates rooms)
|
|
1145
|
+
if (txt === 'Room creation is restricted') {
|
|
1146
|
+
type = AUTH_ERROR_TYPES.ROOM_CREATION_RESTRICTION;
|
|
1147
|
+
if (!this.options.disableRoomCreationRetry) {
|
|
1148
|
+
if (!this._roomCreationRetries) {
|
|
1149
|
+
this._roomCreationRetries = 0;
|
|
1150
|
+
}
|
|
1151
|
+
this._roomCreationRetries++;
|
|
1152
|
+
if (this._roomCreationRetries <= 3) {
|
|
1153
|
+
const retryDelay = getJitterDelay(
|
|
1154
|
+
/* retry */ this._roomCreationRetries,
|
|
1155
|
+
/* minDelay */ 500, 1.5);
|
|
1156
|
+
// let's retry inviting jicofo and joining the room, retries will take between 1 and 3 seconds
|
|
1157
|
+
setTimeout(() => this.join(this.password, this.replaceParticipant), retryDelay);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
else if (exists(pres, ':scope>error[type="cancel"]>authentication-required[*|xmlns="http://jitsi.org/jitmeet"]')) {
|
|
1163
|
+
type = AUTH_ERROR_TYPES.ROOM_UNAUTHENTICATED_ACCESS_DISABLED;
|
|
1164
|
+
}
|
|
1165
|
+
else if (exists(pres, ':scope>error[type="cancel"]>no-main-participants[*|xmlns="jitsi:visitors"]')) {
|
|
1166
|
+
type = AUTH_ERROR_TYPES.NO_MAIN_PARTICIPANTS;
|
|
1167
|
+
}
|
|
1168
|
+
else if (exists(pres, ':scope>error[type="cancel"]>promotion-not-allowed[*|xmlns="jitsi:visitors"]')) {
|
|
1169
|
+
type = AUTH_ERROR_TYPES.PROMOTION_NOT_ALLOWED;
|
|
1170
|
+
}
|
|
1171
|
+
else if (exists(pres, ':scope>error[type="cancel"]>no-visitors-lobby[*|xmlns="jitsi:visitors"]')) {
|
|
1172
|
+
type = AUTH_ERROR_TYPES.NO_VISITORS_LOBBY;
|
|
1173
|
+
}
|
|
1174
|
+
this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_NOT_ALLOWED_ERROR, type, txt);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
else if (exists(pres, ':scope>error>service-unavailable')) {
|
|
1178
|
+
logger.warn('Maximum users limit for the room has been reached', pres);
|
|
1179
|
+
this.eventEmitter.emit(XMPPEvents.ROOM_MAX_USERS_ERROR, {
|
|
1180
|
+
visitorsSupported: this.xmpp.moderator.visitorsSupported
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
else if (exists(pres, ':scope>error[type="auth"]>registration-required[*|xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]')) {
|
|
1184
|
+
// let's extract the lobby jid from the custom field
|
|
1185
|
+
const lobbyRoomNode = findFirst(pres, ':scope>error[type="auth"]>lobbyroom');
|
|
1186
|
+
let lobbyRoomJid;
|
|
1187
|
+
if (lobbyRoomNode) {
|
|
1188
|
+
lobbyRoomJid = getText(lobbyRoomNode);
|
|
1189
|
+
}
|
|
1190
|
+
const waitingForHost = exists(pres, ':scope>error[type="auth"]>waiting-for-host');
|
|
1191
|
+
this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_MEMBERS_ONLY_ERROR, lobbyRoomJid, waitingForHost);
|
|
1192
|
+
}
|
|
1193
|
+
else if (errorDescriptionNode = findFirst(pres, ':scope>error[type="modify"]>displayname-required[*|xmlns="http://jitsi.org/jitmeet"]')) {
|
|
1194
|
+
logger.warn('display name required ', pres);
|
|
1195
|
+
this.eventEmitter.emit(XMPPEvents.DISPLAY_NAME_REQUIRED, getAttribute(errorDescriptionNode, 'lobby'));
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
logger.warn('onPresError ', pres);
|
|
1199
|
+
this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
*
|
|
1204
|
+
* @param jid
|
|
1205
|
+
* @param affiliation
|
|
1206
|
+
*/
|
|
1207
|
+
setAffiliation(jid, affiliation) {
|
|
1208
|
+
const grantIQ = $iq({
|
|
1209
|
+
to: this.roomjid,
|
|
1210
|
+
type: 'set'
|
|
1211
|
+
})
|
|
1212
|
+
.c('query', { xmlns: 'http://jabber.org/protocol/muc#admin' })
|
|
1213
|
+
.c('item', {
|
|
1214
|
+
affiliation,
|
|
1215
|
+
jid: Strophe.getBareJidFromJid(jid)
|
|
1216
|
+
})
|
|
1217
|
+
.c('reason').t(`Your affiliation has been changed to '${affiliation}'.`)
|
|
1218
|
+
.up().up().up();
|
|
1219
|
+
this.connection.sendIQ(grantIQ, result => logger.info('Set affiliation of participant with jid: ', jid, 'to', affiliation, result), error => {
|
|
1220
|
+
handleStropheError(error, {
|
|
1221
|
+
affiliation,
|
|
1222
|
+
operation: 'set affiliation of participant',
|
|
1223
|
+
participantJid: jid,
|
|
1224
|
+
roomJid: this.roomjid,
|
|
1225
|
+
userJid: this.connection.jid
|
|
1226
|
+
});
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
*
|
|
1231
|
+
* @param jid
|
|
1232
|
+
* @param reason
|
|
1233
|
+
*/
|
|
1234
|
+
kick(jid, reason = 'You have been kicked.') {
|
|
1235
|
+
const kickIQ = $iq({ to: this.roomjid,
|
|
1236
|
+
type: 'set' })
|
|
1237
|
+
.c('query', { xmlns: 'http://jabber.org/protocol/muc#admin' })
|
|
1238
|
+
.c('item', { nick: Strophe.getResourceFromJid(jid),
|
|
1239
|
+
role: 'none' })
|
|
1240
|
+
.c('reason').t(reason).up().up().up();
|
|
1241
|
+
this.connection.sendIQ(kickIQ, result => logger.info('Kick participant with jid: ', jid, result), error => {
|
|
1242
|
+
handleStropheError(error, {
|
|
1243
|
+
operation: 'kick participant',
|
|
1244
|
+
participantJid: jid,
|
|
1245
|
+
reason,
|
|
1246
|
+
roomJid: this.roomjid,
|
|
1247
|
+
userJid: this.connection.jid
|
|
1248
|
+
});
|
|
1249
|
+
}, undefined);
|
|
1250
|
+
}
|
|
1251
|
+
/* eslint-disable max-params */
|
|
1252
|
+
/**
|
|
1253
|
+
*
|
|
1254
|
+
* @param key
|
|
1255
|
+
* @param onSuccess
|
|
1256
|
+
* @param onError
|
|
1257
|
+
* @param onNotSupported
|
|
1258
|
+
*/
|
|
1259
|
+
lockRoom(key, onSuccess, onError, onNotSupported) {
|
|
1260
|
+
// http://xmpp.org/extensions/xep-0045.html#roomconfig
|
|
1261
|
+
this.connection.sendIQ($iq({
|
|
1262
|
+
to: this.roomjid,
|
|
1263
|
+
type: 'get'
|
|
1264
|
+
})
|
|
1265
|
+
.c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' }), res => {
|
|
1266
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
1267
|
+
if (exists(res, ':scope>query>x[*|xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]')) {
|
|
1268
|
+
const formsubmit = $iq({
|
|
1269
|
+
to: this.roomjid,
|
|
1270
|
+
type: 'set'
|
|
1271
|
+
})
|
|
1272
|
+
.c('query', {
|
|
1273
|
+
xmlns: 'http://jabber.org/protocol/muc#owner'
|
|
1274
|
+
});
|
|
1275
|
+
formsubmit.c('x', {
|
|
1276
|
+
type: 'submit',
|
|
1277
|
+
xmlns: 'jabber:x:data'
|
|
1278
|
+
});
|
|
1279
|
+
formsubmit
|
|
1280
|
+
.c('field', { 'var': 'FORM_TYPE' })
|
|
1281
|
+
.c('value')
|
|
1282
|
+
.t('http://jabber.org/protocol/muc#roomconfig')
|
|
1283
|
+
.up()
|
|
1284
|
+
.up();
|
|
1285
|
+
formsubmit
|
|
1286
|
+
.c('field', { 'var': 'muc#roomconfig_roomsecret' })
|
|
1287
|
+
.c('value')
|
|
1288
|
+
.t(key)
|
|
1289
|
+
.up()
|
|
1290
|
+
.up();
|
|
1291
|
+
formsubmit
|
|
1292
|
+
.c('field', { 'var': 'muc#roomconfig_passwordprotectedroom' })
|
|
1293
|
+
.c('value')
|
|
1294
|
+
.t(key === null || key.length === 0 ? '0' : '1')
|
|
1295
|
+
.up()
|
|
1296
|
+
.up();
|
|
1297
|
+
// if members only enabled
|
|
1298
|
+
if (this.membersOnlyEnabled) {
|
|
1299
|
+
formsubmit
|
|
1300
|
+
.c('field', { 'var': 'muc#roomconfig_membersonly' })
|
|
1301
|
+
.c('value')
|
|
1302
|
+
.t('true')
|
|
1303
|
+
.up()
|
|
1304
|
+
.up();
|
|
1305
|
+
}
|
|
1306
|
+
// Fixes a bug in prosody 0.9.+
|
|
1307
|
+
// https://prosody.im/issues/issue/373
|
|
1308
|
+
formsubmit
|
|
1309
|
+
.c('field', { 'var': 'muc#roomconfig_whois' })
|
|
1310
|
+
.c('value')
|
|
1311
|
+
.t('anyone')
|
|
1312
|
+
.up()
|
|
1313
|
+
.up();
|
|
1314
|
+
this.connection.sendIQ(formsubmit, () => {
|
|
1315
|
+
// we set the password in chat room so we can use it
|
|
1316
|
+
// later when dialing out
|
|
1317
|
+
this.password = key;
|
|
1318
|
+
onSuccess();
|
|
1319
|
+
}, error => {
|
|
1320
|
+
handleStropheError(error, {
|
|
1321
|
+
operation: 'lock room (submit form)',
|
|
1322
|
+
roomJid: this.roomjid,
|
|
1323
|
+
userJid: this.connection.jid
|
|
1324
|
+
});
|
|
1325
|
+
onError(error);
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
onNotSupported();
|
|
1330
|
+
}
|
|
1331
|
+
}, error => {
|
|
1332
|
+
handleStropheError(error, {
|
|
1333
|
+
operation: 'lock room (get configuration)',
|
|
1334
|
+
roomJid: this.roomjid,
|
|
1335
|
+
userJid: this.connection.jid
|
|
1336
|
+
});
|
|
1337
|
+
onError(error);
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
/* eslint-enable max-params */
|
|
1341
|
+
/**
|
|
1342
|
+
* Turns off or on the members only config for the main room.
|
|
1343
|
+
*
|
|
1344
|
+
* @param {boolean} enabled - Whether to turn it on or off.
|
|
1345
|
+
* @param onSuccess - optional callback.
|
|
1346
|
+
* @param onError - optional callback.
|
|
1347
|
+
*/
|
|
1348
|
+
setMembersOnly(enabled, onSuccess, onError) {
|
|
1349
|
+
if (enabled && Object.values(this.members).filter(m => !m.isFocus).length) {
|
|
1350
|
+
// first grant membership to all that are in the room
|
|
1351
|
+
const affiliationsIq = $iq({
|
|
1352
|
+
to: this.roomjid,
|
|
1353
|
+
type: 'set'
|
|
1354
|
+
})
|
|
1355
|
+
.c('query', {
|
|
1356
|
+
xmlns: 'http://jabber.org/protocol/muc#admin'
|
|
1357
|
+
});
|
|
1358
|
+
let sendIq = false;
|
|
1359
|
+
Object.values(this.members).forEach(m => {
|
|
1360
|
+
if (m.jid && !MEMBERS_AFFILIATIONS.includes(m.affiliation)) {
|
|
1361
|
+
affiliationsIq.c('item', {
|
|
1362
|
+
'affiliation': 'member',
|
|
1363
|
+
'jid': Strophe.getBareJidFromJid(m.jid)
|
|
1364
|
+
}).up();
|
|
1365
|
+
sendIq = true;
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
sendIq && this.xmpp.connection.sendIQ(affiliationsIq.up(), undefined, error => {
|
|
1369
|
+
handleStropheError(error, {
|
|
1370
|
+
operation: 'set members affiliation',
|
|
1371
|
+
roomJid: this.roomjid,
|
|
1372
|
+
userJid: this.xmpp.connection.jid
|
|
1373
|
+
});
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
1377
|
+
const errorCallback = onError ? onError : () => { };
|
|
1378
|
+
this.xmpp.connection.sendIQ($iq({
|
|
1379
|
+
to: this.roomjid,
|
|
1380
|
+
type: 'get'
|
|
1381
|
+
}).c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' }), res => {
|
|
1382
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
1383
|
+
if (exists(res, ':scope>query>x[*|xmlns="jabber:x:data"]>field[var="muc#roomconfig_membersonly"]')) {
|
|
1384
|
+
const formToSubmit = $iq({
|
|
1385
|
+
to: this.roomjid,
|
|
1386
|
+
type: 'set'
|
|
1387
|
+
}).c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' });
|
|
1388
|
+
formToSubmit.c('x', {
|
|
1389
|
+
type: 'submit',
|
|
1390
|
+
xmlns: 'jabber:x:data'
|
|
1391
|
+
});
|
|
1392
|
+
formToSubmit
|
|
1393
|
+
.c('field', { 'var': 'FORM_TYPE' })
|
|
1394
|
+
.c('value')
|
|
1395
|
+
.t('http://jabber.org/protocol/muc#roomconfig')
|
|
1396
|
+
.up()
|
|
1397
|
+
.up();
|
|
1398
|
+
formToSubmit
|
|
1399
|
+
.c('field', { 'var': 'muc#roomconfig_membersonly' })
|
|
1400
|
+
.c('value')
|
|
1401
|
+
.t(enabled ? 'true' : 'false')
|
|
1402
|
+
.up()
|
|
1403
|
+
.up();
|
|
1404
|
+
// if room is locked from other participant or we are locking it
|
|
1405
|
+
if (this.locked) {
|
|
1406
|
+
formToSubmit
|
|
1407
|
+
.c('field', { 'var': 'muc#roomconfig_passwordprotectedroom' })
|
|
1408
|
+
.c('value')
|
|
1409
|
+
.t('1')
|
|
1410
|
+
.up()
|
|
1411
|
+
.up();
|
|
1412
|
+
}
|
|
1413
|
+
this.xmpp.connection.sendIQ(formToSubmit, onSuccess, error => {
|
|
1414
|
+
handleStropheError(error, {
|
|
1415
|
+
enabled,
|
|
1416
|
+
operation: 'set members only (submit form)',
|
|
1417
|
+
roomJid: this.roomjid,
|
|
1418
|
+
userJid: this.xmpp.connection.jid
|
|
1419
|
+
});
|
|
1420
|
+
errorCallback(error);
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
errorCallback(new Error('Setting members only room not supported!'));
|
|
1425
|
+
}
|
|
1426
|
+
}, error => {
|
|
1427
|
+
handleStropheError(error, {
|
|
1428
|
+
enabled,
|
|
1429
|
+
operation: 'set members only (get configuration)',
|
|
1430
|
+
roomJid: this.roomjid,
|
|
1431
|
+
userJid: this.xmpp.connection.jid
|
|
1432
|
+
});
|
|
1433
|
+
errorCallback(error);
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Adds the key to the presence map, overriding any previous value.
|
|
1438
|
+
* @param key The key to add or replace.
|
|
1439
|
+
* @param values The new values.
|
|
1440
|
+
* @returns {boolean|null} <tt>true</tt> if the operation succeeded or <tt>false</tt> when no add or replace was
|
|
1441
|
+
* performed as the value was already there.
|
|
1442
|
+
* @internal
|
|
1443
|
+
*/
|
|
1444
|
+
addOrReplaceInPresence(key, values) {
|
|
1445
|
+
values.tagName = key;
|
|
1446
|
+
const matchingNodes = this.presMap.nodes.filter(node => key === node.tagName);
|
|
1447
|
+
// if we have found just one, let's check is it the same
|
|
1448
|
+
if (matchingNodes.length === 1 && isEqual(matchingNodes[0], values)) {
|
|
1449
|
+
return false;
|
|
1450
|
+
}
|
|
1451
|
+
this.removeFromPresence(key);
|
|
1452
|
+
this.presMap.nodes.push(values);
|
|
1453
|
+
this.presenceUpdateTime = Date.now();
|
|
1454
|
+
return true;
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Retrieves a value from the presence map.
|
|
1458
|
+
*
|
|
1459
|
+
* @param {string} key - The key to find the value for.
|
|
1460
|
+
* @returns {Object?}
|
|
1461
|
+
* @internal
|
|
1462
|
+
*/
|
|
1463
|
+
getFromPresence(key) {
|
|
1464
|
+
return this.presMap.nodes.find(node => key === node.tagName);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Removes a key from the presence map.
|
|
1468
|
+
* @param key
|
|
1469
|
+
* @internal
|
|
1470
|
+
*/
|
|
1471
|
+
removeFromPresence(key) {
|
|
1472
|
+
const nodes = this.presMap.nodes.filter(node => key !== node.tagName);
|
|
1473
|
+
this.presMap.nodes = nodes;
|
|
1474
|
+
this.presenceUpdateTime = Date.now();
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
*
|
|
1478
|
+
* @param name
|
|
1479
|
+
* @param handler
|
|
1480
|
+
* @internal
|
|
1481
|
+
*/
|
|
1482
|
+
addPresenceListener(name, handler) {
|
|
1483
|
+
if (typeof handler !== 'function') {
|
|
1484
|
+
throw new Error('"handler" is not a function');
|
|
1485
|
+
}
|
|
1486
|
+
let tagHandlers = this.presHandlers[name];
|
|
1487
|
+
if (!tagHandlers) {
|
|
1488
|
+
this.presHandlers[name] = tagHandlers = [];
|
|
1489
|
+
}
|
|
1490
|
+
if (tagHandlers.indexOf(handler) === -1) {
|
|
1491
|
+
tagHandlers.push(handler);
|
|
1492
|
+
}
|
|
1493
|
+
else {
|
|
1494
|
+
logger.warn(`Trying to add the same handler more than once for: ${name}`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
*
|
|
1499
|
+
* @param name
|
|
1500
|
+
* @param handler
|
|
1501
|
+
* @internal
|
|
1502
|
+
*/
|
|
1503
|
+
removePresenceListener(name, handler) {
|
|
1504
|
+
const tagHandlers = this.presHandlers[name];
|
|
1505
|
+
const handlerIdx = tagHandlers ? tagHandlers.indexOf(handler) : -1;
|
|
1506
|
+
// eslint-disable-next-line no-negated-condition
|
|
1507
|
+
if (handlerIdx !== -1) {
|
|
1508
|
+
tagHandlers.splice(handlerIdx, 1);
|
|
1509
|
+
}
|
|
1510
|
+
else {
|
|
1511
|
+
logger.warn(`Handler for: ${name} was not registered`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Checks if the user identified by given <tt>mucJid</tt> is the conference
|
|
1516
|
+
* focus.
|
|
1517
|
+
* @param mucJid the full MUC address of the user to be checked.
|
|
1518
|
+
* @returns {boolean|null} <tt>true</tt> if MUC user is the conference focus
|
|
1519
|
+
* or <tt>false</tt> if is not. When given <tt>mucJid</tt> does not exist in
|
|
1520
|
+
* the MUC then <tt>null</tt> is returned.
|
|
1521
|
+
*/
|
|
1522
|
+
isFocus(mucJid) {
|
|
1523
|
+
const member = this.members[mucJid];
|
|
1524
|
+
if (member) {
|
|
1525
|
+
return member.isFocus;
|
|
1526
|
+
}
|
|
1527
|
+
return null;
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
*
|
|
1531
|
+
*/
|
|
1532
|
+
isModerator() {
|
|
1533
|
+
return this.role === 'moderator';
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Obtains the info about given media advertised (in legacy format) in the MUC presence of the participant
|
|
1537
|
+
* identified by the given endpoint JID. This is for mantining interop with endpoints that do not support
|
|
1538
|
+
* source-name signaling (Jigasi and very old mobile clients).
|
|
1539
|
+
*
|
|
1540
|
+
* @param {string} endpointId the endpoint ID mapped to the participant which corresponds to MUC nickname.
|
|
1541
|
+
* @param {MediaType} mediaType the type of the media for which presence info will be obtained.
|
|
1542
|
+
* @return {PeerMediaInfo} presenceInfo an object with media presence info or <tt>null</tt> either if there
|
|
1543
|
+
* is no presence available or if the media type given is invalid.
|
|
1544
|
+
*/
|
|
1545
|
+
getMediaPresenceInfo(endpointId, mediaType) {
|
|
1546
|
+
// Will figure out current muted status by looking up owner's presence
|
|
1547
|
+
const pres = this.lastPresences[`${this.roomjid}/${endpointId}`];
|
|
1548
|
+
if (!pres) {
|
|
1549
|
+
// No presence available
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
const data = {
|
|
1553
|
+
muted: true, // muted by default
|
|
1554
|
+
videoType: mediaType === MediaType.VIDEO ? VideoType.CAMERA : undefined // 'camera' by default
|
|
1555
|
+
};
|
|
1556
|
+
let mutedNode = null;
|
|
1557
|
+
if (mediaType === MediaType.AUDIO) {
|
|
1558
|
+
mutedNode = filterNodeFromPresenceJSON(pres, 'audiomuted');
|
|
1559
|
+
}
|
|
1560
|
+
else if (mediaType === MediaType.VIDEO) {
|
|
1561
|
+
mutedNode = filterNodeFromPresenceJSON(pres, 'videomuted');
|
|
1562
|
+
const codecTypeNode = filterNodeFromPresenceJSON(pres, 'jitsi_participant_codecType');
|
|
1563
|
+
const videoTypeNode = filterNodeFromPresenceJSON(pres, 'videoType');
|
|
1564
|
+
if (videoTypeNode.length > 0) {
|
|
1565
|
+
data.videoType = videoTypeNode[0].value;
|
|
1566
|
+
}
|
|
1567
|
+
if (codecTypeNode.length > 0) {
|
|
1568
|
+
data.codecType = codecTypeNode[0].value;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
else {
|
|
1572
|
+
logger.error(`Unsupported media type: ${mediaType}`);
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
if (mutedNode.length > 0) {
|
|
1576
|
+
data.muted = mutedNode[0].value === 'true';
|
|
1577
|
+
}
|
|
1578
|
+
return data;
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
*
|
|
1582
|
+
* @param peerJid
|
|
1583
|
+
*/
|
|
1584
|
+
getMemberRole(peerJid) {
|
|
1585
|
+
if (this.members[peerJid]) {
|
|
1586
|
+
return this.members[peerJid].role;
|
|
1587
|
+
}
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Returns the last presence advertised by a MUC member.
|
|
1592
|
+
* @param {string} mucNick
|
|
1593
|
+
* @returns {*}
|
|
1594
|
+
* @internal
|
|
1595
|
+
*/
|
|
1596
|
+
getLastPresence(mucNick) {
|
|
1597
|
+
return this.lastPresences[`${this.roomjid}/${mucNick}`];
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Dials a number.
|
|
1601
|
+
* @param number the number
|
|
1602
|
+
*/
|
|
1603
|
+
dial(number) {
|
|
1604
|
+
return this.connection.rayo.dial(number, 'fromnumber', Strophe.getBareJidFromJid(this.myroomjid), this.password, this.focusMucJid);
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Hangup an existing call
|
|
1608
|
+
*/
|
|
1609
|
+
hangup() {
|
|
1610
|
+
return this.connection.rayo.hangup();
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
*
|
|
1614
|
+
* @returns {Lobby}
|
|
1615
|
+
*/
|
|
1616
|
+
getLobby() {
|
|
1617
|
+
return this.lobby;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* @returns {AVModeration}
|
|
1621
|
+
*/
|
|
1622
|
+
getAVModeration() {
|
|
1623
|
+
return this.avModeration;
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* @returns {BreakoutRooms}
|
|
1627
|
+
*/
|
|
1628
|
+
getBreakoutRooms() {
|
|
1629
|
+
return this.breakoutRooms;
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* @returns {FileSharing}
|
|
1633
|
+
*/
|
|
1634
|
+
getFileSharing() {
|
|
1635
|
+
return this.fileSharing;
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* @returns {RoomMetadata}
|
|
1639
|
+
*/
|
|
1640
|
+
getMetadataHandler() {
|
|
1641
|
+
return this.roomMetadata;
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* @returns {Polls}
|
|
1645
|
+
*/
|
|
1646
|
+
getPolls() {
|
|
1647
|
+
return this.polls;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Returns the phone number for joining the conference.
|
|
1651
|
+
*/
|
|
1652
|
+
getPhoneNumber() {
|
|
1653
|
+
return this.phoneNumber;
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Returns the pin for joining the conference with phone.
|
|
1657
|
+
*/
|
|
1658
|
+
getPhonePin() {
|
|
1659
|
+
return this.phonePin;
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Returns the meeting unique ID if any came from backend.
|
|
1663
|
+
*
|
|
1664
|
+
* @returns {string} - The meeting ID.
|
|
1665
|
+
*/
|
|
1666
|
+
getMeetingId() {
|
|
1667
|
+
return this.meetingId;
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Mutes remote participant.
|
|
1671
|
+
* @param jid of the participant
|
|
1672
|
+
* @param mute
|
|
1673
|
+
* @param mediaType
|
|
1674
|
+
*/
|
|
1675
|
+
muteParticipant(jid, mute, mediaType) {
|
|
1676
|
+
logger.info('set mute', mute, jid);
|
|
1677
|
+
const iqToFocus = $iq({ to: this.focusMucJid,
|
|
1678
|
+
type: 'set' })
|
|
1679
|
+
.c('mute', {
|
|
1680
|
+
jid,
|
|
1681
|
+
xmlns: `http://jitsi.org/jitmeet/${mediaType}`
|
|
1682
|
+
})
|
|
1683
|
+
.t(mute.toString())
|
|
1684
|
+
.up();
|
|
1685
|
+
this.connection.sendIQ(iqToFocus, result => logger.info('set mute', result), error => {
|
|
1686
|
+
handleStropheError(error, {
|
|
1687
|
+
mediaType,
|
|
1688
|
+
mute,
|
|
1689
|
+
operation: 'set mute participant',
|
|
1690
|
+
participantJid: jid,
|
|
1691
|
+
roomJid: this.roomjid,
|
|
1692
|
+
userJid: this.connection.jid
|
|
1693
|
+
});
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Handle remote mute reqyest from focus.
|
|
1698
|
+
*
|
|
1699
|
+
* @param iq
|
|
1700
|
+
* @internal
|
|
1701
|
+
*/
|
|
1702
|
+
onMute(iq) {
|
|
1703
|
+
const from = iq.getAttribute('from');
|
|
1704
|
+
if (from !== this.focusMucJid) {
|
|
1705
|
+
logger.warn('Ignored mute from non focus peer');
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
const mute = findFirst(iq, 'mute');
|
|
1709
|
+
if (getText(mute) === 'true') {
|
|
1710
|
+
this.eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS, getAttribute(mute, 'actor'));
|
|
1711
|
+
}
|
|
1712
|
+
else {
|
|
1713
|
+
// XXX Why do we support anything but muting? Why do we encode the
|
|
1714
|
+
// value in the text of the element? Why do we use a separate XML
|
|
1715
|
+
// namespace?
|
|
1716
|
+
logger.warn('Ignoring a mute request which does not explicitly '
|
|
1717
|
+
+ 'specify a positive mute command.');
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Handle remote video mute request from focus.
|
|
1722
|
+
*
|
|
1723
|
+
* @param iq
|
|
1724
|
+
* @internal
|
|
1725
|
+
*/
|
|
1726
|
+
onMuteVideo(iq) {
|
|
1727
|
+
const from = iq.getAttribute('from');
|
|
1728
|
+
if (from !== this.focusMucJid) {
|
|
1729
|
+
logger.warn('Ignored mute from non focus peer');
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
const mute = findFirst(iq, 'mute');
|
|
1733
|
+
if (getText(mute) === 'true') {
|
|
1734
|
+
this.eventEmitter.emit(XMPPEvents.VIDEO_MUTED_BY_FOCUS, getAttribute(mute, 'actor'));
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
// XXX Why do we support anything but muting? Why do we encode the
|
|
1738
|
+
// value in the text of the element? Why do we use a separate XML
|
|
1739
|
+
// namespace?
|
|
1740
|
+
logger.warn('Ignoring a mute request which does not explicitly '
|
|
1741
|
+
+ 'specify a positive mute command.');
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Handle remote desktop sharing mute request from focus.
|
|
1746
|
+
*
|
|
1747
|
+
* @param iq
|
|
1748
|
+
* @internal
|
|
1749
|
+
*/
|
|
1750
|
+
onMuteDesktop(iq) {
|
|
1751
|
+
const from = iq.getAttribute('from');
|
|
1752
|
+
if (from !== this.focusMucJid) {
|
|
1753
|
+
logger.warn('Ignored mute from non focus peer');
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const mute = findFirst(iq, 'mute');
|
|
1757
|
+
if (getText(mute) === 'true') {
|
|
1758
|
+
this.eventEmitter.emit(XMPPEvents.DESKTOP_MUTED_BY_FOCUS, getAttribute(mute, 'actor'));
|
|
1759
|
+
}
|
|
1760
|
+
else {
|
|
1761
|
+
// XXX Why do we support anything but muting? Why do we encode the
|
|
1762
|
+
// value in the text of the element? Why do we use a separate XML
|
|
1763
|
+
// namespace?
|
|
1764
|
+
logger.warn('Ignoring a mute request which does not explicitly '
|
|
1765
|
+
+ 'specify a positive mute command.');
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Leaves the room. Closes the jingle session.
|
|
1770
|
+
* @returns {Promise} which is resolved if XMPPEvents.MUC_LEFT is received
|
|
1771
|
+
* less than 5s after sending presence unavailable. Otherwise the promise is
|
|
1772
|
+
* rejected.
|
|
1773
|
+
*/
|
|
1774
|
+
leave(reason) {
|
|
1775
|
+
this.avModeration.dispose();
|
|
1776
|
+
this.breakoutRooms.dispose();
|
|
1777
|
+
this.fileSharing.dispose();
|
|
1778
|
+
this.polls.dispose();
|
|
1779
|
+
this.roomMetadata.dispose();
|
|
1780
|
+
const promises = [];
|
|
1781
|
+
this.lobby?.lobbyRoom && promises.push(this.lobby.leave());
|
|
1782
|
+
promises.push(new Promise((resolve, reject) => {
|
|
1783
|
+
let timeout = -1;
|
|
1784
|
+
const onMucLeft = (doReject = false) => {
|
|
1785
|
+
this.eventEmitter.removeListener(XMPPEvents.MUC_LEFT, onMucLeft);
|
|
1786
|
+
clearTimeout(timeout);
|
|
1787
|
+
// This will reset the joined flag to false. If we reset it earlier any self presence will be
|
|
1788
|
+
// interpreted as muc join. That's why we reset the flag once we have received presence unavalable
|
|
1789
|
+
// (MUC_LEFT).
|
|
1790
|
+
this.clean();
|
|
1791
|
+
if (doReject) {
|
|
1792
|
+
// The timeout expired. Make sure we clean the EMUC state.
|
|
1793
|
+
this.connection.emuc.doLeave(this.roomjid);
|
|
1794
|
+
reject(new Error('The timeout for the confirmation about leaving the room expired.'));
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
resolve(undefined);
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
if (this.joined) {
|
|
1801
|
+
timeout = setTimeout(() => onMucLeft(true), 5000);
|
|
1802
|
+
this.eventEmitter.on(XMPPEvents.MUC_LEFT, onMucLeft);
|
|
1803
|
+
this.doLeave(reason);
|
|
1804
|
+
}
|
|
1805
|
+
else {
|
|
1806
|
+
// we are clearing up, and we haven't joined the room
|
|
1807
|
+
// there is no point of sending presence unavailable and check for timeout
|
|
1808
|
+
// let's just clean
|
|
1809
|
+
this.connection.emuc.doLeave(this.roomjid);
|
|
1810
|
+
this.clean();
|
|
1811
|
+
resolve(undefined);
|
|
1812
|
+
}
|
|
1813
|
+
}));
|
|
1814
|
+
return Promise.allSettled(promises);
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Ends the conference for all participants.
|
|
1818
|
+
*/
|
|
1819
|
+
end() {
|
|
1820
|
+
if (this.breakoutRooms.isBreakoutRoom()) {
|
|
1821
|
+
logger.warn('Cannot end conference: this is a breakout room.');
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
// Send the end conference message.
|
|
1825
|
+
const msg = $msg({ to: this.xmpp.endConferenceComponentAddress });
|
|
1826
|
+
msg.c('end_conference').up();
|
|
1827
|
+
this.xmpp.connection.send(msg);
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Requests short-lived credentials for a service.
|
|
1831
|
+
* The function does not use anything from the room, but the backend requires the sender
|
|
1832
|
+
* to be in the room as the credentials contain the meeting ID and are valid only for the room.
|
|
1833
|
+
* @param service
|
|
1834
|
+
*/
|
|
1835
|
+
getShortTermCredentials(service) {
|
|
1836
|
+
// Gets credentials via xep-0215 and cache it
|
|
1837
|
+
return new Promise((resolve, reject) => {
|
|
1838
|
+
const cachedCredentials = this.cachedShortTermCredentials || {};
|
|
1839
|
+
if (cachedCredentials[service]) {
|
|
1840
|
+
resolve(cachedCredentials[service]);
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
this.connection.sendIQ($iq({
|
|
1844
|
+
// @ts-ignore will fix after xmpp PR merge
|
|
1845
|
+
to: this.xmpp.options.hosts.domain,
|
|
1846
|
+
type: 'get'
|
|
1847
|
+
})
|
|
1848
|
+
.c('credentials', { xmlns: 'urn:xmpp:extdisco:2' })
|
|
1849
|
+
.c('service', {
|
|
1850
|
+
host: service,
|
|
1851
|
+
type: 'short-lived-token'
|
|
1852
|
+
}), res => {
|
|
1853
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
1854
|
+
const resultServiceEl = findFirst(res, ':scope>credentials[*|xmlns="urn:xmpp:extdisco:2"]>service');
|
|
1855
|
+
const currentDate = new Date();
|
|
1856
|
+
const expirationDate = new Date(getAttribute(resultServiceEl, 'expires'));
|
|
1857
|
+
cachedCredentials[service] = getAttribute(resultServiceEl, 'password');
|
|
1858
|
+
this.cachedShortTermCredentials = cachedCredentials;
|
|
1859
|
+
setTimeout(() => {
|
|
1860
|
+
this.cachedShortTermCredentials[service] = undefined;
|
|
1861
|
+
}, expirationDate.getTime() - currentDate.getTime() - 10000);
|
|
1862
|
+
resolve(this.cachedShortTermCredentials[service]);
|
|
1863
|
+
}, error => {
|
|
1864
|
+
handleStropheError(error, {
|
|
1865
|
+
operation: 'get short term credentials',
|
|
1866
|
+
roomJid: this.roomjid,
|
|
1867
|
+
service,
|
|
1868
|
+
userJid: this.connection.jid
|
|
1869
|
+
});
|
|
1870
|
+
reject(error);
|
|
1871
|
+
});
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
export { ChatRoom };
|
|
1876
|
+
/* eslint-enable newline-per-chained-call */
|
|
1877
|
+
//# sourceMappingURL=ChatRoom.js.map
|