From 2680289ff7c0635d3ec29bdf28b95c29ced7f342 Mon Sep 17 00:00:00 2001 From: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com> Date: Thu, 3 Aug 2023 02:53:56 +0800 Subject: [PATCH] Sanitize url before fetching favicon and display letter avatar if it can't be retrieved (#1035) * Sanitize url before fetching favicon and display letter avatar if it can't be retrieved Co-authored-by: v1b3m Co-authored-by: RubensRafael * Priorotise www for apple.com domain Co-authored-by: v1b3m Co-authored-by: RubensRafael * Add requested changes Co-authored-by: v1b3m Co-authored-by: RubensRafael * Fix the tests Co-authored-by: v1b3m Co-authored-by: RubensRafael * Change avatar generation strategy Co-authored-by: v1b3m Co-authored-by: RubensRafael --------- Co-authored-by: v1b3m Co-authored-by: RubensRafael --- .../components/GenericEditableURLCell.tsx | 5 +- front/src/modules/users/components/Avatar.tsx | 18 +++++- front/src/utils/__tests__/utils.test.ts | 58 ++++++++++++++----- front/src/utils/index.ts | 15 ++++- 4 files changed, 76 insertions(+), 20 deletions(-) diff --git a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx index aadf2f4cf..7a080f781 100644 --- a/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx +++ b/front/src/modules/ui/table/editable-cell/type/components/GenericEditableURLCell.tsx @@ -8,6 +8,7 @@ import { ViewFieldDefinition, ViewFieldURLMetadata, } from '@/ui/table/types/ViewField'; +import { sanitizeURL } from '~/utils'; import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode'; @@ -33,7 +34,9 @@ export function GenericEditableURLCell({ } - nonEditModeContent={} + nonEditModeContent={ + + } > ); } diff --git a/front/src/modules/users/components/Avatar.tsx b/front/src/modules/users/components/Avatar.tsx index cf7b9ab47..25718306c 100644 --- a/front/src/modules/users/components/Avatar.tsx +++ b/front/src/modules/users/components/Avatar.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from 'react'; import styled from '@emotion/styled'; import { isNonEmptyString } from '~/utils/isNonEmptyString'; @@ -86,6 +87,20 @@ export function Avatar({ type = 'squared', }: OwnProps) { const noAvatarUrl = !isNonEmptyString(avatarUrl); + const [isInvalidAvatarUrl, setIsInvalidAvatarUrl] = useState(false); + + useEffect(() => { + if (avatarUrl) { + new Promise((resolve) => { + const img = new Image(); + img.onload = () => resolve(false); + img.onerror = () => resolve(true); + img.src = getImageAbsoluteURIOrBase64(avatarUrl) as string; + }).then((res) => { + setIsInvalidAvatarUrl(res as boolean); + }); + } + }, [avatarUrl]); return ( - {noAvatarUrl && placeholder[0]?.toLocaleUpperCase()} + {(noAvatarUrl || isInvalidAvatarUrl) && + placeholder[0]?.toLocaleUpperCase()} ); } diff --git a/front/src/utils/__tests__/utils.test.ts b/front/src/utils/__tests__/utils.test.ts index 8ac6b5475..9fa996f49 100644 --- a/front/src/utils/__tests__/utils.test.ts +++ b/front/src/utils/__tests__/utils.test.ts @@ -1,21 +1,49 @@ -import { getLogoUrlFromDomainName } from '..'; +import { getLogoUrlFromDomainName, sanitizeURL } from '..'; + +describe('sanitizeURL', () => { + test('should sanitize the URL correctly', () => { + expect(sanitizeURL('http://example.com/')).toBe('example.com'); + expect(sanitizeURL('https://www.example.com/')).toBe('example.com'); + expect(sanitizeURL('www.example.com')).toBe('example.com'); + expect(sanitizeURL('example.com')).toBe('example.com'); + expect(sanitizeURL('example.com/')).toBe('example.com'); + }); + + test('should handle undefined input', () => { + expect(sanitizeURL(undefined)).toBe(''); + }); +}); describe('getLogoUrlFromDomainName', () => { - it(`should generate logo url if undefined `, () => { + test('should return the correct logo URL for a given domain', () => { + expect(getLogoUrlFromDomainName('example.com')).toBe( + 'https://favicon.twenty.com/example.com', + ); + + expect(getLogoUrlFromDomainName('http://example.com/')).toBe( + 'https://favicon.twenty.com/example.com', + ); + + expect(getLogoUrlFromDomainName('https://www.example.com/')).toBe( + 'https://favicon.twenty.com/example.com', + ); + + expect(getLogoUrlFromDomainName('www.example.com')).toBe( + 'https://favicon.twenty.com/example.com', + ); + + expect(getLogoUrlFromDomainName('example.com/')).toBe( + 'https://favicon.twenty.com/example.com', + ); + + expect(getLogoUrlFromDomainName('apple.com')).toBe( + 'https://favicon.twenty.com/apple.com', + ); + }); + + test('should handle undefined input', () => { expect(getLogoUrlFromDomainName(undefined)).toBe( - 'https://api.faviconkit.com/undefined/144', - ); - }); - - it(`should generate logo url if defined `, () => { - expect(getLogoUrlFromDomainName('test.com')).toBe( - 'https://api.faviconkit.com/test.com/144', - ); - }); - - it(`should generate logo url if empty `, () => { - expect(getLogoUrlFromDomainName('')).toBe( - 'https://api.faviconkit.com//144', + 'https://favicon.twenty.com/', ); }); }); diff --git a/front/src/utils/index.ts b/front/src/utils/index.ts index fcab4d1e0..ed5a6745c 100644 --- a/front/src/utils/index.ts +++ b/front/src/utils/index.ts @@ -10,6 +10,15 @@ export function formatToHumanReadableDate(date: Date | string) { }).format(parsedJSDate); } -export const getLogoUrlFromDomainName = (domainName?: string): string => { - return `https://api.faviconkit.com/${domainName}/144`; -}; +export function sanitizeURL(link: string | null | undefined) { + return link + ? link.replace(/(https?:\/\/)|(www\.)/g, '').replace(/\/$/, '') + : ''; +} + +export function getLogoUrlFromDomainName( + domainName?: string, +): string | undefined { + const sanitizedDomain = sanitizeURL(domainName); + return `https://favicon.twenty.com/${sanitizedDomain}`; +}