Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / topBanners / TimeOutOfSyncTopBanner.tsx
blob8401992aea8c4579d1d43172564c6a6c2b8a09b3
1 import { useEffect, useRef, useState } from 'react';
3 import { c } from 'ttag';
5 import { Href } from '@proton/atoms';
6 import useApi from '@proton/components/hooks/useApi';
7 import { ping } from '@proton/shared/lib/api/tests';
8 import { HOUR, SECOND } from '@proton/shared/lib/constants';
9 import { captureMessage } from '@proton/shared/lib/helpers/sentry';
10 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
11 import noop from '@proton/utils/noop';
13 import useApiServerTime from '../../hooks/useApiServerTime';
14 import TopBanner from './TopBanner';
16 const isOutOfSync = (serverTime: Date, localTime: Date) => {
17     const timeDifference = Math.abs(serverTime.getTime() - localTime.getTime());
18     // We should allow at least a 14-hour time difference,
19     // because of potential internal clock issues when using dual-boot with Windows and a different OS
20     return timeDifference > 24 * HOUR;
23 /**
24  * The serverTime update might be stale if e.g. the device has been asleep/idle, and it's processing the update after a delay.
25  */
26 const isStaleServerTimeUpdate = (previousUpdateLocalTime: Date, localTime: Date) => {
27     const timeDifference = Math.abs(previousUpdateLocalTime.getTime() - localTime.getTime());
28     // The event loop runs every 30s, so we expect the server time to be updated at least with that frequency
29     // (with a margin of a few seconds)
30     return timeDifference > 35 * SECOND;
33 const TimeOutOfSyncTopBanner = () => {
34     const [ignore, setIgnore] = useState(false);
35     // This is only used to keep track of the time past since the previous re-render, aka server time update.
36     const previousUpdateLocalTime = useRef(new Date());
38     const api = useApi();
39     const serverTime = useApiServerTime();
40     const currentUpdateLocalTime = new Date();
41     const isStaleServerTime = isStaleServerTimeUpdate(previousUpdateLocalTime.current, currentUpdateLocalTime);
43     useEffect(() => {
44         // Check for stale server time every 5s, and ping the server if needed to ensure that we retrieve an updated
45         // server time at most 5s after waking from an idle state: we do not want to wait up to 30s for the event loop
46         // request to be processed, since the stale serverTime() value will be used by the apps in the meantime.
47         const stalePingInterval = setInterval(() => {
48             if (isStaleServerTimeUpdate(previousUpdateLocalTime.current, new Date())) {
49                 void api({ ...ping() }).catch(noop); // if it fails, we'll try again in 5s
50             }
51         }, 5000);
52         return () => {
53             clearInterval(stalePingInterval);
54         };
55     }, []); // run only once, on first render
57     useEffect(() => {
58         previousUpdateLocalTime.current = currentUpdateLocalTime;
59     }); // run at every re-render
61     // We warn the user if the server time is too far off from local time.
62     // We do not want the server to set arbitrary times (either past or future), to avoid signature replay issues and more.
63     const showWarning = !ignore && serverTime && !isStaleServerTime && isOutOfSync(serverTime, currentUpdateLocalTime);
65     // Log warning to have an idea of how many clients might be affected
66     const onceRef = useRef(false);
67     useEffect(() => {
68         if (!showWarning || onceRef.current) {
69             return;
70         }
72         onceRef.current = true;
73         captureMessage('Client time difference larger than 24 hours', {
74             level: 'info',
75             extra: {
76                 serverTime: serverTime?.toString(),
77                 localTime: currentUpdateLocalTime.toString(),
78                 isStaleServerTime,
79             },
80         });
81     }, [showWarning]);
83     if (!showWarning) {
84         return null;
85     }
87     const learnMore = <Href href={getKnowledgeBaseUrl('/device-time-warning')}>{c('Link').t`Learn more`}</Href>;
89     return (
90         <TopBanner onClose={() => setIgnore(true)} className="bg-warning">
91             {c('Warning').jt`The date and time settings on your device are out of sync. ${learnMore}`}
92         </TopBanner>
93     );
96 export default TimeOutOfSyncTopBanner;