Remove client-side isLoggedIn value
[ProtonMail-WebClient.git] / packages / pass / components / Navigation / routing.ts
blob3d72d3eebdfc7381d1878cd2b69eb593b6a376af
1 import { type Location, createBrowserHistory } from 'history';
3 import { decodeUtf8Base64, encodeUtf8Base64 } from '@proton/crypto/lib/utils';
4 import { authStore } from '@proton/pass/lib/auth/store';
5 import type { ItemFilters, ItemType, MaybeNull } from '@proton/pass/types';
6 import { partialMerge } from '@proton/pass/utils/object/merge';
7 import { getLocalIDPath, stripLocalBasenameFromPathname } from '@proton/shared/lib/authentication/pathnameHelper';
8 import { APPS } from '@proton/shared/lib/constants';
9 import { getAppUrlFromApiUrl } from '@proton/shared/lib/helpers/url';
11 export type ItemNewRouteParams = { type: ItemType };
12 export type ItemRouteOptions = { trashed?: boolean; prefix?: string };
13 export type AuthRouteState = { error?: string; userInitiatedLock?: boolean };
15 export enum UnauthorizedRoutes {
16     SecureLink = '/secure-link/:token',
19 export const history = createBrowserHistory();
21 /** Appends the localID path to the provided path. If `localID` is not
22  * defined returns the original path (this may be the case in the extension
23  * for user's using legacy AuthSessions which were not persisted with the
24  * session's LocalID */
25 export const getLocalPath = (path: string = '') => {
26     const localID = authStore.getLocalID();
27     return localID !== undefined ? `/${getLocalIDPath(localID)}/${path}` : `/${path}`;
30 export const removeLocalPath = (path: string) => {
31     const re = /\/u\/\d+(?:\/(.+))?\/?$/;
32     if (!re.test(path)) return path;
34     const match = path.match(re);
35     return match?.[1] ?? '';
38 export const subPath = (path: string, sub: string) => `${path}/${sub}`;
39 export const maybeTrash = (path: string, inTrash?: boolean) => (inTrash ? subPath('trash', path) : path);
41 /** Resolves the item route given a shareId and an itemId. */
42 export const getItemRoute = (shareId: string, itemId: string, options?: ItemRouteOptions) => {
43     const basePath = maybeTrash(`share/${shareId}/item/${itemId}`, options?.trashed);
44     const prefixed = options?.prefix ? subPath(options.prefix, basePath) : basePath;
45     return getLocalPath(prefixed);
48 export const getItemHistoryRoute = (shareId: string, itemId: string, options?: ItemRouteOptions) =>
49     `${getItemRoute(shareId, itemId, options)}/history`;
51 /** Resolves the new item route given an item type. */
52 export const getNewItemRoute = (type: ItemType) => getLocalPath(`item/new/${type}`);
53 export const getTrashRoute = () => getLocalPath('trash');
54 export const getOnboardingRoute = () => getLocalPath('onboarding');
56 export const getInitialFilters = (): ItemFilters => ({ search: '', sort: 'recent', type: '*', selectedShareId: null });
58 export const decodeFilters = (encodedFilters: MaybeNull<string>): ItemFilters =>
59     partialMerge(
60         getInitialFilters(),
61         (() => {
62             try {
63                 if (!encodedFilters) return {};
64                 return JSON.parse(decodeUtf8Base64(encodedFilters));
65             } catch {
66                 return {};
67             }
68         })()
69     );
71 export const decodeFiltersFromSearch = (search: string) => {
72     const params = new URLSearchParams(search);
73     return decodeFilters(params.get('filters'));
76 export const encodeFilters = (filters: ItemFilters): string => encodeUtf8Base64(JSON.stringify(filters));
78 export const getPassWebUrl = (apiUrl: string, subPath: string = '') => {
79     const appUrl = getAppUrlFromApiUrl(apiUrl, APPS.PROTONPASS);
80     return appUrl.toString() + subPath;
83 export const getRouteError = (search: string) => new URLSearchParams(search).get('error');
85 export const getBootRedirectPath = (bootLocation: Location) => {
86     const searchParams = new URLSearchParams(bootLocation.search);
88     const redirectPath = (() => {
89         if (searchParams.get('filters') !== null) {
90             return bootLocation.pathname;
91         }
93         const [, shareId, itemId] = bootLocation.pathname.match('share/([^/]+)(/item/([^/]+))?') || [];
94         if (shareId || itemId) {
95             const filters = partialMerge(getInitialFilters(), { selectedShareId: shareId });
96             searchParams.set('filters', encodeFilters(filters));
97             return `${bootLocation.pathname}?${searchParams.toString()}`;
98         }
100         return bootLocation.pathname;
101     })();
103     return stripLocalBasenameFromPathname(redirectPath);
105 const extractPathWithoutFragment = (path: string): string => {
106     const indexOfHash = path.indexOf('#');
107     return indexOfHash === -1 ? path : path.substring(0, indexOfHash);
110 const pathMatchesRoute = (path: string, routeTemplate: string): boolean => {
111     const cleanedPath = extractPathWithoutFragment(path);
112     const templateParts = routeTemplate.split('/');
113     const pathParts = cleanedPath.split('/');
115     return (
116         templateParts.length === pathParts.length &&
117         templateParts.every((part, i) => part.startsWith(':') || part === pathParts[i])
118     );
121 export const isUnauthorizedPath = ({ pathname }: Location): boolean =>
122     Object.values(UnauthorizedRoutes).some((route) => pathMatchesRoute(pathname, route));
124 /** In Electron, direct `location.href` mutations don't work due
125  * to custom URL schemes. We use IPC via the `ContextBridgeApi` to
126  * properly reload the page in desktop builds. For non-desktop,
127  * standard `window.location.href` assignment is used. */
128 export const reloadHref = (href: string) => {
129     if (DESKTOP_BUILD) void window?.ctxBridge?.navigate(href);
130     else window.location.href = href;