Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / shared / lib / busy / busy.ts
blob30259482690e314f43e4fa05155e029b64dc44e9
1 import noop from '@proton/utils/noop';
3 import { isElectronApp } from '../helpers/desktop';
4 import { traceError } from '../helpers/sentry';
5 import type { ProtonConfig } from '../interfaces';
7 let uid = 0;
8 let busies = [] as number[];
10 const unregister = (id: number) => {
11     busies = busies.filter((busy) => busy !== id);
14 const register = () => {
15     const id = uid++;
17     busies.push(id);
19     return () => {
20         unregister(id);
21     };
24 const getIsBusy = () => {
25     return busies.length > 0;
28 export default {
29     unregister,
30     register,
31     getIsBusy,
34 export const dialogRootClassName = 'modal-container';
36 export const modalTwoRootClassName = 'modal-two';
37 export const modalTwoBackdropRootClassName = 'modal-two-backdrop';
39 export const dropdownRootClassName = 'dropdown';
41 const textInputSelectors = ['email', 'number', 'password', 'search', 'tel', 'text', 'url'].map(
42     (type) => `input[type=${type}]`
45 const allTextInputsSelector = `input:not([type]), textarea, ${textInputSelectors.join(',')}`;
47 export const isDialogOpen = () => document.querySelector(`.${dialogRootClassName}, [role="dialog"]`) !== null;
48 export const isModalOpen = () => document.querySelector(`.${modalTwoRootClassName}`) !== null;
49 export const isModalBackdropOpen = () => document.querySelector(`.${modalTwoBackdropRootClassName}`) !== null;
50 export const isDropdownOpen = () => document.querySelector(`.${dropdownRootClassName}`) !== null;
52 export const isEditing = () => {
53     const { activeElement } = document;
55     if (activeElement === null) {
56         return false;
57     }
59     if (
60         (activeElement.closest('form') ||
61             activeElement.closest('iframe') ||
62             activeElement.closest('[contenteditable]')) !== null
63     ) {
64         return true;
65     }
67     return false;
70 export const domIsBusy = () => {
71     /*
72      * These verifications perform some dom querying operations so in
73      * order to not unnecessarily waste performance we return early
74      * should any of the conditions fail before evaluating all of them
75      */
76     if (isDialogOpen()) {
77         return true;
78     }
80     if (isModalOpen()) {
81         return true;
82     }
84     if (isDropdownOpen()) {
85         return true;
86     }
88     const allInputs = document.querySelectorAll<HTMLInputElement>(allTextInputsSelector);
90     const allTextInputsAreEmpty = Array.from(allInputs).every((element) => !element.value);
92     if (!allTextInputsAreEmpty) {
93         return true;
94     }
96     return isEditing();
99 const THIRTY_MINUTES = 30 * 60 * 1000;
101 const isDifferent = (a?: string, b?: string) => !!a && !!b && b !== a;
103 export const newVersionUpdater = (config: ProtonConfig) => {
104     const { VERSION_PATH, COMMIT } = config;
106     let reloadTimeoutId: number | null = null;
107     let versionIntervalId: number | null = null;
109     const getVersion = () => fetch(VERSION_PATH).then((response) => response.json());
111     const isNewVersionAvailable = async () => {
112         try {
113             const { commit } = await getVersion();
115             return isDifferent(commit, COMMIT);
116         } catch (error: any) {
117             traceError(error);
118         }
119     };
121     const clearReload = () => {
122         if (reloadTimeoutId) {
123             window.clearTimeout(reloadTimeoutId);
124             reloadTimeoutId = null;
125         }
126     };
128     const clearVersionCheck = () => {
129         if (versionIntervalId) {
130             window.clearInterval(versionIntervalId);
131             versionIntervalId = null;
132         }
133     };
135     /*
136      * Instead of immediately reloading as soon as we detect the user to
137      * not be busy and also having left the tab / browser / window, we
138      * schedule a reload in case the user only left for a little while
139      * and is about to come back soon.
140      */
141     const scheduleReload = () => {
142         clearReload();
143         reloadTimeoutId = window.setTimeout(() => {
144             // If the user turns out to be busy here for some reason, abort the reload, and await a new visibilitychange event
145             if (domIsBusy() || getIsBusy()) {
146                 return;
147             }
149             // In the case of the electron app, we also check if the app is focused, we abort reloading if that's the case
150             if (isElectronApp && document.hasFocus()) {
151                 return;
152             }
154             window.location.reload();
155         }, THIRTY_MINUTES);
156     };
158     const handleVisibilityChange = () => {
159         const documentIsVisible = document.visibilityState === 'visible';
161         if (documentIsVisible) {
162             clearReload();
163             return;
164         }
166         if (domIsBusy() || getIsBusy()) {
167             return;
168         }
170         scheduleReload();
171     };
173     const registerVisibilityChangeListener = () => {
174         document.addEventListener('visibilitychange', handleVisibilityChange);
175     };
177     // The 'visibilitychange' event is different on Electron and only triggered when the app is completely hidden
178     // This happens when the reduce the app in the dock or switch to a full screen application
179     // To mitigate this, we check if the app is not focused, if that's the case we schedule a reload
180     const handleElectronFocus = () => {
181         if (!document.hasFocus()) {
182             scheduleReload();
183         }
184     };
186     const checkForNewVersion = async () => {
187         if (await isNewVersionAvailable()) {
188             clearVersionCheck();
189             if (isElectronApp) {
190                 handleElectronFocus();
191             } else {
192                 registerVisibilityChangeListener();
193             }
194         }
195     };
197     versionIntervalId = window.setInterval(() => {
198         checkForNewVersion().catch(noop);
199     }, THIRTY_MINUTES);