fix(app): redraw the favicon badge when branding changes (#3314)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-08 20:20:49 +02:00
1 parent 4ecbe9603c
commit 4b1afc953f
2 files changed
+46 -9

No files matched your search

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import RuntimeConfig from '@app/features/app/state/RuntimeConfig'; 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 DesktopBackgroundGateway from '@app/features/gateway/transport/DesktopBackgroundGateway';
import GuildReadState from '@app/features/guild/state/GuildReadState'; import GuildReadState from '@app/features/guild/state/GuildReadState';
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
@@ -18,14 +19,40 @@ const logger = new Logger('AppBadge');
const UNREAD_INDICATOR = -1; const UNREAD_INDICATOR = -1;
let favico: Favico | null = null; 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<HTMLLinkElement>('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 => { const initFavico = (): Favico | null => {
if (favico) return favico; if (favico) return favico;
try { 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; return favico;
} catch (e) { } catch (e) {
logger.warn('Failed to initialize Favico', e); logger.warn('Failed to initialize Favico', e);
releaseFavico();
return null; return null;
} }
}; };
@@ -39,7 +66,11 @@ const setElectronBadge = (badge: number): void => {
logger.warn('Failed to set Electron badge', e); 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; if (badge === 0 && favico === null) return;
const fav = initFavico(); const fav = initFavico();
if (!fav) return; if (!fav) return;
@@ -69,9 +100,9 @@ const setPwaBadge = (badge: number): void => {
logger.warn('Failed to set PWA badge', e); logger.warn('Failed to set PWA badge', e);
} }
}; };
const setBadge = (badge: number): void => { const setBadge = (badge: number, faviconSource: string | null): void => {
setElectronBadge(badge); setElectronBadge(badge);
setFaviconBadge(badge); setFaviconBadge(badge, faviconSource);
setPwaBadge(badge); setPwaBadge(badge);
}; };
export const AppBadge: React.FC = observer(() => { export const AppBadge: React.FC = observer(() => {
@@ -89,15 +120,16 @@ export const AppBadge: React.FC = observer(() => {
} else if (hasUnread && unreadMessageBadgeEnabled) { } else if (hasUnread && unreadMessageBadgeEnabled) {
badge = UNREAD_INDICATOR; badge = UNREAD_INDICATOR;
} }
const faviconSource = getDocumentFaviconUrl(RuntimeConfig.getSnapshotOrNull()?.appPublic ?? null);
useEffect(() => { useEffect(() => {
setBadge(badge); setBadge(badge, faviconSource);
}, [badge]); }, [badge, faviconSource]);
useEffect(() => { useEffect(() => {
updateDocumentTitleBadge(totalCount, hasUnread && unreadMessageBadgeEnabled); updateDocumentTitleBadge(totalCount, hasUnread && unreadMessageBadgeEnabled);
}, [totalCount, hasUnread, unreadMessageBadgeEnabled]); }, [totalCount, hasUnread, unreadMessageBadgeEnabled]);
useEffect(() => { useEffect(() => {
return () => { return () => {
setBadge(0); setBadge(0, favicoSource);
updateDocumentTitleBadge(0, false); updateDocumentTitleBadge(0, false);
}; };
}, []); }, []);
@@ -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 { export function applyRuntimeDocumentBranding(appPublic: InstanceAppPublic): void {
applyDocumentBranding( applyDocumentBranding(
appPublic.branding.product_name, appPublic.branding.product_name,
appPublic.branding.favicon_url ?? appPublic.branding.icon_url, getDocumentFaviconUrl(appPublic),
appPublic.branding.theme_color, appPublic.branding.theme_color,
); );
} }
@@ -88,7 +93,7 @@ export function applyRuntimeDocumentBranding(appPublic: InstanceAppPublic): void
export function applyDefaultAppShellDocumentBranding(): void { export function applyDefaultAppShellDocumentBranding(): void {
applyDocumentBranding( applyDocumentBranding(
DEFAULT_APP_SHELL_BRANDING.productName, DEFAULT_APP_SHELL_BRANDING.productName,
DEFAULT_APP_SHELL_BRANDING.faviconUrl, getDocumentFaviconUrl(null),
DEFAULT_APP_SHELL_BRANDING.themeColor, DEFAULT_APP_SHELL_BRANDING.themeColor,
); );
} }