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.

actions.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // @flow
  2. import i18next from 'i18next';
  3. import _ from 'lodash';
  4. import type { Dispatch } from 'redux';
  5. import {
  6. conferenceLeft,
  7. conferenceWillLeave,
  8. createConference,
  9. getCurrentConference
  10. } from '../base/conference';
  11. import { setAudioMuted, setVideoMuted } from '../base/media';
  12. import { getRemoteParticipants } from '../base/participants';
  13. import { clearNotifications } from '../notifications';
  14. import {
  15. getBreakoutRooms,
  16. getMainRoom
  17. } from './functions';
  18. import logger from './logger';
  19. declare var APP: Object;
  20. /**
  21. * Action to create a breakout room.
  22. *
  23. * @param {string} name - Name / subject for the breakout room.
  24. * @returns {Function}
  25. */
  26. export function createBreakoutRoom(name?: string) {
  27. return (dispatch: Dispatch<any>, getState: Function) => {
  28. const rooms = getBreakoutRooms(getState);
  29. // TODO: remove this once we add UI to customize the name.
  30. const index = Object.keys(rooms).length;
  31. const subject = name || i18next.t('breakoutRooms.defaultName', { index });
  32. // $FlowExpectedError
  33. getCurrentConference(getState)?.getBreakoutRooms()
  34. ?.createBreakoutRoom(subject);
  35. };
  36. }
  37. /**
  38. * Action to close a room and send participants to the main room.
  39. *
  40. * @param {string} roomId - The id of the room to close.
  41. * @returns {Function}
  42. */
  43. export function closeBreakoutRoom(roomId: string) {
  44. return (dispatch: Dispatch<any>, getState: Function) => {
  45. const rooms = getBreakoutRooms(getState);
  46. const room = rooms[roomId];
  47. const mainRoom = getMainRoom(getState);
  48. if (room && mainRoom) {
  49. Object.values(room.participants).forEach(p => {
  50. // $FlowExpectedError
  51. dispatch(sendParticipantToRoom(p.jid, mainRoom.id));
  52. });
  53. }
  54. };
  55. }
  56. /**
  57. * Action to remove a breakout room.
  58. *
  59. * @param {string} breakoutRoomJid - The jid of the breakout room to remove.
  60. * @returns {Function}
  61. */
  62. export function removeBreakoutRoom(breakoutRoomJid: string) {
  63. return (dispatch: Dispatch<any>, getState: Function) => {
  64. // $FlowExpectedError
  65. getCurrentConference(getState)?.getBreakoutRooms()
  66. ?.removeBreakoutRoom(breakoutRoomJid);
  67. };
  68. }
  69. /**
  70. * Action to auto-assign the participants to breakout rooms.
  71. *
  72. * @returns {Function}
  73. */
  74. export function autoAssignToBreakoutRooms() {
  75. return (dispatch: Dispatch<any>, getState: Function) => {
  76. const rooms = getBreakoutRooms(getState);
  77. const breakoutRooms = _.filter(rooms, (room: Object) => !room.isMainRoom);
  78. if (breakoutRooms) {
  79. const participantIds = Array.from(getRemoteParticipants(getState).keys());
  80. const length = Math.ceil(participantIds.length / breakoutRooms.length);
  81. _.chunk(_.shuffle(participantIds), length).forEach((group, index) =>
  82. group.forEach(participantId => {
  83. dispatch(sendParticipantToRoom(participantId, breakoutRooms[index].id));
  84. })
  85. );
  86. }
  87. };
  88. }
  89. /**
  90. * Action to send a participant to a room.
  91. *
  92. * @param {string} participantId - The participant id.
  93. * @param {string} roomId - The room id.
  94. * @returns {Function}
  95. */
  96. export function sendParticipantToRoom(participantId: string, roomId: string) {
  97. return (dispatch: Dispatch<any>, getState: Function) => {
  98. const rooms = getBreakoutRooms(getState);
  99. const room = rooms[roomId];
  100. if (!room) {
  101. logger.warn(`Invalid room: ${roomId}`);
  102. return;
  103. }
  104. // Get the full JID of the participant. We could be getting the endpoint ID or
  105. // a participant JID. We want to find the connection JID.
  106. const participantJid = _findParticipantJid(getState, participantId);
  107. if (!participantJid) {
  108. logger.warn(`Could not find participant ${participantId}`);
  109. return;
  110. }
  111. // $FlowExpectedError
  112. getCurrentConference(getState)?.getBreakoutRooms()
  113. ?.sendParticipantToRoom(participantJid, room.jid);
  114. };
  115. }
  116. /**
  117. * Action to move to a room.
  118. *
  119. * @param {string} roomId - The room id to move to. If omitted move to the main room.
  120. * @returns {Function}
  121. */
  122. export function moveToRoom(roomId?: string) {
  123. return (dispatch: Dispatch<any>, getState: Function) => {
  124. let _roomId = roomId || getMainRoom(getState)?.id;
  125. // Check if we got a full JID.
  126. // $FlowExpectedError
  127. if (_roomId?.indexOf('@') !== -1) {
  128. // $FlowExpectedError
  129. const [ id, ...domainParts ] = _roomId.split('@');
  130. // On mobile we first store the room and the connection is created
  131. // later, so let's attach the domain to the room String object as
  132. // a little hack.
  133. // eslint-disable-next-line no-new-wrappers
  134. _roomId = new String(id);
  135. // $FlowExpectedError
  136. _roomId.domain = domainParts.join('@');
  137. }
  138. if (navigator.product === 'ReactNative') {
  139. const conference = getCurrentConference(getState);
  140. const { audio, video } = getState()['features/base/media'];
  141. dispatch(conferenceWillLeave(conference));
  142. conference.leave()
  143. .catch(error => {
  144. logger.warn(
  145. 'JitsiConference.leave() rejected with:',
  146. error);
  147. dispatch(conferenceLeft(conference));
  148. });
  149. dispatch(clearNotifications());
  150. // dispatch(setRoom(_roomId));
  151. dispatch(createConference(_roomId));
  152. dispatch(setAudioMuted(audio.muted));
  153. dispatch(setVideoMuted(video.muted));
  154. } else {
  155. APP.conference.leaveRoom()
  156. .finally(() => APP.conference.joinRoom(_roomId));
  157. }
  158. };
  159. }
  160. /**
  161. * Finds a participant's connection JID given its ID.
  162. *
  163. * @param {Function} getState - The redux store state getter.
  164. * @param {string} participantId - ID of the given participant.
  165. * @returns {string|undefined} - The participant connection JID if found.
  166. */
  167. function _findParticipantJid(getState: Function, participantId: string) {
  168. const conference = getCurrentConference(getState);
  169. if (!conference) {
  170. return;
  171. }
  172. // Get the full JID of the participant. We could be getting the endpoint ID or
  173. // a participant JID. We want to find the connection JID.
  174. let _participantId = participantId;
  175. let participantJid;
  176. if (!participantId.includes('@')) {
  177. const p = conference.getParticipantById(participantId);
  178. // $FlowExpectedError
  179. _participantId = p?.getJid(); // This will be the room JID.
  180. }
  181. if (_participantId) {
  182. const rooms = getBreakoutRooms(getState);
  183. for (const room of Object.values(rooms)) {
  184. // $FlowExpectedError
  185. const participants = room.participants || {};
  186. const p = participants[_participantId]
  187. // $FlowExpectedError
  188. || Object.values(participants).find(item => item.jid === _participantId);
  189. if (p) {
  190. participantJid = p.jid;
  191. break;
  192. }
  193. }
  194. }
  195. return participantJid;
  196. }