diff --git a/src/components/pages/sharedAlbum/AbuseReportForm.tsx b/src/components/pages/sharedAlbum/AbuseReportForm.tsx index e06008512..262b588a7 100644 --- a/src/components/pages/sharedAlbum/AbuseReportForm.tsx +++ b/src/components/pages/sharedAlbum/AbuseReportForm.tsx @@ -86,7 +86,7 @@ export function AbuseReportForm({ show, close, url }: Iprops) { publicCollectionGalleryContent.setDialogMessage({ title: constants.REPORT_SUBMIT_SUCCESS_TITLE, content: constants.REPORT_SUBMIT_SUCCESS_CONTENT, - close: { text: constants.CLOSE }, + close: { text: constants.OK }, }); } catch (e) { setFieldError('signature', constants.REPORT_SUBMIT_FAILED); diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 7fc0b3552..18a3d2594 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -348,7 +348,7 @@ export default function Gallery() { isLoadingBarRunning.current = true; }; const finishLoading = () => { - loadingBar.current?.complete(); + isLoadingBarRunning.current && loadingBar.current?.complete(); isLoadingBarRunning.current = false; }; diff --git a/src/pages/shared-albums/index.tsx b/src/pages/shared-albums/index.tsx index f8cc6b335..11bf8f730 100644 --- a/src/pages/shared-albums/index.tsx +++ b/src/pages/shared-albums/index.tsx @@ -5,6 +5,7 @@ import { getLocalPublicCollection, getLocalPublicFiles, getPublicCollection, + getPublicCollectionUID, removePublicCollectionWithFiles, syncPublicFiles, } from 'services/publicCollectionService'; @@ -19,7 +20,7 @@ import { defaultPublicCollectionGalleryContext, PublicCollectionGalleryContext, } from 'utils/publicCollectionGallery'; -import { CustomError } from 'utils/error'; +import { CustomError, parseSharingErrorCodes } from 'utils/error'; import Container from 'components/Container'; import constants from 'utils/strings/constants'; import EnteSpinner from 'components/EnteSpinner'; @@ -28,11 +29,18 @@ import CryptoWorker from 'utils/crypto'; import { PAGES } from 'constants/pages'; import router from 'next/router'; +const Loader = () => ( + + + Loading... + + +); export default function PublicCollectionGallery() { const token = useRef(null); const collectionKey = useRef(null); const url = useRef(null); - const [publicFiles, setPublicFiles] = useState([]); + const [publicFiles, setPublicFiles] = useState(null); const [publicCollection, setPublicCollection] = useState(null); const appContext = useContext(AppContext); const [abuseReportFormView, setAbuseReportFormView] = useState(false); @@ -42,23 +50,22 @@ export default function PublicCollectionGallery() { const openReportForm = () => setAbuseReportFormView(true); const closeReportForm = () => setAbuseReportFormView(false); const loadingBar = useRef(null); - const [isLoadingBarRunning, setIsLoadingBarRunning] = useState(false); + const isLoadingBarRunning = useRef(false); const openMessageDialog = () => setMessageDialogView(true); const closeMessageDialog = () => setMessageDialogView(false); - const startLoading = () => { - !isLoadingBarRunning && loadingBar.current?.continuousStart(); - setIsLoadingBarRunning(true); + const startLoadingBar = () => { + !isLoadingBarRunning.current && loadingBar.current?.continuousStart(); + isLoadingBarRunning.current = true; }; - const finishLoading = () => { - loadingBar.current?.complete(); - setIsLoadingBarRunning(false); + const finishLoadingBar = () => { + isLoadingBarRunning.current && loadingBar.current?.complete(); + isLoadingBarRunning.current = false; }; useEffect(() => { appContext.showNavBar(true); - setLoading(false); const currentURL = new URL(window.location.href); if (currentURL.pathname !== PAGES.ROOT) { router.push( @@ -75,30 +82,38 @@ export default function PublicCollectionGallery() { ); } const main = async () => { - const worker = await new CryptoWorker(); - url.current = window.location.href; - const currentURL = new URL(url.current); - const t = currentURL.searchParams.get('t'); - const ck = currentURL.hash.slice(1); - const dck = await worker.fromHex(ck); - if (!t || !dck) { - setLoading(false); - return; - } - token.current = t; - collectionKey.current = dck; - url.current = window.location.href; - const localCollection = await getLocalPublicCollection( - collectionKey.current - ); - if (localCollection) { - setPublicCollection(localCollection); - const localPublicFiles = sortFiles( - mergeMetadata(await getLocalPublicFiles(localCollection)) + try { + const worker = await new CryptoWorker(); + url.current = window.location.href; + const currentURL = new URL(url.current); + const t = currentURL.searchParams.get('t'); + const ck = currentURL.hash.slice(1); + const dck = await worker.fromHex(ck); + if (!t || !dck) { + return; + } + token.current = t; + collectionKey.current = dck; + url.current = window.location.href; + const localCollection = await getLocalPublicCollection( + collectionKey.current ); - setPublicFiles(localPublicFiles); + if (localCollection) { + setPublicCollection(localCollection); + const collectionUID = getPublicCollectionUID( + collectionKey.current, + token.current + ); + const localFiles = await getLocalPublicFiles(collectionUID); + const localPublicFiles = sortFiles( + mergeMetadata(localFiles) + ); + setPublicFiles(localPublicFiles); + } + await syncWithRemote(); + } finally { + setLoading(false); } - syncWithRemote(); }; main(); }, []); @@ -107,7 +122,7 @@ export default function PublicCollectionGallery() { const syncWithRemote = async () => { try { - startLoading(); + startLoadingBar(); const collection = await getPublicCollection( token.current, collectionKey.current @@ -116,29 +131,29 @@ export default function PublicCollectionGallery() { await syncPublicFiles(token.current, collection, setPublicFiles); } catch (e) { - if (e.message === CustomError.TOKEN_EXPIRED) { + const parsedError = parseSharingErrorCodes(e); + if (parsedError.message === CustomError.TOKEN_EXPIRED) { // share has been disabled // local cache should be cleared - removePublicCollectionWithFiles(collectionKey.current); + removePublicCollectionWithFiles( + token.current, + collectionKey.current + ); setPublicCollection(null); setPublicFiles([]); } } finally { - finishLoading(); + finishLoadingBar(); } }; - if (loading) { - return ( - - - Loading... - - - ); + + if (!publicFiles && loading) { + return ; } - if (!isLoadingBarRunning && !publicFiles) { + if (!publicFiles?.length && !loading) { return {constants.NOT_FOUND}; } + return ( { } const createPublicAccessTokenRequest: CreatePublicAccessTokenRequest = { collectionID: collection.id, - validTill: - Date.now() * 1000 + COLLECTION_SHARE_DEFAULT_VALID_DURATION, - deviceLimit: COLLECTION_SHARE_DEFAULT_DEVICE_LIMIT, }; const resp = await HTTPService.post( `${ENDPOINT}/collections/share-url`, diff --git a/src/services/machineLearning/machineLearningService.ts b/src/services/machineLearning/machineLearningService.ts index 68b72bfab..6505efbee 100644 --- a/src/services/machineLearning/machineLearningService.ts +++ b/src/services/machineLearning/machineLearningService.ts @@ -368,7 +368,8 @@ class MachineLearningService { let error = e; console.error('Error in ml sync, fileId: ', enteFile.id, error); if ('status' in error) { - error = parseServerError(error).parsedError || error; + const parsedMessage = parseServerError(error); + error = parsedMessage ? new Error(parsedMessage) : error; } // TODO: throw errors not related to specific file // sync job run should stop after these errors diff --git a/src/services/publicCollectionService.ts b/src/services/publicCollectionService.ts index 33164c7bc..faf14b349 100644 --- a/src/services/publicCollectionService.ts +++ b/src/services/publicCollectionService.ts @@ -12,17 +12,18 @@ import { } from 'types/publicCollection'; import CryptoWorker from 'utils/crypto'; import { REPORT_REASON } from 'constants/publicCollection'; -import { CustomError, ServerErrorCodes } from 'utils/error'; const ENDPOINT = getEndpoint(); const PUBLIC_COLLECTION_FILES_TABLE = 'public-collection-files'; const PUBLIC_COLLECTIONS_TABLE = 'public-collections'; -const getCollectionUID = (collection: Collection) => `${collection.key}`; -const getCollectionSyncTimeUID = (collectionUID: string) => +export const getPublicCollectionUID = (collectionKey: string, token: string) => + `${collectionKey.slice(0, 8)}-${token.slice(0, 8)}`; + +const getPublicCollectionSyncTimeUID = (collectionUID: string) => `public-${collectionUID}-time`; -export const getLocalPublicFiles = async (collection: Collection) => { +export const getLocalPublicFiles = async (collectionUID: string) => { const localSavedPublicCollectionFiles = ( (await localForage.getItem( @@ -30,8 +31,7 @@ export const getLocalPublicFiles = async (collection: Collection) => { )) || [] ).find( (localSavedPublicCollectionFiles) => - localSavedPublicCollectionFiles.collectionUID === - getCollectionUID(collection) + localSavedPublicCollectionFiles.collectionUID === collectionUID ) || ({ collectionUID: null, @@ -106,13 +106,17 @@ const dedupeCollectionFiles = ( const getPublicCollectionLastSyncTime = async (collectionUID: string) => (await localForage.getItem( - getCollectionSyncTimeUID(collectionUID) + getPublicCollectionSyncTimeUID(collectionUID) )) ?? 0; const setPublicCollectionLastSyncTime = async ( collectionUID: string, time: number -) => await localForage.setItem(getCollectionSyncTimeUID(collectionUID), time); +) => + await localForage.setItem( + getPublicCollectionSyncTimeUID(collectionUID), + time + ); export const syncPublicFiles = async ( token: string, @@ -121,14 +125,15 @@ export const syncPublicFiles = async ( ) => { try { let files: EnteFile[] = []; - const localFiles = await getLocalPublicFiles(collection); + const collectionUID = getPublicCollectionUID(collection.key, token); + const localFiles = await getLocalPublicFiles(collectionUID); files.push(...localFiles); try { if (!token) { return files; } const lastSyncTime = await getPublicCollectionLastSyncTime( - getCollectionUID(collection) + collectionUID ); if (collection.updationTime === lastSyncTime) { return files; @@ -160,12 +165,9 @@ export const syncPublicFiles = async ( } files.push(file); } - await savePublicCollectionFiles( - getCollectionUID(collection), - files - ); + await savePublicCollectionFiles(collectionUID, files); await setPublicCollectionLastSyncTime( - getCollectionUID(collection), + collectionUID, collection.updationTime ); setPublicFiles([...sortFiles(mergeMetadata(files))]); @@ -175,7 +177,7 @@ export const syncPublicFiles = async ( return [...sortFiles(mergeMetadata(files))]; } catch (e) { logError(e, 'failed to get local or sync shared collection files'); - return []; + throw e; } }; @@ -260,20 +262,12 @@ export const getPublicCollection = async ( }; await savePublicCollection(collection); return collection; - } catch (error) { - logError(error, 'failed to get public collection', { + } catch (e) { + logError(e, 'failed to get public collection', { collectionKey, token, }); - if ('status' in error) { - const errorCode = error.status.toString(); - if ( - errorCode === ServerErrorCodes.SESSION_EXPIRED || - errorCode === ServerErrorCodes.TOKEN_EXPIRED - ) { - throw Error(CustomError.TOKEN_EXPIRED); - } - } + throw e; } }; @@ -317,21 +311,17 @@ export const reportAbuse = async ( }; export const removePublicCollectionWithFiles = async ( + token: string, collectionKey: string ) => { + const collectionUID = getPublicCollectionUID(collectionKey, token); const publicCollections = - (await localForage.getItem(PUBLIC_COLLECTIONS_TABLE)) ?? + (await localForage.getItem(PUBLIC_COLLECTIONS_TABLE)) || []; - const collectionToRemove = publicCollections.find( - (collection) => (collection.key = collectionKey) - ); - if (!collectionToRemove) { - return; - } await localForage.setItem( PUBLIC_COLLECTIONS_TABLE, publicCollections.filter( - (collection) => collection.id !== collectionToRemove.id + (collection) => collection.key !== collectionKey ) ); @@ -342,9 +332,7 @@ export const removePublicCollectionWithFiles = async ( await localForage.setItem( PUBLIC_COLLECTION_FILES_TABLE, publicCollectionFiles.filter( - (collectionFiles) => - collectionFiles.collectionUID === - getCollectionUID(collectionToRemove) + (collectionFiles) => collectionFiles.collectionUID !== collectionUID ) ); }; diff --git a/src/types/collection/index.ts b/src/types/collection/index.ts index 0becd3778..83dffdda5 100644 --- a/src/types/collection/index.ts +++ b/src/types/collection/index.ts @@ -28,8 +28,8 @@ export interface PublicURL { export interface CreatePublicAccessTokenRequest { collectionID: number; - validTill: number; - deviceLimit: number; + validTill?: number; + deviceLimit?: number; } export interface EncryptedFileKey { diff --git a/src/utils/error/index.ts b/src/utils/error/index.ts index 85a417b50..85ff7ac1f 100644 --- a/src/utils/error/index.ts +++ b/src/utils/error/index.ts @@ -8,6 +8,10 @@ export const ServerErrorCodes = { STORAGE_LIMIT_EXCEEDED: '426', FILE_TOO_LARGE: '413', TOKEN_EXPIRED: '410', + TOO_MANY_REQUEST: '429', + BAD_REQUEST: '400', + PAYMENT_REQUIRED: '402', + NOT_FOUND: '404', }; export enum CustomError { @@ -32,9 +36,35 @@ export enum CustomError { REQUEST_CANCELLED = 'request canceled', NETWORK_ERROR = 'Network Error', TOKEN_EXPIRED = 'token expired', + BAD_REQUEST = 'bad request', + SUBSCRIPTION_NEEDED = 'subscription not present', + NOT_FOUND = 'not found ', } -export function parseServerError(error: AxiosResponse) { +export function parseServerError(error: AxiosResponse): string { + let parsedMessage: string = null; + const errorCode = error.status.toString(); + switch (errorCode) { + case ServerErrorCodes.NO_ACTIVE_SUBSCRIPTION: + parsedMessage = CustomError.SUBSCRIPTION_EXPIRED; + break; + case ServerErrorCodes.STORAGE_LIMIT_EXCEEDED: + parsedMessage = CustomError.STORAGE_QUOTA_EXCEEDED; + break; + case ServerErrorCodes.SESSION_EXPIRED: + parsedMessage = CustomError.SESSION_EXPIRED_MESSAGE; + break; + case ServerErrorCodes.FILE_TOO_LARGE: + parsedMessage = CustomError.FILE_TOO_LARGE; + break; + default: + parsedMessage = `${constants.UNKNOWN_ERROR} statusCode:${errorCode}`; + } + + return parsedMessage; +} + +function parseUploadErrorCodes(error) { let parsedMessage = null; if (error?.status) { const errorCode = error.status.toString(); @@ -54,19 +84,15 @@ export function parseServerError(error: AxiosResponse) { default: parsedMessage = `${constants.UNKNOWN_ERROR} statusCode:${errorCode}`; } + } else { + parsedMessage = error.message; } - return { - parsedError: new Error(parsedMessage), - }; + return new Error(parsedMessage); } -export function handleUploadError(error: AxiosResponse | Error): Error { - let parsedError: Error = null; - if ('status' in error) { - parsedError = parseServerError(error).parsedError; - } else { - parsedError = error; - } +export function handleUploadError(error): Error { + const parsedError = parseUploadErrorCodes(error); + // breaking errors switch (parsedError.message) { case CustomError.SUBSCRIPTION_EXPIRED: @@ -101,25 +127,49 @@ export function errorWithContext(originalError: Error, context: string) { originalError.stack; return errorWithContext; } - -export const handleSharingErrors = (e) => { - let errorMessage = null; - if ('status' in e) { - switch (e?.status) { - case 400: - errorMessage = constants.SHARING_BAD_REQUEST_ERROR; +export const parseSharingErrorCodes = (error) => { + let parsedMessage = null; + if (error?.status) { + const errorCode = error.status.toString(); + switch (errorCode) { + case ServerErrorCodes.BAD_REQUEST: + parsedMessage = CustomError.BAD_REQUEST; break; - case 402: - errorMessage = constants.SHARING_DISABLED_FOR_FREE_ACCOUNTS; + case ServerErrorCodes.PAYMENT_REQUIRED: + parsedMessage = CustomError.SUBSCRIPTION_NEEDED; break; - case 404: - errorMessage = constants.USER_DOES_NOT_EXIST; + case ServerErrorCodes.NOT_FOUND: + parsedMessage = CustomError.NOT_FOUND; + break; + case ServerErrorCodes.SESSION_EXPIRED: + case ServerErrorCodes.TOKEN_EXPIRED: + case ServerErrorCodes.TOO_MANY_REQUEST: + parsedMessage = CustomError.TOKEN_EXPIRED; break; default: - errorMessage = `${constants.UNKNOWN_ERROR} statusCode:${e.status}`; + parsedMessage = `${constants.UNKNOWN_ERROR} statusCode:${errorCode}`; } } else { - errorMessage = e.message; + parsedMessage = error.message; + } + return new Error(parsedMessage); +}; + +export const handleSharingErrors = (error) => { + const parsedError = parseSharingErrorCodes(error); + let errorMessage = ''; + switch (parsedError.message) { + case CustomError.BAD_REQUEST: + errorMessage = constants.SHARING_BAD_REQUEST_ERROR; + break; + case CustomError.SUBSCRIPTION_NEEDED: + errorMessage = constants.SHARING_DISABLED_FOR_FREE_ACCOUNTS; + break; + case CustomError.NOT_FOUND: + errorMessage = constants.USER_DOES_NOT_EXIST; + break; + default: + errorMessage = parsedError.message; } return errorMessage; }; diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index da3911e39..fe07e736a 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -630,10 +630,10 @@ const englishConstants = { OPEN_PLAN_SELECTOR_MODAL_FAILED: 'failed to open plans', COMMENT: 'comment', ABUSE_REPORT_DESCRIPTION: - 'fill out the following form to submit the abuse report. Refer the dmca section for more detail for privacy policy of submitting a report ', + 'Note: Submitting this report will notify the album owner.', OTHER_REASON_REQUIRES_COMMENTS: 'reason = other, require a mandatory comment ', - REPORT_SUBMIT_SUCCESS_CONTENT: 'thank you, your report have been submitted', + REPORT_SUBMIT_SUCCESS_CONTENT: 'your report has been submitted', REPORT_SUBMIT_SUCCESS_TITLE: 'report sent', REPORT_SUBMIT_FAILED: 'failed to sent report, try again', INSTALL: 'install',