@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.
Files changed (350) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +26 -0
  3. package/dist/esm/JitsiConference.js +3692 -0
  4. package/dist/esm/JitsiConference.js.map +1 -0
  5. package/dist/esm/JitsiConferenceErrors.js +126 -0
  6. package/dist/esm/JitsiConferenceErrors.js.map +1 -0
  7. package/dist/esm/JitsiConferenceEventManager.js +424 -0
  8. package/dist/esm/JitsiConferenceEventManager.js.map +1 -0
  9. package/dist/esm/JitsiConferenceEvents.js +431 -0
  10. package/dist/esm/JitsiConferenceEvents.js.map +1 -0
  11. package/dist/esm/JitsiConnection.js +187 -0
  12. package/dist/esm/JitsiConnection.js.map +1 -0
  13. package/dist/esm/JitsiConnectionErrors.js +52 -0
  14. package/dist/esm/JitsiConnectionErrors.js.map +1 -0
  15. package/dist/esm/JitsiConnectionEvents.js +57 -0
  16. package/dist/esm/JitsiConnectionEvents.js.map +1 -0
  17. package/dist/esm/JitsiMediaDevices.js +221 -0
  18. package/dist/esm/JitsiMediaDevices.js.map +1 -0
  19. package/dist/esm/JitsiMediaDevicesEvents.js +29 -0
  20. package/dist/esm/JitsiMediaDevicesEvents.js.map +1 -0
  21. package/dist/esm/JitsiMeetJS.js +499 -0
  22. package/dist/esm/JitsiMeetJS.js.map +1 -0
  23. package/dist/esm/JitsiParticipant.js +323 -0
  24. package/dist/esm/JitsiParticipant.js.map +1 -0
  25. package/dist/esm/JitsiTrackError.js +122 -0
  26. package/dist/esm/JitsiTrackError.js.map +1 -0
  27. package/dist/esm/JitsiTrackErrors.js +91 -0
  28. package/dist/esm/JitsiTrackErrors.js.map +1 -0
  29. package/dist/esm/JitsiTrackEvents.js +60 -0
  30. package/dist/esm/JitsiTrackEvents.js.map +1 -0
  31. package/dist/esm/JitsiTranscriptionStatus.js +15 -0
  32. package/dist/esm/JitsiTranscriptionStatus.js.map +1 -0
  33. package/dist/esm/modules/RTC/BridgeChannel.js +398 -0
  34. package/dist/esm/modules/RTC/BridgeChannel.js.map +1 -0
  35. package/dist/esm/modules/RTC/JitsiLocalTrack.js +896 -0
  36. package/dist/esm/modules/RTC/JitsiLocalTrack.js.map +1 -0
  37. package/dist/esm/modules/RTC/JitsiRemoteTrack.js +427 -0
  38. package/dist/esm/modules/RTC/JitsiRemoteTrack.js.map +1 -0
  39. package/dist/esm/modules/RTC/JitsiTrack.js +453 -0
  40. package/dist/esm/modules/RTC/JitsiTrack.js.map +1 -0
  41. package/dist/esm/modules/RTC/MockClasses.js +388 -0
  42. package/dist/esm/modules/RTC/MockClasses.js.map +1 -0
  43. package/dist/esm/modules/RTC/RTC.js +658 -0
  44. package/dist/esm/modules/RTC/RTC.js.map +1 -0
  45. package/dist/esm/modules/RTC/RTCUtils.js +762 -0
  46. package/dist/esm/modules/RTC/RTCUtils.js.map +1 -0
  47. package/dist/esm/modules/RTC/ScreenObtainer.js +380 -0
  48. package/dist/esm/modules/RTC/ScreenObtainer.js.map +1 -0
  49. package/dist/esm/modules/RTC/TPCUtils.js +803 -0
  50. package/dist/esm/modules/RTC/TPCUtils.js.map +1 -0
  51. package/dist/esm/modules/RTC/TraceablePeerConnection.js +2223 -0
  52. package/dist/esm/modules/RTC/TraceablePeerConnection.js.map +1 -0
  53. package/dist/esm/modules/RTCStats/DefaulLogStorage.js +35 -0
  54. package/dist/esm/modules/RTCStats/DefaulLogStorage.js.map +1 -0
  55. package/dist/esm/modules/RTCStats/RTCStats.js +219 -0
  56. package/dist/esm/modules/RTCStats/RTCStats.js.map +1 -0
  57. package/dist/esm/modules/RTCStats/RTCStatsEvents.js +92 -0
  58. package/dist/esm/modules/RTCStats/RTCStatsEvents.js.map +1 -0
  59. package/dist/esm/modules/RTCStats/interfaces.js +2 -0
  60. package/dist/esm/modules/RTCStats/interfaces.js.map +1 -0
  61. package/dist/esm/modules/browser/BrowserCapabilities.js +345 -0
  62. package/dist/esm/modules/browser/BrowserCapabilities.js.map +1 -0
  63. package/dist/esm/modules/browser/index.js +3 -0
  64. package/dist/esm/modules/browser/index.js.map +1 -0
  65. package/dist/esm/modules/connectivity/ConnectionQuality.js +389 -0
  66. package/dist/esm/modules/connectivity/ConnectionQuality.js.map +1 -0
  67. package/dist/esm/modules/connectivity/IceFailedHandling.js +84 -0
  68. package/dist/esm/modules/connectivity/IceFailedHandling.js.map +1 -0
  69. package/dist/esm/modules/connectivity/NetworkInfo.js +49 -0
  70. package/dist/esm/modules/connectivity/NetworkInfo.js.map +1 -0
  71. package/dist/esm/modules/connectivity/TrackStreamingStatus.js +453 -0
  72. package/dist/esm/modules/connectivity/TrackStreamingStatus.js.map +1 -0
  73. package/dist/esm/modules/detection/ActiveDeviceDetector.js +79 -0
  74. package/dist/esm/modules/detection/ActiveDeviceDetector.js.map +1 -0
  75. package/dist/esm/modules/detection/DetectionEvents.js +58 -0
  76. package/dist/esm/modules/detection/DetectionEvents.js.map +1 -0
  77. package/dist/esm/modules/detection/NoAudioSignalDetection.js +127 -0
  78. package/dist/esm/modules/detection/NoAudioSignalDetection.js.map +1 -0
  79. package/dist/esm/modules/detection/P2PDominantSpeakerDetection.js +47 -0
  80. package/dist/esm/modules/detection/P2PDominantSpeakerDetection.js.map +1 -0
  81. package/dist/esm/modules/detection/TrackVADEmitter.js +190 -0
  82. package/dist/esm/modules/detection/TrackVADEmitter.js.map +1 -0
  83. package/dist/esm/modules/detection/VADAudioAnalyser.js +199 -0
  84. package/dist/esm/modules/detection/VADAudioAnalyser.js.map +1 -0
  85. package/dist/esm/modules/detection/VADNoiseDetection.js +168 -0
  86. package/dist/esm/modules/detection/VADNoiseDetection.js.map +1 -0
  87. package/dist/esm/modules/detection/VADReportingService.js +203 -0
  88. package/dist/esm/modules/detection/VADReportingService.js.map +1 -0
  89. package/dist/esm/modules/detection/VADTalkMutedDetection.js +131 -0
  90. package/dist/esm/modules/detection/VADTalkMutedDetection.js.map +1 -0
  91. package/dist/esm/modules/e2ee/Context.js +274 -0
  92. package/dist/esm/modules/e2ee/Context.js.map +1 -0
  93. package/dist/esm/modules/e2ee/E2EEContext.js +158 -0
  94. package/dist/esm/modules/e2ee/E2EEContext.js.map +1 -0
  95. package/dist/esm/modules/e2ee/E2EEErrors.js +10 -0
  96. package/dist/esm/modules/e2ee/E2EEErrors.js.map +1 -0
  97. package/dist/esm/modules/e2ee/E2EEncryption.js +87 -0
  98. package/dist/esm/modules/e2ee/E2EEncryption.js.map +1 -0
  99. package/dist/esm/modules/e2ee/ExternallyManagedKeyHandler.js +24 -0
  100. package/dist/esm/modules/e2ee/ExternallyManagedKeyHandler.js.map +1 -0
  101. package/dist/esm/modules/e2ee/KeyHandler.js +137 -0
  102. package/dist/esm/modules/e2ee/KeyHandler.js.map +1 -0
  103. package/dist/esm/modules/e2ee/ManagedKeyHandler.js +182 -0
  104. package/dist/esm/modules/e2ee/ManagedKeyHandler.js.map +1 -0
  105. package/dist/esm/modules/e2ee/OlmAdapter.js +860 -0
  106. package/dist/esm/modules/e2ee/OlmAdapter.js.map +1 -0
  107. package/dist/esm/modules/e2ee/SAS.js +128 -0
  108. package/dist/esm/modules/e2ee/SAS.js.map +1 -0
  109. package/dist/esm/modules/e2ee/Worker.js +102 -0
  110. package/dist/esm/modules/e2ee/Worker.js.map +1 -0
  111. package/dist/esm/modules/e2ee/crypto-utils.js +53 -0
  112. package/dist/esm/modules/e2ee/crypto-utils.js.map +1 -0
  113. package/dist/esm/modules/e2ee/utils.js +15 -0
  114. package/dist/esm/modules/e2ee/utils.js.map +1 -0
  115. package/dist/esm/modules/e2eping/e2eping.js +314 -0
  116. package/dist/esm/modules/e2eping/e2eping.js.map +1 -0
  117. package/dist/esm/modules/flags/FeatureFlags.js +36 -0
  118. package/dist/esm/modules/flags/FeatureFlags.js.map +1 -0
  119. package/dist/esm/modules/litemode/LiteModeContext.js +50 -0
  120. package/dist/esm/modules/litemode/LiteModeContext.js.map +1 -0
  121. package/dist/esm/modules/proxyconnection/CustomSignalingLayer.js +98 -0
  122. package/dist/esm/modules/proxyconnection/CustomSignalingLayer.js.map +1 -0
  123. package/dist/esm/modules/proxyconnection/ProxyConnectionPC.js +348 -0
  124. package/dist/esm/modules/proxyconnection/ProxyConnectionPC.js.map +1 -0
  125. package/dist/esm/modules/proxyconnection/ProxyConnectionService.js +279 -0
  126. package/dist/esm/modules/proxyconnection/ProxyConnectionService.js.map +1 -0
  127. package/dist/esm/modules/proxyconnection/constants.js +14 -0
  128. package/dist/esm/modules/proxyconnection/constants.js.map +1 -0
  129. package/dist/esm/modules/qualitycontrol/CodecSelection.js +222 -0
  130. package/dist/esm/modules/qualitycontrol/CodecSelection.js.map +1 -0
  131. package/dist/esm/modules/qualitycontrol/MockClasses.js +120 -0
  132. package/dist/esm/modules/qualitycontrol/MockClasses.js.map +1 -0
  133. package/dist/esm/modules/qualitycontrol/QualityController.js +366 -0
  134. package/dist/esm/modules/qualitycontrol/QualityController.js.map +1 -0
  135. package/dist/esm/modules/qualitycontrol/ReceiveAudioController.js +73 -0
  136. package/dist/esm/modules/qualitycontrol/ReceiveAudioController.js.map +1 -0
  137. package/dist/esm/modules/qualitycontrol/ReceiveVideoController.js +216 -0
  138. package/dist/esm/modules/qualitycontrol/ReceiveVideoController.js.map +1 -0
  139. package/dist/esm/modules/qualitycontrol/SendVideoController.js +133 -0
  140. package/dist/esm/modules/qualitycontrol/SendVideoController.js.map +1 -0
  141. package/dist/esm/modules/recording/JibriSession.js +279 -0
  142. package/dist/esm/modules/recording/JibriSession.js.map +1 -0
  143. package/dist/esm/modules/recording/RecordingManager.js +257 -0
  144. package/dist/esm/modules/recording/RecordingManager.js.map +1 -0
  145. package/dist/esm/modules/recording/recordingConstants.js +21 -0
  146. package/dist/esm/modules/recording/recordingConstants.js.map +1 -0
  147. package/dist/esm/modules/recording/recordingXMLUtils.js +69 -0
  148. package/dist/esm/modules/recording/recordingXMLUtils.js.map +1 -0
  149. package/dist/esm/modules/red/red.js +108 -0
  150. package/dist/esm/modules/red/red.js.map +1 -0
  151. package/dist/esm/modules/sdp/LocalSdpMunger.js +143 -0
  152. package/dist/esm/modules/sdp/LocalSdpMunger.js.map +1 -0
  153. package/dist/esm/modules/sdp/RtxModifier.js +179 -0
  154. package/dist/esm/modules/sdp/RtxModifier.js.map +1 -0
  155. package/dist/esm/modules/sdp/SDP.js +848 -0
  156. package/dist/esm/modules/sdp/SDP.js.map +1 -0
  157. package/dist/esm/modules/sdp/SDPDiffer.js +96 -0
  158. package/dist/esm/modules/sdp/SDPDiffer.js.map +1 -0
  159. package/dist/esm/modules/sdp/SDPUtil.js +798 -0
  160. package/dist/esm/modules/sdp/SDPUtil.js.map +1 -0
  161. package/dist/esm/modules/sdp/SampleSdpStrings.js +589 -0
  162. package/dist/esm/modules/sdp/SampleSdpStrings.js.map +1 -0
  163. package/dist/esm/modules/sdp/SdpSimulcast.js +196 -0
  164. package/dist/esm/modules/sdp/SdpSimulcast.js.map +1 -0
  165. package/dist/esm/modules/sdp/SdpTransformUtil.js +337 -0
  166. package/dist/esm/modules/sdp/SdpTransformUtil.js.map +1 -0
  167. package/dist/esm/modules/sdp/constants.js +2 -0
  168. package/dist/esm/modules/sdp/constants.js.map +1 -0
  169. package/dist/esm/modules/settings/Settings.js +95 -0
  170. package/dist/esm/modules/settings/Settings.js.map +1 -0
  171. package/dist/esm/modules/statistics/AnalyticsAdapter.js +277 -0
  172. package/dist/esm/modules/statistics/AnalyticsAdapter.js.map +1 -0
  173. package/dist/esm/modules/statistics/AvgRTPStatsReporter.js +817 -0
  174. package/dist/esm/modules/statistics/AvgRTPStatsReporter.js.map +1 -0
  175. package/dist/esm/modules/statistics/LocalStatsCollector.js +149 -0
  176. package/dist/esm/modules/statistics/LocalStatsCollector.js.map +1 -0
  177. package/dist/esm/modules/statistics/PreCallTest.js +15 -0
  178. package/dist/esm/modules/statistics/PreCallTest.js.map +1 -0
  179. package/dist/esm/modules/statistics/RTPStatsCollector.js +601 -0
  180. package/dist/esm/modules/statistics/RTPStatsCollector.js.map +1 -0
  181. package/dist/esm/modules/statistics/SpeakerStats.js +163 -0
  182. package/dist/esm/modules/statistics/SpeakerStats.js.map +1 -0
  183. package/dist/esm/modules/statistics/SpeakerStatsCollector.js +161 -0
  184. package/dist/esm/modules/statistics/SpeakerStatsCollector.js.map +1 -0
  185. package/dist/esm/modules/statistics/constants.js +7 -0
  186. package/dist/esm/modules/statistics/constants.js.map +1 -0
  187. package/dist/esm/modules/statistics/statistics.js +362 -0
  188. package/dist/esm/modules/statistics/statistics.js.map +1 -0
  189. package/dist/esm/modules/util/AsyncQueue.js +102 -0
  190. package/dist/esm/modules/util/AsyncQueue.js.map +1 -0
  191. package/dist/esm/modules/util/Deferred.js +41 -0
  192. package/dist/esm/modules/util/Deferred.js.map +1 -0
  193. package/dist/esm/modules/util/EventEmitter.js +17 -0
  194. package/dist/esm/modules/util/EventEmitter.js.map +1 -0
  195. package/dist/esm/modules/util/EventEmitterForwarder.js +52 -0
  196. package/dist/esm/modules/util/EventEmitterForwarder.js.map +1 -0
  197. package/dist/esm/modules/util/Listenable.js +106 -0
  198. package/dist/esm/modules/util/Listenable.js.map +1 -0
  199. package/dist/esm/modules/util/MathUtil.js +103 -0
  200. package/dist/esm/modules/util/MathUtil.js.map +1 -0
  201. package/dist/esm/modules/util/RandomUtil.js +66 -0
  202. package/dist/esm/modules/util/RandomUtil.js.map +1 -0
  203. package/dist/esm/modules/util/Retry.js +15 -0
  204. package/dist/esm/modules/util/Retry.js.map +1 -0
  205. package/dist/esm/modules/util/ScriptUtil.js +58 -0
  206. package/dist/esm/modules/util/ScriptUtil.js.map +1 -0
  207. package/dist/esm/modules/util/StringUtils.js +21 -0
  208. package/dist/esm/modules/util/StringUtils.js.map +1 -0
  209. package/dist/esm/modules/util/TestUtils.js +14 -0
  210. package/dist/esm/modules/util/TestUtils.js.map +1 -0
  211. package/dist/esm/modules/util/UsernameGenerator.js +436 -0
  212. package/dist/esm/modules/util/UsernameGenerator.js.map +1 -0
  213. package/dist/esm/modules/util/XMLUtils.js +135 -0
  214. package/dist/esm/modules/util/XMLUtils.js.map +1 -0
  215. package/dist/esm/modules/version/ComponentsVersions.js +52 -0
  216. package/dist/esm/modules/version/ComponentsVersions.js.map +1 -0
  217. package/dist/esm/modules/videosipgw/JitsiVideoSIPGWSession.js +137 -0
  218. package/dist/esm/modules/videosipgw/JitsiVideoSIPGWSession.js.map +1 -0
  219. package/dist/esm/modules/videosipgw/VideoSIPGW.js +102 -0
  220. package/dist/esm/modules/videosipgw/VideoSIPGW.js.map +1 -0
  221. package/dist/esm/modules/videosipgw/VideoSIPGWConstants.js +65 -0
  222. package/dist/esm/modules/videosipgw/VideoSIPGWConstants.js.map +1 -0
  223. package/dist/esm/modules/watchRTC/WatchRTC.js +69 -0
  224. package/dist/esm/modules/watchRTC/WatchRTC.js.map +1 -0
  225. package/dist/esm/modules/watchRTC/functions.js +31 -0
  226. package/dist/esm/modules/watchRTC/functions.js.map +1 -0
  227. package/dist/esm/modules/watchRTC/interfaces.js +2 -0
  228. package/dist/esm/modules/watchRTC/interfaces.js.map +1 -0
  229. package/dist/esm/modules/webaudio/AudioMixer.js +74 -0
  230. package/dist/esm/modules/webaudio/AudioMixer.js.map +1 -0
  231. package/dist/esm/modules/webaudio/WebAudioUtils.js +13 -0
  232. package/dist/esm/modules/webaudio/WebAudioUtils.js.map +1 -0
  233. package/dist/esm/modules/xmpp/AVModeration.js +156 -0
  234. package/dist/esm/modules/xmpp/AVModeration.js.map +1 -0
  235. package/dist/esm/modules/xmpp/BreakoutRooms.js +230 -0
  236. package/dist/esm/modules/xmpp/BreakoutRooms.js.map +1 -0
  237. package/dist/esm/modules/xmpp/Caps.js +223 -0
  238. package/dist/esm/modules/xmpp/Caps.js.map +1 -0
  239. package/dist/esm/modules/xmpp/ChatRoom.js +1877 -0
  240. package/dist/esm/modules/xmpp/ChatRoom.js.map +1 -0
  241. package/dist/esm/modules/xmpp/ConnectionPlugin.js +37 -0
  242. package/dist/esm/modules/xmpp/ConnectionPlugin.js.map +1 -0
  243. package/dist/esm/modules/xmpp/FileSharing.js +95 -0
  244. package/dist/esm/modules/xmpp/FileSharing.js.map +1 -0
  245. package/dist/esm/modules/xmpp/JingleHelperFunctions.js +168 -0
  246. package/dist/esm/modules/xmpp/JingleHelperFunctions.js.map +1 -0
  247. package/dist/esm/modules/xmpp/JingleSession.js +166 -0
  248. package/dist/esm/modules/xmpp/JingleSession.js.map +1 -0
  249. package/dist/esm/modules/xmpp/JingleSessionPC.js +1969 -0
  250. package/dist/esm/modules/xmpp/JingleSessionPC.js.map +1 -0
  251. package/dist/esm/modules/xmpp/JingleSessionState.js +23 -0
  252. package/dist/esm/modules/xmpp/JingleSessionState.js.map +1 -0
  253. package/dist/esm/modules/xmpp/Lobby.js +384 -0
  254. package/dist/esm/modules/xmpp/Lobby.js.map +1 -0
  255. package/dist/esm/modules/xmpp/MediaSessionEvents.js +12 -0
  256. package/dist/esm/modules/xmpp/MediaSessionEvents.js.map +1 -0
  257. package/dist/esm/modules/xmpp/MockClasses.js +77 -0
  258. package/dist/esm/modules/xmpp/MockClasses.js.map +1 -0
  259. package/dist/esm/modules/xmpp/Polls.js +87 -0
  260. package/dist/esm/modules/xmpp/Polls.js.map +1 -0
  261. package/dist/esm/modules/xmpp/ResumeTask.js +149 -0
  262. package/dist/esm/modules/xmpp/ResumeTask.js.map +1 -0
  263. package/dist/esm/modules/xmpp/RoomMetadata.js +96 -0
  264. package/dist/esm/modules/xmpp/RoomMetadata.js.map +1 -0
  265. package/dist/esm/modules/xmpp/SignalingLayerImpl.js +313 -0
  266. package/dist/esm/modules/xmpp/SignalingLayerImpl.js.map +1 -0
  267. package/dist/esm/modules/xmpp/StropheErrorHandler.js +53 -0
  268. package/dist/esm/modules/xmpp/StropheErrorHandler.js.map +1 -0
  269. package/dist/esm/modules/xmpp/StropheLastSuccess.js +52 -0
  270. package/dist/esm/modules/xmpp/StropheLastSuccess.js.map +1 -0
  271. package/dist/esm/modules/xmpp/XmppConnection.js +579 -0
  272. package/dist/esm/modules/xmpp/XmppConnection.js.map +1 -0
  273. package/dist/esm/modules/xmpp/moderator.js +524 -0
  274. package/dist/esm/modules/xmpp/moderator.js.map +1 -0
  275. package/dist/esm/modules/xmpp/sha1.js +165 -0
  276. package/dist/esm/modules/xmpp/sha1.js.map +1 -0
  277. package/dist/esm/modules/xmpp/strophe.disco.js +222 -0
  278. package/dist/esm/modules/xmpp/strophe.disco.js.map +1 -0
  279. package/dist/esm/modules/xmpp/strophe.emuc.js +206 -0
  280. package/dist/esm/modules/xmpp/strophe.emuc.js.map +1 -0
  281. package/dist/esm/modules/xmpp/strophe.jingle.js +404 -0
  282. package/dist/esm/modules/xmpp/strophe.jingle.js.map +1 -0
  283. package/dist/esm/modules/xmpp/strophe.logger.js +44 -0
  284. package/dist/esm/modules/xmpp/strophe.logger.js.map +1 -0
  285. package/dist/esm/modules/xmpp/strophe.ping.js +170 -0
  286. package/dist/esm/modules/xmpp/strophe.ping.js.map +1 -0
  287. package/dist/esm/modules/xmpp/strophe.rayo.js +117 -0
  288. package/dist/esm/modules/xmpp/strophe.rayo.js.map +1 -0
  289. package/dist/esm/modules/xmpp/strophe.stream-management.js +365 -0
  290. package/dist/esm/modules/xmpp/strophe.stream-management.js.map +1 -0
  291. package/dist/esm/modules/xmpp/strophe.util.js +116 -0
  292. package/dist/esm/modules/xmpp/strophe.util.js.map +1 -0
  293. package/dist/esm/modules/xmpp/xmpp.js +973 -0
  294. package/dist/esm/modules/xmpp/xmpp.js.map +1 -0
  295. package/dist/esm/service/RTC/BridgeVideoType.js +24 -0
  296. package/dist/esm/service/RTC/BridgeVideoType.js.map +1 -0
  297. package/dist/esm/service/RTC/CameraFacingMode.js +21 -0
  298. package/dist/esm/service/RTC/CameraFacingMode.js.map +1 -0
  299. package/dist/esm/service/RTC/CodecMimeType.js +36 -0
  300. package/dist/esm/service/RTC/CodecMimeType.js.map +1 -0
  301. package/dist/esm/service/RTC/MediaDirection.js +23 -0
  302. package/dist/esm/service/RTC/MediaDirection.js.map +1 -0
  303. package/dist/esm/service/RTC/MediaType.js +20 -0
  304. package/dist/esm/service/RTC/MediaType.js.map +1 -0
  305. package/dist/esm/service/RTC/RTCEvents.js +111 -0
  306. package/dist/esm/service/RTC/RTCEvents.js.map +1 -0
  307. package/dist/esm/service/RTC/ReceiverAudioSubscription.js +27 -0
  308. package/dist/esm/service/RTC/ReceiverAudioSubscription.js.map +1 -0
  309. package/dist/esm/service/RTC/Resolutions.js +56 -0
  310. package/dist/esm/service/RTC/Resolutions.js.map +1 -0
  311. package/dist/esm/service/RTC/SignalingEvents.js +42 -0
  312. package/dist/esm/service/RTC/SignalingEvents.js.map +1 -0
  313. package/dist/esm/service/RTC/SignalingLayer.js +153 -0
  314. package/dist/esm/service/RTC/SignalingLayer.js.map +1 -0
  315. package/dist/esm/service/RTC/StandardVideoQualitySettings.js +180 -0
  316. package/dist/esm/service/RTC/StandardVideoQualitySettings.js.map +1 -0
  317. package/dist/esm/service/RTC/VideoEncoderScalabilityMode.js +36 -0
  318. package/dist/esm/service/RTC/VideoEncoderScalabilityMode.js.map +1 -0
  319. package/dist/esm/service/RTC/VideoType.js +19 -0
  320. package/dist/esm/service/RTC/VideoType.js.map +1 -0
  321. package/dist/esm/service/authentication/AuthenticationEvents.js +13 -0
  322. package/dist/esm/service/authentication/AuthenticationEvents.js.map +1 -0
  323. package/dist/esm/service/connectivity/ConnectionQualityEvents.js +13 -0
  324. package/dist/esm/service/connectivity/ConnectionQualityEvents.js.map +1 -0
  325. package/dist/esm/service/connectivity/Constants.js +3 -0
  326. package/dist/esm/service/connectivity/Constants.js.map +1 -0
  327. package/dist/esm/service/e2eping/E2ePingEvents.js +8 -0
  328. package/dist/esm/service/e2eping/E2ePingEvents.js.map +1 -0
  329. package/dist/esm/service/statistics/AnalyticsEvents.js +521 -0
  330. package/dist/esm/service/statistics/AnalyticsEvents.js.map +1 -0
  331. package/dist/esm/service/statistics/Events.js +36 -0
  332. package/dist/esm/service/statistics/Events.js.map +1 -0
  333. package/dist/esm/service/statistics/constants.js +2 -0
  334. package/dist/esm/service/statistics/constants.js.map +1 -0
  335. package/dist/esm/service/xmpp/XMPPEvents.js +359 -0
  336. package/dist/esm/service/xmpp/XMPPEvents.js.map +1 -0
  337. package/dist/esm/service/xmpp/XMPPExtensioProtocols.js +64 -0
  338. package/dist/esm/service/xmpp/XMPPExtensioProtocols.js.map +1 -0
  339. package/dist/esm/test-setup-polyfill.js +34 -0
  340. package/dist/esm/test-setup-polyfill.js.map +1 -0
  341. package/dist/esm/tools/gen-version.js +15 -0
  342. package/dist/esm/tools/gen-version.js.map +1 -0
  343. package/dist/esm/version.js +3 -0
  344. package/dist/esm/version.js.map +1 -0
  345. package/dist/umd/lib-jitsi-meet.e2ee-worker.js +484 -0
  346. package/dist/umd/lib-jitsi-meet.min.js +3 -0
  347. package/dist/umd/lib-jitsi-meet.min.js.LICENSE.txt +18 -0
  348. package/dist/umd/lib-jitsi-meet.min.map +1 -0
  349. package/package.json +93 -0
  350. 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