From 27a400743cf4a9b93d511c99642730c57855f640 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 17:14:31 +0530 Subject: [PATCH 1/8] Sketch --- .../new/albums/services/public-albums-fdb.ts | 50 +++++++++++++++++-- .../new/photos/services/photos-fdb.ts | 8 ++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/web/packages/new/albums/services/public-albums-fdb.ts b/web/packages/new/albums/services/public-albums-fdb.ts index 7c25ff2502..bbed0cbd3d 100644 --- a/web/packages/new/albums/services/public-albums-fdb.ts +++ b/web/packages/new/albums/services/public-albums-fdb.ts @@ -5,6 +5,7 @@ import { LocalCollections, LocalEnteFiles, + LocalTimestamp, transformFilesIfNeeded, } from "ente-gallery/services/files-db"; import { type Collection } from "ente-media/collection"; @@ -91,7 +92,8 @@ type ES = LocalSavedPublicCollectionFilesEntry[]; * * Use {@link savePublicCollectionFiles} to update the database. * - * @param accessToken The access token of the public album whose files we want. + * @param accessToken The access token that identifies the public album under + * consideration. */ export const savedPublicCollectionFiles = async ( accessToken: string, @@ -108,8 +110,8 @@ export const savedPublicCollectionFiles = async ( * * This is the setter corresponding to {@link savedPublicCollectionFiles}. * - * @param accessToken The access token of the public album whose files we want - * to replace. + * @param accessToken The access token that identifies the public album under + * consideration. * * @param files The files to save. */ @@ -125,6 +127,44 @@ export const savePublicCollectionFiles = async ( ]); }; +/** + * Return the locally persisted "last sync time" for a public collection that we + * have pulled from remote. This can be used to perform a paginated delta pull + * from the saved time onwards. + * + * Use {@link savePublic CollectionLastSyncTime} to update the value saved in + * the database, and {@link removePublicCollectionLastSyncTime} to remove the + * saved value from the database. + * + * @param accessToken The access token that identifies the public album under + * consideration. + */ +export const savedPublicCollectionLastSyncTime = async (accessToken: string) => + LocalTimestamp.parse( + await localForage.getItem(`public-${accessToken}-time`), + ); + +/** + * Update the locally persisted timestamp that will be returned by subsequent + * calls to {@link savedPublicCollectionLastSyncTime}. + */ +export const savePublicCollectionLastSyncTime = async ( + accessToken: string, + time: number, +) => { + await localForage.setItem(`public-${accessToken}-time`, time); +}; + +/** + * Remove the locally persisted timestamp, if any, previously saved for a + * collection using {@link savedPublicCollectionLastSyncTime}. + */ +export const removePublicCollectionLastSyncTime = async ( + accessToken: string, +) => { + await localForage.removeItem(`public-${accessToken}-time`); +}; + const LocalUploaderName = z.string().nullish().transform(nullToUndefined); /** @@ -143,8 +183,8 @@ const LocalUploaderName = z.string().nullish().transform(nullToUndefined); * public collection, in the local database so that it can prefill it the next * time there is an upload from the same client. * - * @param accessToken The access token of the public album whose persisted - * uploader name we we want. + * @param accessToken The access token that identifies the public album under + * consideration. */ export const savedPublicCollectionUploaderName = async (accessToken: string) => LocalUploaderName.parse( diff --git a/web/packages/new/photos/services/photos-fdb.ts b/web/packages/new/photos/services/photos-fdb.ts index 8c549c2a2c..4c5329d94e 100644 --- a/web/packages/new/photos/services/photos-fdb.ts +++ b/web/packages/new/photos/services/photos-fdb.ts @@ -188,8 +188,12 @@ export const saveCollectionFiles = async (files: EnteFile[]) => { }; /** - * Return the locally persisted {@link updationTime} of the latest file from the - * given {@link collection} that we have pulled from remote. + * Return the locally persisted "last sync time" for a collection that we have + * pulled from remote. This can be used to perform a paginated delta pull from + * the saved time onwards. + * + * > Specifically, this is the {@link updationTime} of the latest file from the + * > {@link collection}, or the the collection itself if it is fully synced. * * Use {@link saveCollectionLastSyncTime} to update the value saved in the * database, and {@link removeCollectionIDLastSyncTime} to remove the saved From 436a5811cb3f85dc776266e85b78f176ec07c84b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 17:16:37 +0530 Subject: [PATCH 2/8] Use --- .../src/services/publicCollectionService.ts | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index 8ade970689..0396a506f3 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -9,10 +9,13 @@ import type { import type { EnteFile, RemoteEnteFile } from "ente-media/file"; import { decryptRemoteFile } from "ente-media/file"; import { + removePublicCollectionLastSyncTime, savedPublicCollectionFiles, + savedPublicCollectionLastSyncTime, savedPublicCollections, saveLastPublicCollectionReferralCode, savePublicCollectionFiles, + savePublicCollectionLastSyncTime, } from "ente-new/albums/services/public-albums-fdb"; import { CustomError, parseSharingErrorCodes } from "ente-shared/error"; import HTTPService from "ente-shared/network/HTTPService"; @@ -25,9 +28,6 @@ const PUBLIC_COLLECTIONS_TABLE = "public-collections"; // eslint-disable-next-line @typescript-eslint/no-unnecessary-template-expression export const getPublicCollectionUID = (token: string) => `${token}`; -const getPublicCollectionLastSyncTimeKey = (collectionUID: string) => - `public-${collectionUID}-time`; - const getPublicCollectionPasswordKey = (collectionUID: string) => `public-${collectionUID}-passkey`; @@ -86,20 +86,6 @@ const dedupeCollections = (collections: Collection[]) => { }); }; -const getPublicCollectionLastSyncTime = async (collectionUID: string) => - (await localForage.getItem( - getPublicCollectionLastSyncTimeKey(collectionUID), - )) ?? 0; - -const savePublicCollectionLastSyncTime = async ( - collectionUID: string, - time: number, -) => - await localForage.setItem( - getPublicCollectionLastSyncTimeKey(collectionUID), - time, - ); - export const syncPublicFiles = async ( token: string, passwordToken: string, @@ -118,7 +104,7 @@ export const syncPublicFiles = async ( return sortFiles(files, sortAsc); } const lastSyncTime = - await getPublicCollectionLastSyncTime(collectionUID); + (await savedPublicCollectionLastSyncTime(collectionUID)) ?? 0; if (collection.updationTime === lastSyncTime) { return sortFiles(files, sortAsc); } @@ -318,9 +304,7 @@ export const removePublicCollectionWithFiles = async ( export const removePublicFiles = async (collectionUID: string) => { await localForage.removeItem(getPublicCollectionPasswordKey(collectionUID)); - await localForage.removeItem( - getPublicCollectionLastSyncTimeKey(collectionUID), - ); + await removePublicCollectionLastSyncTime(collectionUID); const publicCollectionFiles = (await localForage.getItem( From a538e852bd185f5f1d473688a15e638cd8cabb14 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 17:27:34 +0530 Subject: [PATCH 3/8] Update --- .../src/services/publicCollectionService.ts | 15 +------- .../new/albums/services/public-albums-fdb.ts | 38 ++++++++++++++----- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index 0396a506f3..35af9787b3 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -9,6 +9,7 @@ import type { import type { EnteFile, RemoteEnteFile } from "ente-media/file"; import { decryptRemoteFile } from "ente-media/file"; import { + removePublicCollectionFiles, removePublicCollectionLastSyncTime, savedPublicCollectionFiles, savedPublicCollectionLastSyncTime, @@ -21,7 +22,6 @@ import { CustomError, parseSharingErrorCodes } from "ente-shared/error"; import HTTPService from "ente-shared/network/HTTPService"; import localForage from "ente-shared/storage/localForage"; -const PUBLIC_COLLECTION_FILES_TABLE = "public-collection-files"; const PUBLIC_COLLECTIONS_TABLE = "public-collections"; // Fix this once we can trust the types. @@ -305,16 +305,5 @@ export const removePublicCollectionWithFiles = async ( export const removePublicFiles = async (collectionUID: string) => { await localForage.removeItem(getPublicCollectionPasswordKey(collectionUID)); await removePublicCollectionLastSyncTime(collectionUID); - - const publicCollectionFiles = - (await localForage.getItem( - PUBLIC_COLLECTION_FILES_TABLE, - )) ?? []; - await localForage.setItem( - PUBLIC_COLLECTION_FILES_TABLE, - publicCollectionFiles.filter( - (collectionFiles) => - collectionFiles.collectionUID !== collectionUID, - ), - ); + await removePublicCollectionFiles(collectionUID); }; diff --git a/web/packages/new/albums/services/public-albums-fdb.ts b/web/packages/new/albums/services/public-albums-fdb.ts index bbed0cbd3d..c3670252a4 100644 --- a/web/packages/new/albums/services/public-albums-fdb.ts +++ b/web/packages/new/albums/services/public-albums-fdb.ts @@ -84,13 +84,11 @@ type LocalSavedPublicCollectionFilesEntry = z.infer< typeof LocalSavedPublicCollectionFilesEntry >; -// A purely synactic and local alias to avoid the code from looking scary. -type ES = LocalSavedPublicCollectionFilesEntry[]; - /** * Return all files for a public collection present in our local database. * - * Use {@link savePublicCollectionFiles} to update the database. + * Use {@link savePublicCollectionFiles} to update the list of files in the + * database, and {@link removePublicCollectionFiles} to remove them. * * @param accessToken The access token that identifies the public album under * consideration. @@ -98,11 +96,23 @@ type ES = LocalSavedPublicCollectionFilesEntry[]; export const savedPublicCollectionFiles = async ( accessToken: string, ): Promise => { + const entry = (await pcfEntries()).find( + (e) => e.collectionUID == accessToken, + ); + return transformFilesIfNeeded(entry ? entry.files : []); +}; + +/** + * A convenience routine to read the DB entries for "public-collection-files". + */ +const pcfEntries = async () => { + // A local alias to avoid the code from looking scary. + type ES = LocalSavedPublicCollectionFilesEntry[]; + // See: [Note: Avoiding Zod parsing for large DB arrays] for why we use an // (implied) cast here instead of parsing using the Zod schema. const entries = await localForage.getItem("public-collection-files"); - const entry = (entries ?? []).find((e) => e.collectionUID == accessToken); - return transformFilesIfNeeded(entry ? entry.files : []); + return entries ?? []; }; /** @@ -119,11 +129,21 @@ export const savePublicCollectionFiles = async ( accessToken: string, files: EnteFile[], ): Promise => { - // See: [Note: Avoiding Zod parsing for large DB arrays]. - const entries = await localForage.getItem("public-collection-files"); await localForage.setItem("public-collection-files", [ { collectionUID: accessToken, files }, - ...(entries ?? []).filter((e) => e.collectionUID != accessToken), + ...(await pcfEntries()).filter((e) => e.collectionUID != accessToken), + ]); +}; + +/** + * Remove the list of files, in any, in our local database for the given + * collection (identified by its {@link accessToken}). + */ +export const removePublicCollectionFiles = async ( + accessToken: string, +): Promise => { + await localForage.setItem("public-collection-files", [ + ...(await pcfEntries()).filter((e) => e.collectionUID != accessToken), ]); }; From b8547305337d105baaf9d5f4df6311d317e90b9f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 17:39:00 +0530 Subject: [PATCH 4/8] Sketch --- .../new/albums/services/public-albums-fdb.ts | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/web/packages/new/albums/services/public-albums-fdb.ts b/web/packages/new/albums/services/public-albums-fdb.ts index c3670252a4..2b4d916133 100644 --- a/web/packages/new/albums/services/public-albums-fdb.ts +++ b/web/packages/new/albums/services/public-albums-fdb.ts @@ -38,6 +38,46 @@ export const savedPublicCollections = async (): Promise => export const savePublicCollections = (collections: Collection[]) => localForage.setItem("public-collections", collections); +/** + * Return the saved public collection with the given {@link key} if present in + * our local database. + * + * Use {@link savePublicCollection} to save collections in our local database. + * + * @param key The collection key that can be used to identify the public album + * we want from amongst all the locally saved public albums. + */ +export const savedPublicCollection = async ( + collectionKey: string, +): Promise => + savedPublicCollections().then((cs) => + cs.find((c) => c.key == collectionKey), + ); + +/** + * Save a public collection to our local database. + * + * The collection can later be retrieved using {@link savedPublicCollection}. + * The collection can be removed using {@link removePublicCollection}. + */ +export const savePublicCollection = async (collection: Collection) => { + const collections = await savedPublicCollections(); + await savePublicCollections([ + collection, + ...collections.filter((c) => c.id != collection.id), + ]); +}; + +/** + * Remove a public collection from our local database. + */ +export const removePublicCollection = async (collection: Collection) => { + const collections = await savedPublicCollections(); + await savePublicCollections([ + ...collections.filter((c) => c.id != collection.id), + ]); +}; + const LocalReferralCode = z.string().nullish().transform(nullToUndefined); /** @@ -90,8 +130,8 @@ type LocalSavedPublicCollectionFilesEntry = z.infer< * Use {@link savePublicCollectionFiles} to update the list of files in the * database, and {@link removePublicCollectionFiles} to remove them. * - * @param accessToken The access token that identifies the public album under - * consideration. + * @param accessToken The access token that identifies the public album whose + * files we want. */ export const savedPublicCollectionFiles = async ( accessToken: string, @@ -120,8 +160,8 @@ const pcfEntries = async () => { * * This is the setter corresponding to {@link savedPublicCollectionFiles}. * - * @param accessToken The access token that identifies the public album under - * consideration. + * @param accessToken The access token that identifies the public album whose + * files we want to update. * * @param files The files to save. */ @@ -156,8 +196,8 @@ export const removePublicCollectionFiles = async ( * the database, and {@link removePublicCollectionLastSyncTime} to remove the * saved value from the database. * - * @param accessToken The access token that identifies the public album under - * consideration. + * @param accessToken The access token that identifies the public album whose + * last sync time we want. */ export const savedPublicCollectionLastSyncTime = async (accessToken: string) => LocalTimestamp.parse( @@ -203,8 +243,8 @@ const LocalUploaderName = z.string().nullish().transform(nullToUndefined); * public collection, in the local database so that it can prefill it the next * time there is an upload from the same client. * - * @param accessToken The access token that identifies the public album under - * consideration. + * @param accessToken The access token that identifies the public album whose + * saved uploader name we want. */ export const savedPublicCollectionUploaderName = async (accessToken: string) => LocalUploaderName.parse( From 5034fb4496fc57b7a9ebdd9d11a597a3413c6921 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 17:43:14 +0530 Subject: [PATCH 5/8] Update --- web/apps/photos/src/pages/shared-albums.tsx | 4 +- .../src/services/publicCollectionService.ts | 43 ++----------------- .../new/albums/services/public-albums-fdb.ts | 12 ++++-- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 23230f49ce..7d73991222 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -52,6 +52,7 @@ import type { Collection } from "ente-media/collection"; import { type EnteFile } from "ente-media/file"; import { savedLastPublicCollectionReferralCode, + savedPublicCollectionByKey, savedPublicCollectionFiles, } from "ente-new/albums/services/public-albums-fdb"; import { verifyPublicAlbumPassword } from "ente-new/albums/services/public-collection"; @@ -68,7 +69,6 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FileWithPath } from "react-dropzone"; import { - getLocalPublicCollection, getLocalPublicCollectionPassword, getPublicCollection, getPublicCollectionUID, @@ -216,7 +216,7 @@ export default function PublicCollectionGallery() { } collectionKey.current = ck; url.current = window.location.href; - const localCollection = await getLocalPublicCollection( + const localCollection = await savedPublicCollectionByKey( collectionKey.current, ); const accessToken = t; diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index 35af9787b3..c9b8765c97 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -9,12 +9,13 @@ import type { import type { EnteFile, RemoteEnteFile } from "ente-media/file"; import { decryptRemoteFile } from "ente-media/file"; import { + removePublicCollectionByKey, removePublicCollectionFiles, removePublicCollectionLastSyncTime, savedPublicCollectionFiles, savedPublicCollectionLastSyncTime, - savedPublicCollections, saveLastPublicCollectionReferralCode, + savePublicCollection, savePublicCollectionFiles, savePublicCollectionLastSyncTime, } from "ente-new/albums/services/public-albums-fdb"; @@ -22,8 +23,6 @@ import { CustomError, parseSharingErrorCodes } from "ente-shared/error"; import HTTPService from "ente-shared/network/HTTPService"; import localForage from "ente-shared/storage/localForage"; -const PUBLIC_COLLECTIONS_TABLE = "public-collections"; - // Fix this once we can trust the types. // eslint-disable-next-line @typescript-eslint/no-unnecessary-template-expression export const getPublicCollectionUID = (token: string) => `${token}`; @@ -56,36 +55,6 @@ export const savePublicCollectionPassword = async ( ); }; -export const getLocalPublicCollection = async (collectionKey: string) => { - const localCollections = await savedPublicCollections(); - const publicCollection = - localCollections.find( - (localSavedPublicCollection) => - localSavedPublicCollection.key === collectionKey, - ) || null; - return publicCollection; -}; - -export const savePublicCollection = async (collection: Collection) => { - const publicCollections = await savedPublicCollections(); - await localForage.setItem( - PUBLIC_COLLECTIONS_TABLE, - dedupeCollections([collection, ...publicCollections]), - ); -}; - -const dedupeCollections = (collections: Collection[]) => { - const keySet = new Set([]); - return collections.filter((collection) => { - if (!keySet.has(collection.key)) { - keySet.add(collection.key); - return true; - } else { - return false; - } - }); -}; - export const syncPublicFiles = async ( token: string, passwordToken: string, @@ -292,13 +261,7 @@ export const removePublicCollectionWithFiles = async ( collectionUID: string, collectionKey: string, ) => { - const publicCollections = await savedPublicCollections(); - await localForage.setItem( - PUBLIC_COLLECTIONS_TABLE, - publicCollections.filter( - (collection) => collection.key !== collectionKey, - ), - ); + await removePublicCollectionByKey(collectionKey); await removePublicFiles(collectionUID); }; diff --git a/web/packages/new/albums/services/public-albums-fdb.ts b/web/packages/new/albums/services/public-albums-fdb.ts index 2b4d916133..91217655e9 100644 --- a/web/packages/new/albums/services/public-albums-fdb.ts +++ b/web/packages/new/albums/services/public-albums-fdb.ts @@ -47,7 +47,7 @@ export const savePublicCollections = (collections: Collection[]) => * @param key The collection key that can be used to identify the public album * we want from amongst all the locally saved public albums. */ -export const savedPublicCollection = async ( +export const savedPublicCollectionByKey = async ( collectionKey: string, ): Promise => savedPublicCollections().then((cs) => @@ -69,12 +69,16 @@ export const savePublicCollection = async (collection: Collection) => { }; /** - * Remove a public collection from our local database. + * Remove a public collection, identified using its collection key, from our + * local database. + * + * @param key The collection key that can be used to identify the public album + * we want to remove. */ -export const removePublicCollection = async (collection: Collection) => { +export const removePublicCollectionByKey = async (collectionKey: string) => { const collections = await savedPublicCollections(); await savePublicCollections([ - ...collections.filter((c) => c.id != collection.id), + ...collections.filter((c) => c.key != collectionKey), ]); }; From 54dde95545460b7ab316dba41c6ebf574aabc7e2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 17:59:08 +0530 Subject: [PATCH 6/8] Sketch --- .../new/albums/services/public-albums-fdb.ts | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/web/packages/new/albums/services/public-albums-fdb.ts b/web/packages/new/albums/services/public-albums-fdb.ts index 91217655e9..4464f76be4 100644 --- a/web/packages/new/albums/services/public-albums-fdb.ts +++ b/web/packages/new/albums/services/public-albums-fdb.ts @@ -82,7 +82,10 @@ export const removePublicCollectionByKey = async (collectionKey: string) => { ]); }; -const LocalReferralCode = z.string().nullish().transform(nullToUndefined); +/** + * Zod schema for a nullish string, with `null` transformed to `undefined`. + */ +const LocalString = z.string().nullish().transform(nullToUndefined); /** * Return the last saved referral code present in our local database. @@ -100,7 +103,7 @@ const LocalReferralCode = z.string().nullish().transform(nullToUndefined); * out a new value using {@link saveLastPublicCollectionReferralCode}. */ export const savedLastPublicCollectionReferralCode = async () => - LocalReferralCode.parse(await localForage.getItem("public-referral-code")); + LocalString.parse(await localForage.getItem("public-referral-code")); /** * Update the referral code present in our local database. @@ -229,7 +232,42 @@ export const removePublicCollectionLastSyncTime = async ( await localForage.removeItem(`public-${accessToken}-time`); }; -const LocalUploaderName = z.string().nullish().transform(nullToUndefined); +/** + * Return the password JWT, if any, present in our local database for the given + * public collection (as identified by its {@link accessToken}). + * + * Use {@link savedPublicCollectionPasswordJWT} to save the value, and + * {@link removePublicCollectionPasswordJWT} to remove it. + */ +export const savedPublicCollectionPasswordJWT = async (accessToken: string) => + LocalString.parse( + await localForage.getItem(`public-${accessToken}-passkey`), + ); + +/** + * Update the password JWT in our local database for the given public + * collection (as identified by its {@link accessToken}). + * + * This is the setter corresponding to {@link savedPublicCollectionPasswordJWT}. + */ +export const savePublicCollectionPasswordJWT = async ( + accessToken: string, + passwordJWT: string, +) => { + await localForage.setItem(`public-${accessToken}-passkey`, passwordJWT); +}; + +/** + * Remove the password JWT in our local database for the given public + * collection (as identified by its {@link accessToken}). + * + * This is the setter corresponding to {@link savedPublicCollectionPasswordJWT}. + */ +export const removePublicCollectionPasswordJWT = async ( + accessToken: string, +) => { + await localForage.removeItem(`public-${accessToken}-passkey`); +}; /** * Return the previously saved uploader name, if any, present in our local @@ -251,7 +289,7 @@ const LocalUploaderName = z.string().nullish().transform(nullToUndefined); * saved uploader name we want. */ export const savedPublicCollectionUploaderName = async (accessToken: string) => - LocalUploaderName.parse( + LocalString.parse( await localForage.getItem(`public-${accessToken}-uploaderName`), ); From 738088e8a5d7bff89a3a2829d71ee4332626d82a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 18:09:34 +0530 Subject: [PATCH 7/8] Update --- web/apps/photos/src/pages/shared-albums.tsx | 15 ++++++---- .../src/services/publicCollectionService.ts | 27 ++--------------- .../new/albums/services/public-albums-fdb.ts | 29 ++++++++++--------- 3 files changed, 26 insertions(+), 45 deletions(-) diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 7d73991222..34d82c9f34 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -51,9 +51,12 @@ import { sortFiles } from "ente-gallery/utils/file"; import type { Collection } from "ente-media/collection"; import { type EnteFile } from "ente-media/file"; import { + removePublicCollectionAccessTokenJWT, savedLastPublicCollectionReferralCode, + savedPublicCollectionAccessTokenJWT, savedPublicCollectionByKey, savedPublicCollectionFiles, + savePublicCollectionAccessTokenJWT, } from "ente-new/albums/services/public-albums-fdb"; import { verifyPublicAlbumPassword } from "ente-new/albums/services/public-collection"; import { @@ -69,12 +72,10 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FileWithPath } from "react-dropzone"; import { - getLocalPublicCollectionPassword, getPublicCollection, getPublicCollectionUID, removePublicCollectionWithFiles, removePublicFiles, - savePublicCollectionPassword, syncPublicFiles, } from "services/publicCollectionService"; import { uploadManager } from "services/upload-manager"; @@ -230,13 +231,12 @@ export default function PublicCollectionGallery() { const isPasswordProtected = localCollection?.publicURLs?.[0]?.passwordEnabled; setIsPasswordProtected(isPasswordProtected); - const collectionUID = getPublicCollectionUID(accessToken); const localFiles = await savedPublicCollectionFiles(accessToken); const localPublicFiles = sortFiles(localFiles, sortAsc); setPublicFiles(localPublicFiles); accessTokenJWT = - await getLocalPublicCollectionPassword(collectionUID); + await savedPublicCollectionAccessTokenJWT(accessToken); } credentials.current = { accessToken, accessTokenJWT }; downloadManager.setPublicAlbumsCredentials(credentials.current); @@ -316,7 +316,7 @@ export default function PublicCollectionGallery() { if (!isPasswordProtected && credentials.current.accessTokenJWT) { credentials.current.accessTokenJWT = undefined; downloadManager.setPublicAlbumsCredentials(credentials.current); - savePublicCollectionPassword(collectionUID, null); + removePublicCollectionAccessTokenJWT(collectionUID); } if ( @@ -395,7 +395,10 @@ export default function PublicCollectionGallery() { const collectionUID = getPublicCollectionUID( credentials.current.accessToken, ); - await savePublicCollectionPassword(collectionUID, accessTokenJWT); + await savePublicCollectionAccessTokenJWT( + collectionUID, + accessTokenJWT, + ); } catch (e) { log.error("Failed to verifyLinkPassword", e); if (isHTTP401Error(e)) { diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index c9b8765c97..e51653efd0 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -9,6 +9,7 @@ import type { import type { EnteFile, RemoteEnteFile } from "ente-media/file"; import { decryptRemoteFile } from "ente-media/file"; import { + removePublicCollectionAccessTokenJWT, removePublicCollectionByKey, removePublicCollectionFiles, removePublicCollectionLastSyncTime, @@ -21,40 +22,16 @@ import { } from "ente-new/albums/services/public-albums-fdb"; import { CustomError, parseSharingErrorCodes } from "ente-shared/error"; import HTTPService from "ente-shared/network/HTTPService"; -import localForage from "ente-shared/storage/localForage"; // Fix this once we can trust the types. // eslint-disable-next-line @typescript-eslint/no-unnecessary-template-expression export const getPublicCollectionUID = (token: string) => `${token}`; -const getPublicCollectionPasswordKey = (collectionUID: string) => - `public-${collectionUID}-passkey`; - export interface LocalSavedPublicCollectionFiles { collectionUID: string; files: EnteFile[]; } -export const getLocalPublicCollectionPassword = async ( - collectionUID: string, -): Promise => { - return ( - (await localForage.getItem( - getPublicCollectionPasswordKey(collectionUID), - )) || "" - ); -}; - -export const savePublicCollectionPassword = async ( - collectionUID: string, - passToken: string, -): Promise => { - return await localForage.setItem( - getPublicCollectionPasswordKey(collectionUID), - passToken, - ); -}; - export const syncPublicFiles = async ( token: string, passwordToken: string, @@ -266,7 +243,7 @@ export const removePublicCollectionWithFiles = async ( }; export const removePublicFiles = async (collectionUID: string) => { - await localForage.removeItem(getPublicCollectionPasswordKey(collectionUID)); + await removePublicCollectionAccessTokenJWT(collectionUID); await removePublicCollectionLastSyncTime(collectionUID); await removePublicCollectionFiles(collectionUID); }; diff --git a/web/packages/new/albums/services/public-albums-fdb.ts b/web/packages/new/albums/services/public-albums-fdb.ts index 4464f76be4..5a20984e1b 100644 --- a/web/packages/new/albums/services/public-albums-fdb.ts +++ b/web/packages/new/albums/services/public-albums-fdb.ts @@ -19,7 +19,7 @@ import { z } from "zod/v4"; * * Use {@link savePublicCollections} to update the database. */ -export const savedPublicCollections = async (): Promise => +const savedPublicCollections = async (): Promise => // TODO: // // See: [Note: strict mode migration] @@ -35,7 +35,7 @@ export const savedPublicCollections = async (): Promise => * * This is the setter corresponding to {@link savedPublicCollections}. */ -export const savePublicCollections = (collections: Collection[]) => +const savePublicCollections = (collections: Collection[]) => localForage.setItem("public-collections", collections); /** @@ -233,24 +233,27 @@ export const removePublicCollectionLastSyncTime = async ( }; /** - * Return the password JWT, if any, present in our local database for the given - * public collection (as identified by its {@link accessToken}). + * Return the access token JWT, if any, present in our local database for the + * given public collection (as identified by its {@link accessToken}). * - * Use {@link savedPublicCollectionPasswordJWT} to save the value, and - * {@link removePublicCollectionPasswordJWT} to remove it. + * Use {@link savePublicCollectionAccessTokenJWT} to save the value, and + * {@link removePublicCollectionAccessTokenJWT} to remove it. */ -export const savedPublicCollectionPasswordJWT = async (accessToken: string) => +export const savedPublicCollectionAccessTokenJWT = async ( + accessToken: string, +) => LocalString.parse( await localForage.getItem(`public-${accessToken}-passkey`), ); /** - * Update the password JWT in our local database for the given public + * Update the access token JWT in our local database for the given public * collection (as identified by its {@link accessToken}). * - * This is the setter corresponding to {@link savedPublicCollectionPasswordJWT}. + * This is the setter corresponding to + * {@link savedPublicCollectionAccessTokenJWT}. */ -export const savePublicCollectionPasswordJWT = async ( +export const savePublicCollectionAccessTokenJWT = async ( accessToken: string, passwordJWT: string, ) => { @@ -258,12 +261,10 @@ export const savePublicCollectionPasswordJWT = async ( }; /** - * Remove the password JWT in our local database for the given public + * Remove the access token JWT in our local database for the given public * collection (as identified by its {@link accessToken}). - * - * This is the setter corresponding to {@link savedPublicCollectionPasswordJWT}. */ -export const removePublicCollectionPasswordJWT = async ( +export const removePublicCollectionAccessTokenJWT = async ( accessToken: string, ) => { await localForage.removeItem(`public-${accessToken}-passkey`); From e47d6a8ece65611615d1c23a426c07b23ac09423 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 1 Jul 2025 18:23:34 +0530 Subject: [PATCH 8/8] Update --- .../Collections/CollectionShare.tsx | 108 ++++++++---------- 1 file changed, 46 insertions(+), 62 deletions(-) diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index d49f9e389e..0bd3871d59 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -68,7 +68,6 @@ import { } from "ente-new/photos/services/collection"; import type { CollectionSummary } from "ente-new/photos/services/collection-summary"; import { usePhotosAppContext } from "ente-new/photos/types/context"; -import { CustomError, parseSharingErrorCodes } from "ente-shared/error"; import { wait } from "ente-utils/promise"; import { useFormik } from "formik"; import { t } from "i18next"; @@ -300,25 +299,6 @@ const SharingDetails: React.FC = ({ ); }; -const handleSharingErrors = (error) => { - const parsedError = parseSharingErrorCodes(error); - let errorMessage = ""; - switch (parsedError.message) { - case CustomError.BAD_REQUEST: - errorMessage = t("sharing_album_not_allowed"); - break; - case CustomError.SUBSCRIPTION_NEEDED: - errorMessage = t("sharing_disabled_for_free_accounts"); - break; - case CustomError.NOT_FOUND: - errorMessage = t("sharing_user_does_not_exist"); - break; - default: - errorMessage = `${t("generic_error_retry")} ${parsedError.message}`; - } - return errorMessage; -}; - type EmailShareProps = { onRootClose: () => void; wrap: (f: () => Promise) => () => void; @@ -956,51 +936,55 @@ const ManageParticipant: React.FC = ({ onClose(); }; - const handleRoleChange = (role: string) => () => { - if (role !== selectedParticipant.role) { - changeRolePermission(selectedParticipant.email, role); - } - }; + const confirmChangeRolePermission = useCallback( + ( + selectedEmail: string, + newRole: CollectionNewParticipantRole, + action: () => Promise, + ) => { + let message: React.ReactNode; + let buttonText: string; - const updateCollectionRole = async (selectedEmail, newRole) => { - try { - await shareCollection(collection, selectedEmail, newRole); - selectedParticipant.role = newRole; - await onRemotePull({ silent: true }); - } catch (e) { - log.error(handleSharingErrors(e), e); - } - }; + if (newRole == "VIEWER") { + message = ( + + ); - const changeRolePermission = (selectedEmail, newRole) => { - let contentText; - let buttonText; + buttonText = t("confirm_convert_to_viewer"); + } else if (newRole == "COLLABORATOR") { + message = t("change_permission_to_collaborator", { + selectedEmail, + }); + buttonText = t("confirm_convert_to_collaborator"); + } - if (newRole == "VIEWER") { - contentText = ( - - ); - - buttonText = t("confirm_convert_to_viewer"); - } else if (newRole == "COLLABORATOR") { - contentText = t("change_permission_to_collaborator", { - selectedEmail, + showMiniDialog({ + title: t("change_permission_title"), + message: message, + continue: { text: buttonText, color: "critical", action }, }); - buttonText = t("confirm_convert_to_collaborator"); - } + }, + [showMiniDialog], + ); - showMiniDialog({ - title: t("change_permission_title"), - message: contentText, - continue: { - text: buttonText, - color: "critical", - action: () => updateCollectionRole(selectedEmail, newRole), - }, - }); + const updateCollectionRole = async ( + selectedEmail: string, + newRole: CollectionNewParticipantRole, + ) => { + await shareCollection(collection, selectedEmail, newRole); + selectedParticipant.role = newRole; + await onRemotePull({ silent: true }); + }; + + const createOnRoleChange = (role: CollectionNewParticipantRole) => () => { + if (role == selectedParticipant.role) return; + const { email } = selectedParticipant; + confirmChangeRolePermission(email, role, () => + updateCollectionRole(email, role), + ); }; const removeParticipant = () => { @@ -1044,7 +1028,7 @@ const ManageParticipant: React.FC = ({ } endIcon={ @@ -1057,7 +1041,7 @@ const ManageParticipant: React.FC = ({ } endIcon={