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 10KB

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