feat: override email, display name and avatar on mobile

Will override email, display name and avatar URL with the values
provided in 'context.user' structure of the JWT token.

Settings will no longer be used to retrieve local display name,
email and avatar URL. Now those values will be obtained from
the /features/base/participants Redux state.

fix(jwt/middleware): use const for default name

fix: wrong default display name on web

ref(base/participants): remove getDisplayName functions

ref(jwt): do not accept unknown user fields
This commit is contained in:
paweldomas 2017-10-06 12:52:23 -05:00 committed by Lyubo Marinov
parent 0eddef4d62
commit 8a4e6a7ec0
12 changed files with 237 additions and 101 deletions

View File

@ -30,7 +30,8 @@ import {
toggleAudioOnly, toggleAudioOnly,
EMAIL_COMMAND, EMAIL_COMMAND,
lockStateChanged, lockStateChanged,
p2pStatusChanged p2pStatusChanged,
setLocalParticipantData
} from './react/features/base/conference'; } from './react/features/base/conference';
import { updateDeviceList } from './react/features/base/devices'; import { updateDeviceList } from './react/features/base/devices';
import { import {
@ -55,6 +56,8 @@ import {
} from './react/features/base/media'; } from './react/features/base/media';
import { import {
dominantSpeakerChanged, dominantSpeakerChanged,
getLocalParticipant,
getParticipantById,
localParticipantConnectionStatusChanged, localParticipantConnectionStatusChanged,
localParticipantRoleChanged, localParticipantRoleChanged,
MAX_DISPLAY_NAME_LENGTH, MAX_DISPLAY_NAME_LENGTH,
@ -143,43 +146,15 @@ function sendData(command, value) {
room.sendCommand(command, {value: 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. * Get user nickname by user id.
* @param {string} id user id * @param {string} id user id
* @returns {string?} user nickname or undefined if user is unknown. * @returns {string?} user nickname or undefined if user is unknown.
*/ */
function getDisplayName(id) { function getDisplayName(id) {
if (APP.conference.isLocalId(id)) { const participant = getParticipantById(APP.store.getState(), id);
return APP.settings.getDisplayName();
}
let participant = room.getParticipantById(id); return participant && participant.name;
if (participant && participant.getDisplayName()) {
return participant.getDisplayName();
}
} }
/** /**
@ -989,6 +964,13 @@ export default {
isConnectionInterrupted() { isConnectionInterrupted() {
return this._room.isConnectionInterrupted(); return this._room.isConnectionInterrupted();
}, },
/**
* Obtains the local display name.
* @returns {string|undefined}
*/
getLocalDisplayName() {
return getDisplayName(this.getMyUserId());
},
/** /**
* Finds JitsiParticipant for given id. * Finds JitsiParticipant for given id.
* *
@ -1162,7 +1144,7 @@ export default {
this._setLocalAudioVideoStreams(localTracks); this._setLocalAudioVideoStreams(localTracks);
this._room = room; // FIXME do not use this this._room = room; // FIXME do not use this
_setupLocalParticipantProperties(); setLocalParticipantData(room, APP.store.getState());
this._setupListeners(); this._setupListeners();
}, },
@ -2420,13 +2402,15 @@ export default {
* @param email {string} the new email * @param email {string} the new email
*/ */
changeLocalEmail(email = '') { changeLocalEmail(email = '') {
const localParticipant = getLocalParticipant(APP.store.getState());
email = String(email).trim(); email = String(email).trim();
if (email === APP.settings.getEmail()) { if (email === localParticipant.email) {
return; return;
} }
const localId = room ? room.myUserId() : undefined; const localId = localParticipant.id;
APP.store.dispatch(participantUpdated({ APP.store.dispatch(participantUpdated({
id: localId, id: localId,
@ -2444,22 +2428,22 @@ export default {
* @param url {string} the new url * @param url {string} the new url
*/ */
changeLocalAvatarUrl(url = '') { changeLocalAvatarUrl(url = '') {
const { avatarURL, id } = getLocalParticipant(APP.store.getState());
url = String(url).trim(); url = String(url).trim();
if (url === APP.settings.getAvatarUrl()) { if (url === avatarURL) {
return; return;
} }
const localId = room ? room.myUserId() : undefined;
APP.store.dispatch(participantUpdated({ APP.store.dispatch(participantUpdated({
id: localId, id,
local: true, local: true,
avatarURL: url avatarURL: url
})); }));
APP.settings.setAvatarUrl(url); APP.settings.setAvatarUrl(url);
APP.UI.setUserAvatarUrl(localId, url); APP.UI.setUserAvatarUrl(id, url);
sendData(commands.AVATAR_URL, url); sendData(commands.AVATAR_URL, url);
}, },
@ -2501,13 +2485,14 @@ export default {
changeLocalDisplayName(nickname = '') { changeLocalDisplayName(nickname = '') {
const formattedNickname const formattedNickname
= nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH); = nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH);
const { id, name } = getLocalParticipant(APP.store.getState());
if (formattedNickname === APP.settings.getDisplayName()) { if (formattedNickname === name) {
return; return;
} }
APP.store.dispatch(participantUpdated({ APP.store.dispatch(participantUpdated({
id: this.getMyUserId(), id,
local: true, local: true,
name: formattedNickname name: formattedNickname
})); }));
@ -2515,7 +2500,7 @@ export default {
APP.settings.setDisplayName(formattedNickname); APP.settings.setDisplayName(formattedNickname);
if (room) { if (room) {
room.setDisplayName(formattedNickname); room.setDisplayName(formattedNickname);
APP.UI.changeDisplayName(this.getMyUserId(), formattedNickname); APP.UI.changeDisplayName(id, formattedNickname);
} }
}, },

View File

@ -19,7 +19,6 @@ import VideoLayout from "./videolayout/VideoLayout";
import Filmstrip from "./videolayout/Filmstrip"; import Filmstrip from "./videolayout/Filmstrip";
import SettingsMenu from "./side_pannels/settings/SettingsMenu"; import SettingsMenu from "./side_pannels/settings/SettingsMenu";
import Profile from "./side_pannels/profile/Profile"; import Profile from "./side_pannels/profile/Profile";
import Settings from "./../settings/Settings";
import { updateDeviceList } from '../../react/features/base/devices'; import { updateDeviceList } from '../../react/features/base/devices';
import { JitsiTrackErrors } from '../../react/features/base/lib-jitsi-meet'; import { JitsiTrackErrors } from '../../react/features/base/lib-jitsi-meet';
@ -42,6 +41,7 @@ import {
maybeShowNotificationWithDoNotDisplay, maybeShowNotificationWithDoNotDisplay,
setNotificationsEnabled setNotificationsEnabled
} from '../../react/features/notifications'; } from '../../react/features/notifications';
import { getLocalParticipant } from '../../react/features/base/participants';
var EventEmitter = require("events"); var EventEmitter = require("events");
UI.messageHandler = messageHandler; UI.messageHandler = messageHandler;
@ -199,7 +199,8 @@ UI.setLocalRaisedHandStatus
* Initialize conference UI. * Initialize conference UI.
*/ */
UI.initConference = function () { 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 // Update default button states before showing the toolbar
// if local role changes buttons state will be again updated. // if local role changes buttons state will be again updated.
@ -207,18 +208,17 @@ UI.initConference = function () {
UI.showToolbar(); UI.showToolbar();
let displayName = config.displayJids ? id : Settings.getDisplayName(); let displayName = config.displayJids ? id : name;
if (displayName) { if (displayName) {
UI.changeDisplayName('localVideoContainer', displayName); UI.changeDisplayName('localVideoContainer', displayName);
} }
// Make sure we configure our avatar id, before creating avatar for us // Make sure we configure our avatar id, before creating avatar for us
let email = Settings.getEmail();
if (email) { if (email) {
UI.setUserEmail(id, email); UI.setUserEmail(id, email);
} else { } else {
UI.setUserAvatarID(id, Settings.getAvatarId()); UI.setUserAvatarID(id, avatarID);
} }
APP.store.dispatch(checkAutoEnableDesktopSharing()); 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 // Update local video now that a conference is joined a user ID should be
// set. // set.
UI.changeDisplayName('localVideoContainer', APP.settings.getDisplayName()); UI.changeDisplayName(
'localVideoContainer',
APP.conference.getLocalDisplayName());
}; };
/*** /***

View File

@ -187,7 +187,7 @@ var Chat = {
*/ */
init (eventEmitter) { init (eventEmitter) {
initHTML(); initHTML();
if (APP.settings.getDisplayName()) { if (APP.conference.getLocalDisplayName()) {
Chat.setChatConversationMode(true); Chat.setChatConversationMode(true);
} }
@ -244,7 +244,7 @@ var Chat = {
// if we are in conversation mode focus on the text input // if we are in conversation mode focus on the text input
// if we are not, focus on the display name input // if we are not, focus on the display name input
if (APP.settings.getDisplayName()) if (APP.conference.getLocalDisplayName())
deferredFocus('usermsg'); deferredFocus('usermsg');
else else
deferredFocus('nickinput'); deferredFocus('nickinput');

View File

@ -5,7 +5,6 @@ import { JitsiConferenceEvents } from '../lib-jitsi-meet';
import { setAudioMuted, setVideoMuted } from '../media'; import { setAudioMuted, setVideoMuted } from '../media';
import { import {
dominantSpeakerChanged, dominantSpeakerChanged,
getLocalParticipant,
participantConnectionStatusChanged, participantConnectionStatusChanged,
participantJoined, participantJoined,
participantLeft, participantLeft,
@ -36,7 +35,10 @@ import {
EMAIL_COMMAND, EMAIL_COMMAND,
JITSI_CONFERENCE_URL_KEY JITSI_CONFERENCE_URL_KEY
} from './constants'; } from './constants';
import { _addLocalTracksToConference } from './functions'; import {
_addLocalTracksToConference,
setLocalParticipantData
} from './functions';
import type { Dispatch } from 'redux'; 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. * Signals that a specific conference has failed.
* *
@ -302,7 +288,7 @@ export function createConference() {
_addConferenceListeners(conference, dispatch); _addConferenceListeners(conference, dispatch);
_setLocalParticipantData(conference, state); setLocalParticipantData(conference, state);
conference.join(password); conference.join(password);
}; };

View File

@ -1,4 +1,10 @@
import {
AVATAR_ID_COMMAND,
AVATAR_URL_COMMAND,
EMAIL_COMMAND
} from './constants';
import { JitsiTrackErrors } from '../lib-jitsi-meet'; import { JitsiTrackErrors } from '../lib-jitsi-meet';
import { getLocalParticipant } from '../participants';
import { toState } from '../redux'; import { toState } from '../redux';
/** /**
@ -121,3 +127,26 @@ function _reportError(msg, err) {
// one. // one.
console.error(msg, err); 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);
}

View File

@ -72,7 +72,8 @@ export function connect() {
APP.keyboardshortcut.init(); APP.keyboardshortcut.init();
if (config.requireDisplayName && !APP.settings.getDisplayName()) { if (config.requireDisplayName
&& !APP.conference.getLocalDisplayName()) {
APP.UI.promptDisplayName(); APP.UI.promptDisplayName();
} }
}) })

View File

@ -12,7 +12,9 @@ import { LIB_INIT_ERROR } from '../lib-jitsi-meet';
import { import {
getLocalParticipant, getLocalParticipant,
getParticipantCount, getParticipantCount,
PARTICIPANT_JOINED LOCAL_PARTICIPANT_DEFAULT_NAME,
PARTICIPANT_JOINED,
participantUpdated
} from '../participants'; } from '../participants';
import { MiddlewareRegistry } from '../redux'; import { MiddlewareRegistry } from '../redux';
@ -119,6 +121,103 @@ function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) {
return result; 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 * Notifies the feature jwt that the action {@link SET_CONFIG} or
* {@link SET_LOCATION_URL} is being dispatched within a specific redux * {@link SET_LOCATION_URL} is being dispatched within a specific redux
@ -183,11 +282,24 @@ function _setJWT(store, next, action) {
action.issuer = iss; action.issuer = iss;
if (context) { if (context) {
action.callee = context.callee; action.callee = context.callee;
action.caller = context.user; action.caller = _normalizeCallerFields(context.user);
action.group = context.group; action.group = context.group;
action.server = context.server; 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); return _maybeSetCallOverlayVisible(store, next, action);

View File

@ -21,6 +21,13 @@ export const DEFAULT_AVATAR_RELATIVE_PATH = 'images/avatar.png';
*/ */
export const LOCAL_PARTICIPANT_DEFAULT_ID = 'local'; 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. * Max length of the display names.
* *

View File

@ -11,6 +11,7 @@ import {
} from './actionTypes'; } from './actionTypes';
import { import {
LOCAL_PARTICIPANT_DEFAULT_ID, LOCAL_PARTICIPANT_DEFAULT_ID,
LOCAL_PARTICIPANT_DEFAULT_NAME,
PARTICIPANT_ROLE PARTICIPANT_ROLE
} from './constants'; } from './constants';
@ -99,7 +100,12 @@ function _participant(state, action) {
// name // name
if (!name) { if (!name) {
// TODO Get the from config and/or localized. // 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 { return {

View File

@ -29,8 +29,6 @@ RouteRegistry.register({
* @returns {void} * @returns {void}
*/ */
function _initConference() { function _initConference() {
_setTokenData();
// Initialize the conference URL handler // Initialize the conference URL handler
APP.ConferenceUrl = new ConferenceUrl(window.location); APP.ConferenceUrl = new ConferenceUrl(window.location);
} }
@ -102,22 +100,3 @@ function _obtainConfigHandler() {
APP.connectionTimes['configuration.fetched'] = now; APP.connectionTimes['configuration.fetched'] = now;
logger.log('(TIME) configuration fetched:\t', 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);
}
}

View File

@ -3,8 +3,7 @@
declare var interfaceConfig: Object; declare var interfaceConfig: Object;
import { import {
getPinnedParticipant, getPinnedParticipant
getLocalParticipant
} from '../base/participants'; } from '../base/participants';
/** /**
@ -17,6 +16,7 @@ import {
export function shouldRemoteVideosBeVisible(state: Object) { export function shouldRemoteVideosBeVisible(state: Object) {
const participants = state['features/base/participants']; const participants = state['features/base/participants'];
const participantsCount = participants.length; const participantsCount = participants.length;
const pinnedParticipant = getPinnedParticipant(state);
const shouldShowVideos const shouldShowVideos
= participantsCount > 2 = participantsCount > 2
@ -27,7 +27,7 @@ export function shouldRemoteVideosBeVisible(state: Object) {
|| (participantsCount > 1 || (participantsCount > 1
&& (state['features/filmstrip'].hovered && (state['features/filmstrip'].hovered
|| state['features/toolbox'].visible || state['features/toolbox'].visible
|| getLocalParticipant(state) === getPinnedParticipant(state))) || (pinnedParticipant && pinnedParticipant.local)))
|| interfaceConfig.filmStripOnly || interfaceConfig.filmStripOnly

View File

@ -1,11 +1,12 @@
/* global APP, interfaceConfig */ /* global interfaceConfig */
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Dialog } from '../../base/dialog'; import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n'; import { translate } from '../../base/i18n';
import { getLocalParticipant } from '../../base/participants';
import SpeakerStatsItem from './SpeakerStatsItem'; import SpeakerStatsItem from './SpeakerStatsItem';
import SpeakerStatsLabels from './SpeakerStatsLabels'; import SpeakerStatsLabels from './SpeakerStatsLabels';
@ -21,6 +22,12 @@ class SpeakerStats extends Component {
* @static * @static
*/ */
static propTypes = { 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. * The JitsiConference from which stats will be pulled.
*/ */
@ -130,7 +137,7 @@ class SpeakerStats extends Component {
const { t } = this.props; const { t } = this.props;
const meString = t('me'); const meString = t('me');
displayName = APP.settings.getDisplayName(); displayName = this.props._localDisplayName;
displayName = displayName ? `${displayName} (${meString})` displayName = displayName ? `${displayName} (${meString})`
: meString; : meString;
} else { } 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));