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.any.ts 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import { IStore } from '../app/types';
  2. import { conferenceWillJoin, setPassword } from '../base/conference/actions';
  3. import { getCurrentConference, sendLocalParticipant } from '../base/conference/functions';
  4. import { getLocalParticipant } from '../base/participants/functions';
  5. import { IParticipant } from '../base/participants/types';
  6. import { onLobbyChatInitialized, removeLobbyChatParticipant, sendMessage } from '../chat/actions.any';
  7. import { LOBBY_CHAT_MESSAGE } from '../chat/constants';
  8. import { handleLobbyMessageReceived } from '../chat/middleware';
  9. import { hideNotification, showNotification } from '../notifications/actions';
  10. import { LOBBY_NOTIFICATION_ID } from '../notifications/constants';
  11. import { joinConference } from '../prejoin/actions';
  12. import {
  13. KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
  14. KNOCKING_PARTICIPANT_LEFT,
  15. REMOVE_LOBBY_CHAT_WITH_MODERATOR,
  16. SET_KNOCKING_STATE,
  17. SET_LOBBY_MODE_ENABLED,
  18. SET_LOBBY_PARTICIPANT_CHAT_STATE,
  19. SET_LOBBY_VISIBILITY,
  20. SET_PASSWORD_JOIN_FAILED
  21. } from './actionTypes';
  22. import { LOBBY_CHAT_INITIALIZED, MODERATOR_IN_CHAT_WITH_LEFT } from './constants';
  23. import { getKnockingParticipants, getLobbyConfig, getLobbyEnabled } from './functions';
  24. import { IKnockingParticipant } from './types';
  25. /**
  26. * Tries to join with a preset password.
  27. *
  28. * @param {string} password - The password to join with.
  29. * @returns {Function}
  30. */
  31. export function joinWithPassword(password: string) {
  32. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  33. const conference = getCurrentConference(getState);
  34. dispatch(setPassword(conference, conference?.join, password));
  35. };
  36. }
  37. /**
  38. * Action to be dispatched when a knocking poarticipant leaves before any response.
  39. *
  40. * @param {string} id - The ID of the participant.
  41. * @returns {{
  42. * id: string,
  43. * type: KNOCKING_PARTICIPANT_LEFT
  44. * }}
  45. */
  46. export function knockingParticipantLeft(id: string) {
  47. return {
  48. id,
  49. type: KNOCKING_PARTICIPANT_LEFT
  50. };
  51. }
  52. /**
  53. * Action to be executed when a participant starts knocking or an already knocking participant gets updated.
  54. *
  55. * @param {Object} participant - The knocking participant.
  56. * @returns {{
  57. * participant: Object,
  58. * type: KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED
  59. * }}
  60. */
  61. export function participantIsKnockingOrUpdated(participant: IKnockingParticipant | Object) {
  62. return {
  63. participant,
  64. type: KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED
  65. };
  66. }
  67. /**
  68. * Handles a knocking participant and dismisses the notification.
  69. *
  70. * @param {string} id - The id of the knocking participant.
  71. * @param {boolean} approved - True if the participant is approved, false otherwise.
  72. * @returns {Function}
  73. */
  74. export function answerKnockingParticipant(id: string, approved: boolean) {
  75. return async (dispatch: IStore['dispatch']) => {
  76. dispatch(setKnockingParticipantApproval(id, approved));
  77. dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
  78. };
  79. }
  80. /**
  81. * Approves (lets in) or rejects a knocking participant.
  82. *
  83. * @param {string} id - The id of the knocking participant.
  84. * @param {boolean} approved - True if the participant is approved, false otherwise.
  85. * @returns {Function}
  86. */
  87. export function setKnockingParticipantApproval(id: string, approved: boolean) {
  88. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  89. const conference = getCurrentConference(getState);
  90. if (conference) {
  91. if (approved) {
  92. conference.lobbyApproveAccess(id);
  93. } else {
  94. conference.lobbyDenyAccess(id);
  95. }
  96. }
  97. };
  98. }
  99. /**
  100. * Action used to admit multiple participants in the conference.
  101. *
  102. * @param {Array<Object>} participants - A list of knocking participants.
  103. * @returns {void}
  104. */
  105. export function admitMultiple(participants: Array<IKnockingParticipant>) {
  106. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  107. const conference = getCurrentConference(getState);
  108. participants.forEach(p => {
  109. conference?.lobbyApproveAccess(p.id);
  110. });
  111. };
  112. }
  113. /**
  114. * Approves the request of a knocking participant to join the meeting.
  115. *
  116. * @param {string} id - The id of the knocking participant.
  117. * @returns {Function}
  118. */
  119. export function approveKnockingParticipant(id: string) {
  120. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  121. const conference = getCurrentConference(getState);
  122. conference?.lobbyApproveAccess(id);
  123. };
  124. }
  125. /**
  126. * Denies the request of a knocking participant to join the meeting.
  127. *
  128. * @param {string} id - The id of the knocking participant.
  129. * @returns {Function}
  130. */
  131. export function rejectKnockingParticipant(id: string) {
  132. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  133. const conference = getCurrentConference(getState);
  134. conference?.lobbyDenyAccess(id);
  135. };
  136. }
  137. /**
  138. * Action to set the knocking state of the participant.
  139. *
  140. * @param {boolean} knocking - The new state.
  141. * @returns {{
  142. * state: boolean,
  143. * type: SET_KNOCKING_STATE
  144. * }}
  145. */
  146. export function setKnockingState(knocking: boolean) {
  147. return {
  148. knocking,
  149. type: SET_KNOCKING_STATE
  150. };
  151. }
  152. /**
  153. * Action to set the new state of the lobby mode.
  154. *
  155. * @param {boolean} enabled - The new state to set.
  156. * @returns {{
  157. * enabled: boolean,
  158. * type: SET_LOBBY_MODE_ENABLED
  159. * }}
  160. */
  161. export function setLobbyModeEnabled(enabled: boolean) {
  162. return {
  163. enabled,
  164. type: SET_LOBBY_MODE_ENABLED
  165. };
  166. }
  167. /**
  168. * Action to be dispatched when we failed to join with a password.
  169. *
  170. * @param {boolean} failed - True of recent password join failed.
  171. * @returns {{
  172. * failed: boolean,
  173. * type: SET_PASSWORD_JOIN_FAILED
  174. * }}
  175. */
  176. export function setPasswordJoinFailed(failed: boolean) {
  177. return {
  178. failed,
  179. type: SET_PASSWORD_JOIN_FAILED
  180. };
  181. }
  182. /**
  183. * Starts knocking and waiting for approval.
  184. *
  185. * @returns {Function}
  186. */
  187. export function startKnocking() {
  188. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  189. const state = getState();
  190. const { membersOnly } = state['features/base/conference'];
  191. if (!membersOnly) {
  192. // no membersOnly, this means we got lobby screen shown as someone
  193. // tried to join a conference that has lobby enabled without setting display name
  194. // join conference should trigger the lobby/member_only path after setting the display name
  195. // this is possible only for web, where we can join without a prejoin screen
  196. dispatch(joinConference());
  197. return;
  198. }
  199. const localParticipant = getLocalParticipant(state);
  200. dispatch(conferenceWillJoin(membersOnly));
  201. // We need to update the conference object with the current display name, if approved
  202. // we want to send that display name, it was not updated in case when pre-join is disabled
  203. sendLocalParticipant(state, membersOnly);
  204. membersOnly?.joinLobby(localParticipant?.name, localParticipant?.email);
  205. dispatch(setLobbyMessageListener());
  206. dispatch(setKnockingState(true));
  207. };
  208. }
  209. /**
  210. * Action to toggle lobby mode on or off.
  211. *
  212. * @param {boolean} enabled - The desired (new) state of the lobby mode.
  213. * @returns {Function}
  214. */
  215. export function toggleLobbyMode(enabled: boolean) {
  216. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  217. const conference = getCurrentConference(getState);
  218. if (enabled) {
  219. conference?.enableLobby();
  220. } else {
  221. conference?.disableLobby();
  222. }
  223. };
  224. }
  225. /**
  226. * Action to open the lobby screen.
  227. *
  228. * @returns {openDialog}
  229. */
  230. export function openLobbyScreen() {
  231. return {
  232. type: SET_LOBBY_VISIBILITY,
  233. visible: true
  234. };
  235. }
  236. /**
  237. * Action to hide the lobby screen.
  238. *
  239. * @returns {hideDialog}
  240. */
  241. export function hideLobbyScreen() {
  242. return {
  243. type: SET_LOBBY_VISIBILITY,
  244. visible: false
  245. };
  246. }
  247. /**
  248. * Action to handle chat initialized in the lobby room.
  249. *
  250. * @param {Object} payload - The payload received,
  251. * contains the information about the two participants
  252. * that will chat with each other in the lobby room.
  253. *
  254. * @returns {Promise<void>}
  255. */
  256. export function handleLobbyChatInitialized(payload: { attendee: IParticipant; moderator: IParticipant; }) {
  257. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  258. const state = getState();
  259. const conference = getCurrentConference(state);
  260. const id = conference?.myLobbyUserId();
  261. dispatch({
  262. type: SET_LOBBY_PARTICIPANT_CHAT_STATE,
  263. participant: payload.attendee,
  264. moderator: payload.moderator
  265. });
  266. dispatch(onLobbyChatInitialized(payload));
  267. const attendeeIsKnocking = getKnockingParticipants(state).some(p => p.id === payload.attendee.id);
  268. if (attendeeIsKnocking && conference?.getRole() === 'moderator' && payload.moderator.id !== id) {
  269. dispatch(showNotification({
  270. titleKey: 'lobby.lobbyChatStartedNotification',
  271. titleArguments: {
  272. moderator: payload.moderator.name ?? '',
  273. attendee: payload.attendee.name ?? ''
  274. }
  275. }));
  276. }
  277. };
  278. }
  279. /**
  280. * Action to send message to the moderator.
  281. *
  282. * @param {string} message - The message to be sent.
  283. *
  284. * @returns {Promise<void>}
  285. */
  286. export function onSendMessage(message: string) {
  287. return async (dispatch: IStore['dispatch']) => {
  288. dispatch(sendMessage(message));
  289. };
  290. }
  291. /**
  292. * Action to send lobby message to every participant. Only allowed for moderators.
  293. *
  294. * @param {Object} message - The message to be sent.
  295. *
  296. * @returns {Promise<void>}
  297. */
  298. export function sendLobbyChatMessage(message: Object) {
  299. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  300. const conference = getCurrentConference(getState);
  301. conference?.sendLobbyMessage(message);
  302. };
  303. }
  304. /**
  305. * Sets lobby listeners if lobby has been enabled.
  306. *
  307. * @returns {Function}
  308. */
  309. export function maybeSetLobbyChatMessageListener() {
  310. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  311. const state = getState();
  312. const lobbyEnabled = getLobbyEnabled(state);
  313. if (lobbyEnabled) {
  314. dispatch(setLobbyMessageListener());
  315. }
  316. };
  317. }
  318. /**
  319. * Action to handle the event when a moderator leaves during lobby chat.
  320. *
  321. * @param {string} participantId - The participant id of the moderator who left.
  322. * @returns {Function}
  323. */
  324. export function updateLobbyParticipantOnLeave(participantId: string) {
  325. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  326. const state = getState();
  327. const { knocking, knockingParticipants } = state['features/lobby'];
  328. const { lobbyMessageRecipient } = state['features/chat'];
  329. const { conference } = state['features/base/conference'];
  330. if (knocking && lobbyMessageRecipient && lobbyMessageRecipient.id === participantId) {
  331. return dispatch(removeLobbyChatParticipant(true));
  332. }
  333. if (!knocking) {
  334. // inform knocking participant when their moderator leaves
  335. const participantToNotify = knockingParticipants.find(p => p.chattingWithModerator === participantId);
  336. if (participantToNotify) {
  337. conference?.sendLobbyMessage({
  338. type: MODERATOR_IN_CHAT_WITH_LEFT,
  339. moderatorId: participantToNotify.chattingWithModerator
  340. }, participantToNotify.id);
  341. }
  342. dispatch({
  343. type: REMOVE_LOBBY_CHAT_WITH_MODERATOR,
  344. moderatorId: participantId
  345. });
  346. }
  347. };
  348. }
  349. /**
  350. * Handles all messages received in the lobby room.
  351. *
  352. * @returns {Function}
  353. */
  354. export function setLobbyMessageListener() {
  355. return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  356. const state = getState();
  357. const conference = getCurrentConference(state);
  358. const { enableChat = true } = getLobbyConfig(state);
  359. if (!enableChat) {
  360. return;
  361. }
  362. conference?.addLobbyMessageListener((message: any, participantId: string) => {
  363. if (message.type === LOBBY_CHAT_MESSAGE) {
  364. return dispatch(handleLobbyMessageReceived(message.message, participantId));
  365. }
  366. if (message.type === LOBBY_CHAT_INITIALIZED) {
  367. return dispatch(handleLobbyChatInitialized(message));
  368. }
  369. if (message.type === MODERATOR_IN_CHAT_WITH_LEFT) {
  370. return dispatch(updateLobbyParticipantOnLeave(message.moderatorId));
  371. }
  372. });
  373. };
  374. }