1 import { captureMessage } from '@proton/shared/lib/helpers/sentry';
2 import { generateProtonWebUID } from '@proton/shared/lib/helpers/uid';
4 import { sendErrorReport } from '../../../utils/errorHandling';
7 // File is being uploading - do not automatically replace file.
8 Uploading = 'uploading',
9 // File upload either failed or user closed the tab before upload
10 // finish - automatic replace is safe.
14 const KEY_PREFIX = 'upload-client-uid';
17 * generateClientUid generates new client UID and two callbacks.
18 * One to be called when file is uploaded - that will remove the UID
19 * from local storage as it is not needed anymore.
20 * Second to be called when file failed to be uplaoded - that will
21 * change the state of the uploading UID in local storage to indicate
22 * it is safe to automatically replace the file draft.
23 * Note it will automatically set failed state when page is unloaded.
25 export function generateClientUid(clientUid?: string) {
26 // When the file is being replaced, we want to reuse the same UID
27 // so it is properly removed from the local storage after successfull
30 clientUid = generateProtonWebUID();
32 const key = getStorageKey(clientUid);
34 // LocalStorage can fail when setting values, which will fail the
35 // transfer. We can ignore this.
37 // The worst case scenario would be not knowing the state, but it's
38 // better to at least upload the file than fail here.
40 localStorage.setItem(key, UploadingState.Uploading);
42 handleLocalStorageError(e);
45 const uploadFailed = () => {
47 localStorage.setItem(key, UploadingState.Failed);
49 handleLocalStorageError(e);
51 removeEventListener('unload', uploadFailed);
54 const uploadFinished = () => {
55 localStorage.removeItem(key);
56 removeEventListener('unload', uploadFailed);
59 // If file is not finished and page is closed, we consider it
60 // as failed upload which is safe to automatically replace with
61 // next upload attempt by the user.
62 addEventListener('unload', uploadFailed);
72 * isClientUidAvailable returns true only if the client UID is known
73 * by the client and file failed to be uploaded, that is we are sure
74 * it is safe to automatically replace.
75 * If the file is still being uploaded or is not known by the client,
76 * user needs to be notified about it and asked what to do.
78 export function isClientUidAvailable(clientUid: string): boolean {
79 const key = getStorageKey(clientUid);
80 const result = localStorage.getItem(key);
81 return result === UploadingState.Failed;
85 * getStorageKey generates key to be used for local storage.
86 * Key should be unique enough to not be conflict with anything else.
88 function getStorageKey(uid: string) {
89 return `${KEY_PREFIX}-${uid}`;
92 function handleLocalStorageError(err: unknown) {
93 // When upload succeeds, the keys are removed. If user uploaded many files
94 // and all failed, it could fill the whole storage. If we cannot fit more
95 // keys anymore, lets simply remove them to keep the local storage
96 // functional. The worst case is we ask user to replace the draft.
97 if (isQuotaExceededError(err)) {
98 captureMessage('The local storage is full, deleting old failed upload statuses');
99 clearAllUploadUIDsFromLocalStorage();
101 sendErrorReport(err);
105 function isQuotaExceededError(err: unknown): boolean {
107 err instanceof DOMException &&
110 err.name === 'QuotaExceededError' ||
113 err.name === 'NS_ERROR_DOM_QUOTA_REACHED')
117 function clearAllUploadUIDsFromLocalStorage() {
118 Object.keys(localStorage).forEach((key) => {
119 if (key.startsWith(KEY_PREFIX)) {
120 localStorage.removeItem(key);