Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / calendar / src / app / bootstrap.ts
blob7b985ec7e0492b0c35ea147530a1ed0ef36fbea3
1 import {
2     addressesThunk,
3     initEvent,
4     organizationThunk,
5     serverEvent,
6     userSettingsThunk,
7     userThunk,
8     welcomeFlagsActions,
9 } from '@proton/account';
10 import * as bootstrap from '@proton/account/bootstrap';
11 import { bootstrapEvent } from '@proton/account/bootstrap/action';
12 import { getDecryptedPersistedState } from '@proton/account/persist/helper';
13 import {
14     calendarSettingsThunk,
15     calendarsThunk,
16     createCalendarModelEventManager,
17     holidaysDirectoryThunk,
18 } from '@proton/calendar';
19 import { setupGuestCrossStorage } from '@proton/cross-storage/account-impl/guestInstance';
20 import { FeatureCode, fetchFeatures } from '@proton/features';
21 import createApi from '@proton/shared/lib/api/createApi';
22 import { getSilentApi } from '@proton/shared/lib/api/helpers/customConfig';
23 import { loadAllowedTimeZones } from '@proton/shared/lib/date/timezone';
24 import { listenFreeTrialSessionExpiration } from '@proton/shared/lib/desktop/endOfTrialHelpers';
25 import { createDrawerApi } from '@proton/shared/lib/drawer/createDrawerApi';
26 import { getIsAuthorizedApp } from '@proton/shared/lib/drawer/helpers';
27 import { isElectronMail } from '@proton/shared/lib/helpers/desktop';
28 import { initElectronClassnames } from '@proton/shared/lib/helpers/initElectronClassnames';
29 import { initSafariFontFixClassnames } from '@proton/shared/lib/helpers/initSafariFontFixClassnames';
30 import { captureMessage } from '@proton/shared/lib/helpers/sentry';
31 import type { ProtonConfig } from '@proton/shared/lib/interfaces';
32 import initLogicalProperties from '@proton/shared/lib/logical/logical';
33 import noop from '@proton/utils/noop';
35 import { embeddedDrawerAppInfos } from './helpers/drawer';
36 import locales from './locales';
37 import { type CalendarState } from './store/rootReducer';
38 import { extendStore, setupStore } from './store/store';
40 const getAppContainer = () =>
41     import(/* webpackChunkName: "MainContainer" */ './containers/calendar/MainContainer').then(
42         (result) => result.default
43     );
45 const { isIframe, isDrawerApp, parentApp } = embeddedDrawerAppInfos;
47 export const bootstrapApp = async ({ config, signal }: { config: ProtonConfig; signal?: AbortSignal }) => {
48     const pathname = window.location.pathname;
49     const searchParams = new URLSearchParams(window.location.search);
51     const api = isDrawerApp ? createDrawerApi({ parentApp, appVersion: config.APP_VERSION }) : createApi({ config });
52     const silentApi = getSilentApi(api);
53     const authentication = bootstrap.createAuthentication();
54     bootstrap.init({ config, authentication, locales });
56     setupGuestCrossStorage();
57     initElectronClassnames();
58     initLogicalProperties();
59     initSafariFontFixClassnames();
61     const appName = config.APP_NAME;
63     if (isElectronMail) {
64         listenFreeTrialSessionExpiration(appName, authentication, api);
65     }
67     // Temporary log for debugging
68     if (isIframe && !isDrawerApp) {
69         captureMessage('Drawer iframe bootstrap', {
70             level: 'info',
71             extra: {
72                 isIframe,
73                 parentApp,
74                 isAuthorizedApp: getIsAuthorizedApp(parentApp || ''),
75                 locationOrigin: window.location.origin,
76                 locationHref: window.location.href,
77             },
78         });
79     }
81     const run = async () => {
82         const appContainerPromise = getAppContainer();
84         const sessionResult =
85             (isDrawerApp && parentApp
86                 ? await bootstrap.loadDrawerSession({
87                       authentication,
88                       api,
89                       parentApp,
90                       pathname,
91                   })
92                 : undefined) || (await bootstrap.loadSession({ authentication, api, pathname, searchParams }));
94         const history = bootstrap.createHistory({ sessionResult, pathname });
95         const unleashClient = bootstrap.createUnleash({ api: silentApi });
96         const unleashPromise = bootstrap.unleashReady({ unleashClient }).catch(noop);
98         const user = sessionResult.session?.User;
99         extendStore({ config, api, authentication, unleashClient, history });
101         await unleashPromise;
102         let persistedState = await getDecryptedPersistedState<Partial<CalendarState>>({
103             authentication,
104             user,
105         });
107         const persistedStateEnabled = unleashClient.isEnabled('PersistedState');
108         if (persistedState?.state && !persistedStateEnabled) {
109             persistedState = undefined;
110         }
112         const store = setupStore({ preloadedState: persistedState?.state, persist: persistedStateEnabled });
113         const dispatch = store.dispatch;
115         if (user) {
116             dispatch(initEvent({ User: user }));
117         }
119         const loadUser = async () => {
120             const [user, userSettings, features] = await Promise.all([
121                 dispatch(userThunk()),
122                 dispatch(userSettingsThunk()),
123                 dispatch(fetchFeatures([FeatureCode.EarlyAccessScope, FeatureCode.AutoAddHolidaysCalendars])),
124             ]);
126             dispatch(welcomeFlagsActions.initial(userSettings));
128             const [scopes] = await Promise.all([
129                 bootstrap.initUser({ appName, user, userSettings }),
130                 bootstrap.loadLocales({ userSettings, locales }),
131             ]);
133             return { user, userSettings, earlyAccessScope: features[FeatureCode.EarlyAccessScope], scopes };
134         };
136         const loadPreload = () => {
137             return Promise.all([
138                 dispatch(addressesThunk()),
139                 dispatch(calendarsThunk()),
140                 dispatch(calendarSettingsThunk()),
141             ]);
142         };
144         const loadPreloadButIgnored = () => {
145             loadAllowedTimeZones(silentApi).catch(noop);
146             dispatch(holidaysDirectoryThunk()).catch(noop);
147             dispatch(organizationThunk()).catch(noop);
148         };
150         const userPromise = loadUser();
151         const preloadPromise = loadPreload();
152         const evPromise = bootstrap.eventManager({ api: silentApi });
153         loadPreloadButIgnored();
155         // Needs unleash to be loaded.
156         await bootstrap.loadCrypto({ appName, unleashClient });
157         const [MainContainer, userData, eventManager] = await Promise.all([
158             appContainerPromise,
159             userPromise,
160             evPromise,
161         ]);
162         // Needs everything to be loaded.
163         await bootstrap.postLoad({ appName, authentication, ...userData, history });
164         // Preloaded models are not needed until the app starts, and also important do it postLoad as these requests might fail due to missing scopes.
165         await preloadPromise;
167         const calendarModelEventManager = createCalendarModelEventManager({ api: silentApi });
169         extendStore({ eventManager, calendarModelEventManager });
170         const unsubscribeEventManager = eventManager.subscribe((event) => {
171             dispatch(serverEvent(event));
172         });
173         eventManager.start();
175         bootstrap.onAbort(signal, () => {
176             unsubscribeEventManager();
177             eventManager.reset();
178             unleashClient.stop();
179             store.unsubscribe();
180         });
182         dispatch(bootstrapEvent({ type: 'complete' }));
184         return {
185             ...userData,
186             eventManager,
187             unleashClient,
188             history,
189             store,
190             MainContainer,
191         };
192     };
194     return bootstrap.wrap({ appName, authentication }, run());