2017-10-13 19:31:05 +00:00
|
|
|
// @flow
|
2017-10-17 19:08:32 +00:00
|
|
|
import md5 from 'js-md5';
|
2017-09-01 21:25:48 +00:00
|
|
|
|
|
|
|
import { toState } from '../redux';
|
|
|
|
|
2017-12-19 23:11:54 +00:00
|
|
|
import {
|
|
|
|
DEFAULT_AVATAR_RELATIVE_PATH,
|
2018-05-17 15:45:51 +00:00
|
|
|
LOCAL_PARTICIPANT_DEFAULT_ID,
|
|
|
|
PARTICIPANT_ROLE
|
2017-12-19 23:11:54 +00:00
|
|
|
} from './constants';
|
2017-08-16 21:28:39 +00:00
|
|
|
|
2017-02-27 21:42:28 +00:00
|
|
|
declare var config: Object;
|
|
|
|
declare var interfaceConfig: Object;
|
2017-03-07 16:50:17 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the URL of the image for the avatar of a specific participant.
|
|
|
|
*
|
|
|
|
* @param {Participant} [participant] - The participant to return the avatar URL
|
|
|
|
* of.
|
|
|
|
* @param {string} [participant.avatarID] - Participant's avatar ID.
|
|
|
|
* @param {string} [participant.avatarURL] - Participant's avatar URL.
|
|
|
|
* @param {string} [participant.email] - Participant's e-mail address.
|
|
|
|
* @param {string} [participant.id] - Participant's ID.
|
2017-09-01 21:25:48 +00:00
|
|
|
* @public
|
2017-03-07 16:50:17 +00:00
|
|
|
* @returns {string} The URL of the image for the avatar of the specified
|
|
|
|
* participant.
|
|
|
|
*/
|
2017-12-28 19:27:23 +00:00
|
|
|
export function getAvatarURL({ avatarID, avatarURL, email, id }: {
|
2017-09-01 21:25:48 +00:00
|
|
|
avatarID: string,
|
|
|
|
avatarURL: string,
|
|
|
|
email: string,
|
2017-12-28 19:27:23 +00:00
|
|
|
id: string
|
2017-09-01 21:25:48 +00:00
|
|
|
}) {
|
2017-03-07 16:50:17 +00:00
|
|
|
// If disableThirdPartyRequests disables third-party avatar services, we are
|
|
|
|
// restricted to a stock image of ours.
|
|
|
|
if (typeof config === 'object' && config.disableThirdPartyRequests) {
|
2017-08-16 21:28:39 +00:00
|
|
|
return DEFAULT_AVATAR_RELATIVE_PATH;
|
2017-03-07 16:50:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If an avatarURL is specified, then obviously there's nothing to generate.
|
|
|
|
if (avatarURL) {
|
|
|
|
return avatarURL;
|
|
|
|
}
|
|
|
|
|
|
|
|
let key = email || avatarID;
|
|
|
|
let urlPrefix;
|
|
|
|
let urlSuffix;
|
|
|
|
|
|
|
|
// If the ID looks like an e-mail address, we'll use Gravatar because it
|
|
|
|
// supports e-mail addresses.
|
|
|
|
if (key && key.indexOf('@') > 0) {
|
|
|
|
urlPrefix = 'https://www.gravatar.com/avatar/';
|
|
|
|
urlSuffix = '?d=wavatar&size=200';
|
|
|
|
} else {
|
|
|
|
// Otherwise, we do not have much a choice but a random avatar (fetched
|
|
|
|
// from a configured avatar service).
|
|
|
|
if (!key) {
|
|
|
|
key = id;
|
2017-02-28 23:12:02 +00:00
|
|
|
if (!key) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2017-03-07 16:50:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// The deployment is allowed to choose the avatar service which is to
|
|
|
|
// generate the random avatars.
|
|
|
|
urlPrefix
|
|
|
|
= typeof interfaceConfig === 'object'
|
|
|
|
&& interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
|
|
|
|
if (urlPrefix) {
|
|
|
|
urlSuffix = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
|
|
|
|
} else {
|
2017-12-28 19:27:23 +00:00
|
|
|
// Otherwise, use a default (meeples, of course).
|
|
|
|
urlPrefix = 'https://abotars.jitsi.net/meeple/';
|
|
|
|
urlSuffix = '';
|
2017-03-07 16:50:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-17 19:08:32 +00:00
|
|
|
return urlPrefix + md5.hex(key.trim().toLowerCase()) + urlSuffix;
|
2017-03-07 16:50:17 +00:00
|
|
|
}
|
2017-02-27 21:42:28 +00:00
|
|
|
|
2017-12-19 23:11:54 +00:00
|
|
|
/**
|
|
|
|
* Returns the avatarURL for the participant associated with the passed in
|
|
|
|
* participant ID.
|
|
|
|
*
|
|
|
|
* @param {(Function|Object|Participant[])} stateful - The redux state
|
|
|
|
* features/base/participants, the (whole) redux state, or redux's
|
|
|
|
* {@code getState} function to be used to retrieve the state
|
|
|
|
* features/base/participants.
|
|
|
|
* @param {string} id - The ID of the participant to retrieve.
|
|
|
|
* @param {boolean} isLocal - An optional parameter indicating whether or not
|
|
|
|
* the partcipant id is for the local user. If true, a different logic flow is
|
|
|
|
* used find the local user, ignoring the id value as it can change through the
|
|
|
|
* beginning and end of a call.
|
|
|
|
* @returns {(string|undefined)}
|
|
|
|
*/
|
|
|
|
export function getAvatarURLByParticipantId(
|
|
|
|
stateful: Object | Function,
|
|
|
|
id: string = LOCAL_PARTICIPANT_DEFAULT_ID) {
|
|
|
|
const participant = getParticipantById(stateful, id);
|
|
|
|
|
|
|
|
return participant && getAvatarURL(participant);
|
|
|
|
}
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
/**
|
|
|
|
* Returns local participant from Redux state.
|
|
|
|
*
|
2017-10-13 19:31:05 +00:00
|
|
|
* @param {(Function|Object|Participant[])} stateful - The redux state
|
2017-05-31 05:32:13 +00:00
|
|
|
* features/base/participants, the (whole) redux state, or redux's
|
2017-10-13 19:31:05 +00:00
|
|
|
* {@code getState} function to be used to retrieve the state
|
|
|
|
* features/base/participants.
|
2016-10-05 14:36:59 +00:00
|
|
|
* @returns {(Participant|undefined)}
|
|
|
|
*/
|
2017-10-13 19:31:05 +00:00
|
|
|
export function getLocalParticipant(stateful: Object | Function) {
|
|
|
|
const participants = _getAllParticipants(stateful);
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
return participants.find(p => p.local);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns participant by ID from Redux state.
|
|
|
|
*
|
2017-10-13 19:31:05 +00:00
|
|
|
* @param {(Function|Object|Participant[])} stateful - The redux state
|
2017-05-31 05:32:13 +00:00
|
|
|
* features/base/participants, the (whole) redux state, or redux's
|
2017-10-13 19:31:05 +00:00
|
|
|
* {@code getState} function to be used to retrieve the state
|
|
|
|
* features/base/participants.
|
2016-10-05 14:36:59 +00:00
|
|
|
* @param {string} id - The ID of the participant to retrieve.
|
|
|
|
* @private
|
|
|
|
* @returns {(Participant|undefined)}
|
|
|
|
*/
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
export function getParticipantById(stateful: Object | Function, id: string) {
|
2017-10-13 19:31:05 +00:00
|
|
|
const participants = _getAllParticipants(stateful);
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
return participants.find(p => p.id === id);
|
|
|
|
}
|
|
|
|
|
2017-06-27 22:56:55 +00:00
|
|
|
/**
|
|
|
|
* Returns a count of the known participants in the passed in redux state,
|
|
|
|
* excluding any fake participants.
|
|
|
|
*
|
2017-10-13 19:31:05 +00:00
|
|
|
* @param {(Function|Object|Participant[])} stateful - The redux state
|
2017-06-27 22:56:55 +00:00
|
|
|
* features/base/participants, the (whole) redux state, or redux's
|
2017-10-13 19:31:05 +00:00
|
|
|
* {@code getState} function to be used to retrieve the state
|
|
|
|
* features/base/participants.
|
2017-06-27 22:56:55 +00:00
|
|
|
* @returns {number}
|
|
|
|
*/
|
2017-10-13 19:31:05 +00:00
|
|
|
export function getParticipantCount(stateful: Object | Function) {
|
|
|
|
return getParticipants(stateful).length;
|
2017-08-30 23:17:55 +00:00
|
|
|
}
|
|
|
|
|
2017-12-12 15:30:23 +00:00
|
|
|
/**
|
|
|
|
* Returns participant's display name.
|
|
|
|
* FIXME: remove the hardcoded strings once interfaceConfig is stored in redux
|
|
|
|
* and merge with a similarly named method in conference.js.
|
|
|
|
*
|
|
|
|
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
|
|
|
|
* {@code getState} function to be used to retrieve the state.
|
|
|
|
* @param {string} id - The ID of the participant's display name to retrieve.
|
|
|
|
* @private
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
export function getParticipantDisplayName(
|
2018-02-13 15:55:18 +00:00
|
|
|
stateful: Object | Function,
|
|
|
|
id: string) {
|
2017-12-12 15:30:23 +00:00
|
|
|
const participant = getParticipantById(stateful, id);
|
|
|
|
|
|
|
|
if (participant) {
|
|
|
|
if (participant.name) {
|
|
|
|
return participant.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (participant.local) {
|
|
|
|
return typeof interfaceConfig === 'object'
|
|
|
|
? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME
|
|
|
|
: 'me';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return typeof interfaceConfig === 'object'
|
|
|
|
? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME
|
|
|
|
: 'Fellow Jitster';
|
|
|
|
}
|
2017-06-27 22:56:55 +00:00
|
|
|
|
2018-05-21 20:52:30 +00:00
|
|
|
/**
|
|
|
|
* Returns the presence status of a participant associated with the passed id.
|
|
|
|
*
|
|
|
|
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's
|
|
|
|
* {@code getState} function to be used to retrieve the state.
|
|
|
|
* @param {string} id - The id of the participant.
|
|
|
|
* @returns {string} - The presence status.
|
|
|
|
*/
|
|
|
|
export function getParticipantPresenceStatus(
|
|
|
|
stateful: Object | Function, id: string) {
|
|
|
|
if (!id) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const participantById = getParticipantById(stateful, id);
|
|
|
|
|
|
|
|
if (!participantById) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return participantById.presence;
|
|
|
|
}
|
|
|
|
|
2017-08-30 23:17:55 +00:00
|
|
|
/**
|
|
|
|
* Selectors for getting all known participants with fake participants filtered
|
|
|
|
* out.
|
|
|
|
*
|
2017-10-13 19:31:05 +00:00
|
|
|
* @param {(Function|Object|Participant[])} stateful - The redux state
|
2017-08-30 23:17:55 +00:00
|
|
|
* features/base/participants, the (whole) redux state, or redux's
|
2017-10-13 19:31:05 +00:00
|
|
|
* {@code getState} function to be used to retrieve the state
|
|
|
|
* features/base/participants.
|
2017-08-30 23:17:55 +00:00
|
|
|
* @returns {Participant[]}
|
|
|
|
*/
|
2017-10-13 19:31:05 +00:00
|
|
|
export function getParticipants(stateful: Object | Function) {
|
|
|
|
return _getAllParticipants(stateful).filter(p => !p.isBot);
|
2017-06-27 22:56:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the participant which has its pinned state set to truthy.
|
|
|
|
*
|
2017-10-13 19:31:05 +00:00
|
|
|
* @param {(Function|Object|Participant[])} stateful - The redux state
|
2017-06-27 22:56:55 +00:00
|
|
|
* features/base/participants, the (whole) redux state, or redux's
|
2017-10-13 19:31:05 +00:00
|
|
|
* {@code getState} function to be used to retrieve the state
|
|
|
|
* features/base/participants.
|
2017-06-27 22:56:55 +00:00
|
|
|
* @returns {(Participant|undefined)}
|
|
|
|
*/
|
2017-10-13 19:31:05 +00:00
|
|
|
export function getPinnedParticipant(stateful: Object | Function) {
|
|
|
|
return _getAllParticipants(stateful).find(p => p.pinned);
|
2017-06-27 22:56:55 +00:00
|
|
|
}
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
/**
|
|
|
|
* Returns array of participants from Redux state.
|
|
|
|
*
|
2017-10-13 19:31:05 +00:00
|
|
|
* @param {(Function|Object|Participant[])} stateful - The redux state
|
2017-05-31 05:32:13 +00:00
|
|
|
* features/base/participants, the (whole) redux state, or redux's
|
2017-10-13 19:31:05 +00:00
|
|
|
* {@code getState} function to be used to retrieve the state
|
|
|
|
* features/base/participants.
|
2016-10-05 14:36:59 +00:00
|
|
|
* @private
|
|
|
|
* @returns {Participant[]}
|
|
|
|
*/
|
2017-10-13 19:31:05 +00:00
|
|
|
function _getAllParticipants(stateful) {
|
2017-09-01 21:25:48 +00:00
|
|
|
return (
|
2017-10-13 19:31:05 +00:00
|
|
|
Array.isArray(stateful)
|
|
|
|
? stateful
|
|
|
|
: toState(stateful)['features/base/participants'] || []);
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
2018-05-17 15:45:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the current local participant is a moderator in the
|
|
|
|
* conference.
|
|
|
|
*
|
|
|
|
* @param {Object|Function} stateful - Object or function that can be resolved
|
|
|
|
* to the Redux state.
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
export function isLocalParticipantModerator(stateful: Object | Function) {
|
|
|
|
const state = toState(stateful);
|
|
|
|
const localParticipant = getLocalParticipant(state);
|
|
|
|
|
|
|
|
if (!localParticipant) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-05-22 22:41:53 +00:00
|
|
|
return (
|
|
|
|
localParticipant.role === PARTICIPANT_ROLE.MODERATOR
|
|
|
|
&& (!state['features/base/config'].enableUserRolesBasedOnToken
|
|
|
|
|| !state['features/base/jwt'].isGuest));
|
2018-05-17 15:45:51 +00:00
|
|
|
}
|