Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const ConfigureAndroidApps = () => {
<List itemLayout="horizontal">
<List.Item>
<List.Item.Meta
title="You will need to make a custom build where you allow Requestly to read your newtwork traffic"
title="You will need to make a custom build where you allow Requestly to read your network traffic"
description="make sure to remove this from production builds"
/>
</List.Item>
Expand Down
5 changes: 2 additions & 3 deletions app/src/features/settings/components/Profile/ManageAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button as AntButton } from "antd";
import UserInfo from "./UserInfo";
//UTILS
import { getUserAuthDetails } from "store/slices/global/user/selectors";
import { getUserAvatarUrl } from "utils/ImageUtils";
import { redirectToDeleteAccount, redirectToSignDPA } from "../../../../utils/RedirectionUtils";
// ACTIONS
import { handleForgotPasswordButtonOnClick } from "features/onboarding/components/auth/components/Form/actions";
Expand All @@ -21,9 +22,7 @@ const ManageAccount = () => {

//Component State
const [isChangePasswordLoading, setIsChangePasswordLoading] = useState(false);
// Fallback image
const defaultImageSrc = "https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&f=y";
let userImageSrc = user.details.profile.photoURL ? user.details.profile.photoURL : defaultImageSrc;
let userImageSrc = getUserAvatarUrl(user.details.profile.email, user.details.profile.photoURL);

const userDisplayName = user.details.profile.displayName ? user.details.profile.displayName : "User";

Expand Down
6 changes: 4 additions & 2 deletions app/src/layouts/DashboardLayout/MenuHeader/HeaderUser.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { handleLogoutButtonOnClick } from "features/onboarding/components/auth/components/Form/actions";
import APP_CONSTANTS from "config/constants";
import { SOURCE } from "modules/analytics/events/common/constants";
import { parseGravatarImage } from "utils/Misc";
import { getUserAvatarUrl } from "utils/ImageUtils";
import { trackHeaderClicked } from "modules/analytics/events/common/onboarding/header";
import { trackUpgradeClicked } from "modules/analytics/events/misc/monetizationExperiment";
import { getAppFlavour } from "utils/AppUtils";
Expand All @@ -43,7 +43,9 @@ export default function HeaderUser() {

const userName = user.loggedIn ? user?.details?.profile?.displayName ?? "User" : null;
const userPhoto =
user.loggedIn && user?.details?.profile?.photoURL ? parseGravatarImage(user.details.profile.photoURL) : null;
user.loggedIn && user?.details?.profile
? getUserAvatarUrl(user.details.profile.email, user.details.profile.photoURL)
: null;
const userEmail = user?.details?.profile?.email;
const planDetails = user?.details?.planDetails;

Expand Down
32 changes: 32 additions & 0 deletions app/src/utils/ImageUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,35 @@ export const getRandomAvatar = () => {
export const generateGravatarURL = (email = "sagar@requestly.io") => {
return `https://www.gravatar.com/avatar/${md5(email)}?s=200&d=${getRandomAvatar()}`;
};

/**
* True when `url`'s host is Gravatar. Parses the URL and matches on the hostname
* rather than a substring β€” `url.includes("gravatar.com")` would also match
* unrelated hosts such as `gravatar.com.evil.com` or `host/path?ref=gravatar.com`.
* Unparseable values return false (treated as "not a Gravatar URL").
*/
const isGravatarUrl = (url) => {
try {
const host = new URL(url).hostname.toLowerCase();
return host === "gravatar.com" || host.endsWith(".gravatar.com");
} catch {
return false;
}
};

/**
* Resolves the avatar to display for a user.
*
* Precedence: the auth provider's photo (e.g. Google) when we have a real one,
* otherwise a Gravatar resolved live from the current login email β€” so changes a
* user makes on Gravatar are reflected here. For email/password accounts we persist
* a synthetic gravatar.com URL as `photoURL`, so any Gravatar-hosted value is treated
* as "no provider photo" and re-resolved live from the email. `d=mp` is Gravatar's
* deterministic fallback, shown when the email has no Gravatar image.
*/
export const getUserAvatarUrl = (email = "", providerPhotoURL = "") => {
if (providerPhotoURL && !isGravatarUrl(providerPhotoURL)) {
return providerPhotoURL;
}
return `https://www.gravatar.com/avatar/${md5(email)}?s=200&d=mp`;
};