Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / shared / lib / drawer / helpers.ts
blob5ba3feebe6badd945cab0e2c8b4ab86474637bb8
1 import { LOCALSTORAGE_DRAWER_KEY } from '@proton/shared/lib/drawer/constants';
2 import { isURLProtonInternal } from '@proton/shared/lib/helpers/url';
4 import { getAppHref } from '../apps/helper';
5 import { getLocalIDFromPathname } from '../authentication/pathnameHelper';
6 import type { APP_NAMES } from '../constants';
7 import { APPS, APPS_CONFIGURATION } from '../constants';
8 import window from '../window';
9 import type { DRAWER_ACTION, DrawerApp } from './interfaces';
10 import { DRAWER_EVENTS, DRAWER_NATIVE_APPS } from './interfaces';
12 const { PROTONMAIL, PROTONCALENDAR, PROTONDRIVE } = APPS;
13 export const drawerAuthorizedApps = [
14     APPS_CONFIGURATION[PROTONMAIL].subdomain,
15     APPS_CONFIGURATION[PROTONCALENDAR].subdomain,
16     APPS_CONFIGURATION[PROTONDRIVE].subdomain,
17 ] as string[];
19 export const authorizedApps: string[] = [APPS.PROTONMAIL, APPS.PROTONCALENDAR, APPS.PROTONDRIVE];
21 export const drawerNativeApps: DrawerApp[] = Object.values(DRAWER_NATIVE_APPS);
22 export const drawerIframeApps: DrawerApp[] = [APPS.PROTONCALENDAR];
24 export const getLocalStorageUserDrawerKey = (userID: string) => `${LOCALSTORAGE_DRAWER_KEY}-${userID}`;
26 export const getIsNativeDrawerApp = (app: string): app is DrawerApp => {
27     const tsDrawerNativeApps: string[] = [...drawerNativeApps];
29     return tsDrawerNativeApps.includes(app);
32 export const getIsIframedDrawerApp = (app: string): app is DrawerApp & APP_NAMES => {
33     const tsDrawerIframeApps: string[] = [...drawerIframeApps];
35     return tsDrawerIframeApps.includes(app);
38 export const getIsDrawerApp = (app: string): app is DrawerApp => {
39     return getIsNativeDrawerApp(app) || getIsIframedDrawerApp(app);
42 export const isAuthorizedDrawerUrl = (url: string, hostname: string) => {
43     try {
44         const originURL = new URL(url);
46         // Get subdomain of the url => e.g. mail, calendar, drive
47         const appFromUrl = originURL.hostname.split('.')[0];
49         return isURLProtonInternal(url, hostname) && drawerAuthorizedApps.includes(appFromUrl);
50     } catch {
51         // the URL constructor will throw if no URL can be built out of url
52         return false;
53     }
56 export const getIsAuthorizedApp = (appName: string): appName is APP_NAMES => {
57     return authorizedApps.includes(appName);
60 export const getIsDrawerPostMessage = (
61     event: MessageEvent,
62     hostname = window.location.hostname
63 ): event is MessageEvent<DRAWER_ACTION> => {
64     const origin = event.origin;
66     // sandboxed iframes might have a "null" origin instead of a valid one
67     // so we need to handle this case, otherwise we will get an error
68     if (!origin || origin === 'null') {
69         return false;
70     }
72     /**
73      * The message is a "valid" side app message if
74      * - The message is coming from an authorized app
75      * - event.data is defined
76      * - event.data.type is part of the SIDE_APP_EVENT enum
77      */
78     return (
79         isAuthorizedDrawerUrl(origin, hostname) && event.data && Object.values(DRAWER_EVENTS).includes(event.data.type)
80     );
83 export const postMessageFromIframe = (message: DRAWER_ACTION, parentApp: APP_NAMES, location = window.location) => {
84     if (!getIsAuthorizedApp(parentApp)) {
85         return;
86     }
87     const parentUrl = getAppHref('/', parentApp, getLocalIDFromPathname(location.pathname), location);
89     window.parent?.postMessage(message, parentUrl);
92 export const postMessageToIframe = (message: DRAWER_ACTION, iframedApp: DrawerApp, location = window.location) => {
93     if (!getIsAuthorizedApp(iframedApp)) {
94         return;
95     }
96     const iframe = document.querySelector('[id^=drawer-app-iframe]') as HTMLIFrameElement | null;
97     const targetOrigin = getAppHref('/', iframedApp, getLocalIDFromPathname(location.pathname), location);
99     iframe?.contentWindow?.postMessage(message, targetOrigin);
103  *  Allow to add the parent app in a URL we will open in the Drawer
104  *  From the child application, we need to know who is the parent. For that, we add it in the URL we want to open
105  *  Depending on the case you might want to replace path or not
107  *  - "replacePath === true": You want to replace the path by the app
108  *      e.g. url = "https://calendar.proton.pink/u/0/event?Action=VIEW&EventID=eventID&RecurrenceID=1670835600" and currentApp = "proton-mail"
109  *        ==> https://calendar.proton.pink/u/0/mail?Action=VIEW&EventID=eventID&RecurrenceID=1670835600
110  *        "event" has been replaced by "mail"
111  *  - "replacePath === false": You want to add your app to the path
112  *      e.g. url = "https://calendar.proton.pink/u/0/something" and currentApp = "proton-mail"
113  *        ==> "https://calendar.proton.pink/u/0/mail/something"
114  *        "mail" has been added to the path
115  */
116 export const addParentAppToUrl = (url: string, currentApp: APP_NAMES, replacePath = true) => {
117     const targetUrl = new URL(url);
118     const splitPathname = targetUrl.pathname.split('/').filter((el) => el !== '');
120     const currentAppSubdomain = APPS_CONFIGURATION[currentApp].subdomain;
122     // splitPathname[0] & splitPathname[1] corresponds to the user local id /u/localID
123     // splitPathname[2] should be the parentApp name
124     // If we find parentApp, we don't need to add it to the pathname
125     if (splitPathname[2] !== currentAppSubdomain) {
126         // In some cases, we want to replace completely this param (e.g. Calendar "view" parameter needs to be mail instead of week)
127         if (replacePath) {
128             splitPathname.splice(2, 1, currentAppSubdomain);
129         } else {
130             splitPathname.splice(2, 0, currentAppSubdomain);
131         }
133         targetUrl.pathname = splitPathname.join('/');
134     }
136     return targetUrl.href;
139 export const getDisplayContactsInDrawer = (app: APP_NAMES) => {
140     return app === APPS.PROTONMAIL || app === APPS.PROTONCALENDAR || app === APPS.PROTONDRIVE;
143 export const getDisplaySettingsInDrawer = (app: APP_NAMES) => {
144     return app === APPS.PROTONMAIL || app === APPS.PROTONCALENDAR || app === APPS.PROTONDRIVE;
147 export const getDisplaySecurityCenterInDrawer = (app: APP_NAMES) => {
148     return app === APPS.PROTONMAIL;
151 export const closeDrawerFromChildApp = (parentApp: APP_NAMES, currentApp: APP_NAMES, closeDefinitely?: boolean) => {
152     if (!getIsIframedDrawerApp(currentApp)) {
153         throw new Error('Cannot close non-iframed app');
154     }
156     postMessageFromIframe(
157         {
158             type: DRAWER_EVENTS.CLOSE,
159             payload: { app: currentApp, closeDefinitely },
160         },
161         parentApp
162     );
165 export const isAppInView = (currentApp: DrawerApp, appInView?: DrawerApp) => {
166     return appInView ? appInView === currentApp : false;
169 export const getDisplayDrawerApp = (currentApp: APP_NAMES, toOpenApp: DrawerApp) => {
170     if (toOpenApp === APPS.PROTONCALENDAR) {
171         return currentApp === APPS.PROTONMAIL || currentApp === APPS.PROTONDRIVE;
172     } else if (toOpenApp === DRAWER_NATIVE_APPS.CONTACTS) {
173         return getDisplayContactsInDrawer(currentApp);
174     } else if (toOpenApp === DRAWER_NATIVE_APPS.QUICK_SETTINGS) {
175         return getDisplaySettingsInDrawer(currentApp);
176     } else if (toOpenApp === DRAWER_NATIVE_APPS.SECURITY_CENTER) {
177         return getDisplaySecurityCenterInDrawer(currentApp);
178     }
181 export const getDrawerAppFromURL = (url: string) => {
182     if (!isAuthorizedDrawerUrl(url, window.location.hostname)) {
183         return;
184     }
186     const originURL = new URL(url);
188     // Get subdomain of the url => e.g. mail, calendar, drive
189     const subdomainFromUrl = originURL.hostname.split('.')[0];
191     const appName = Object.keys(APPS_CONFIGURATION).find((app) => {
192         return APPS_CONFIGURATION[app as APP_NAMES].subdomain === subdomainFromUrl;
193     });
195     return appName as DrawerApp;