Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / components / hooks / useDynamicFavicon.ts
blob4727c056a2b2c01856c061f81af331d650eb7b3a
1 import { useEffect, useRef } from 'react';
3 import { HTTP_STATUS_CODE } from '@proton/shared/lib/constants';
5 import useApiStatus from './useApiStatus';
7 const useDynamicFavicon = (faviconSrc: string) => {
8     const faviconRef = useRef<string>('');
9     const { offline, apiUnreachable } = useApiStatus();
11     // We can't rely solely on the Boolean offline because browsers may not catch all offline instances properly.
12     // We will get some false positives with the condition below, but that's ok
13     const isPossiblyOffline = offline || !!apiUnreachable;
15     useEffect(
16         () => {
17             const run = async () => {
18                 if (faviconSrc === faviconRef.current) {
19                     // no need to update the favicon
20                     return;
21                 }
22                 // Add random param to force refresh
23                 const randomParameter = Math.random().toString(36).substring(2);
24                 const href = `${faviconSrc}?v=${randomParameter}`;
26                 try {
27                     /**
28                      * Proactively fetch favicon to test if /assets are reachable.
29                      * * If that goes well, the request is cached and not launched again below when actually changing the favicon in the HTML
30                      * * If that doesn't work, we want to handle the error here since we can't attach an error handled to the link tag that controls the favicon
31                      */
32                     const { status } = await fetch(href);
34                     if (status !== HTTP_STATUS_CODE.OK) {
35                         throw new Error('New favicon was not fetched properly');
36                     }
37                 } catch (e) {
38                     // if we cannot fetch the favicon, do not attempt to change it
39                     return;
40                 }
42                 // Ensure all favicons are removed, otherwise chrome has trouble updating to the dynamic icon
43                 const links = document.querySelectorAll('link[rel="icon"]:not([data-dynamic-favicon])');
44                 links.forEach((link) => {
45                     link.remove();
46                 });
48                 const favicon = document.querySelector('link[rel="icon"][type="image/svg+xml"][data-dynamic-favicon]');
50                 if (favicon) {
51                     favicon.setAttribute('href', href);
52                 } else {
53                     const link = document.createElement('link');
54                     link.setAttribute('rel', 'icon');
55                     link.setAttribute('type', 'image/svg+xml');
56                     link.setAttribute('data-dynamic-favicon', '');
57                     link.setAttribute('href', href);
58                     document.head.appendChild(link);
59                 }
60                 faviconRef.current = faviconSrc;
61             };
63             run();
64         },
65         // isPossiblyOffline is a dependency so that we re-try to update the favicon when going offline and back online
66         [faviconSrc, isPossiblyOffline]
67     );
70 export default useDynamicFavicon;