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';
8 let busies = [] as number[];
10 const unregister = (id: number) => {
11 busies = busies.filter((busy) => busy !== id);
14 const register = () => {
24 const getIsBusy = () => {
25 return busies.length > 0;
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) {
60 (activeElement.closest('form') ||
61 activeElement.closest('iframe') ||
62 activeElement.closest('[contenteditable]')) !== null
70 export const domIsBusy = () => {
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
84 if (isDropdownOpen()) {
88 const allInputs = document.querySelectorAll<HTMLInputElement>(allTextInputsSelector);
90 const allTextInputsAreEmpty = Array.from(allInputs).every((element) => !element.value);
92 if (!allTextInputsAreEmpty) {
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 () => {
113 const { commit } = await getVersion();
115 return isDifferent(commit, COMMIT);
116 } catch (error: any) {
121 const clearReload = () => {
122 if (reloadTimeoutId) {
123 window.clearTimeout(reloadTimeoutId);
124 reloadTimeoutId = null;
128 const clearVersionCheck = () => {
129 if (versionIntervalId) {
130 window.clearInterval(versionIntervalId);
131 versionIntervalId = null;
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.
141 const scheduleReload = () => {
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()) {
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()) {
154 window.location.reload();
158 const handleVisibilityChange = () => {
159 const documentIsVisible = document.visibilityState === 'visible';
161 if (documentIsVisible) {
166 if (domIsBusy() || getIsBusy()) {
173 const registerVisibilityChangeListener = () => {
174 document.addEventListener('visibilitychange', handleVisibilityChange);
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()) {
186 const checkForNewVersion = async () => {
187 if (await isNewVersionAvailable()) {
190 handleElectronFocus();
192 registerVisibilityChangeListener();
197 versionIntervalId = window.setInterval(() => {
198 checkForNewVersion().catch(noop);