"Fossies" - the Fresh Open Source Software Archive

Member "jitsi-meet-7561/react/features/lobby/middleware.ts" (29 Sep 2023, 14434 Bytes) of package /linux/misc/jitsi-meet-7561.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 import i18n from 'i18next';
    2 import { batch } from 'react-redux';
    3 import { AnyAction } from 'redux';
    4 
    5 import { IStore } from '../app/types';
    6 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
    7 import {
    8     CONFERENCE_FAILED,
    9     CONFERENCE_JOINED
   10 } from '../base/conference/actionTypes';
   11 import { conferenceWillJoin } from '../base/conference/actions';
   12 import {
   13     JitsiConferenceErrors,
   14     JitsiConferenceEvents
   15 } from '../base/lib-jitsi-meet';
   16 import {
   17     getFirstLoadableAvatarUrl,
   18     getParticipantDisplayName
   19 } from '../base/participants/functions';
   20 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
   21 import StateListenerRegistry from '../base/redux/StateListenerRegistry';
   22 import {
   23     playSound,
   24     registerSound,
   25     unregisterSound
   26 } from '../base/sounds/actions';
   27 import { isTestModeEnabled } from '../base/testing/functions';
   28 import { BUTTON_TYPES } from '../base/ui/constants.any';
   29 import { openChat } from '../chat/actions';
   30 import {
   31     handleLobbyChatInitialized,
   32     removeLobbyChatParticipant
   33 } from '../chat/actions.any';
   34 import { hideNotification, showNotification } from '../notifications/actions';
   35 import {
   36     LOBBY_NOTIFICATION_ID,
   37     NOTIFICATION_ICON,
   38     NOTIFICATION_TIMEOUT_TYPE,
   39     NOTIFICATION_TYPE
   40 } from '../notifications/constants';
   41 import { INotificationProps } from '../notifications/types';
   42 import { open as openParticipantsPane } from '../participants-pane/actions';
   43 import { getParticipantsPaneOpen } from '../participants-pane/functions';
   44 import { isPrejoinPageVisible, shouldAutoKnock } from '../prejoin/functions';
   45 
   46 import {
   47     KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
   48     KNOCKING_PARTICIPANT_LEFT
   49 } from './actionTypes';
   50 import {
   51     approveKnockingParticipant,
   52     hideLobbyScreen,
   53     knockingParticipantLeft,
   54     openLobbyScreen,
   55     participantIsKnockingOrUpdated,
   56     rejectKnockingParticipant,
   57     setLobbyMessageListener,
   58     setLobbyModeEnabled,
   59     setPasswordJoinFailed,
   60     startKnocking
   61 } from './actions';
   62 import { updateLobbyParticipantOnLeave } from './actions.any';
   63 import { KNOCKING_PARTICIPANT_SOUND_ID } from './constants';
   64 import { getKnockingParticipants, showLobbyChatButton } from './functions';
   65 import { KNOCKING_PARTICIPANT_FILE } from './sounds';
   66 import { IKnockingParticipant } from './types';
   67 
   68 
   69 MiddlewareRegistry.register(store => next => action => {
   70     switch (action.type) {
   71     case APP_WILL_MOUNT:
   72         store.dispatch(registerSound(KNOCKING_PARTICIPANT_SOUND_ID, KNOCKING_PARTICIPANT_FILE));
   73         break;
   74     case APP_WILL_UNMOUNT:
   75         store.dispatch(unregisterSound(KNOCKING_PARTICIPANT_SOUND_ID));
   76         break;
   77     case CONFERENCE_FAILED:
   78         return _conferenceFailed(store, next, action);
   79     case CONFERENCE_JOINED:
   80         return _conferenceJoined(store, next, action);
   81     case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED: {
   82         // We need the full update result to be in the store already
   83         const result = next(action);
   84 
   85         _findLoadableAvatarForKnockingParticipant(store, action.participant);
   86         _handleLobbyNotification(store);
   87 
   88         return result;
   89     }
   90     case KNOCKING_PARTICIPANT_LEFT: {
   91         // We need the full update result to be in the store already
   92         const result = next(action);
   93 
   94         _handleLobbyNotification(store);
   95 
   96         return result;
   97     }
   98     }
   99 
  100     return next(action);
  101 });
  102 
  103 /**
  104  * Registers a change handler for state['features/base/conference'].conference to
  105  * set the event listeners needed for the lobby feature to operate.
  106  */
  107 StateListenerRegistry.register(
  108     state => state['features/base/conference'].conference,
  109     (conference, { dispatch, getState }, previousConference) => {
  110         if (conference && !previousConference) {
  111             conference.on(JitsiConferenceEvents.MEMBERS_ONLY_CHANGED, (enabled: boolean) => {
  112                 dispatch(setLobbyModeEnabled(enabled));
  113                 if (enabled) {
  114                     dispatch(setLobbyMessageListener());
  115                 }
  116             });
  117 
  118             conference.on(JitsiConferenceEvents.LOBBY_USER_JOINED, (id: string, name: string) => {
  119                 const { soundsParticipantKnocking } = getState()['features/base/settings'];
  120 
  121                 batch(() => {
  122                     dispatch(
  123                         participantIsKnockingOrUpdated({
  124                             id,
  125                             name
  126                         })
  127                     );
  128                     if (soundsParticipantKnocking) {
  129                         dispatch(playSound(KNOCKING_PARTICIPANT_SOUND_ID));
  130                     }
  131 
  132                     const isParticipantsPaneVisible = getParticipantsPaneOpen(getState());
  133 
  134                     if (typeof APP !== 'undefined') {
  135                         APP.API.notifyKnockingParticipant({
  136                             id,
  137                             name
  138                         });
  139                     }
  140 
  141                     if (isParticipantsPaneVisible || navigator.product === 'ReactNative') {
  142                         return;
  143                     }
  144 
  145                     _handleLobbyNotification({
  146                         dispatch,
  147                         getState
  148                     });
  149                 });
  150             });
  151 
  152             conference.on(JitsiConferenceEvents.LOBBY_USER_UPDATED, (id: string, participant: IKnockingParticipant) => {
  153                 dispatch(
  154                     participantIsKnockingOrUpdated({
  155                         ...participant,
  156                         id
  157                     })
  158                 );
  159             });
  160 
  161             conference.on(JitsiConferenceEvents.LOBBY_USER_LEFT, (id: string) => {
  162                 batch(() => {
  163                     dispatch(knockingParticipantLeft(id));
  164                     dispatch(removeLobbyChatParticipant());
  165                     dispatch(updateLobbyParticipantOnLeave(id));
  166                 });
  167             });
  168 
  169             conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, (origin: any, sender: any) =>
  170                 _maybeSendLobbyNotification(origin, sender, {
  171                     dispatch,
  172                     getState
  173                 })
  174             );
  175         }
  176     }
  177 );
  178 
  179 /**
  180  * Function to handle the lobby notification.
  181  *
  182  * @param {Object} store - The Redux store.
  183  * @returns {void}
  184  */
  185 function _handleLobbyNotification(store: IStore) {
  186     const { dispatch, getState } = store;
  187     const knockingParticipants = getKnockingParticipants(getState());
  188 
  189     if (knockingParticipants.length === 0) {
  190         dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
  191 
  192         return;
  193     }
  194 
  195     let notificationTitle;
  196     let customActionNameKey;
  197     let customActionHandler;
  198     let customActionType;
  199     let descriptionKey;
  200     let icon;
  201 
  202     if (knockingParticipants.length === 1) {
  203         const firstParticipant = knockingParticipants[0];
  204         const { disablePolls } = getState()['features/base/config'];
  205         const showChat = showLobbyChatButton(firstParticipant)(getState());
  206 
  207         descriptionKey = 'notify.participantWantsToJoin';
  208         notificationTitle = firstParticipant.name;
  209         icon = NOTIFICATION_ICON.PARTICIPANT;
  210         customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
  211         customActionType = [ BUTTON_TYPES.PRIMARY, BUTTON_TYPES.DESTRUCTIVE ];
  212         customActionHandler = [ () => batch(() => {
  213             dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
  214             dispatch(approveKnockingParticipant(firstParticipant.id));
  215         }),
  216         () => batch(() => {
  217             dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
  218             dispatch(rejectKnockingParticipant(firstParticipant.id));
  219         }) ];
  220 
  221         // This checks if lobby chat button is available
  222         // and, if so, it adds it to the customActionNameKey array
  223         if (showChat) {
  224             customActionNameKey.splice(1, 0, 'lobby.chat');
  225             customActionType.splice(1, 0, BUTTON_TYPES.SECONDARY);
  226             customActionHandler.splice(1, 0, () => batch(() => {
  227                 dispatch(handleLobbyChatInitialized(firstParticipant.id));
  228                 dispatch(openChat({}, disablePolls));
  229             }));
  230         }
  231     } else {
  232         descriptionKey = 'notify.participantsWantToJoin';
  233         notificationTitle = i18n.t('notify.waitingParticipants', {
  234             waitingParticipants: knockingParticipants.length
  235         });
  236         icon = NOTIFICATION_ICON.PARTICIPANTS;
  237         customActionNameKey = [ 'notify.viewLobby' ];
  238         customActionType = [ BUTTON_TYPES.PRIMARY ];
  239         customActionHandler = [ () => batch(() => {
  240             dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
  241             dispatch(openParticipantsPane());
  242         }) ];
  243     }
  244 
  245     dispatch(showNotification({
  246         title: notificationTitle,
  247         descriptionKey,
  248         uid: LOBBY_NOTIFICATION_ID,
  249         customActionNameKey,
  250         customActionType,
  251         customActionHandler,
  252         icon
  253     }, NOTIFICATION_TIMEOUT_TYPE.STICKY));
  254 }
  255 
  256 /**
  257  * Function to handle the conference failed event and navigate the user to the lobby screen
  258  * based on the failure reason.
  259  *
  260  * @param {Object} store - The Redux store.
  261  * @param {Function} next - The Redux next function.
  262  * @param {Object} action - The Redux action.
  263  * @returns {Object}
  264  */
  265 function _conferenceFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
  266     const { error } = action;
  267     const state = getState();
  268     const { membersOnly } = state['features/base/conference'];
  269     const nonFirstFailure = Boolean(membersOnly);
  270     const { isDisplayNameRequiredError } = state['features/lobby'];
  271     const { prejoinConfig } = state['features/base/config'];
  272 
  273     if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
  274         if (typeof error.recoverable === 'undefined') {
  275             error.recoverable = true;
  276         }
  277 
  278         // eslint-disable-next-line @typescript-eslint/no-unused-vars
  279         const [ _lobbyJid, lobbyWaitingForHost ] = error.params;
  280 
  281         const result = next(action);
  282 
  283         dispatch(openLobbyScreen());
  284 
  285         // if there was an error about display name and pre-join is not enabled
  286         if (shouldAutoKnock(state) || (isDisplayNameRequiredError && !prejoinConfig?.enabled) || lobbyWaitingForHost) {
  287             dispatch(startKnocking());
  288         }
  289 
  290         // In case of wrong password we need to be in the right state if in the meantime someone allows us to join
  291         if (nonFirstFailure) {
  292             dispatch(conferenceWillJoin(membersOnly));
  293         }
  294 
  295         dispatch(setPasswordJoinFailed(nonFirstFailure));
  296 
  297         return result;
  298     } else if (error.name === JitsiConferenceErrors.DISPLAY_NAME_REQUIRED) {
  299         const [ isLobbyEnabled ] = error.params;
  300 
  301         const result = next(action);
  302 
  303         // if the error is due to required display name because lobby is enabled for the room
  304         // if not showing the prejoin page then show lobby UI
  305         if (isLobbyEnabled && !isPrejoinPageVisible(state)) {
  306             dispatch(openLobbyScreen());
  307         }
  308 
  309         return result;
  310     }
  311 
  312     dispatch(hideLobbyScreen());
  313 
  314     if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
  315         dispatch(
  316             showNotification({
  317                 appearance: NOTIFICATION_TYPE.ERROR,
  318                 hideErrorSupportLink: true,
  319                 titleKey: 'lobby.joinRejectedTitle',
  320                 descriptionKey: 'lobby.joinRejectedMessage'
  321             }, NOTIFICATION_TIMEOUT_TYPE.LONG)
  322         );
  323     }
  324 
  325     return next(action);
  326 }
  327 
  328 /**
  329  * Handles cleanup of lobby state when a conference is joined.
  330  *
  331  * @param {Object} store - The Redux store.
  332  * @param {Function} next - The Redux next function.
  333  * @param {Object} action - The Redux action.
  334  * @returns {Object}
  335  */
  336 function _conferenceJoined({ dispatch }: IStore, next: Function, action: AnyAction) {
  337     dispatch(hideLobbyScreen());
  338 
  339     return next(action);
  340 }
  341 
  342 /**
  343  * Finds the loadable avatar URL and updates the participant accordingly.
  344  *
  345  * @param {Object} store - The Redux store.
  346  * @param {Object} participant - The knocking participant.
  347  * @returns {void}
  348  */
  349 function _findLoadableAvatarForKnockingParticipant(store: IStore, { id }: { id: string; }) {
  350     const { dispatch, getState } = store;
  351     const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
  352     const { disableThirdPartyRequests } = getState()['features/base/config'];
  353 
  354     if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
  355         getFirstLoadableAvatarUrl(updatedParticipant, store).then((result: { isUsingCORS: boolean; src: string; }) => {
  356             if (result) {
  357                 const { isUsingCORS, src } = result;
  358 
  359                 dispatch(
  360                     participantIsKnockingOrUpdated({
  361                         loadableAvatarUrl: src,
  362                         id,
  363                         isUsingCORS
  364                     })
  365                 );
  366             }
  367         });
  368     }
  369 }
  370 
  371 /**
  372  * Check the endpoint message that arrived through the conference and
  373  * sends a lobby notification, if the message belongs to the feature.
  374  *
  375  * @param {Object} origin - The origin (initiator) of the message.
  376  * @param {Object} message - The actual message.
  377  * @param {Object} store - The Redux store.
  378  * @returns {void}
  379  */
  380 function _maybeSendLobbyNotification(origin: any, message: any, { dispatch, getState }: IStore) {
  381     if (!origin?._id || message?.type !== 'lobby-notify') {
  382         return;
  383     }
  384 
  385     const notificationProps: INotificationProps = {
  386         descriptionArguments: {
  387             originParticipantName: getParticipantDisplayName(getState, origin._id),
  388             targetParticipantName: message.name
  389         },
  390         titleKey: 'lobby.notificationTitle'
  391     };
  392 
  393     switch (message.event) {
  394     case 'LOBBY-ENABLED':
  395         notificationProps.descriptionKey = `lobby.notificationLobby${message.value ? 'En' : 'Dis'}abled`;
  396         break;
  397     case 'LOBBY-ACCESS-GRANTED':
  398         notificationProps.descriptionKey = 'lobby.notificationLobbyAccessGranted';
  399         break;
  400     case 'LOBBY-ACCESS-DENIED':
  401         notificationProps.descriptionKey = 'lobby.notificationLobbyAccessDenied';
  402         break;
  403     }
  404 
  405     dispatch(
  406         showNotification(
  407             notificationProps,
  408             isTestModeEnabled(getState()) ? NOTIFICATION_TIMEOUT_TYPE.STICKY : NOTIFICATION_TIMEOUT_TYPE.MEDIUM
  409         )
  410     );
  411 }