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.

middleware.js 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // @flow
  2. import { getCurrentConference } from '../base/conference';
  3. import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
  4. import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
  5. import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
  6. import { playSound } from '../base/sounds';
  7. import { INCOMING_MSG_SOUND_ID } from '../chat/constants';
  8. import {
  9. NOTIFICATION_TIMEOUT_TYPE,
  10. NOTIFICATION_TYPE,
  11. showNotification
  12. } from '../notifications';
  13. import { RECEIVE_POLL } from './actionTypes';
  14. import { clearPolls, receiveAnswer, receivePoll } from './actions';
  15. import {
  16. COMMAND_ANSWER_POLL,
  17. COMMAND_NEW_POLL,
  18. COMMAND_OLD_POLLS
  19. } from './constants';
  20. import type { Answer, Poll } from './types';
  21. /**
  22. * Set up state change listener to perform maintenance tasks when the conference
  23. * is left or failed, e.g. Clear messages or close the chat modal if it's left
  24. * open.
  25. */
  26. StateListenerRegistry.register(
  27. state => getCurrentConference(state),
  28. (conference, { dispatch }, previousConference) => {
  29. if (conference !== previousConference) {
  30. // conference changed, left or failed...
  31. // clean old polls
  32. dispatch(clearPolls());
  33. }
  34. });
  35. const parsePollData = (pollData): Poll | null => {
  36. if (typeof pollData !== 'object' || pollData === null) {
  37. return null;
  38. }
  39. const { id, senderId, senderName, question, answers } = pollData;
  40. if (typeof id !== 'string' || typeof senderId !== 'string' || typeof senderName !== 'string'
  41. || typeof question !== 'string' || !(answers instanceof Array)) {
  42. return null;
  43. }
  44. const answersParsed = [];
  45. for (const answer of answers) {
  46. const voters = new Map();
  47. for (const [ voterId, voter ] of Object.entries(answer.voters)) {
  48. if (typeof voter !== 'string') {
  49. return null;
  50. }
  51. voters.set(voterId, voter);
  52. }
  53. answersParsed.push({
  54. name: answer.name,
  55. voters
  56. });
  57. }
  58. return {
  59. changingVote: false,
  60. senderId,
  61. senderName,
  62. question,
  63. showResults: true,
  64. lastVote: null,
  65. answers: answersParsed
  66. };
  67. };
  68. MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
  69. const result = next(action);
  70. switch (action.type) {
  71. case CONFERENCE_JOIN_IN_PROGRESS: {
  72. const { conference } = action;
  73. conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  74. (_, data) => _handleReceivePollsMessage(data, dispatch));
  75. conference.on(JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
  76. (_, data) => _handleReceivePollsMessage(data, dispatch));
  77. break;
  78. }
  79. // Middleware triggered when a poll is received
  80. case RECEIVE_POLL: {
  81. const state = getState();
  82. const isChatOpen: boolean = state['features/chat'].isOpen;
  83. const isPollsTabFocused: boolean = state['features/chat'].isPollsTabFocused;
  84. // Finally, we notify user they received a new poll if their pane is not opened
  85. if (action.notify && (!isChatOpen || !isPollsTabFocused)) {
  86. dispatch(playSound(INCOMING_MSG_SOUND_ID));
  87. }
  88. break;
  89. }
  90. }
  91. return result;
  92. });
  93. /**
  94. * Handles receiving of polls message command.
  95. *
  96. * @param {Object} data - The json data carried by the polls message.
  97. * @param {Function} dispatch - The dispatch function.
  98. *
  99. * @returns {void}
  100. */
  101. function _handleReceivePollsMessage(data, dispatch) {
  102. switch (data.type) {
  103. case COMMAND_NEW_POLL: {
  104. const { question, answers, pollId, senderId, senderName } = data;
  105. const poll = {
  106. changingVote: false,
  107. senderId,
  108. senderName,
  109. showResults: false,
  110. lastVote: null,
  111. question,
  112. answers: answers.map(answer => {
  113. return {
  114. name: answer,
  115. voters: new Map()
  116. };
  117. })
  118. };
  119. dispatch(receivePoll(pollId, poll, true));
  120. dispatch(showNotification({
  121. appearance: NOTIFICATION_TYPE.NORMAL,
  122. titleKey: 'polls.notification.title',
  123. descriptionKey: 'polls.notification.description'
  124. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  125. break;
  126. }
  127. case COMMAND_ANSWER_POLL: {
  128. const { pollId, answers, voterId, voterName } = data;
  129. const receivedAnswer: Answer = {
  130. voterId,
  131. voterName,
  132. pollId,
  133. answers
  134. };
  135. dispatch(receiveAnswer(pollId, receivedAnswer));
  136. break;
  137. }
  138. case COMMAND_OLD_POLLS: {
  139. const { polls } = data;
  140. for (const pollData of polls) {
  141. const poll = parsePollData(pollData);
  142. if (poll === null) {
  143. console.warn('[features/polls] Invalid old poll data');
  144. } else {
  145. dispatch(receivePoll(pollData.id, poll, false));
  146. }
  147. }
  148. break;
  149. }
  150. }
  151. }