diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js index 75b2bd03e..016d6b87b 100644 --- a/react/features/base/conference/actions.js +++ b/react/features/base/conference/actions.js @@ -6,10 +6,7 @@ import { participantLeft, participantRoleChanged } from '../participants'; -import { - trackAdded, - trackRemoved -} from '../tracks'; +import { trackAdded, trackRemoved } from '../tracks'; import { CONFERENCE_JOINED, @@ -46,7 +43,7 @@ export function createConference() { const conference = connection.initJitsiConference(room, { openSctp: true }); - dispatch(_setupConferenceListeners(conference)); + _setupConferenceListeners(conference, dispatch); conference.join(); }; @@ -60,11 +57,12 @@ export function createConference() { * joined by the local participant. * @returns {Function} */ -export function conferenceJoined(conference) { +function _conferenceJoined(conference) { return (dispatch, getState) => { - const localTracks = getState()['features/base/tracks'] - .filter(t => t.local) - .map(t => t.jitsiTrack); + const localTracks + = getState()['features/base/tracks'] + .filter(t => t.local) + .map(t => t.jitsiTrack); if (localTracks.length) { _addLocalTracksToConference(conference, localTracks); @@ -91,7 +89,7 @@ export function conferenceJoined(conference) { * } * }} */ -export function conferenceLeft(conference) { +function _conferenceLeft(conference) { return { type: CONFERENCE_LEFT, conference: { @@ -144,48 +142,45 @@ export function setRoom(room) { /** * Setup various conference event handlers. * - * @param {JitsiConference} conference - Conference instance. + * @param {JitsiConference} conference - The JitsiConference instance. + * @param {Dispatch} dispatch - The Redux dispatch function. * @private - * @returns {Function} + * @returns {void} */ -function _setupConferenceListeners(conference) { - return dispatch => { - conference.on( +function _setupConferenceListeners(conference, dispatch) { + conference.on( JitsiConferenceEvents.CONFERENCE_JOINED, - () => dispatch(conferenceJoined(conference))); - conference.on( + () => dispatch(_conferenceJoined(conference))); + conference.on( JitsiConferenceEvents.CONFERENCE_LEFT, - () => dispatch(conferenceLeft(conference))); + () => dispatch(_conferenceLeft(conference))); - conference.on( + conference.on( JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => dispatch(dominantSpeakerChanged(id))); - conference.on( + conference.on( JitsiConferenceEvents.TRACK_ADDED, - track => - track && !track.isLocal() && dispatch(trackAdded(track))); - conference.on( + t => t && !t.isLocal() && dispatch(trackAdded(t))); + conference.on( JitsiConferenceEvents.TRACK_REMOVED, - track => - track && !track.isLocal() && dispatch(trackRemoved(track))); + t => t && !t.isLocal() && dispatch(trackRemoved(t))); - conference.on( + conference.on( JitsiConferenceEvents.USER_JOINED, (id, user) => dispatch(participantJoined({ id, name: user.getDisplayName(), role: user.getRole() }))); - conference.on( + conference.on( JitsiConferenceEvents.USER_LEFT, id => dispatch(participantLeft(id))); - conference.on( + conference.on( JitsiConferenceEvents.USER_ROLE_CHANGED, (id, role) => dispatch(participantRoleChanged(id, role))); - conference.addCommandListener( + conference.addCommandListener( EMAIL_COMMAND, (data, id) => dispatch(changeParticipantEmail(id, data.value))); - }; } diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js index 62539ca77..6c96f57b2 100644 --- a/react/features/base/conference/middleware.js +++ b/react/features/base/conference/middleware.js @@ -1,14 +1,13 @@ +import { CONNECTION_ESTABLISHED } from '../connection'; import { getLocalParticipant, getParticipantById, PIN_PARTICIPANT } from '../participants'; import { MiddlewareRegistry } from '../redux'; -import { - TRACK_ADDED, - TRACK_REMOVED -} from '../tracks'; +import { TRACK_ADDED, TRACK_REMOVED } from '../tracks'; +import { createConference } from './actions'; import { _addLocalTracksToConference, _handleParticipantError, @@ -16,45 +15,68 @@ import { } from './functions'; /** - * This middleware intercepts TRACK_ADDED and TRACK_REMOVED actions to sync - * conference's local tracks with local tracks in state. Also captures - * PIN_PARTICIPANT action to pin participant in conference. + * Implements the middleware of the feature base/conference. * * @param {Store} store - Redux store. * @returns {Function} */ MiddlewareRegistry.register(store => next => action => { switch (action.type) { + case CONNECTION_ESTABLISHED: + return _connectionEstablished(store, next, action); + case PIN_PARTICIPANT: - pinParticipant(store, action.participant.id); - break; + return _pinParticipant(store, next, action); case TRACK_ADDED: - case TRACK_REMOVED: { - const track = action.track; - - if (track && track.local) { - return syncConferenceLocalTracksWithState(store, action) - .then(() => next(action)); - } - break; - } + case TRACK_REMOVED: + return _trackAddedOrRemoved(store, next, action); } return next(action); }); /** - * Pins remote participant in conference, ignores local participant. + * Notifies the feature base/conference that the action CONNECTION_ESTABLISHED + * is being dispatched within a specific Redux store. * - * @param {Store} store - Redux store. - * @param {string|null} id - Participant id or null if no one is currently - * pinned. - * @returns {void} + * @param {Store} store - The Redux store in which the specified action is being + * dispatched. + * @param {Dispatch} next - The Redux dispatch function to dispatch the + * specified action to the specified store. + * @param {Action} action - The Redux action CONNECTION_ESTABLISHED which is + * being dispatched in the specified store. + * @private + * @returns {Object} The new state that is the result of the reduction of the + * specified action. */ -function pinParticipant(store, id) { +function _connectionEstablished(store, next, action) { + const result = next(action); + + store.dispatch(createConference()); + + return result; +} + +/** + * Notifies the feature base/conference that the action PIN_PARTICIPANT is being + * dispatched within a specific Redux store. Pins the specified remote + * participant in the associated conference, ignores the local participant. + * + * @param {Store} store - The Redux store in which the specified action is being + * dispatched. + * @param {Dispatch} next - The Redux dispatch function to dispatch the + * specified action to the specified store. + * @param {Action} action - The Redux action PIN_PARTICIPANT which is being + * dispatched in the specified store. + * @private + * @returns {Object} The new state that is the result of the reduction of the + * specified action. + */ +function _pinParticipant(store, next, action) { const state = store.getState(); const participants = state['features/base/participants']; + const id = action.participant.id; const participantById = getParticipantById(participants, id); let pin; @@ -81,16 +103,46 @@ function pinParticipant(store, id) { _handleParticipantError(err); } } + + return next(action); } /** - * Syncs local tracks from state with local tracks in JitsiConference instance. + * Notifies the feature base/conference that the action TRACK_ADDED + * or TRACK_REMOVED is being dispatched within a specific Redux store. + * + * @param {Store} store - The Redux store in which the specified action is being + * dispatched. + * @param {Dispatch} next - The Redux dispatch function to dispatch the + * specified action to the specified store. + * @param {Action} action - The Redux action TRACK_ADDED or TRACK_REMOVED which + * is being dispatched in the specified store. + * @private + * @returns {Object} The new state that is the result of the reduction of the + * specified action. + */ +function _trackAddedOrRemoved(store, next, action) { + const track = action.track; + + if (track && track.local) { + return ( + _syncConferenceLocalTracksWithState(store, action) + .then(() => next(action))); + } + + return next(action); +} + +/** + * Synchronizes local tracks from state with local tracks in JitsiConference + * instance. * * @param {Store} store - Redux store. * @param {Object} action - Action object. + * @private * @returns {Promise} */ -function syncConferenceLocalTracksWithState(store, action) { +function _syncConferenceLocalTracksWithState(store, action) { const conferenceState = store.getState()['features/base/conference']; const conference = conferenceState.jitsiConference; const leavingConference = conferenceState.leavingJitsiConference; diff --git a/react/features/base/conference/reducer.js b/react/features/base/conference/reducer.js index bfa70d83a..2c333303c 100644 --- a/react/features/base/conference/reducer.js +++ b/react/features/base/conference/reducer.js @@ -1,4 +1,4 @@ -import { ReducerRegistry } from '../redux'; +import { ReducerRegistry, setStateProperty } from '../redux'; import { CONFERENCE_JOINED, @@ -33,10 +33,11 @@ ReducerRegistry.register('features/base/conference', (state = INITIAL_STATE, action) => { switch (action.type) { case CONFERENCE_JOINED: - return { - ...state, - jitsiConference: action.conference.jitsiConference - }; + return ( + setStateProperty( + state, + 'jitsiConference', + action.conference.jitsiConference)); case CONFERENCE_LEFT: if (state.jitsiConference === action.conference.jitsiConference) { @@ -52,10 +53,11 @@ ReducerRegistry.register('features/base/conference', break; case CONFERENCE_WILL_LEAVE: - return { - ...state, - leavingJitsiConference: action.conference.jitsiConference - }; + return ( + setStateProperty( + state, + 'leavingJitsiConference', + action.conference.jitsiConference)); case SET_ROOM: { let room = action.room; @@ -63,16 +65,9 @@ ReducerRegistry.register('features/base/conference', // Technically, there're multiple values which don't represent // valid room names. Practically, each of them is as bad as the rest // of them because we can't use any of them to join a conference. - if (!isRoomValid(room)) { - room = INITIAL_STATE.room; - } - if (state.room !== room) { - return { - ...state, - room - }; - } - break; + isRoomValid(room) || (room = INITIAL_STATE.room); + + return setStateProperty(state, 'room', room); } } diff --git a/react/features/base/connection/actions.js b/react/features/base/connection/actions.js index 1282fe16d..5cf1fa258 100644 --- a/react/features/base/connection/actions.js +++ b/react/features/base/connection/actions.js @@ -1,8 +1,6 @@ -import { - conferenceWillLeave, - createConference -} from '../conference'; +import { conferenceWillLeave } from '../conference'; import JitsiMeetJS from '../lib-jitsi-meet'; + import { CONNECTION_DISCONNECTED, CONNECTION_ESTABLISHED, @@ -21,90 +19,81 @@ const JitsiConnectionEvents = JitsiMeetJS.events.connection; export function connect() { return (dispatch, getState) => { const state = getState(); - const connectionOpts + const connectionOptions = state['features/base/connection'].connectionOptions; const room = state['features/base/conference'].room; - const connection = new JitsiMeetJS.JitsiConnection( - connectionOpts.appId, - connectionOpts.token, - { - ...connectionOpts, - bosh: connectionOpts.bosh + ( - room ? `?room=${room}` : '' - ) - } - ); + const connection + = new JitsiMeetJS.JitsiConnection( + connectionOptions.appId, + connectionOptions.token, + { + ...connectionOptions, + bosh: connectionOptions.bosh + (room ? `?room=${room}` : '') + }); - return new Promise((resolve, reject) => { - connection.addEventListener( + connection.addEventListener( JitsiConnectionEvents.CONNECTION_DISCONNECTED, - handleConnectionDisconnected); - connection.addEventListener( + connectionDisconnected); + connection.addEventListener( JitsiConnectionEvents.CONNECTION_ESTABLISHED, - handleConnectionEstablished); - connection.addEventListener( + connectionEstablished); + connection.addEventListener( JitsiConnectionEvents.CONNECTION_FAILED, - handleConnectionFailed); + connectionFailed); - connection.connect(); + connection.connect(); - /** - * Dispatches CONNECTION_DISCONNECTED action when connection is - * disconnected. - * - * @param {string} message - Disconnect reason. - * @returns {void} - */ - function handleConnectionDisconnected(message) { - connection.removeEventListener( + /** + * Dispatches CONNECTION_DISCONNECTED action when connection is + * disconnected. + * + * @param {string} message - Disconnect reason. + * @returns {void} + */ + function connectionDisconnected(message) { + connection.removeEventListener( JitsiConnectionEvents.CONNECTION_DISCONNECTED, - handleConnectionDisconnected); + connectionDisconnected); - dispatch(_connectionDisconnected(connection, message)); - } + dispatch(_connectionDisconnected(connection, message)); + } - /** - * Resolves external promise when connection is established. - * - * @returns {void} - */ - function handleConnectionEstablished() { - unsubscribe(); - resolve(connection); - } + /** + * Resolves external promise when connection is established. + * + * @returns {void} + */ + function connectionEstablished() { + unsubscribe(); + dispatch(_connectionEstablished(connection)); + } - /** - * Rejects external promise when connection fails. - * - * @param {JitsiConnectionErrors} err - Connection error. - * @returns {void} - */ - function handleConnectionFailed(err) { - unsubscribe(); - console.error('CONNECTION FAILED:', err); - reject(err); - } + /** + * Rejects external promise when connection fails. + * + * @param {JitsiConnectionErrors} err - Connection error. + * @returns {void} + */ + function connectionFailed(err) { + unsubscribe(); + console.error('CONNECTION FAILED:', err); + dispatch(_connectionFailed(connection, err)); + } - /** - * Unsubscribes connection instance from CONNECTION_ESTABLISHED - * and CONNECTION_FAILED events. - * - * @returns {void} - */ - function unsubscribe() { - connection.removeEventListener( + /** + * Unsubscribes connection instance from CONNECTION_ESTABLISHED + * and CONNECTION_FAILED events. + * + * @returns {void} + */ + function unsubscribe() { + connection.removeEventListener( JitsiConnectionEvents.CONNECTION_ESTABLISHED, - handleConnectionEstablished - ); - connection.removeEventListener( + connectionEstablished); + connection.removeEventListener( JitsiConnectionEvents.CONNECTION_FAILED, - handleConnectionFailed - ); - } - }) - .catch(err => dispatch(_connectionFailed(err))) - .then(con => dispatch(_connectionEstablished(con))) - .then(() => dispatch(createConference())); + connectionFailed); + } }; } @@ -162,8 +151,7 @@ export function setDomain(domain) { /** * Create an action for when the signaling connection has been lost. * - * @param {JitsiConnection} connection - The JitsiConnection which was - * disconnected. + * @param {JitsiConnection} connection - The JitsiConnection which disconnected. * @param {string} message - Error message. * @private * @returns {{ @@ -183,9 +171,13 @@ function _connectionDisconnected(connection, message) { /** * Create an action for when the signaling connection has been established. * - * @param {JitsiConnection} connection - JitsiConnection instance. + * @param {JitsiConnection} connection - The JitsiConnection which was + * established. * @private - * @returns {{type: CONNECTION_ESTABLISHED, connection: JitsiConnection}} + * @returns {{ + * type: CONNECTION_ESTABLISHED, + * connection: JitsiConnection + * }} */ function _connectionEstablished(connection) { return { @@ -197,13 +189,19 @@ function _connectionEstablished(connection) { /** * Create an action for when the signaling connection could not be created. * + * @param {JitsiConnection} connection - The JitsiConnection which failed. * @param {string} error - Error message. * @private - * @returns {{type: CONNECTION_FAILED, error: string}} + * @returns {{ + * type: CONNECTION_FAILED, + * connection: JitsiConnection, + * error: string + * }} */ -function _connectionFailed(error) { +function _connectionFailed(connection, error) { return { type: CONNECTION_FAILED, + connection, error }; } diff --git a/react/features/base/connection/reducer.js b/react/features/base/connection/reducer.js index 360ccb97e..cedb00f04 100644 --- a/react/features/base/connection/reducer.js +++ b/react/features/base/connection/reducer.js @@ -1,4 +1,4 @@ -import { ReducerRegistry } from '../redux'; +import { ReducerRegistry, setStateProperty } from '../redux'; import { CONNECTION_DISCONNECTED, @@ -7,62 +7,64 @@ import { } from './actionTypes'; /** - * Initial Redux state. - * - * @type {{ - * jitsiConnection: (JitsiConnection|null), - * connectionOptions: Object - * }} + * Reduces the Redux actions of the feature base/connection. */ -const INITIAL_STATE = { - jitsiConnection: null, - connectionOptions: null -}; +ReducerRegistry.register('features/base/connection', (state = {}, action) => { + switch (action.type) { + case CONNECTION_DISCONNECTED: + return _connectionDisconnected(state, action); + + case CONNECTION_ESTABLISHED: + return _connectionEstablished(state, action); + + case SET_DOMAIN: + return _setDomain(state, action); + } + + return state; +}); /** - * Listen for actions that contain the connection object, so that - * it can be stored for use by other action creators. + * Reduces a specific Redux action CONNECTION_DISCONNECTED of the feature + * base/connection. + * + * @param {Object} state - The Redux state of the feature base/connection. + * @param {Action} action - The Redux action CONNECTION_DISCONNECTED to reduce. + * @private + * @returns {Object} The new state of the feature base/connection after the + * reduction of the specified action. */ -ReducerRegistry.register('features/base/connection', - (state = INITIAL_STATE, action) => { - switch (action.type) { - case CONNECTION_DISCONNECTED: - if (state.jitsiConnection === action.connection) { - return { - ...state, - jitsiConnection: null - }; - } +function _connectionDisconnected(state, action) { + if (state.jitsiConnection === action.connection) { + return setStateProperty(state, 'jitsiConnection', undefined); + } - return state; - - case CONNECTION_ESTABLISHED: - return { - ...state, - jitsiConnection: action.connection - }; - - case SET_DOMAIN: - return { - ...state, - connectionOptions: { - ...state.connectionOptions, - ...buildConnectionOptions(action.domain) - } - }; - - default: - return state; - } - }); + return state; +} /** - * Builds connection options based on domain. + * Reduces a specific Redux action CONNECTION_ESTABLISHED of the feature + * base/connection. * - * @param {string} domain - Domain name. + * @param {Object} state - The Redux state of the feature base/connection. + * @param {Action} action - The Redux action CONNECTION_ESTABLISHED to reduce. + * @private + * @returns {Object} The new state of the feature base/connection after the + * reduction of the specified action. + */ +function _connectionEstablished(state, action) { + return setStateProperty(state, 'jitsiConnection', action.connection); +} + +/** + * Constructs options to be passed to the constructor of JitsiConnection based + * on a specific domain. + * + * @param {string} domain - The domain with which the returned options are to be + * populated. * @returns {Object} */ -function buildConnectionOptions(domain) { +function _constructConnectionOptions(domain) { // FIXME The HTTPS scheme for the BOSH URL works with meet.jit.si on both // mobile & Web. It also works with beta.meet.jit.si on Web. Unfortunately, // it doesn't work with beta.meet.jit.si on mobile. Temporarily, use the @@ -79,15 +81,11 @@ function buildConnectionOptions(domain) { boshProtocol = windowLocation.protocol; } } - if (!boshProtocol) { - boshProtocol = 'http:'; - } + boshProtocol || (boshProtocol = 'http:'); } // Default to the HTTPS scheme for the BOSH URL. - if (!boshProtocol) { - boshProtocol = 'https:'; - } + boshProtocol || (boshProtocol = 'https:'); return { bosh: `${boshProtocol}//${domain}/http-bind`, @@ -98,3 +96,22 @@ function buildConnectionOptions(domain) { } }; } + +/** + * Reduces a specific Redux action SET_DOMAIN of the feature base/connection. + * + * @param {Object} state - The Redux state of the feature base/connection. + * @param {Action} action - The Redux action SET_DOMAIN to reduce. + * @private + * @returns {Object} The new state of the feature base/connection after the + * reduction of the specified action. + */ +function _setDomain(state, action) { + return { + ...state, + connectionOptions: { + ...state.connectionOptions, + ..._constructConnectionOptions(action.domain) + } + }; +} diff --git a/react/features/base/participants/reducer.js b/react/features/base/participants/reducer.js index fb598a67f..efb6be753 100644 --- a/react/features/base/participants/reducer.js +++ b/react/features/base/participants/reducer.js @@ -1,6 +1,6 @@ /* global MD5 */ -import { ReducerRegistry } from '../redux'; +import { ReducerRegistry, setStateProperty } from '../redux'; import { DOMINANT_SPEAKER_CHANGED, @@ -55,7 +55,7 @@ function participant(state, action) { case DOMINANT_SPEAKER_CHANGED: // Only one dominant speaker is allowed. return ( - _setStateProperty( + setStateProperty( state, 'dominantSpeaker', state.id === action.participant.id)); @@ -123,7 +123,7 @@ function participant(state, action) { case PIN_PARTICIPANT: // Currently, only one pinned participant is allowed. return ( - _setStateProperty( + setStateProperty( state, 'pinned', state.id === action.participant.id)); @@ -201,30 +201,3 @@ function _getAvatarURL(participantId, email) { return urlPref + avatarId + urlSuf; } - -/** - * Sets a specific property of a specific state to a specific value. Prevents - * unnecessary state changes (when the specified value is equal to the - * value of the specified property of the specified state). - * - * @param {Object} state - The (Redux) state from which a new state is to be - * constructed by setting the specified property to the specified - * value. - * @param {string} property - The property of state which is to be - * assigned the specified value (in the new state). - * @param {*} value - The value to assign to the specified property. - * @returns {Object} The specified state if the value of the specified - * property equals the specified value/tt>; otherwise, a new state - * constructed from the specified state by setting the specified - * property to the specified value. - */ -function _setStateProperty(state, property, value) { - if (state[property] !== value) { - return { - ...state, - [property]: value - }; - } - - return state; -} diff --git a/react/features/base/redux/functions.js b/react/features/base/redux/functions.js new file mode 100644 index 000000000..a64ee6130 --- /dev/null +++ b/react/features/base/redux/functions.js @@ -0,0 +1,37 @@ +/** + * Sets a specific property of a specific state to a specific value. Prevents + * unnecessary state changes (when the specified value is equal to the + * value of the specified property of the specified state). + * + * @param {Object} state - The (Redux) state from which a new state is to be + * constructed by setting the specified property to the specified + * value. + * @param {string} property - The property of state which is to be + * assigned the specified value (in the new state). + * @param {*} value - The value to assign to the specified property. + * @returns {Object} The specified state if the value of the specified + * property equals the specified value/tt>; otherwise, a new state + * constructed from the specified state by setting the specified + * property to the specified value. + */ +export function setStateProperty(state, property, value) { + // Delete state properties that are to be set to undefined. (It is a matter + // of personal preference, mostly.) + if (typeof value === 'undefined' + && Object.prototype.hasOwnProperty.call(state, property)) { + const newState = { ...state }; + + if (delete newState[property]) { + return newState; + } + } + + if (state[property] !== value) { + return { + ...state, + [property]: value + }; + } + + return state; +} diff --git a/react/features/base/redux/index.js b/react/features/base/redux/index.js index f27529bc3..9658a8706 100644 --- a/react/features/base/redux/index.js +++ b/react/features/base/redux/index.js @@ -1,2 +1,3 @@ +export * from './functions'; export { default as MiddlewareRegistry } from './MiddlewareRegistry'; export { default as ReducerRegistry } from './ReducerRegistry'; diff --git a/react/features/base/tracks/middleware.js b/react/features/base/tracks/middleware.js index 75a871c37..f7bc75e2d 100644 --- a/react/features/base/tracks/middleware.js +++ b/react/features/base/tracks/middleware.js @@ -109,7 +109,8 @@ function _mutedChanged(store, action, mediaType) { * @param {Action} action - The Redux action TRACK_UPDATED which is * being dispatched in the specified store. * @private - * @returns {void} + * @returns {Object} The new state that is the result of the reduction of the + * specified action. */ function _trackUpdated(store, next, action) { // Determine the muted state of the local track before the update. diff --git a/react/features/filmStrip/components/FilmStrip.js b/react/features/filmStrip/components/FilmStrip.js index df94a0035..972b7cbfe 100644 --- a/react/features/filmStrip/components/FilmStrip.js +++ b/react/features/filmStrip/components/FilmStrip.js @@ -95,7 +95,6 @@ class FilmStrip extends Component { * @param {Object} state - Redux state. * @returns {{ * participants: Participant[], - * tracks: (JitsiLocalTrack|JitsiRemoteTrack)[] * }} */ function mapStateToProps(state) {