Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / applications / drive / src / app / store / _views / useShareURLView.tsx
blobfd83113aee458bd28742f81eb2c2ec0768c2f525
1 import { useCallback, useEffect, useState } from 'react';
3 import { c } from 'ttag';
5 import { useNotifications } from '@proton/components';
6 import { useLoading } from '@proton/hooks';
7 import { SHARE_GENERATED_PASSWORD_LENGTH } from '@proton/shared/lib/drive/constants';
8 import { isProtonDocument } from '@proton/shared/lib/helpers/mimetype';
9 import type { SharedURLSessionKeyPayload } from '@proton/shared/lib/interfaces/drive/sharing';
11 import { sendErrorReport } from '../../utils/errorHandling';
12 import { useDriveDocsPublicSharingFF } from '../_documents';
13 import type { DecryptedLink } from '../_links';
14 import { useLink } from '../_links';
15 import type { ShareURL } from '../_shares';
16 import { getSharedLink, splitGeneratedAndCustomPassword, useShareActions, useShareUrl } from '../_shares';
17 import type useShareMemberView from './useShareMemberView';
19 const getLoadingMessage = (isLinkLoading: boolean, haveShareUrl: boolean, isFile: boolean) => {
20     if (isLinkLoading) {
21         return c('Info').t`Loading link`;
22     }
23     if (haveShareUrl) {
24         return isFile ? c('Info').t`Preparing link to file` : c('Info').t`Preparing link to folder`;
25     }
27     return isFile ? c('Info').t`Creating link to file` : c('Info').t`Creating link to folder`;
30 const getConfirmationMessage = () => {
31     return c('Info').t`This action will delete the link and revoke access for all users.`;
34 const getErrorMessage = (isCreationError: boolean, error: string) => {
35     if (isCreationError) {
36         return error
37             ? c('Info').t`Failed to generate a secure link. The reason is: ${error}`
38             : c('Info').t`Failed to generate a secure link. Try again later`;
39     }
40     return c('Info').t`Failed to open a secure link. The reason is: ${error}`;
43 const getSharingInfoMessage = (isFile: boolean) => {
44     return isFile
45         ? c('Info').t`Anyone with this link can access your file.`
46         : c('Info').t`Anyone with this link can access your folder.`;
49 const getPasswordProtectedSharingInfoMessage = (isFile: boolean) => {
50     return isFile
51         ? c('Info').t`Only the people with the link and the password can access this file.`
52         : c('Info').t`Only the people with the link and the password can access this folder.`;
55 /**
56  * useLinkView loads link if not cached yet.
57  */
58 export default function useShareURLView(shareId: string, linkId: string) {
59     const { getLink } = useLink();
60     const [shareUrlInfo, setShareUrlInfo] = useState<{
61         shareUrl: ShareURL;
62         keyInfo: SharedURLSessionKeyPayload;
63     }>();
64     const { loadShareUrl, updateShareUrl, deleteShareUrl, createShareUrl, getShareIdWithSessionkey } = useShareUrl();
65     const { deleteShare } = useShareActions();
67     const [sharedLink, setSharedLink] = useState('');
68     const [password, setPassword] = useState('');
69     const [initialExpiration, setInitialExpiration] = useState<number | null>(null);
70     const [error, setError] = useState('');
72     const [link, setLink] = useState<DecryptedLink>();
73     const [isLinkLoading, withLinkLoading] = useLoading(true);
74     const [isShareUrlLoading, withShareUrlLoading] = useLoading(true);
75     const [isSaving, withSaving] = useLoading();
76     const [isDeleting, withDeleting] = useLoading();
77     const [isCreating, withCreating] = useLoading();
78     const { createNotification } = useNotifications();
80     const { isDocsPublicSharingEnabled } = useDriveDocsPublicSharingFF();
82     const shareUrl = shareUrlInfo?.shareUrl;
84     const [, customPassword] = splitGeneratedAndCustomPassword(password, shareUrl);
86     useEffect(() => {
87         const abortController = new AbortController();
88         void withLinkLoading(
89             getLink(abortController.signal, shareId, linkId)
90                 .then(setLink)
91                 .catch((err) => {
92                     setError(err);
93                     sendErrorReport(err);
94                 })
95         );
96         return () => {
97             abortController.abort();
98         };
99     }, [shareId, linkId]);
101     const ShareID = shareUrl?.shareId;
102     useEffect(() => {
103         const abortController = new AbortController();
104         void withShareUrlLoading(() => {
105             if (ShareID) {
106                 return Promise.resolve();
107             }
109             return loadShareUrl(abortController.signal, shareId, linkId)
110                 .then((shareUrlInfo) => {
111                     if (shareUrlInfo) {
112                         setShareUrlInfo(shareUrlInfo);
113                         setPassword(shareUrlInfo.shareUrl.password);
114                         setInitialExpiration(shareUrlInfo.shareUrl.expirationTime);
115                         const sharedLink = getSharedLink(shareUrlInfo.shareUrl);
116                         if (sharedLink) {
117                             setSharedLink(sharedLink);
118                         }
119                     }
120                 })
121                 .catch((err) => {
122                     setError(err);
123                 });
124         });
126         return () => {
127             abortController.abort();
128         };
129     }, [shareId, linkId, ShareID]);
131     const updateLinkState = useCallback(
132         async (abortSignal: AbortSignal) => {
133             const link = await getLink(abortSignal, shareId, linkId);
134             setLink(link);
135         },
136         [shareId, linkId]
137     );
139     const createSharedLink = () => {
140         void withCreating(async () => {
141             const abortController = new AbortController();
142             const { shareId: linkShareId, sessionKey } = await getShareIdWithSessionkey(
143                 abortController.signal,
144                 shareId,
145                 linkId
146             );
148             return createShareUrl(abortController.signal, shareId, linkShareId, sessionKey)
149                 .then(async (shareUrlInfo) => {
150                     if (shareUrlInfo) {
151                         setShareUrlInfo(shareUrlInfo);
152                         setPassword(shareUrlInfo.shareUrl.password);
153                         setInitialExpiration(shareUrlInfo.shareUrl.expirationTime);
154                         const sharedLink = getSharedLink(shareUrlInfo.shareUrl);
155                         if (sharedLink) {
156                             setSharedLink(sharedLink);
157                             await updateLinkState(abortController.signal);
158                         }
159                     }
160                 })
161                 .catch((err) => {
162                     setError(err);
163                 });
164         });
165     };
167     const saveSharedLink = async (newCustomPassword?: string, newDuration?: number | null) => {
168         if (!shareUrl) {
169             return;
170         }
172         // Empty string as a newCustomPassword will remove it from the link.
173         // `undefined` is to leave the password as it is.
174         let newPassword = newCustomPassword;
175         if (newCustomPassword !== undefined && shareUrl.hasGeneratedPasswordIncluded) {
176             newPassword = password.substring(0, SHARE_GENERATED_PASSWORD_LENGTH) + newCustomPassword;
177         }
179         const update = () => {
180             const abortController = new AbortController();
181             return updateShareUrl(
182                 abortController.signal,
183                 {
184                     shareId: shareUrl.shareId,
185                     shareUrlId: shareUrl.shareUrlId,
186                     flags: shareUrl.flags,
187                     keyInfo: shareUrlInfo.keyInfo,
188                 },
189                 newDuration,
190                 newPassword
191             );
192         };
194         const updatedFields = await withSaving(update()).catch((error) => {
195             createNotification({
196                 type: 'error',
197                 text: c('Notification').t`Your settings failed to be saved`,
198             });
199             throw error;
200         });
201         createNotification({
202             text: c('Notification').t`Your settings have been changed successfully`,
203         });
204         setShareUrlInfo({
205             shareUrl: {
206                 ...shareUrl,
207                 ...updatedFields,
208             },
209             keyInfo: shareUrlInfo.keyInfo,
210         });
212         if (updatedFields && updatedFields.password !== undefined) {
213             setPassword(updatedFields.password);
214         }
215         if (updatedFields && updatedFields.expirationTime !== undefined) {
216             setInitialExpiration(updatedFields.expirationTime);
217         }
219         return updatedFields;
220     };
222     // TODO: Remove this parameter when we will have share events
223     const deleteLink = async (deleteIfEmptyCallback: ReturnType<typeof useShareMemberView>['deleteShareIfEmpty']) => {
224         if (!link || !shareUrl) {
225             return;
226         }
228         await withDeleting(async () => {
229             const abortController = new AbortController();
230             await deleteShareUrl(shareUrl.shareId, shareUrl.shareUrlId)
231                 .then(() => {
232                     setShareUrlInfo(undefined);
233                     setSharedLink('');
234                     setPassword('');
235                     setInitialExpiration(null);
236                     createNotification({
237                         text: c('Notification').t`The link to your item was deleted`,
238                     });
239                 })
240                 .catch(() => {
241                     createNotification({
242                         type: 'error',
243                         text: c('Notification').t`The link to your item failed to be deleted`,
244                     });
245                 });
246             await updateLinkState(abortController.signal);
247             await deleteIfEmptyCallback();
248         });
249     };
251     const stopSharing = async () => {
252         return withDeleting(async () => {
253             const abortController = new AbortController();
254             const loadedLink = await getLink(abortController.signal, shareId, linkId);
255             if (!loadedLink?.sharingDetails) {
256                 return;
257             }
258             await deleteShare(loadedLink.sharingDetails.shareId, { force: true })
259                 .then(() => {
260                     createNotification({
261                         text: c('Notification').t`You stopped sharing this item`,
262                     });
263                 })
264                 .catch(() => {
265                     createNotification({
266                         type: 'error',
267                         text: c('Notification').t`Stopping the sharing of this item has failed`,
268                     });
269                 });
270             await updateLinkState(abortController.signal);
271         });
272     };
274     const loadingMessage =
275         isLinkLoading || isShareUrlLoading
276             ? getLoadingMessage(isLinkLoading, !!link?.shareUrl, !!link?.isFile)
277             : undefined;
278     const confirmationMessage = getConfirmationMessage();
279     const haveError = error || (!isLinkLoading && !link);
280     const errorMessage = haveError ? getErrorMessage(!link?.shareUrl, error) : undefined;
281     // Show message "protected by password" only when password is saved.
282     const sharedInfoMessage = customPassword
283         ? getPasswordProtectedSharingInfoMessage(!!link?.isFile)
284         : getSharingInfoMessage(!!link?.isFile);
286     return {
287         isDeleting,
288         isSaving,
289         name: link?.name || '', // If the link is not loaded we will return an error message anyway
290         isShareUrlEnabled: !!link?.mimeType && isProtonDocument(link.mimeType) ? isDocsPublicSharingEnabled : true,
291         initialExpiration,
292         customPassword,
293         sharedLink,
294         hasSharedLink: !!link?.shareUrl,
295         isShareUrlLoading,
296         loadingMessage,
297         confirmationMessage,
298         errorMessage,
299         sharedInfoMessage,
300         hasGeneratedPasswordIncluded: !!shareUrl?.hasGeneratedPasswordIncluded,
301         createSharedLink,
302         saveSharedLink,
303         deleteLink,
304         stopSharing,
305         isCreating,
306     };