From 769f0a845233bc17c835f1dbcaf36e71339f963c Mon Sep 17 00:00:00 2001 From: Robert Pintilii Date: Fri, 20 May 2022 11:45:09 +0100 Subject: [PATCH] feat: Add name overwrite API (#11543) --- modules/API/API.js | 21 ++++- modules/API/external/external_api.js | 2 + .../features/base/participants/actionTypes.ts | 19 +++++ react/features/base/participants/actions.js | 32 +++++++- .../features/base/participants/middleware.js | 79 +++++++++++++++++++ react/features/base/participants/reducer.js | 13 +++ react/features/breakout-rooms/middleware.js | 48 +++++++++-- 7 files changed, 207 insertions(+), 7 deletions(-) diff --git a/modules/API/API.js b/modules/API/API.js index e4a58ae5c..54b61d371 100644 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -40,7 +40,8 @@ import { isParticipantModerator, isLocalParticipantModerator, hasRaisedHand, - grantModerator + grantModerator, + overwriteParticipantsNames } from '../../react/features/base/participants'; import { updateSettings } from '../../react/features/base/settings'; import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks'; @@ -428,6 +429,11 @@ function initCommands() { logger.error('Failed sending endpoint text message', err); } }, + 'overwrite-names': participantList => { + logger.debug('Overwrite names command received'); + + APP.store.dispatch(overwriteParticipantsNames(participantList)); + }, 'toggle-e2ee': enabled => { logger.debug('Toggle E2EE key command received'); APP.store.dispatch(toggleE2EE(enabled)); @@ -1660,6 +1666,19 @@ class API { }); } + /** + * Notify external application that the breakout rooms changed. + * + * @param {Array} rooms - Array of breakout rooms. + * @returns {void} + */ + notifyBreakoutRoomsUpdated(rooms) { + this._sendEvent({ + name: 'breakout-rooms-updated', + rooms + }); + } + /** * Disposes the allocated resources. * diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index bf859ad3e..1acf1d197 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -46,6 +46,7 @@ const commands = { kickParticipant: 'kick-participant', muteEveryone: 'mute-everyone', overwriteConfig: 'overwrite-config', + overwriteNames: 'overwrite-names', password: 'password', pinParticipant: 'pin-participant', rejectParticipant: 'reject-participant', @@ -94,6 +95,7 @@ const events = { 'avatar-changed': 'avatarChanged', 'audio-availability-changed': 'audioAvailabilityChanged', 'audio-mute-status-changed': 'audioMuteStatusChanged', + 'breakout-rooms-updated': 'breakoutRoomsUpdated', 'browser-support': 'browserSupport', 'camera-error': 'cameraError', 'chat-updated': 'chatUpdated', diff --git a/react/features/base/participants/actionTypes.ts b/react/features/base/participants/actionTypes.ts index b31534dfd..daa0ccf0d 100644 --- a/react/features/base/participants/actionTypes.ts +++ b/react/features/base/participants/actionTypes.ts @@ -212,3 +212,22 @@ export const RAISE_HAND_UPDATED = 'RAISE_HAND_UPDATED'; * } */ export const LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED = 'LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED' + +/** + * The type of Redux action which overwrites the name of a participant. + * { + * type: OVERWRITE_PARTICIPANT_NAME, + * id: string, + * name: string + * } + */ +export const OVERWRITE_PARTICIPANT_NAME = 'OVERWRITE_PARTICIPANT_NAME'; + +/** + * The type of Redux action which overwrites the names of multiple participants. + * { + * type: OVERWRITE_PARTICIPANTS_NAMES, + * participantsList: Array, + * } + */ +export const OVERWRITE_PARTICIPANTS_NAMES = 'OVERWRITE_PARTICIPANTS_NAMES'; diff --git a/react/features/base/participants/actions.js b/react/features/base/participants/actions.js index 67bf3f702..0842b3183 100644 --- a/react/features/base/participants/actions.js +++ b/react/features/base/participants/actions.js @@ -18,7 +18,9 @@ import { PIN_PARTICIPANT, SCREENSHARE_PARTICIPANT_NAME_CHANGED, SET_LOADABLE_AVATAR_URL, - RAISE_HAND_UPDATED + RAISE_HAND_UPDATED, + OVERWRITE_PARTICIPANT_NAME, + OVERWRITE_PARTICIPANTS_NAMES } from './actionTypes'; import { DISCO_REMOTE_CONTROL_FEATURE @@ -653,3 +655,31 @@ export function localParticipantAudioLevelChanged(level) { level }; } + +/** + * Overwrites the name of the participant with the given id. + * + * @param {string} id - Participant id;. + * @param {string} name - New participant name. + * @returns {Object} + */ +export function overwriteParticipantName(id, name) { + return { + type: OVERWRITE_PARTICIPANT_NAME, + id, + name + }; +} + +/** + * Overwrites the names of the given participants. + * + * @param {Array} participantList - The list of participants to overwrite. + * @returns {Object} + */ +export function overwriteParticipantsNames(participantList) { + return { + type: OVERWRITE_PARTICIPANTS_NAMES, + participantList + }; +} diff --git a/react/features/base/participants/middleware.js b/react/features/base/participants/middleware.js index 3a1c2f98e..c9b1c5cce 100644 --- a/react/features/base/participants/middleware.js +++ b/react/features/base/participants/middleware.js @@ -5,6 +5,8 @@ import { batch } from 'react-redux'; import UIEvents from '../../../../service/UI/UIEvents'; import { approveParticipant } from '../../av-moderation/actions'; +import { UPDATE_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes'; +import { getBreakoutRooms } from '../../breakout-rooms/functions'; import { toggleE2EE } from '../../e2ee/actions'; import { MAX_MODE } from '../../e2ee/constants'; import { @@ -34,6 +36,8 @@ import { LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED, LOCAL_PARTICIPANT_RAISE_HAND, MUTE_REMOTE_PARTICIPANT, + OVERWRITE_PARTICIPANTS_NAMES, + OVERWRITE_PARTICIPANT_NAME, PARTICIPANT_DISPLAY_NAME_CHANGED, PARTICIPANT_JOINED, PARTICIPANT_LEFT, @@ -44,6 +48,7 @@ import { localParticipantIdChanged, localParticipantJoined, localParticipantLeft, + overwriteParticipantName, participantLeft, participantUpdated, raiseHand, @@ -68,6 +73,7 @@ import { hasRaisedHand, isLocalParticipantModerator } from './functions'; +import logger from './logger'; import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds'; import './subscriber'; @@ -231,6 +237,74 @@ MiddlewareRegistry.register(store => next => action => { case PARTICIPANT_UPDATED: return _participantJoinedOrUpdated(store, next, action); + case OVERWRITE_PARTICIPANTS_NAMES: { + const { participantList } = action; + + if (!Array.isArray(participantList)) { + logger.error('Overwrite names failed. Argument is not an array.'); + + return; + } + batch(() => { + participantList.forEach(p => { + store.dispatch(overwriteParticipantName(p.id, p.name)); + }); + }); + break; + } + + case OVERWRITE_PARTICIPANT_NAME: { + const { dispatch, getState } = store; + const state = getState(); + const { id, name } = action; + + let breakoutRoom = false, identifier = id; + + if (id.indexOf('@') !== -1) { + identifier = id.slice(id.indexOf('/') + 1); + breakoutRoom = true; + action.id = identifier; + } + + if (breakoutRoom) { + const rooms = getBreakoutRooms(state); + const roomCounter = state['features/breakout-rooms'].roomCounter; + const newRooms = {}; + + Object.entries(rooms).forEach(([ key, r ]) => { + const participants = r?.participants || {}; + const jid = Object.keys(participants).find(p => + p.slice(p.indexOf('/') + 1) === identifier); + + if (jid) { + newRooms[key] = { + ...r, + participants: { + ...participants, + [jid]: { + ...participants[jid], + displayName: name + } + } + }; + } else { + newRooms[key] = r; + } + }); + dispatch({ + type: UPDATE_BREAKOUT_ROOMS, + rooms, + roomCounter, + updatedNames: true + }); + } else { + dispatch(participantUpdated({ + id: identifier, + name + })); + } + break; + } } return next(action); @@ -491,6 +565,7 @@ function _maybePlaySounds({ getState, dispatch }, action) { */ function _participantJoinedOrUpdated(store, next, action) { const { dispatch, getState } = store; + const { overwrittenNameList } = store.getState()['features/base/participants']; const { participant: { avatarURL, email, id, local, name, raisedHandTimestamp } } = action; // Send an external update of the local participant's raised hand state @@ -508,6 +583,10 @@ function _participantJoinedOrUpdated(store, next, action) { } } + if (overwrittenNameList[id]) { + action.participant.name = overwrittenNameList[id]; + } + // Allow the redux update to go through and compare the old avatar // to the new avatar and emit out change events if necessary. const result = next(action); diff --git a/react/features/base/participants/reducer.js b/react/features/base/participants/reducer.js index 6f539354c..b87bc4432 100644 --- a/react/features/base/participants/reducer.js +++ b/react/features/base/participants/reducer.js @@ -7,6 +7,7 @@ import { ReducerRegistry, set } from '../redux'; import { DOMINANT_SPEAKER_CHANGED, + OVERWRITE_PARTICIPANT_NAME, PARTICIPANT_ID_CHANGED, PARTICIPANT_JOINED, PARTICIPANT_LEFT, @@ -63,6 +64,7 @@ const DEFAULT_STATE = { haveParticipantWithScreenSharingFeature: false, local: undefined, localScreenShare: undefined, + overwrittenNameList: {}, pinnedParticipant: undefined, raisedHandsQueue: [], remote: new Map(), @@ -413,6 +415,17 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a return { ...state }; } + case OVERWRITE_PARTICIPANT_NAME: { + const { id, name } = action; + + return { + ...state, + overwrittenNameList: { + ...state.overwrittenNameList, + [id]: name + } + }; + } } return state; diff --git a/react/features/breakout-rooms/middleware.js b/react/features/breakout-rooms/middleware.js index 88b611a89..75f2f59a8 100644 --- a/react/features/breakout-rooms/middleware.js +++ b/react/features/breakout-rooms/middleware.js @@ -7,9 +7,10 @@ import { editMessage, MESSAGE_TYPE_REMOTE } from '../chat'; import { UPDATE_BREAKOUT_ROOMS } from './actionTypes'; import { moveToRoom } from './actions'; -import { getBreakoutRooms } from './functions'; import logger from './logger'; +declare var APP: Object; + /** * Registers a change handler for state['features/base/conference'].conference to * set the event listeners needed for the breakout rooms feature to operate. @@ -25,6 +26,9 @@ StateListenerRegistry.register( conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED, ({ rooms, roomCounter }) => { logger.debug('Room list updated'); + if (typeof APP !== 'undefined') { + APP.API.notifyBreakoutRoomsUpdated(rooms); + } dispatch({ type: UPDATE_BREAKOUT_ROOMS, rooms, @@ -36,16 +40,50 @@ StateListenerRegistry.register( MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { const { type } = action; - const result = next(action); switch (type) { case UPDATE_BREAKOUT_ROOMS: { - const { messages } = getState()['features/chat']; + // edit name if it was overwritten + if (!action.updatedNames) { + const { overwrittenNameList } = getState()['features/base/participants']; + + if (Object.keys(overwrittenNameList).length > 0) { + const newRooms = {}; + + Object.entries(action.rooms).forEach(([ key, r ]) => { + let participants = r?.participants || {}; + let jid; + + for (const id of Object.keys(overwrittenNameList)) { + jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id); + + if (jid) { + participants = { + ...participants, + [jid]: { + ...participants[jid], + displayName: overwrittenNameList[id] + } + }; + } + } + + newRooms[key] = { + ...r, + participants + }; + }); + + action.rooms = newRooms; + } + } // edit the chat history to match names for participants in breakout rooms + const { messages } = getState()['features/chat']; + messages && messages.forEach(m => { if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.id)) { - const rooms = getBreakoutRooms(getState); + const rooms = action.rooms; for (const room of Object.values(rooms)) { // $FlowExpectedError @@ -65,5 +103,5 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => { } } - return result; + return next(action); });