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';
14 calendarSettingsThunk,
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
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;
64 listenFreeTrialSessionExpiration(appName, authentication, api);
67 // Temporary log for debugging
68 if (isIframe && !isDrawerApp) {
69 captureMessage('Drawer iframe bootstrap', {
74 isAuthorizedApp: getIsAuthorizedApp(parentApp || ''),
75 locationOrigin: window.location.origin,
76 locationHref: window.location.href,
81 const run = async () => {
82 const appContainerPromise = getAppContainer();
85 (isDrawerApp && parentApp
86 ? await bootstrap.loadDrawerSession({
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>>({
107 const persistedStateEnabled = unleashClient.isEnabled('PersistedState');
108 if (persistedState?.state && !persistedStateEnabled) {
109 persistedState = undefined;
112 const store = setupStore({ preloadedState: persistedState?.state, persist: persistedStateEnabled });
113 const dispatch = store.dispatch;
116 dispatch(initEvent({ User: user }));
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])),
126 dispatch(welcomeFlagsActions.initial(userSettings));
128 const [scopes] = await Promise.all([
129 bootstrap.initUser({ appName, user, userSettings }),
130 bootstrap.loadLocales({ userSettings, locales }),
133 return { user, userSettings, earlyAccessScope: features[FeatureCode.EarlyAccessScope], scopes };
136 const loadPreload = () => {
138 dispatch(addressesThunk()),
139 dispatch(calendarsThunk()),
140 dispatch(calendarSettingsThunk()),
144 const loadPreloadButIgnored = () => {
145 loadAllowedTimeZones(silentApi).catch(noop);
146 dispatch(holidaysDirectoryThunk()).catch(noop);
147 dispatch(organizationThunk()).catch(noop);
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([
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));
173 eventManager.start();
175 bootstrap.onAbort(signal, () => {
176 unsubscribeEventManager();
177 eventManager.reset();
178 unleashClient.stop();
182 dispatch(bootstrapEvent({ type: 'complete' }));
194 return bootstrap.wrap({ appName, authentication }, run());