You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Lobby.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import { getLogger } from 'jitsi-meet-logger';
  2. import { $msg, Strophe } from 'strophe.js';
  3. import XMPPEvents from '../../service/xmpp/XMPPEvents';
  4. const logger = getLogger(__filename);
  5. /**
  6. * The command type for updating a lobby participant's e-mail address.
  7. *
  8. * @type {string}
  9. */
  10. const EMAIL_COMMAND = 'email';
  11. /**
  12. * The Lobby room implementation. Setting a room to members only, joining the lobby room
  13. * approving or denying access to participants from the lobby room.
  14. */
  15. export default class Lobby {
  16. /**
  17. * Constructs lobby room.
  18. *
  19. * @param {ChatRoom} room the main room.
  20. */
  21. constructor(room) {
  22. this.xmpp = room.xmpp;
  23. this.mainRoom = room;
  24. const maybeJoinLobbyRoom = this._maybeJoinLobbyRoom.bind(this);
  25. this.mainRoom.addEventListener(
  26. XMPPEvents.LOCAL_ROLE_CHANGED,
  27. maybeJoinLobbyRoom);
  28. this.mainRoom.addEventListener(
  29. XMPPEvents.MUC_MEMBERS_ONLY_CHANGED,
  30. maybeJoinLobbyRoom);
  31. this.mainRoom.addEventListener(
  32. XMPPEvents.ROOM_CONNECT_MEMBERS_ONLY_ERROR,
  33. jid => {
  34. this.lobbyRoomJid = jid;
  35. });
  36. }
  37. /**
  38. * Whether lobby is supported on backend.
  39. *
  40. * @returns {boolean} whether lobby is supported on backend.
  41. */
  42. isSupported() {
  43. return this.xmpp.lobbySupported;
  44. }
  45. /**
  46. * Enables lobby by setting the main room to be members only and joins the lobby chat room.
  47. *
  48. * @returns {Promise}
  49. */
  50. enable() {
  51. if (!this.isSupported()) {
  52. return Promise.reject(new Error('Lobby not supported!'));
  53. }
  54. return new Promise((resolve, reject) => {
  55. this.mainRoom.setMembersOnly(true, resolve, reject);
  56. });
  57. }
  58. /**
  59. * Disable lobby by setting the main room to be non members only and levaes the lobby chat room if joined.
  60. *
  61. * @returns {void}
  62. */
  63. disable() {
  64. if (!this.isSupported() || !this.mainRoom.isModerator()
  65. || !this.lobbyRoom || !this.mainRoom.membersOnlyEnabled) {
  66. return;
  67. }
  68. this.mainRoom.setMembersOnly(false);
  69. }
  70. /**
  71. * Leaves the lobby room.
  72. * @private
  73. */
  74. _leaveLobbyRoom() {
  75. if (this.lobbyRoom) {
  76. this.lobbyRoom.leave()
  77. .then(() => {
  78. this.lobbyRoom = undefined;
  79. logger.info('Lobby room left!');
  80. })
  81. .catch(() => {}); // eslint-disable-line no-empty-function
  82. }
  83. }
  84. /**
  85. * We had received a jid for the lobby room.
  86. *
  87. * @param jid the lobby room jid to join.
  88. */
  89. setLobbyRoomJid(jid) {
  90. this.lobbyRoomJid = jid;
  91. }
  92. /**
  93. * Checks the state of mainRoom, lobbyRoom and current user role to decide whether to join lobby room.
  94. * @private
  95. */
  96. _maybeJoinLobbyRoom() {
  97. if (!this.isSupported()) {
  98. return;
  99. }
  100. const isModerator = this.mainRoom.joined && this.mainRoom.isModerator();
  101. if (isModerator && this.mainRoom.membersOnlyEnabled && !this.lobbyRoom) {
  102. // join the lobby
  103. this.join()
  104. .then(() => logger.info('Joined lobby room'))
  105. .catch(e => logger.error('Failed joining lobby', e));
  106. }
  107. }
  108. /**
  109. * Joins a lobby room setting display name and eventually avatar(using the email provided).
  110. *
  111. * @param {string} username is required.
  112. * @param {string} email is optional.
  113. * @returns {Promise} resolves once we join the room.
  114. */
  115. join(displayName, email) {
  116. const isModerator = this.mainRoom.joined && this.mainRoom.isModerator();
  117. if (!this.lobbyRoomJid) {
  118. return Promise.reject(new Error('Missing lobbyRoomJid, cannot join lobby room.'));
  119. }
  120. const roomName = Strophe.getNodeFromJid(this.lobbyRoomJid);
  121. const customDomain = Strophe.getDomainFromJid(this.lobbyRoomJid);
  122. this.lobbyRoom = this.xmpp.createRoom(
  123. roomName, {
  124. customDomain,
  125. disableDiscoInfo: true,
  126. disableFocus: true,
  127. enableLobby: false
  128. }
  129. );
  130. if (displayName) {
  131. // remove previously set nickname
  132. this.lobbyRoom.removeFromPresence('nick');
  133. this.lobbyRoom.addToPresence('nick', {
  134. attributes: { xmlns: 'http://jabber.org/protocol/nick' },
  135. value: displayName
  136. });
  137. }
  138. if (isModerator) {
  139. this.lobbyRoom.addPresenceListener(EMAIL_COMMAND, (node, from) => {
  140. this.mainRoom.eventEmitter.emit(XMPPEvents.MUC_LOBBY_MEMBER_UPDATED, from, { email: node.value });
  141. });
  142. this.lobbyRoom.addEventListener(
  143. XMPPEvents.MUC_MEMBER_JOINED,
  144. // eslint-disable-next-line max-params
  145. (from, nick, role, isHiddenDomain, statsID, status, identity, botType, jid) => {
  146. // we need to ignore joins on lobby for participants that are already in the main room
  147. if (Object.values(this.mainRoom.members).find(m => m.jid === jid)) {
  148. return;
  149. }
  150. // we emit the new event on the main room so we can propagate
  151. // events to the conference
  152. this.mainRoom.eventEmitter.emit(
  153. XMPPEvents.MUC_LOBBY_MEMBER_JOINED,
  154. Strophe.getResourceFromJid(from),
  155. nick,
  156. identity ? identity.avatar : undefined
  157. );
  158. });
  159. this.lobbyRoom.addEventListener(
  160. XMPPEvents.MUC_MEMBER_LEFT, from => {
  161. // we emit the new event on the main room so we can propagate
  162. // events to the conference
  163. this.mainRoom.eventEmitter.emit(
  164. XMPPEvents.MUC_LOBBY_MEMBER_LEFT,
  165. Strophe.getResourceFromJid(from)
  166. );
  167. });
  168. this.lobbyRoom.addEventListener(
  169. XMPPEvents.MUC_DESTROYED,
  170. () => {
  171. // let's make sure we emit that all lobby users had left
  172. Object.keys(this.lobbyRoom.members)
  173. .forEach(j => this.mainRoom.eventEmitter.emit(
  174. XMPPEvents.MUC_LOBBY_MEMBER_LEFT, Strophe.getResourceFromJid(j)));
  175. this.lobbyRoom.clean();
  176. this.lobbyRoom = undefined;
  177. logger.info('Lobby room left(destroyed)!');
  178. });
  179. } else {
  180. // this should only be handled by those waiting in lobby
  181. this.lobbyRoom.addEventListener(XMPPEvents.KICKED, isSelfPresence => {
  182. if (isSelfPresence) {
  183. this.mainRoom.eventEmitter.emit(XMPPEvents.MUC_DENIED_ACCESS);
  184. this.lobbyRoom.clean();
  185. return;
  186. }
  187. });
  188. // As there is still reference of the main room
  189. // the invite will be detected and addressed to its eventEmitter, even though we are not in it
  190. // the invite message should be received directly to the xmpp conn in general
  191. this.mainRoom.addEventListener(
  192. XMPPEvents.INVITE_MESSAGE_RECEIVED,
  193. (roomJid, from, txt, invitePassword) => {
  194. logger.debug(`Received approval to join ${roomJid} ${from} ${txt}`);
  195. if (roomJid === this.mainRoom.roomjid) {
  196. // we are now allowed let's join and leave lobby
  197. this.mainRoom.join(invitePassword);
  198. this._leaveLobbyRoom();
  199. }
  200. });
  201. this.lobbyRoom.addEventListener(
  202. XMPPEvents.MUC_DESTROYED,
  203. (reason, jid) => {
  204. // we are receiving the jid of the main room
  205. // means we are invited to join, maybe lobby was disabled
  206. if (jid && jid === this.mainRoom.roomjid) {
  207. this.mainRoom.join();
  208. return;
  209. }
  210. this.lobbyRoom.clean();
  211. this.mainRoom.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
  212. });
  213. // If participant retries joining shared password while waiting in the lobby
  214. // and succeeds make sure we leave lobby
  215. this.mainRoom.addEventListener(
  216. XMPPEvents.MUC_JOINED,
  217. () => {
  218. this._leaveLobbyRoom();
  219. });
  220. }
  221. return new Promise((resolve, reject) => {
  222. this.lobbyRoom.addEventListener(XMPPEvents.MUC_JOINED, () => {
  223. resolve();
  224. // send our email, as we do not handle this on initial presence we need a second one
  225. if (email && !isModerator) {
  226. this.lobbyRoom.removeFromPresence(EMAIL_COMMAND);
  227. this.lobbyRoom.addToPresence(EMAIL_COMMAND, { value: email });
  228. this.lobbyRoom.sendPresence();
  229. }
  230. });
  231. this.lobbyRoom.addEventListener(XMPPEvents.ROOM_JOIN_ERROR, reject);
  232. this.lobbyRoom.addEventListener(XMPPEvents.ROOM_CONNECT_NOT_ALLOWED_ERROR, reject);
  233. this.lobbyRoom.addEventListener(XMPPEvents.ROOM_CONNECT_ERROR, reject);
  234. this.lobbyRoom.join();
  235. });
  236. }
  237. /**
  238. * Should be possible only for moderators.
  239. * @param id
  240. */
  241. denyAccess(id) {
  242. if (!this.isSupported() || !this.mainRoom.isModerator()) {
  243. return;
  244. }
  245. const jid = Object.keys(this.lobbyRoom.members)
  246. .find(j => Strophe.getResourceFromJid(j) === id);
  247. if (jid) {
  248. this.lobbyRoom.kick(jid);
  249. } else {
  250. logger.error(`Not found member for ${id} in lobby room.`);
  251. }
  252. }
  253. /**
  254. * Should be possible only for moderators.
  255. * @param id
  256. */
  257. approveAccess(id) {
  258. if (!this.isSupported() || !this.mainRoom.isModerator()) {
  259. return;
  260. }
  261. const memberRoomJid = Object.keys(this.lobbyRoom.members)
  262. .find(j => Strophe.getResourceFromJid(j) === id);
  263. if (memberRoomJid) {
  264. const jid = this.lobbyRoom.members[memberRoomJid].jid;
  265. const msgToSend
  266. = $msg({ to: this.mainRoom.roomjid })
  267. .c('x', { xmlns: 'http://jabber.org/protocol/muc#user' })
  268. .c('invite', { to: jid });
  269. this.xmpp.connection.sendIQ(msgToSend,
  270. () => { }, // eslint-disable-line no-empty-function
  271. e => {
  272. logger.error(`Error sending invite for ${jid}`, e);
  273. });
  274. } else {
  275. logger.error(`Not found member for ${memberRoomJid} in lobby room.`);
  276. }
  277. }
  278. }