From 75d83006350415d7d06fded373774bd304ea8e71 Mon Sep 17 00:00:00 2001 From: Dhairyashil Shinde <93669429+dhairyashiil@users.noreply.github.com> Date: Wed, 24 Dec 2025 00:54:26 +0530 Subject: [PATCH] fix/companion-consider-all-profile-links (#26158) --- companion/components/Header.tsx | 5 ++-- companion/utils/getAvatarUrl.ts | 49 +++++++++++++++++++++++++++++++++ companion/utils/index.ts | 3 ++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 companion/utils/getAvatarUrl.ts diff --git a/companion/components/Header.tsx b/companion/components/Header.tsx index b7a4f5af45..18be182093 100644 --- a/companion/components/Header.tsx +++ b/companion/components/Header.tsx @@ -1,5 +1,6 @@ import { CalComAPIService, UserProfile } from "../services/calcom"; import { openInAppBrowser } from "../utils/browser"; +import { getAvatarUrl } from "../utils/getAvatarUrl"; import { CalComLogo } from "./CalComLogo"; import { FullScreenModal } from "./FullScreenModal"; import { Ionicons } from "@expo/vector-icons"; @@ -164,7 +165,7 @@ export function Header() { ) : userProfile?.avatarUrl ? ( @@ -203,7 +204,7 @@ export function Header() { {userProfile?.avatarUrl ? ( diff --git a/companion/utils/getAvatarUrl.ts b/companion/utils/getAvatarUrl.ts new file mode 100644 index 0000000000..d3fb78aaf1 --- /dev/null +++ b/companion/utils/getAvatarUrl.ts @@ -0,0 +1,49 @@ +import { z } from "zod"; + +const CAL_URL = "https://cal.com"; +const AVATAR_FALLBACK = "/avatar.png"; + +/** + * Base64 image data URL regex pattern + * Matches: data:image/[type];base64,[base64data] + */ +const BASE64_IMAGE_REGEX = + /^data:image\/[^;]+;base64,(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; + +/** + * Normalizes avatar URL to a format that can be used by React Native Image component. + * Handles three formats: + * 1. Absolute HTTPS URLs - returns as-is + * 2. Base64 data URLs (data:image/...) - returns as-is + * 3. Relative URLs (e.g., /api/avatar/[uuid]) - prefixes with CAL_URL + * + * @param avatarUrl - The avatar URL from the API (can be absolute URL, base64, or relative path) + * @returns A normalized URL that can be used by React Native Image component, or fallback URL + * + * @example + * getAvatarUrl("https://example.com/avatar.jpg") // "https://example.com/avatar.jpg" + * getAvatarUrl("data:image/png;base64,iVBORw0KG...") // "data:image/png;base64,iVBORw0KG..." + * getAvatarUrl("/api/avatar/123") // "https://cal.com/api/avatar/123" + * getAvatarUrl(undefined) // "https://cal.com/avatar.png" + */ +export const getAvatarUrl = (avatarUrl: string | null | undefined): string => { + if (!avatarUrl) { + return CAL_URL + AVATAR_FALLBACK; + } + + // Check if it's a base64 data URL + if (BASE64_IMAGE_REGEX.test(avatarUrl)) { + return avatarUrl; + } + + // Check if it's already an absolute URL (HTTPS or HTTP) + const isAbsoluteUrl = z.string().url().safeParse(avatarUrl).success; + if (isAbsoluteUrl) { + return avatarUrl; + } + + // Treat as relative URL and prefix with CAL_URL + // Ensure the relative URL starts with / + const relativePath = avatarUrl.startsWith("/") ? avatarUrl : `/${avatarUrl}`; + return CAL_URL + relativePath; +}; diff --git a/companion/utils/index.ts b/companion/utils/index.ts index 224349c17d..8ab29c45ce 100644 --- a/companion/utils/index.ts +++ b/companion/utils/index.ts @@ -39,6 +39,9 @@ export { slugify } from "./slugify"; // App icon utilities export { getAppIconUrl } from "./getAppIconUrl"; +// Avatar utilities +export { getAvatarUrl } from "./getAvatarUrl"; + // Default location utilities export { defaultLocations,