diff --git a/conference.js b/conference.js index d5fb113b2..bca7f92fa 100644 --- a/conference.js +++ b/conference.js @@ -30,7 +30,8 @@ import { toggleAudioOnly, EMAIL_COMMAND, lockStateChanged, - p2pStatusChanged + p2pStatusChanged, + setLocalParticipantData } from './react/features/base/conference'; import { updateDeviceList } from './react/features/base/devices'; import { @@ -55,6 +56,8 @@ import { } from './react/features/base/media'; import { dominantSpeakerChanged, + getLocalParticipant, + getParticipantById, localParticipantConnectionStatusChanged, localParticipantRoleChanged, MAX_DISPLAY_NAME_LENGTH, @@ -143,43 +146,15 @@ function sendData(command, value) { room.sendCommand(command, {value: value}); } -/** - * Sets up initially the properties of the local participant - email, avatarID, - * avatarURL, displayName, etc. - */ -function _setupLocalParticipantProperties() { - const email = APP.settings.getEmail(); - email && sendData(commands.EMAIL, email); - - const avatarUrl = APP.settings.getAvatarUrl(); - avatarUrl && sendData(commands.AVATAR_URL, avatarUrl); - - if (!email && !avatarUrl) { - sendData(commands.AVATAR_ID, APP.settings.getAvatarId()); - } - - let nick = APP.settings.getDisplayName(); - if (config.useNicks && !nick) { - nick = APP.UI.askForNickname(); - APP.settings.setDisplayName(nick); - } - nick && room.setDisplayName(nick); -} - /** * Get user nickname by user id. * @param {string} id user id * @returns {string?} user nickname or undefined if user is unknown. */ function getDisplayName(id) { - if (APP.conference.isLocalId(id)) { - return APP.settings.getDisplayName(); - } + const participant = getParticipantById(APP.store.getState(), id); - let participant = room.getParticipantById(id); - if (participant && participant.getDisplayName()) { - return participant.getDisplayName(); - } + return participant && participant.name; } /** @@ -989,6 +964,13 @@ export default { isConnectionInterrupted() { return this._room.isConnectionInterrupted(); }, + /** + * Obtains the local display name. + * @returns {string|undefined} + */ + getLocalDisplayName() { + return getDisplayName(this.getMyUserId()); + }, /** * Finds JitsiParticipant for given id. * @@ -1162,7 +1144,7 @@ export default { this._setLocalAudioVideoStreams(localTracks); this._room = room; // FIXME do not use this - _setupLocalParticipantProperties(); + setLocalParticipantData(room, APP.store.getState()); this._setupListeners(); }, @@ -2420,13 +2402,15 @@ export default { * @param email {string} the new email */ changeLocalEmail(email = '') { + const localParticipant = getLocalParticipant(APP.store.getState()); + email = String(email).trim(); - if (email === APP.settings.getEmail()) { + if (email === localParticipant.email) { return; } - const localId = room ? room.myUserId() : undefined; + const localId = localParticipant.id; APP.store.dispatch(participantUpdated({ id: localId, @@ -2444,22 +2428,22 @@ export default { * @param url {string} the new url */ changeLocalAvatarUrl(url = '') { + const { avatarURL, id } = getLocalParticipant(APP.store.getState()); + url = String(url).trim(); - if (url === APP.settings.getAvatarUrl()) { + if (url === avatarURL) { return; } - const localId = room ? room.myUserId() : undefined; - APP.store.dispatch(participantUpdated({ - id: localId, + id, local: true, avatarURL: url })); APP.settings.setAvatarUrl(url); - APP.UI.setUserAvatarUrl(localId, url); + APP.UI.setUserAvatarUrl(id, url); sendData(commands.AVATAR_URL, url); }, @@ -2501,13 +2485,14 @@ export default { changeLocalDisplayName(nickname = '') { const formattedNickname = nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH); + const { id, name } = getLocalParticipant(APP.store.getState()); - if (formattedNickname === APP.settings.getDisplayName()) { + if (formattedNickname === name) { return; } APP.store.dispatch(participantUpdated({ - id: this.getMyUserId(), + id, local: true, name: formattedNickname })); @@ -2515,7 +2500,7 @@ export default { APP.settings.setDisplayName(formattedNickname); if (room) { room.setDisplayName(formattedNickname); - APP.UI.changeDisplayName(this.getMyUserId(), formattedNickname); + APP.UI.changeDisplayName(id, formattedNickname); } }, diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 9b9852464..e9c5de3f9 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -19,7 +19,6 @@ import VideoLayout from "./videolayout/VideoLayout"; import Filmstrip from "./videolayout/Filmstrip"; import SettingsMenu from "./side_pannels/settings/SettingsMenu"; import Profile from "./side_pannels/profile/Profile"; -import Settings from "./../settings/Settings"; import { updateDeviceList } from '../../react/features/base/devices'; import { JitsiTrackErrors } from '../../react/features/base/lib-jitsi-meet'; @@ -42,6 +41,7 @@ import { maybeShowNotificationWithDoNotDisplay, setNotificationsEnabled } from '../../react/features/notifications'; +import { getLocalParticipant } from '../../react/features/base/participants'; var EventEmitter = require("events"); UI.messageHandler = messageHandler; @@ -199,7 +199,8 @@ UI.setLocalRaisedHandStatus * Initialize conference UI. */ UI.initConference = function () { - let id = APP.conference.getMyUserId(); + const { id, avatarID, email, name } + = getLocalParticipant(APP.store.getState()); // Update default button states before showing the toolbar // if local role changes buttons state will be again updated. @@ -207,18 +208,17 @@ UI.initConference = function () { UI.showToolbar(); - let displayName = config.displayJids ? id : Settings.getDisplayName(); + let displayName = config.displayJids ? id : name; if (displayName) { UI.changeDisplayName('localVideoContainer', displayName); } // Make sure we configure our avatar id, before creating avatar for us - let email = Settings.getEmail(); if (email) { UI.setUserEmail(id, email); } else { - UI.setUserAvatarID(id, Settings.getAvatarId()); + UI.setUserAvatarID(id, avatarID); } APP.store.dispatch(checkAutoEnableDesktopSharing()); @@ -235,7 +235,9 @@ UI.mucJoined = function () { // Update local video now that a conference is joined a user ID should be // set. - UI.changeDisplayName('localVideoContainer', APP.settings.getDisplayName()); + UI.changeDisplayName( + 'localVideoContainer', + APP.conference.getLocalDisplayName()); }; /*** diff --git a/modules/UI/side_pannels/chat/Chat.js b/modules/UI/side_pannels/chat/Chat.js index 6b1a643c7..abf99633c 100644 --- a/modules/UI/side_pannels/chat/Chat.js +++ b/modules/UI/side_pannels/chat/Chat.js @@ -187,7 +187,7 @@ var Chat = { */ init (eventEmitter) { initHTML(); - if (APP.settings.getDisplayName()) { + if (APP.conference.getLocalDisplayName()) { Chat.setChatConversationMode(true); } @@ -244,7 +244,7 @@ var Chat = { // if we are in conversation mode focus on the text input // if we are not, focus on the display name input - if (APP.settings.getDisplayName()) + if (APP.conference.getLocalDisplayName()) deferredFocus('usermsg'); else deferredFocus('nickinput'); diff --git a/react/features/base/conference/actions.js b/react/features/base/conference/actions.js index 2d394c844..ad986f249 100644 --- a/react/features/base/conference/actions.js +++ b/react/features/base/conference/actions.js @@ -5,7 +5,6 @@ import { JitsiConferenceEvents } from '../lib-jitsi-meet'; import { setAudioMuted, setVideoMuted } from '../media'; import { dominantSpeakerChanged, - getLocalParticipant, participantConnectionStatusChanged, participantJoined, participantLeft, @@ -36,7 +35,10 @@ import { EMAIL_COMMAND, JITSI_CONFERENCE_URL_KEY } from './constants'; -import { _addLocalTracksToConference } from './functions'; +import { + _addLocalTracksToConference, + setLocalParticipantData +} from './functions'; import type { Dispatch } from 'redux'; @@ -147,22 +149,6 @@ function _addConferenceListeners(conference, dispatch) { }))); } -/** - * Sets the data for the local participant to the conference. - * - * @param {JitsiConference} conference - The JitsiConference instance. - * @param {Object} state - The Redux state. - * @returns {void} - */ -function _setLocalParticipantData(conference, state) { - const { avatarID } = getLocalParticipant(state); - - conference.removeCommand(AVATAR_ID_COMMAND); - conference.sendCommand(AVATAR_ID_COMMAND, { - value: avatarID - }); -} - /** * Signals that a specific conference has failed. * @@ -302,7 +288,7 @@ export function createConference() { _addConferenceListeners(conference, dispatch); - _setLocalParticipantData(conference, state); + setLocalParticipantData(conference, state); conference.join(password); }; diff --git a/react/features/base/conference/functions.js b/react/features/base/conference/functions.js index 234c67add..7e23b1daa 100644 --- a/react/features/base/conference/functions.js +++ b/react/features/base/conference/functions.js @@ -1,4 +1,10 @@ +import { + AVATAR_ID_COMMAND, + AVATAR_URL_COMMAND, + EMAIL_COMMAND +} from './constants'; import { JitsiTrackErrors } from '../lib-jitsi-meet'; +import { getLocalParticipant } from '../participants'; import { toState } from '../redux'; /** @@ -121,3 +127,26 @@ function _reportError(msg, err) { // one. console.error(msg, err); } + +/** + * Sets the data like avatar URL, email and display name for the local + * participant to the conference. + * + * @param {JitsiConference} conference - The JitsiConference instance. + * @param {Object} state - The whole Redux state. + * @returns {void} + */ +export function setLocalParticipantData(conference, state) { + const { avatarID, avatarURL, email, name } = getLocalParticipant(state); + + avatarID && conference.sendCommand(AVATAR_ID_COMMAND, { + value: avatarID + }); + avatarURL && conference.sendCommand(AVATAR_URL_COMMAND, { + value: avatarURL + }); + email && conference.sendCommand(EMAIL_COMMAND, { + value: email + }); + conference.setDisplayName(name); +} diff --git a/react/features/base/connection/actions.web.js b/react/features/base/connection/actions.web.js index 044196951..6afd213f7 100644 --- a/react/features/base/connection/actions.web.js +++ b/react/features/base/connection/actions.web.js @@ -72,7 +72,8 @@ export function connect() { APP.keyboardshortcut.init(); - if (config.requireDisplayName && !APP.settings.getDisplayName()) { + if (config.requireDisplayName + && !APP.conference.getLocalDisplayName()) { APP.UI.promptDisplayName(); } }) diff --git a/react/features/base/jwt/middleware.js b/react/features/base/jwt/middleware.js index c4b7caccb..4ad5d5414 100644 --- a/react/features/base/jwt/middleware.js +++ b/react/features/base/jwt/middleware.js @@ -12,7 +12,9 @@ import { LIB_INIT_ERROR } from '../lib-jitsi-meet'; import { getLocalParticipant, getParticipantCount, - PARTICIPANT_JOINED + LOCAL_PARTICIPANT_DEFAULT_NAME, + PARTICIPANT_JOINED, + participantUpdated } from '../participants'; import { MiddlewareRegistry } from '../redux'; @@ -119,6 +121,103 @@ function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) { return result; } +/** + * Converts 'context.user' JWT token structure to the format compatible with the + * corresponding fields overridden in base/participants. + * + * @param {Object} user - The 'jwt.context.user' structure parsed from the JWT + * token. + * @returns {({ + * avatarURL: string?, + * email: string?, + * name: string? + * })} + * @private + */ +function _normalizeCallerFields(user) { + const { avatar, avatarUrl, email, name } = user; + const caller = { }; + + if (typeof (avatarUrl || avatar) === 'string') { + caller.avatarURL = (avatarUrl || avatar).trim(); + } + if (typeof email === 'string') { + caller.email = email.trim(); + } + if (typeof name === 'string') { + caller.name = name.trim(); + } + + return Object.keys(caller).length ? caller : undefined; +} + +/** + * Eventually overwrites 'avatarURL', 'email' and 'name' fields with the values + * from JWT token for the local participant stored in the 'base/participants' + * Redux store by dispatching the participant updated action. + * + * @param {Store} store - The redux store. + * @param {Object} caller - The "caller" structure parsed from 'context.user' + * part of the JWT token and then normalized using + * {@link _normalizeCallerFields}. + * @returns {void} + * @private + */ +function _overwriteLocalParticipant({ dispatch, getState }, caller) { + const { avatarURL, email, name } = caller; + const localParticipant = getLocalParticipant(getState()); + + if (localParticipant && (avatarURL || email || name)) { + const newProperties = { id: localParticipant.id }; + + if (avatarURL) { + newProperties.avatarURL = avatarURL; + } + if (email) { + newProperties.email = email; + } + if (name) { + newProperties.name = name; + } + dispatch(participantUpdated(newProperties)); + } +} + +/** + * Will reset the values overridden by {@link _overwriteLocalParticipant} + * by either clearing them or setting to default values. Only the values that + * have not changed since the override happened will be restored. + * + * NOTE Once there is the possibility to edit and save participant properties, + * this method should restore values from the storage instead. + * + * @param {Store} store - The Redux store. + * @param {Object} caller - The 'caller' part of the JWT Redux state which tells + * which local participant's fields's been overridden when the JWT token was + * set. + * @returns {void} + * @private + */ +function _resetLocalParticipantOverrides({ dispatch, getState }, caller) { + const { avatarURL, name, email } = caller; + const localParticipant = getLocalParticipant(getState()); + + if (localParticipant && (avatarURL || name || email)) { + const newProperties = { id: localParticipant.id }; + + if (avatarURL === localParticipant.avatarURL) { + newProperties.avatarURL = undefined; + } + if (name === localParticipant.name) { + newProperties.name = LOCAL_PARTICIPANT_DEFAULT_NAME; + } + if (email === localParticipant.email) { + newProperties.email = undefined; + } + dispatch(participantUpdated(newProperties)); + } +} + /** * Notifies the feature jwt that the action {@link SET_CONFIG} or * {@link SET_LOCATION_URL} is being dispatched within a specific redux @@ -183,11 +282,24 @@ function _setJWT(store, next, action) { action.issuer = iss; if (context) { action.callee = context.callee; - action.caller = context.user; + action.caller = _normalizeCallerFields(context.user); action.group = context.group; action.server = context.server; + + if (action.caller) { + _overwriteLocalParticipant(store, action.caller); + } } } + } else if (!jwt && !Object.keys(actionPayload).length) { + const jwtState = store.getState()['features/base/jwt']; + + // The logic of restoring JWT overrides make sense only on mobile. On + // web it should eventually be restored from storage, but there's no + // such use case yet. + if (jwtState.caller && typeof APP === 'undefined') { + _resetLocalParticipantOverrides(store, jwtState.caller); + } } return _maybeSetCallOverlayVisible(store, next, action); diff --git a/react/features/base/participants/constants.js b/react/features/base/participants/constants.js index 39f379a7b..81d46f540 100644 --- a/react/features/base/participants/constants.js +++ b/react/features/base/participants/constants.js @@ -21,6 +21,13 @@ export const DEFAULT_AVATAR_RELATIVE_PATH = 'images/avatar.png'; */ export const LOCAL_PARTICIPANT_DEFAULT_ID = 'local'; +/** + * The default display name for the local participant. + * TODO Get the from config and/or localized. + * @type {string} + */ +export const LOCAL_PARTICIPANT_DEFAULT_NAME = 'me'; + /** * Max length of the display names. * diff --git a/react/features/base/participants/reducer.js b/react/features/base/participants/reducer.js index 80d7c8674..ed692d67a 100644 --- a/react/features/base/participants/reducer.js +++ b/react/features/base/participants/reducer.js @@ -11,6 +11,7 @@ import { } from './actionTypes'; import { LOCAL_PARTICIPANT_DEFAULT_ID, + LOCAL_PARTICIPANT_DEFAULT_NAME, PARTICIPANT_ROLE } from './constants'; @@ -99,7 +100,12 @@ function _participant(state, action) { // name if (!name) { // TODO Get the from config and/or localized. - name = local ? 'me' : 'Fellow Jitster'; + // On web default value is handled in: + // conference.js getParticipantDisplayName + if (typeof APP === 'undefined') { + name + = local ? LOCAL_PARTICIPANT_DEFAULT_NAME : 'Fellow Jitster'; + } } return { diff --git a/react/features/conference/route.js b/react/features/conference/route.js index 883f59eac..b8918f742 100644 --- a/react/features/conference/route.js +++ b/react/features/conference/route.js @@ -29,8 +29,6 @@ RouteRegistry.register({ * @returns {void} */ function _initConference() { - _setTokenData(); - // Initialize the conference URL handler APP.ConferenceUrl = new ConferenceUrl(window.location); } @@ -102,22 +100,3 @@ function _obtainConfigHandler() { APP.connectionTimes['configuration.fetched'] = now; logger.log('(TIME) configuration fetched:\t', now); } - -/** - * If JWT token data it will be used for local user settings. - * - * @private - * @returns {void} - */ -function _setTokenData() { - const state = APP.store.getState(); - const { caller } = state['features/base/jwt']; - - if (caller) { - const { avatarUrl, avatar, email, name } = caller; - - APP.settings.setEmail((email || '').trim(), true); - APP.settings.setAvatarUrl((avatarUrl || avatar || '').trim()); - APP.settings.setDisplayName((name || '').trim(), true); - } -} diff --git a/react/features/filmstrip/functions.js b/react/features/filmstrip/functions.js index 542c31e44..ad7cfd72b 100644 --- a/react/features/filmstrip/functions.js +++ b/react/features/filmstrip/functions.js @@ -3,8 +3,7 @@ declare var interfaceConfig: Object; import { - getPinnedParticipant, - getLocalParticipant + getPinnedParticipant } from '../base/participants'; /** @@ -17,6 +16,7 @@ import { export function shouldRemoteVideosBeVisible(state: Object) { const participants = state['features/base/participants']; const participantsCount = participants.length; + const pinnedParticipant = getPinnedParticipant(state); const shouldShowVideos = participantsCount > 2 @@ -27,7 +27,7 @@ export function shouldRemoteVideosBeVisible(state: Object) { || (participantsCount > 1 && (state['features/filmstrip'].hovered || state['features/toolbox'].visible - || getLocalParticipant(state) === getPinnedParticipant(state))) + || (pinnedParticipant && pinnedParticipant.local))) || interfaceConfig.filmStripOnly diff --git a/react/features/speaker-stats/components/SpeakerStats.js b/react/features/speaker-stats/components/SpeakerStats.js index ec637c4f5..cec28fbdb 100644 --- a/react/features/speaker-stats/components/SpeakerStats.js +++ b/react/features/speaker-stats/components/SpeakerStats.js @@ -1,11 +1,12 @@ -/* global APP, interfaceConfig */ +/* global interfaceConfig */ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { connect } from 'react-redux'; import { Dialog } from '../../base/dialog'; import { translate } from '../../base/i18n'; - +import { getLocalParticipant } from '../../base/participants'; import SpeakerStatsItem from './SpeakerStatsItem'; import SpeakerStatsLabels from './SpeakerStatsLabels'; @@ -21,6 +22,12 @@ class SpeakerStats extends Component { * @static */ static propTypes = { + /** + * The display name for the local participant obtained from the Redux + * store. + */ + _localDisplayName: PropTypes.string, + /** * The JitsiConference from which stats will be pulled. */ @@ -130,7 +137,7 @@ class SpeakerStats extends Component { const { t } = this.props; const meString = t('me'); - displayName = APP.settings.getDisplayName(); + displayName = this.props._localDisplayName; displayName = displayName ? `${displayName} (${meString})` : meString; } else { @@ -149,4 +156,26 @@ class SpeakerStats extends Component { } } -export default translate(SpeakerStats); +/** + * Maps (parts of) the Redux state to the associated SpeakerStats's props. + * + * @param {Object} state - The Redux state. + * @private + * @returns {{ + * _localDisplayName: string? + * }} + */ +function _mapStateToProps(state) { + const localParticipant = getLocalParticipant(state); + + return { + /** + * The local display name. + * @private + * @type {string|undefined} + */ + _localDisplayName: localParticipant && localParticipant.name + }; +} + +export default translate(connect(_mapStateToProps)(SpeakerStats));