diff --git a/fluxer_app/src/features/app/components/AppNotificationBadge.ts b/fluxer_app/src/features/app/components/AppNotificationBadge.ts index 6b4e68254..fe5d78e44 100644 --- a/fluxer_app/src/features/app/components/AppNotificationBadge.ts +++ b/fluxer_app/src/features/app/components/AppNotificationBadge.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import RuntimeConfig from '@app/features/app/state/RuntimeConfig'; +import {getDocumentFaviconUrl} from '@app/features/app/state/RuntimeDocumentBranding'; import DesktopBackgroundGateway from '@app/features/gateway/transport/DesktopBackgroundGateway'; import GuildReadState from '@app/features/guild/state/GuildReadState'; import {Logger} from '@app/features/platform/utils/AppLogger'; @@ -18,14 +19,40 @@ const logger = new Logger('AppBadge'); const UNREAD_INDICATOR = -1; let favico: Favico | null = null; +let favicoLink: HTMLLinkElement | null = null; +let favicoLinkHref: string | null = null; +let favicoSource: string | null = null; + +const findIconLink = (): HTMLLinkElement | null => { + const links = document.head.querySelectorAll('link'); + for (let index = links.length - 1; index >= 0; index--) { + if (/(^|\s)icon(\s|$)/i.test(links[index].getAttribute('rel') ?? '')) { + return links[index]; + } + } + return null; +}; + +const releaseFavico = (): void => { + if (favicoLink !== null && favicoLinkHref !== null && favicoLink.getAttribute('href')?.startsWith('data:')) { + favicoLink.setAttribute('href', favicoLinkHref); + } + favico = null; + favicoLink = null; + favicoLinkHref = null; +}; const initFavico = (): Favico | null => { if (favico) return favico; try { - favico = new Favico({animation: 'none'}); + const link = findIconLink(); + favicoLink = link; + favicoLinkHref = link?.getAttribute('href') ?? null; + favico = new Favico(link === null ? {animation: 'none'} : {animation: 'none', element: link}); return favico; } catch (e) { logger.warn('Failed to initialize Favico', e); + releaseFavico(); return null; } }; @@ -39,7 +66,11 @@ const setElectronBadge = (badge: number): void => { logger.warn('Failed to set Electron badge', e); } }; -const setFaviconBadge = (badge: number): void => { +const setFaviconBadge = (badge: number, source: string | null): void => { + if (source !== favicoSource) { + releaseFavico(); + favicoSource = source; + } if (badge === 0 && favico === null) return; const fav = initFavico(); if (!fav) return; @@ -69,9 +100,9 @@ const setPwaBadge = (badge: number): void => { logger.warn('Failed to set PWA badge', e); } }; -const setBadge = (badge: number): void => { +const setBadge = (badge: number, faviconSource: string | null): void => { setElectronBadge(badge); - setFaviconBadge(badge); + setFaviconBadge(badge, faviconSource); setPwaBadge(badge); }; export const AppBadge: React.FC = observer(() => { @@ -89,15 +120,16 @@ export const AppBadge: React.FC = observer(() => { } else if (hasUnread && unreadMessageBadgeEnabled) { badge = UNREAD_INDICATOR; } + const faviconSource = getDocumentFaviconUrl(RuntimeConfig.getSnapshotOrNull()?.appPublic ?? null); useEffect(() => { - setBadge(badge); - }, [badge]); + setBadge(badge, faviconSource); + }, [badge, faviconSource]); useEffect(() => { updateDocumentTitleBadge(totalCount, hasUnread && unreadMessageBadgeEnabled); }, [totalCount, hasUnread, unreadMessageBadgeEnabled]); useEffect(() => { return () => { - setBadge(0); + setBadge(0, favicoSource); updateDocumentTitleBadge(0, false); }; }, []); diff --git a/fluxer_app/src/features/app/state/RuntimeDocumentBranding.ts b/fluxer_app/src/features/app/state/RuntimeDocumentBranding.ts index beda074ee..c7ae0c315 100644 --- a/fluxer_app/src/features/app/state/RuntimeDocumentBranding.ts +++ b/fluxer_app/src/features/app/state/RuntimeDocumentBranding.ts @@ -77,10 +77,15 @@ function applyDocumentBranding(productName: string, faviconUrl: string | null, t } } +export function getDocumentFaviconUrl(appPublic: InstanceAppPublic | null): string | null { + if (appPublic === null) return DEFAULT_APP_SHELL_BRANDING.faviconUrl; + return appPublic.branding.favicon_url ?? appPublic.branding.icon_url; +} + export function applyRuntimeDocumentBranding(appPublic: InstanceAppPublic): void { applyDocumentBranding( appPublic.branding.product_name, - appPublic.branding.favicon_url ?? appPublic.branding.icon_url, + getDocumentFaviconUrl(appPublic), appPublic.branding.theme_color, ); } @@ -88,7 +93,7 @@ export function applyRuntimeDocumentBranding(appPublic: InstanceAppPublic): void export function applyDefaultAppShellDocumentBranding(): void { applyDocumentBranding( DEFAULT_APP_SHELL_BRANDING.productName, - DEFAULT_APP_SHELL_BRANDING.faviconUrl, + getDocumentFaviconUrl(null), DEFAULT_APP_SHELL_BRANDING.themeColor, ); }