diff --git a/react/features/base/conference/functions.js b/react/features/base/conference/functions.js index 573b47954..90a5d22bc 100644 --- a/react/features/base/conference/functions.js +++ b/react/features/base/conference/functions.js @@ -7,7 +7,8 @@ import { toState } from '../redux'; import { AVATAR_ID_COMMAND, AVATAR_URL_COMMAND, - EMAIL_COMMAND + EMAIL_COMMAND, + JITSI_CONFERENCE_URL_KEY } from './constants'; /** @@ -40,6 +41,45 @@ export function _addLocalTracksToConference( return Promise.all(promises); } +/** + * Evaluates a specific predicate for each {@link JitsiConference} known to the + * redux state features/base/conference while it returns {@code true}. + * + * @param {Function | Object} stateful - The redux store, state, or + * {@code getState} function. + * @param {Function} predicate - The predicate to evaluate for each + * {@code JitsiConference} know to the redux state features/base/conference + * while it returns {@code true}. + * @returns {boolean} If the specified {@code predicate} returned {@code true} + * for all {@code JitsiConference} instances known to the redux state + * features/base/conference. + */ +export function forEachConference( + stateful: Function | Object, + predicate: (Object, URL) => boolean) { + const state = toState(stateful)['features/base/conference']; + + for (const v of Object.values(state)) { + // Does the value of the base/conference's property look like a + // JitsiConference? + if (v && typeof v === 'object') { + // $FlowFixMe + const url: URL = v[JITSI_CONFERENCE_URL_KEY]; + + // XXX The Web version of Jitsi Meet does not utilize + // JITSI_CONFERENCE_URL_KEY at the time of this writing. An + // alternative is necessary then to recognize JitsiConference + // instances and myUserId is as good as any other property. + if ((url || typeof v.myUserId === 'function') + && !predicate(v, url)) { + return false; + } + } + } + + return true; +} + /** * Returns the current {@code JitsiConference} which is joining or joined and is * not leaving. Please note the contrast with merely reading the diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js index 27e53feb1..b997b7eb2 100644 --- a/react/features/base/conference/middleware.js +++ b/react/features/base/conference/middleware.js @@ -34,9 +34,9 @@ import { SET_RECEIVE_VIDEO_QUALITY, SET_ROOM } from './actionTypes'; -import { JITSI_CONFERENCE_URL_KEY } from './constants'; import { _addLocalTracksToConference, + forEachConference, _handleParticipantError, _removeLocalTracksFromConference } from './functions'; @@ -354,31 +354,20 @@ function _setRoom({ dispatch, getState }, next, action) { // base/conference's leaving should be the only conference-related sources // of truth. const state = getState(); - const { - leaving, - ...stateFeaturesBaseConference - } = state['features/base/conference']; + const { leaving } = state['features/base/conference']; const { locationURL } = state['features/base/connection']; const dispatchConferenceLeft = new Set(); // Figure out which of the JitsiConferences referenced by base/conference // have not dispatched or are not likely to dispatch CONFERENCE_FAILED and // CONFERENCE_LEFT. - - // eslint-disable-next-line guard-for-in - for (const p in stateFeaturesBaseConference) { - const v = stateFeaturesBaseConference[p]; - - // Does the value of the base/conference's property look like a - // JitsiConference? - if (v && typeof v === 'object') { - const url = v[JITSI_CONFERENCE_URL_KEY]; - - if (url && v !== leaving && url !== locationURL) { - dispatchConferenceLeft.add(v); - } + forEachConference(state, (conference, url) => { + if (conference !== leaving && url && url !== locationURL) { + dispatchConferenceLeft.add(conference); } - } + + return true; // All JitsiConference instances are to be examined. + }); // Dispatch CONFERENCE_LEFT for the JitsiConferences referenced by // base/conference which have not dispatched or are not likely to dispatch diff --git a/react/features/base/participants/middleware.js b/react/features/base/participants/middleware.js index d0fc0edc6..93210bab2 100644 --- a/react/features/base/participants/middleware.js +++ b/react/features/base/participants/middleware.js @@ -1,7 +1,7 @@ // @flow import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app'; -import { CONFERENCE_LEFT, CONFERENCE_WILL_JOIN } from '../conference'; +import { CONFERENCE_WILL_JOIN, forEachConference } from '../conference'; import { CALLING, INVITED } from '../../presence-status'; import { MiddlewareRegistry, StateListenerRegistry } from '../redux'; import UIEvents from '../../../../service/UI/UIEvents'; @@ -59,10 +59,6 @@ MiddlewareRegistry.register(store => next => action => { store.dispatch(localParticipantIdChanged(action.conference.myUserId())); break; - case CONFERENCE_LEFT: - store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID)); - break; - case DOMINANT_SPEAKER_CHANGED: { // Ensure the raised hand state is cleared for the dominant speaker. @@ -146,6 +142,41 @@ StateListenerRegistry.register( } }); +/** + * Reset the ID of the local participant to + * {@link LOCAL_PARTICIPANT_DEFAULT_ID}. Such a reset is deemed possible only if + * the local participant and, respectively, her ID is not involved in a + * conference which is still of interest to the user and, consequently, the app. + * For example, a conference which is in the process of leaving is no longer of + * interest the user, is unrecoverable from the perspective of the user and, + * consequently, the app. + */ +StateListenerRegistry.register( + /* selector */ state => state['features/base/conference'], + /* listener */ ({ leaving }, { dispatch, getState }) => { + const state = getState(); + const localParticipant = getLocalParticipant(state); + let id; + + if (!localParticipant + || (id = localParticipant.id) + === LOCAL_PARTICIPANT_DEFAULT_ID) { + // The ID of the local participant has been reset already. + return; + } + + // The ID of the local may be reset only if it is not in use. + const dispatchLocalParticipantIdChanged + = forEachConference( + state, + conference => + conference === leaving || conference.myUserId() !== id); + + dispatchLocalParticipantIdChanged + && dispatch( + localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID)); + }); + /** * Initializes the local participant and signals that it joined. * diff --git a/react/features/mobile/external-api/middleware.js b/react/features/mobile/external-api/middleware.js index 89e807635..64ae1c241 100644 --- a/react/features/mobile/external-api/middleware.js +++ b/react/features/mobile/external-api/middleware.js @@ -11,6 +11,7 @@ import { CONFERENCE_WILL_LEAVE, JITSI_CONFERENCE_URL_KEY, SET_ROOM, + forEachConference, isRoomValid } from '../../base/conference'; import { LOAD_CONFIG_ERROR } from '../../base/config'; @@ -249,28 +250,18 @@ function _swallowConferenceLeft({ getState }, action, { url }) { // the same URL string multiple times) to try to send CONFERENCE_LEFT // externally for a URL string which identifies a JitsiConference that the // app is internally legitimately working with. + let swallowConferenceLeft = false; - if (url) { - const stateFeaturesBaseConference - = getState()['features/base/conference']; - - // eslint-disable-next-line guard-for-in - for (const p in stateFeaturesBaseConference) { - const v = stateFeaturesBaseConference[p]; - - // Does the value of the base/conference's property look like a - // JitsiConference? - if (v && typeof v === 'object') { - const vURL = v[JITSI_CONFERENCE_URL_KEY]; - - if (vURL && vURL.toString() === url) { - return true; - } + url + && forEachConference(getState, (conference, conferenceURL) => { + if (conferenceURL && conferenceURL.toString() === url) { + swallowConferenceLeft = true; } - } - } - return false; + return !swallowConferenceLeft; + }); + + return swallowConferenceLeft; } /**