feat(avatar) add ability to customize Gravatar base URL

Also, default to libravatar.

Closes: https://github.com/jitsi/jitsi-meet/issues/4927
This commit is contained in:
Saúl Ibarra Corretgé 2020-11-15 22:33:55 +01:00 committed by Saúl Ibarra Corretgé
parent 696ec36c8c
commit a7de8be0aa
6 changed files with 44 additions and 18 deletions

View File

@ -393,6 +393,9 @@ var config = {
// Document should be focused for this option to work // Document should be focused for this option to work
// enableAutomaticUrlCopy: false, // enableAutomaticUrlCopy: false,
// Base URL for a Gravatar-compatible service. Defaults to libravatar.
// gravatarBaseURL: 'https://seccdn.libravatar.org/avatar/';
// Stats // Stats
// //

20
package-lock.json generated
View File

@ -3302,9 +3302,9 @@
} }
}, },
"@jitsi/js-utils": { "@jitsi/js-utils": {
"version": "1.0.2", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.3.tgz",
"integrity": "sha512-ls+X9tn9EemUQwPEBr7Z0UD4sjRtwcu1Bh4MUo0Hv4arp0KVzcCYCW+mofsvuZvHg8xJX12LLNVgUKi1X5XTGg==", "integrity": "sha512-m6mZz7R716mHP21lTKQffyM0nNFu3Fe/EHCaOVLFY/vdPsaUl9DhypJqtPIYzRUfPnmnugdaxcxrUeSZQXQzVA==",
"requires": { "requires": {
"bowser": "2.7.0", "bowser": "2.7.0",
"js-md5": "0.7.3" "js-md5": "0.7.3"
@ -10792,6 +10792,20 @@
"webrtc-adapter": "7.5.0" "webrtc-adapter": "7.5.0"
}, },
"dependencies": { "dependencies": {
"@jitsi/js-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.2.tgz",
"integrity": "sha512-ls+X9tn9EemUQwPEBr7Z0UD4sjRtwcu1Bh4MUo0Hv4arp0KVzcCYCW+mofsvuZvHg8xJX12LLNVgUKi1X5XTGg==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3"
}
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"uuid": { "uuid": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",

View File

@ -32,7 +32,7 @@
"@atlaskit/theme": "7.0.2", "@atlaskit/theme": "7.0.2",
"@atlaskit/toggle": "5.0.14", "@atlaskit/toggle": "5.0.14",
"@atlaskit/tooltip": "12.1.13", "@atlaskit/tooltip": "12.1.13",
"@jitsi/js-utils": "1.0.2", "@jitsi/js-utils": "1.0.3",
"@microsoft/microsoft-graph-client": "1.1.0", "@microsoft/microsoft-graph-client": "1.1.0",
"@react-native-community/async-storage": "1.3.4", "@react-native-community/async-storage": "1.3.4",
"@react-native-community/google-signin": "3.0.1", "@react-native-community/google-signin": "3.0.1",

View File

@ -1,6 +1,7 @@
// @flow // @flow
import { getGravatarURL } from '@jitsi/js-utils/avatar'; import { getGravatarURL } from '@jitsi/js-utils/avatar';
import type { Store } from 'redux';
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet'; import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media'; import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
@ -23,30 +24,35 @@ declare var interfaceConfig: Object;
*/ */
const AVATAR_QUEUE = []; const AVATAR_QUEUE = [];
const AVATAR_CHECKED_URLS = new Map(); const AVATAR_CHECKED_URLS = new Map();
/* eslint-disable arrow-body-style */ /* eslint-disable arrow-body-style, no-unused-vars */
const AVATAR_CHECKER_FUNCTIONS = [ const AVATAR_CHECKER_FUNCTIONS = [
participant => { (participant, _) => {
return participant && participant.isJigasi ? JIGASI_PARTICIPANT_ICON : null; return participant && participant.isJigasi ? JIGASI_PARTICIPANT_ICON : null;
}, },
participant => { (participant, _) => {
return participant && participant.avatarURL ? participant.avatarURL : null; return participant && participant.avatarURL ? participant.avatarURL : null;
}, },
participant => { (participant, store) => {
return participant && participant.email ? getGravatarURL(participant.email) : null; if (participant && participant.email) {
return getGravatarURL(participant.email, store.getState()['features/base/config'].gravatarBaseURL);
}
return null;
} }
]; ];
/* eslint-enable arrow-body-style */ /* eslint-enable arrow-body-style, no-unused-vars */
/** /**
* Resolves the first loadable avatar URL for a participant. * Resolves the first loadable avatar URL for a participant.
* *
* @param {Object} participant - The participant to resolve avatars for. * @param {Object} participant - The participant to resolve avatars for.
* @param {Store} store - Redux store.
* @returns {Promise} * @returns {Promise}
*/ */
export function getFirstLoadableAvatarUrl(participant: Object) { export function getFirstLoadableAvatarUrl(participant: Object, store: Store<any, any>) {
const deferred = createDeferred(); const deferred = createDeferred();
const fullPromise = deferred.promise const fullPromise = deferred.promise
.then(() => _getFirstLoadableAvatarUrl(participant)) .then(() => _getFirstLoadableAvatarUrl(participant, store))
.then(src => { .then(src => {
if (AVATAR_QUEUE.length) { if (AVATAR_QUEUE.length) {
@ -402,11 +408,12 @@ export function figureOutMutedWhileDisconnectedStatus(
* Resolves the first loadable avatar URL for a participant. * Resolves the first loadable avatar URL for a participant.
* *
* @param {Object} participant - The participant to resolve avatars for. * @param {Object} participant - The participant to resolve avatars for.
* @param {Store} store - Redux store.
* @returns {?string} * @returns {?string}
*/ */
async function _getFirstLoadableAvatarUrl(participant) { async function _getFirstLoadableAvatarUrl(participant, store) {
for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) { for (let i = 0; i < AVATAR_CHECKER_FUNCTIONS.length; i++) {
const url = AVATAR_CHECKER_FUNCTIONS[i](participant); const url = AVATAR_CHECKER_FUNCTIONS[i](participant, store);
if (url) { if (url) {
if (AVATAR_CHECKED_URLS.has(url)) { if (AVATAR_CHECKED_URLS.has(url)) {

View File

@ -365,7 +365,8 @@ function _maybePlaySounds({ getState, dispatch }, action) {
* @private * @private
* @returns {Object} The value returned by {@code next(action)}. * @returns {Object} The value returned by {@code next(action)}.
*/ */
function _participantJoinedOrUpdated({ dispatch, getState }, next, action) { function _participantJoinedOrUpdated(store, next, action) {
const { dispatch, getState } = store;
const { participant: { avatarURL, e2eeEnabled, email, id, local, name, raisedHand } } = action; const { participant: { avatarURL, e2eeEnabled, email, id, local, name, raisedHand } } = action;
// Send an external update of the local participant's raised hand state // Send an external update of the local participant's raised hand state
@ -401,7 +402,7 @@ function _participantJoinedOrUpdated({ dispatch, getState }, next, action) {
const participantId = !id && local ? getLocalParticipant(getState()).id : id; const participantId = !id && local ? getLocalParticipant(getState()).id : id;
const updatedParticipant = getParticipantById(getState(), participantId); const updatedParticipant = getParticipantById(getState(), participantId);
getFirstLoadableAvatarUrl(updatedParticipant) getFirstLoadableAvatarUrl(updatedParticipant, store)
.then(url => { .then(url => {
dispatch(setLoadableAvatarUrl(participantId, url)); dispatch(setLoadableAvatarUrl(participantId, url));
}); });

View File

@ -144,12 +144,13 @@ function _conferenceJoined({ dispatch }, next, action) {
* @param {Object} participant - The knocking participant. * @param {Object} participant - The knocking participant.
* @returns {void} * @returns {void}
*/ */
function _findLoadableAvatarForKnockingParticipant({ dispatch, getState }, { id }) { function _findLoadableAvatarForKnockingParticipant(store, { id }) {
const { dispatch, getState } = store;
const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id); const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
const { disableThirdPartyRequests } = getState()['features/base/config']; const { disableThirdPartyRequests } = getState()['features/base/config'];
if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) { if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
getFirstLoadableAvatarUrl(updatedParticipant).then(loadableAvatarUrl => { getFirstLoadableAvatarUrl(updatedParticipant, store).then(loadableAvatarUrl => {
if (loadableAvatarUrl) { if (loadableAvatarUrl) {
dispatch(participantIsKnockingOrUpdated({ dispatch(participantIsKnockingOrUpdated({
loadableAvatarUrl, loadableAvatarUrl,