snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
154
react/features/polls/middleware.ts
Normal file
154
react/features/polls/middleware.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getParticipantById, getParticipantDisplayName } from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { playSound } from '../base/sounds/actions';
|
||||
import { ChatTabs, INCOMING_MSG_SOUND_ID } from '../chat/constants';
|
||||
import { arePollsDisabled } from '../conference/functions.any';
|
||||
import { showNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
|
||||
|
||||
import { RECEIVE_POLL } from './actionTypes';
|
||||
import { clearPolls, receiveAnswer, receivePoll } from './actions';
|
||||
import { IAnswerData, IIncomingAnswerData } from './types';
|
||||
|
||||
/**
|
||||
* The maximum number of answers a poll can have.
|
||||
*/
|
||||
const MAX_ANSWERS = 32;
|
||||
|
||||
/**
|
||||
* Normalizes the answers received for a poll, keeping only the expected fields
|
||||
* and shapes. Answers arrive from remote participants and cannot be trusted to
|
||||
* match the expected structure, so we coerce the name to a string and only keep
|
||||
* voters when it is an array.
|
||||
*
|
||||
* @param {any} answers - The raw answers array carried by the polls message.
|
||||
* @returns {Array<IAnswerData>}
|
||||
*/
|
||||
function _sanitizeAnswers(answers: any): Array<IAnswerData> {
|
||||
if (!Array.isArray(answers)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return answers.slice(0, MAX_ANSWERS).map((answer: any) => ({
|
||||
name: String(answer?.name ?? ''),
|
||||
voters: Array.isArray(answer?.voters) ? answer.voters : undefined
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up state change listener to perform maintenance tasks when the conference
|
||||
* is left or failed, e.g. Clear messages or close the chat modal if it's left
|
||||
* open.
|
||||
* When joining new conference set up the listeners for polls.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
state => getCurrentConference(state),
|
||||
(conference, { dispatch, getState }, previousConference): void => {
|
||||
if (conference !== previousConference) {
|
||||
dispatch(clearPolls());
|
||||
|
||||
if (conference && !previousConference) {
|
||||
conference.on(JitsiConferenceEvents.POLL_RECEIVED, (data: any) => {
|
||||
_handleReceivedPollsData(data, dispatch, getState);
|
||||
});
|
||||
conference.on(JitsiConferenceEvents.POLL_ANSWER_RECEIVED, (data: any) => {
|
||||
_handleReceivedPollsAnswer(data, dispatch, getState);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case RECEIVE_POLL: {
|
||||
const state = getState();
|
||||
|
||||
if (arePollsDisabled(state)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const isChatOpen: boolean = state['features/chat'].isOpen;
|
||||
const isPollsTabFocused: boolean = state['features/chat'].focusedTab === ChatTabs.POLLS;
|
||||
|
||||
// Finally, we notify user they received a new poll if their pane is not opened
|
||||
if (action.notify && (!isChatOpen || !isPollsTabFocused)) {
|
||||
dispatch(playSound(INCOMING_MSG_SOUND_ID));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles receiving of new or history polls to load.
|
||||
*
|
||||
* @param {Object} data - The json data carried by the polls message.
|
||||
* @param {Function} dispatch - The dispatch function.
|
||||
* @param {Function} getState - The getState function.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleReceivedPollsData(data: any, dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
if (arePollsDisabled(getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { pollId, answers, senderId, question, history } = data;
|
||||
const poll = {
|
||||
changingVote: false,
|
||||
senderId,
|
||||
showResults: false,
|
||||
lastVote: null,
|
||||
question,
|
||||
answers: _sanitizeAnswers(answers),
|
||||
saved: false,
|
||||
editing: false,
|
||||
pollId
|
||||
};
|
||||
|
||||
dispatch(receivePoll(poll, !history));
|
||||
|
||||
if (!history) {
|
||||
dispatch(showNotification({
|
||||
appearance: NOTIFICATION_TYPE.NORMAL,
|
||||
titleKey: 'polls.notification.title',
|
||||
descriptionKey: 'polls.notification.description'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles receiving of pools answers.
|
||||
*
|
||||
* @param {Object} data - The json data carried by the polls message.
|
||||
* @param {Function} dispatch - The dispatch function.
|
||||
* @param {Function} getState - The getState function.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleReceivedPollsAnswer(data: any, dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
if (arePollsDisabled(getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { pollId, answers, senderId, senderName } = data;
|
||||
|
||||
const receivedAnswer: IIncomingAnswerData = {
|
||||
answers: Array.isArray(answers) ? answers.slice(0, MAX_ANSWERS).map(Boolean) : [],
|
||||
pollId,
|
||||
senderId,
|
||||
voterName: getParticipantById(getState(), senderId)
|
||||
? getParticipantDisplayName(getState(), senderId) : senderName
|
||||
};
|
||||
|
||||
dispatch(receiveAnswer(receivedAnswer));
|
||||
}
|
||||
Reference in New Issue
Block a user