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.ts 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import throttle from 'lodash/throttle';
  2. import { IStore } from '../app/types';
  3. import { NOTIFICATIONS_ENABLED } from '../base/flags/constants';
  4. import { getFeatureFlag } from '../base/flags/functions';
  5. import { getParticipantCount } from '../base/participants/functions';
  6. import {
  7. CLEAR_NOTIFICATIONS,
  8. HIDE_NOTIFICATION,
  9. SET_NOTIFICATIONS_ENABLED,
  10. SHOW_NOTIFICATION
  11. } from './actionTypes';
  12. import {
  13. NOTIFICATION_ICON,
  14. NOTIFICATION_TIMEOUT_TYPE,
  15. NOTIFICATION_TIMEOUT,
  16. NOTIFICATION_TYPE,
  17. SILENT_JOIN_THRESHOLD,
  18. SILENT_LEFT_THRESHOLD
  19. } from './constants';
  20. /**
  21. * Function that returns notification timeout value based on notification timeout type.
  22. *
  23. * @param {string} type - Notification type.
  24. * @param {Object} notificationTimeouts - Config notification timeouts.
  25. * @returns {number}
  26. */
  27. function getNotificationTimeout(type?: string, notificationTimeouts?: {
  28. long?: number;
  29. medium?: number;
  30. short?: number;
  31. }) {
  32. if (type === NOTIFICATION_TIMEOUT_TYPE.SHORT) {
  33. return notificationTimeouts?.short ?? NOTIFICATION_TIMEOUT.SHORT;
  34. } else if (type === NOTIFICATION_TIMEOUT_TYPE.MEDIUM) {
  35. return notificationTimeouts?.medium ?? NOTIFICATION_TIMEOUT.MEDIUM;
  36. } else if (type === NOTIFICATION_TIMEOUT_TYPE.LONG) {
  37. return notificationTimeouts?.long ?? NOTIFICATION_TIMEOUT.LONG;
  38. }
  39. return NOTIFICATION_TIMEOUT.STICKY;
  40. }
  41. /**
  42. * Clears (removes) all the notifications.
  43. *
  44. * @returns {{
  45. * type: CLEAR_NOTIFICATIONS
  46. * }}
  47. */
  48. export function clearNotifications() {
  49. return {
  50. type: CLEAR_NOTIFICATIONS
  51. };
  52. }
  53. /**
  54. * Removes the notification with the passed in id.
  55. *
  56. * @param {string} uid - The unique identifier for the notification to be
  57. * removed.
  58. * @returns {{
  59. * type: HIDE_NOTIFICATION,
  60. * uid: string
  61. * }}
  62. */
  63. export function hideNotification(uid: string) {
  64. return {
  65. type: HIDE_NOTIFICATION,
  66. uid
  67. };
  68. }
  69. /**
  70. * Stops notifications from being displayed.
  71. *
  72. * @param {boolean} enabled - Whether or not notifications should display.
  73. * @returns {{
  74. * type: SET_NOTIFICATIONS_ENABLED,
  75. * enabled: boolean
  76. * }}
  77. */
  78. export function setNotificationsEnabled(enabled: boolean) {
  79. return {
  80. type: SET_NOTIFICATIONS_ENABLED,
  81. enabled
  82. };
  83. }
  84. interface INotificationProps {
  85. appearance?: string;
  86. concatText?: boolean;
  87. customActionHandler?: Function[];
  88. customActionNameKey?: string[];
  89. description?: string;
  90. descriptionKey?: string;
  91. icon?: string;
  92. title?: string;
  93. titleArguments?: {
  94. [key: string]: string;
  95. };
  96. titleKey?: string;
  97. uid?: string;
  98. }
  99. /**
  100. * Queues an error notification for display.
  101. *
  102. * @param {Object} props - The props needed to show the notification component.
  103. * @param {string} type - Notification type.
  104. * @returns {Object}
  105. */
  106. export function showErrorNotification(props: INotificationProps, type?: string) {
  107. return showNotification({
  108. ...props,
  109. appearance: NOTIFICATION_TYPE.ERROR
  110. }, type);
  111. }
  112. /**
  113. * Queues a notification for display.
  114. *
  115. * @param {Object} props - The props needed to show the notification component.
  116. * @param {string} type - Timeout type.
  117. * @returns {Function}
  118. */
  119. export function showNotification(props: INotificationProps = {}, type?: string) {
  120. return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
  121. const { disabledNotifications = [], notifications, notificationTimeouts } = getState()['features/base/config'];
  122. const enabledFlag = getFeatureFlag(getState(), NOTIFICATIONS_ENABLED, true);
  123. const shouldDisplay = enabledFlag
  124. && !(disabledNotifications.includes(props.descriptionKey ?? '')
  125. || disabledNotifications.includes(props.titleKey ?? ''))
  126. && (!notifications
  127. || notifications.includes(props.descriptionKey ?? '')
  128. || notifications.includes(props.titleKey ?? ''));
  129. if (shouldDisplay) {
  130. return dispatch({
  131. type: SHOW_NOTIFICATION,
  132. props,
  133. timeout: getNotificationTimeout(type, notificationTimeouts),
  134. uid: props.uid || window.Date.now().toString()
  135. });
  136. }
  137. };
  138. }
  139. /**
  140. * Queues a warning notification for display.
  141. *
  142. * @param {Object} props - The props needed to show the notification component.
  143. * @param {string} type - Notification type.
  144. * @returns {Object}
  145. */
  146. export function showWarningNotification(props: INotificationProps, type?: string) {
  147. return showNotification({
  148. ...props,
  149. appearance: NOTIFICATION_TYPE.WARNING
  150. }, type);
  151. }
  152. /**
  153. * Queues a message notification for display.
  154. *
  155. * @param {Object} props - The props needed to show the notification component.
  156. * @param {string} type - Notification type.
  157. * @returns {Object}
  158. */
  159. export function showMessageNotification(props: INotificationProps, type?: string) {
  160. return showNotification({
  161. ...props,
  162. concatText: true,
  163. titleKey: 'notify.chatMessages',
  164. appearance: NOTIFICATION_TYPE.NORMAL,
  165. icon: NOTIFICATION_ICON.MESSAGE
  166. }, type);
  167. }
  168. /**
  169. * An array of names of participants that have joined the conference. The array
  170. * is replaced with an empty array as notifications are displayed.
  171. *
  172. * @private
  173. * @type {string[]}
  174. */
  175. let joinedParticipantsNames: string[] = [];
  176. /**
  177. * A throttled internal function that takes the internal list of participant
  178. * names, {@code joinedParticipantsNames}, and triggers the display of a
  179. * notification informing of their joining.
  180. *
  181. * @private
  182. * @type {Function}
  183. */
  184. const _throttledNotifyParticipantConnected = throttle((dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  185. const participantCount = getParticipantCount(getState());
  186. // Skip join notifications altogether for large meetings.
  187. if (participantCount > SILENT_JOIN_THRESHOLD) {
  188. joinedParticipantsNames = [];
  189. return;
  190. }
  191. const joinedParticipantsCount = joinedParticipantsNames.length;
  192. let notificationProps;
  193. if (joinedParticipantsCount >= 3) {
  194. notificationProps = {
  195. titleArguments: {
  196. name: joinedParticipantsNames[0]
  197. },
  198. titleKey: 'notify.connectedThreePlusMembers'
  199. };
  200. } else if (joinedParticipantsCount === 2) {
  201. notificationProps = {
  202. titleArguments: {
  203. first: joinedParticipantsNames[0],
  204. second: joinedParticipantsNames[1]
  205. },
  206. titleKey: 'notify.connectedTwoMembers'
  207. };
  208. } else if (joinedParticipantsCount) {
  209. notificationProps = {
  210. titleArguments: {
  211. name: joinedParticipantsNames[0]
  212. },
  213. titleKey: 'notify.connectedOneMember'
  214. };
  215. }
  216. if (notificationProps) {
  217. dispatch(
  218. showNotification(notificationProps, NOTIFICATION_TIMEOUT_TYPE.SHORT));
  219. }
  220. joinedParticipantsNames = [];
  221. }, 2000, { leading: false });
  222. /**
  223. * An array of names of participants that have left the conference. The array
  224. * is replaced with an empty array as notifications are displayed.
  225. *
  226. * @private
  227. * @type {string[]}
  228. */
  229. let leftParticipantsNames: string[] = [];
  230. /**
  231. * A throttled internal function that takes the internal list of participant
  232. * names, {@code leftParticipantsNames}, and triggers the display of a
  233. * notification informing of their leaving.
  234. *
  235. * @private
  236. * @type {Function}
  237. */
  238. const _throttledNotifyParticipantLeft = throttle((dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  239. const participantCount = getParticipantCount(getState());
  240. // Skip left notifications altogether for large meetings.
  241. if (participantCount > SILENT_LEFT_THRESHOLD) {
  242. leftParticipantsNames = [];
  243. return;
  244. }
  245. const leftParticipantsCount = leftParticipantsNames.length;
  246. let notificationProps;
  247. if (leftParticipantsCount >= 3) {
  248. notificationProps = {
  249. titleArguments: {
  250. name: leftParticipantsNames[0]
  251. },
  252. titleKey: 'notify.leftThreePlusMembers'
  253. };
  254. } else if (leftParticipantsCount === 2) {
  255. notificationProps = {
  256. titleArguments: {
  257. first: leftParticipantsNames[0],
  258. second: leftParticipantsNames[1]
  259. },
  260. titleKey: 'notify.leftTwoMembers'
  261. };
  262. } else if (leftParticipantsCount) {
  263. notificationProps = {
  264. titleArguments: {
  265. name: leftParticipantsNames[0]
  266. },
  267. titleKey: 'notify.leftOneMember'
  268. };
  269. }
  270. if (notificationProps) {
  271. dispatch(
  272. showNotification(notificationProps, NOTIFICATION_TIMEOUT_TYPE.SHORT));
  273. }
  274. leftParticipantsNames = [];
  275. }, 2000, { leading: false });
  276. /**
  277. * Queues the display of a notification of a participant having connected to
  278. * the meeting. The notifications are batched so that quick consecutive
  279. * connection events are shown in one notification.
  280. *
  281. * @param {string} displayName - The name of the participant that connected.
  282. * @returns {Function}
  283. */
  284. export function showParticipantJoinedNotification(displayName: string) {
  285. joinedParticipantsNames.push(displayName);
  286. return (dispatch: IStore['dispatch'], getState: IStore['getState']) =>
  287. _throttledNotifyParticipantConnected(dispatch, getState);
  288. }
  289. /**
  290. * Queues the display of a notification of a participant having left to
  291. * the meeting. The notifications are batched so that quick consecutive
  292. * connection events are shown in one notification.
  293. *
  294. * @param {string} displayName - The name of the participant that left.
  295. * @returns {Function}
  296. */
  297. export function showParticipantLeftNotification(displayName: string) {
  298. leftParticipantsNames.push(displayName);
  299. return (dispatch: IStore['dispatch'], getState: IStore['getState']) =>
  300. _throttledNotifyParticipantLeft(dispatch, getState);
  301. }