From f0b3c4a30a6dbc637911dddffbc3c95db7a15feb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 14:36:43 +0530 Subject: [PATCH 01/20] idb-keyval --- web/apps/photos/package.json | 1 - web/docs/dependencies.md | 4 +++- web/docs/storage.md | 9 +++++++++ web/packages/new/package.json | 2 ++ web/yarn.lock | 5 +++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/package.json b/web/apps/photos/package.json index 77a63ec007..2945bee60f 100644 --- a/web/apps/photos/package.json +++ b/web/apps/photos/package.json @@ -22,7 +22,6 @@ "fast-srp-hap": "^2.0.4", "ffmpeg-wasm": "file:./thirdparty/ffmpeg-wasm", "hdbscan": "0.0.1-alpha.5", - "idb": "^8", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.1", "localforage": "^1.9.0", diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index 56b6712e00..c4d4e9479e 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -172,7 +172,9 @@ For more details, see [translations.md](translations.md). layer on top of Web Workers to make them more easier to use. - [idb](https://github.com/jakearchibald/idb) provides a promise API over the - browser-native IndexedDB APIs. + browser-native IndexedDB APIs, and is used as our primary tabular database. + [idb-keyval](https://github.com/jakearchibald/idb-keyval) is its sibling + library that we use for ad-hoc key value storage. > For more details about IDB and its role, see [storage.md](storage.md). diff --git a/web/docs/storage.md b/web/docs/storage.md index 0247415d98..8f1ca8bba8 100644 --- a/web/docs/storage.md +++ b/web/docs/storage.md @@ -28,6 +28,8 @@ IndexedDB is a transactional NoSQL store provided by browsers. It has quite large storage limits, and data is stored per origin (and remains persistent across tab restarts). +Unlike local storage, IndexedDB is also accessible from Web Workers. + Older code used the LocalForage library for storing things in Indexed DB. This library falls back to localStorage in case Indexed DB storage is not available. @@ -39,6 +41,13 @@ For more details, see: - https://web.dev/articles/indexeddb - https://github.com/jakearchibald/idb +## IndexedDB KV + +We earlier used local storage for ad-hoc key value storage, but local storage is +not accessible from web workers which we use quite a bit. So now we use _idb_'s +sibling libary, idb-keyval, for storing key value pairs that need to be accessed +from both the main thread and web workers. + ## OPFS OPFS is used for caching entire files when we're running under Electron (the Web diff --git a/web/packages/new/package.json b/web/packages/new/package.json index 8ce5399055..e1107100ff 100644 --- a/web/packages/new/package.json +++ b/web/packages/new/package.json @@ -7,6 +7,8 @@ "@/utils": "*", "@ente/shared": "*", "formik": "^2.4", + "idb": "^8", + "idb-keyval": "^6", "zod": "^3" }, "devDependencies": {} diff --git a/web/yarn.lock b/web/yarn.lock index be1c37c63d..1a89c87063 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2922,6 +2922,11 @@ i18next@^23.10: dependencies: "@babel/runtime" "^7.23.2" +idb-keyval@^6: + version "6.2.1" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" + integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== + idb@^8: version "8.0.0" resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.0.tgz#33d7ed894ed36e23bcb542fb701ad579bfaad41f" From 1aef9cf1792f5ea14934c15a8b2e2a1fc05a43d3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 15:13:36 +0530 Subject: [PATCH 02/20] Update dev-settings page --- .../new/photos/components/DevSettings.tsx | 197 +++++++++++------- 1 file changed, 123 insertions(+), 74 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 1f0481c0f1..67e95a5d3e 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -14,7 +14,8 @@ import { } from "@mui/material"; import { useFormik } from "formik"; import { t } from "i18next"; -import React from "react"; +import { del, get, set } from "idb-keyval"; +import React, { useEffect, useState } from "react"; import { z } from "zod"; import { FocusVisibleButton } from "./FocusVisibleButton"; import { SlideTransition } from "./SlideTransition"; @@ -38,9 +39,61 @@ export const DevSettings: React.FC = ({ open, onClose }) => { if (reason != "backdropClick") onClose(); }; + return ( + + + + ); +}; + +type ContentsProps = Pick; + +const Contents: React.FC = (props) => { + // We need two nested components. + // + // - The initialAPIOrigin cannot be in our parent (the top level + // DevSettings) otherwise it gets preserved across dialog reopens + // instead of being read from storage on opening the dialog. + // + // - The initialAPIOrigin cannot be in our child (Form) because Formik + // doesn't have supported for async initial values. + const [initialAPIOrigin, setInitialAPIOrigin] = useState< + string | undefined + >(); + + useEffect( + () => + void get("apiOrigin").then((o) => + setInitialAPIOrigin( + // TODO: Migration of apiOrigin from local storage to indexed DB + // Remove me after a bit (27 June 2024). + o ?? localStorage.getItem("apiOrigin") ?? undefined, + ), + ), + [], + ); + + // Even though this is async, this should be instantanous, we're just + // reading the value from the local IndexedDB. + if (!initialAPIOrigin) return <>; + + return
; +}; + +type FormProps = ContentsProps & { + /** The initial value of API origin to prefill in the text input field. */ + initialAPIOrigin: string | undefined; +}; + +const Form: React.FC = ({ initialAPIOrigin, onClose }) => { const form = useFormik({ initialValues: { - apiOrigin: localStorage.getItem("apiOrigin") ?? "", + apiOrigin: initialAPIOrigin ?? "", }, validate: ({ apiOrigin }) => { try { @@ -77,79 +130,72 @@ export const DevSettings: React.FC = ({ open, onClose }) => { !!form.errors.apiOrigin; return ( - - - {t("developer_settings")} - + {t("developer_settings")} + + + + + + + + + ), }} + /> + + + - - - - - - - - ), - }} - /> - - - - {t("save")} - - - {t("CANCEL")} - - - - + {t("save")} + + + {t("CANCEL")} + + + ); }; @@ -167,6 +213,9 @@ export const DevSettings: React.FC = ({ open, onClose }) => { */ const updateAPIOrigin = async (origin: string) => { if (!origin) { + await del("apiOrigin"); + // TODO: Migration of apiOrigin from local storage to indexed DB + // Remove me after a bit (27 June 2024). localStorage.removeItem("apiOrigin"); return; } @@ -181,7 +230,7 @@ const updateAPIOrigin = async (origin: string) => { throw new Error("Invalid response"); } - localStorage.setItem("apiOrigin", origin); + await set("apiOrigin", origin); }; const PingResponse = z.object({ From 1b77c899dacd8cbb643530c6f3171861d9c35008 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 15:20:50 +0530 Subject: [PATCH 03/20] The rest --- web/apps/cast/src/services/render.ts | 3 +- .../photos/src/components/Sidebar/index.tsx | 6 ++-- web/apps/photos/src/pages/index.tsx | 19 ++++++------ .../src/services/download/clients/photos.ts | 9 ++++-- .../services/download/clients/publicAlbums.ts | 8 +++-- .../src/services/upload/uploadHttpClient.ts | 9 ++++-- web/packages/accounts/pages/login.tsx | 4 +-- web/packages/accounts/pages/signup.tsx | 4 +-- web/packages/next/origins.ts | 31 +++++++++++++------ .../shared/components/LoginComponents.tsx | 6 ++-- 10 files changed, 62 insertions(+), 37 deletions(-) diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 672f95f269..750b9bac85 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -317,8 +317,9 @@ const downloadFile = async ( if (!isImageOrLivePhoto(file)) throw new Error("Can only cast images and live photos"); + const customOrigin = await customAPIOrigin(); + const getFile = () => { - const customOrigin = customAPIOrigin(); if (customOrigin) { // See: [Note: Passing credentials for self-hosted file fetches] const params = new URLSearchParams({ castToken }); diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index 7ec6f70559..463405be75 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -679,15 +679,15 @@ const ExitSection: React.FC = () => { const DebugSection: React.FC = () => { const appContext = useContext(AppContext); const [appVersion, setAppVersion] = useState(); + const [host, setHost] = useState(); const electron = globalThis.electron; useEffect(() => { - electron?.appVersion().then((v) => setAppVersion(v)); + void electron?.appVersion().then(setAppVersion); + void customAPIHost().then(setHost); }); - const host = customAPIHost(); - const confirmLogDownload = () => appContext.setDialogMessage({ title: t("DOWNLOAD_LOGS"), diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index ec37ca3bf8..ec6a10b911 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -22,7 +22,7 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel"; import "pure-react-carousel/dist/react-carousel.es.css"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { Trans } from "react-i18next"; import { useAppContext } from "./_app"; @@ -31,14 +31,17 @@ export default function LandingPage() { const [loading, setLoading] = useState(true); const [showLogin, setShowLogin] = useState(true); - // This is kept as state because it can change as a result of user action - // while we're on this page (there currently isn't an event listener we can - // attach to for observing changes to local storage by the same window). - const [host, setHost] = useState(customAPIHost()); + const [host, setHost] = useState(); const router = useRouter(); + const refreshHost = useCallback( + () => void customAPIHost().then(setHost), + [], + ); + useEffect(() => { + refreshHost(); showNavBar(false); const currentURL = new URL(window.location.href); const albumsURL = new URL(albumsAppOrigin()); @@ -51,9 +54,7 @@ export default function LandingPage() { } else { handleNormalRedirect(); } - }, []); - - const handleMaybeChangeHost = () => setHost(customAPIHost()); + }, [refreshHost]); const handleAlbumsRedirect = async (currentURL: URL) => { const end = currentURL.hash.lastIndexOf("&"); @@ -117,7 +118,7 @@ export default function LandingPage() { const redirectToLoginPage = () => router.push(PAGES.LOGIN); return ( - + {loading ? ( ) : ( diff --git a/web/apps/photos/src/services/download/clients/photos.ts b/web/apps/photos/src/services/download/clients/photos.ts index 3740061805..110c51a093 100644 --- a/web/apps/photos/src/services/download/clients/photos.ts +++ b/web/apps/photos/src/services/download/clients/photos.ts @@ -19,10 +19,11 @@ export class PhotosDownloadClient implements DownloadClient { const token = this.token; if (!token) throw Error(CustomError.TOKEN_MISSING); + const customOrigin = await customAPIOrigin(); + // See: [Note: Passing credentials for self-hosted file fetches] const getThumbnail = () => { const opts = { responseType: "arraybuffer", timeout: this.timeout }; - const customOrigin = customAPIOrigin(); if (customOrigin) { const params = new URLSearchParams({ token }); return HTTPService.get( @@ -53,6 +54,8 @@ export class PhotosDownloadClient implements DownloadClient { const token = this.token; if (!token) throw Error(CustomError.TOKEN_MISSING); + const customOrigin = await customAPIOrigin(); + // See: [Note: Passing credentials for self-hosted file fetches] const getFile = () => { const opts = { @@ -61,7 +64,6 @@ export class PhotosDownloadClient implements DownloadClient { onDownloadProgress, }; - const customOrigin = customAPIOrigin(); if (customOrigin) { const params = new URLSearchParams({ token }); return HTTPService.get( @@ -89,6 +91,8 @@ export class PhotosDownloadClient implements DownloadClient { const token = this.token; if (!token) throw Error(CustomError.TOKEN_MISSING); + const customOrigin = await customAPIOrigin(); + // [Note: Passing credentials for self-hosted file fetches] // // Fetching files (or thumbnails) in the default self-hosted Ente @@ -126,7 +130,6 @@ export class PhotosDownloadClient implements DownloadClient { // signed URL and stream back the response. const getFile = () => { - const customOrigin = customAPIOrigin(); if (customOrigin) { const params = new URLSearchParams({ token }); return fetch( diff --git a/web/apps/photos/src/services/download/clients/publicAlbums.ts b/web/apps/photos/src/services/download/clients/publicAlbums.ts index 9875c8d0fc..d471591e63 100644 --- a/web/apps/photos/src/services/download/clients/publicAlbums.ts +++ b/web/apps/photos/src/services/download/clients/publicAlbums.ts @@ -20,6 +20,7 @@ export class PublicAlbumsDownloadClient implements DownloadClient { const accessToken = this.token; const accessTokenJWT = this.passwordToken; if (!accessToken) throw Error(CustomError.TOKEN_MISSING); + const customOrigin = await customAPIOrigin(); // See: [Note: Passing credentials for self-hosted file fetches] const getThumbnail = () => { @@ -27,7 +28,6 @@ export class PublicAlbumsDownloadClient implements DownloadClient { responseType: "arraybuffer", }; - const customOrigin = customAPIOrigin(); if (customOrigin) { const params = new URLSearchParams({ accessToken, @@ -67,6 +67,8 @@ export class PublicAlbumsDownloadClient implements DownloadClient { const accessTokenJWT = this.passwordToken; if (!accessToken) throw Error(CustomError.TOKEN_MISSING); + const customOrigin = await customAPIOrigin(); + // See: [Note: Passing credentials for self-hosted file fetches] const getFile = () => { const opts = { @@ -75,7 +77,6 @@ export class PublicAlbumsDownloadClient implements DownloadClient { onDownloadProgress, }; - const customOrigin = customAPIOrigin(); if (customOrigin) { const params = new URLSearchParams({ accessToken, @@ -112,9 +113,10 @@ export class PublicAlbumsDownloadClient implements DownloadClient { const accessTokenJWT = this.passwordToken; if (!accessToken) throw Error(CustomError.TOKEN_MISSING); + const customOrigin = await customAPIOrigin(); + // See: [Note: Passing credentials for self-hosted file fetches] const getFile = () => { - const customOrigin = customAPIOrigin(); if (customOrigin) { const params = new URLSearchParams({ accessToken, diff --git a/web/apps/photos/src/services/upload/uploadHttpClient.ts b/web/apps/photos/src/services/upload/uploadHttpClient.ts index 67e52c2143..badf03aa0d 100644 --- a/web/apps/photos/src/services/upload/uploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/uploadHttpClient.ts @@ -117,9 +117,10 @@ class UploadHttpClient { progressTracker, ): Promise { try { + const origin = await uploaderOrigin(); await retryHTTPCall(() => HTTPService.put( - `${uploaderOrigin()}/file-upload`, + `${origin}/file-upload`, file, null, { @@ -173,9 +174,10 @@ class UploadHttpClient { progressTracker, ) { try { + const origin = await uploaderOrigin(); const response = await retryHTTPCall(async () => { const resp = await HTTPService.put( - `${uploaderOrigin()}/multipart-upload`, + `${origin}/multipart-upload`, filePart, null, { @@ -214,9 +216,10 @@ class UploadHttpClient { async completeMultipartUploadV2(completeURL: string, reqBody: any) { try { + const origin = await uploaderOrigin(); await retryHTTPCall(() => HTTPService.post( - `${uploaderOrigin()}/multipart-complete`, + `${origin}/multipart-complete`, reqBody, null, { diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index b6ea5ed207..170a190c9b 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -13,12 +13,12 @@ const Page: React.FC = ({ appContext }) => { const { appName, showNavBar } = appContext; const [loading, setLoading] = useState(true); + const [host, setHost] = useState(); const router = useRouter(); - const host = customAPIHost(); - useEffect(() => { + void customAPIHost().then(setHost); const user = getData(LS_KEYS.USER); if (user?.email) { router.push(PAGES.VERIFY); diff --git a/web/packages/accounts/pages/signup.tsx b/web/packages/accounts/pages/signup.tsx index c55a2a13e0..72fde8ba74 100644 --- a/web/packages/accounts/pages/signup.tsx +++ b/web/packages/accounts/pages/signup.tsx @@ -13,12 +13,12 @@ const Page: React.FC = ({ appContext }) => { const { appName } = appContext; const [loading, setLoading] = useState(true); + const [host, setHost] = useState(); const router = useRouter(); - const host = customAPIHost(); - useEffect(() => { + void customAPIHost().then(setHost); const user = getData(LS_KEYS.USER); if (user?.email) { router.push(PAGES.VERIFY); diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts index 66e6a9aefa..b21dbd59a6 100644 --- a/web/packages/next/origins.ts +++ b/web/packages/next/origins.ts @@ -1,4 +1,5 @@ import { nullToUndefined } from "@/utils/transform"; +import { get, set } from "idb-keyval"; /** * Return the origin (scheme, host, port triple) that should be used for making @@ -7,7 +8,8 @@ import { nullToUndefined } from "@/utils/transform"; * This defaults "https://api.ente.io", Ente's production API servers. but can * be overridden when self hosting or developing (see {@link customAPIOrigin}). */ -export const apiOrigin = () => customAPIOrigin() ?? "https://api.ente.io"; +export const apiOrigin = async () => + (await customAPIOrigin()) ?? "https://api.ente.io"; /** * Return the overridden API origin, if one is defined by either (in priority @@ -20,10 +22,21 @@ export const apiOrigin = () => customAPIOrigin() ?? "https://api.ente.io"; * * Otherwise return undefined. */ -export const customAPIOrigin = () => - nullToUndefined(localStorage.getItem("apiOrigin")) ?? - process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? - undefined; +export const customAPIOrigin = async () => { + let origin = await get("apiOrigin"); + if (!origin) { + // TODO: Migration of apiOrigin from local storage to indexed DB + // Remove me after a bit (27 June 2024). + const legacyOrigin = localStorage.getItem("apiOrigin"); + if (legacyOrigin !== null) { + origin = nullToUndefined(legacyOrigin); + if (origin) await set("apiOrigin", origin); + localStorage.removeItem("apiOrigin"); + } + } + + return origin ?? process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? undefined; +}; /** * A convenience wrapper over {@link customAPIOrigin} that returns the only the @@ -31,8 +44,8 @@ export const customAPIOrigin = () => * * This is useful in places where we indicate the custom origin in the UI. */ -export const customAPIHost = () => { - const origin = customAPIOrigin(); +export const customAPIHost = async () => { + const origin = await customAPIOrigin(); return origin ? new URL(origin).host : undefined; }; @@ -44,8 +57,8 @@ export const customAPIHost = () => { * this value is set to the {@link customAPIOrigin} itself, effectively * bypassing the Cloudflare worker for non-Ente deployments. */ -export const uploaderOrigin = () => - customAPIOrigin() ?? "https://uploader.ente.io"; +export const uploaderOrigin = async () => + (await customAPIOrigin()) ?? "https://uploader.ente.io"; /** * Return the origin that serves the accounts app. diff --git a/web/packages/shared/components/LoginComponents.tsx b/web/packages/shared/components/LoginComponents.tsx index 8201ccacc9..46cdc102da 100644 --- a/web/packages/shared/components/LoginComponents.tsx +++ b/web/packages/shared/components/LoginComponents.tsx @@ -10,7 +10,7 @@ import EnteButton from "@ente/shared/components/EnteButton"; import { CircularProgress, Stack, Typography, styled } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { VerticallyCentered } from "./Container"; import type { DialogBoxAttributesV2 } from "./DialogBoxV2/types"; import { genericErrorAttributes } from "./ErrorComponents"; @@ -48,7 +48,9 @@ const Header_ = styled("div")` export const LoginFlowFormFooter: React.FC = ({ children, }) => { - const host = customAPIHost(); + const [host, setHost] = useState(); + + useEffect(() => void customAPIHost().then(setHost), []); return ( From f543b402f8978ac5a1bf499d36ff19b9ce914e63 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 15:40:35 +0530 Subject: [PATCH 04/20] New abstraction --- web/apps/accounts/src/services/passkey.ts | 4 ++-- web/packages/next/origins.ts | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index b30155e483..859dd9abf7 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -1,5 +1,5 @@ import { isDevBuild } from "@/next/env"; -import { apiOrigin } from "@/next/origins"; +import { apiOrigin, apiURL } from "@/next/origins"; import { clientPackageName } from "@/next/types/app"; import { TwoFactorAuthorizationResponse } from "@/next/types/credentials"; import { ensure } from "@/utils/ensure"; @@ -58,7 +58,7 @@ const GetPasskeysResponse = z.object({ * has no passkeys. */ export const getPasskeys = async (token: string) => { - const url = `${apiOrigin()}/passkeys`; + const url = await apiURL("passkeys"); const res = await fetch(url, { headers: accountsAuthenticatedRequestHeaders(token), }); diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts index b21dbd59a6..45b8149890 100644 --- a/web/packages/next/origins.ts +++ b/web/packages/next/origins.ts @@ -11,6 +11,19 @@ import { get, set } from "idb-keyval"; export const apiOrigin = async () => (await customAPIOrigin()) ?? "https://api.ente.io"; +/** + * A convenience function to construct an endpoint in a one-liner. + * + * This avoids us having to create a temporary variable or otherwise complicate + * the call sites since async functions cannot be used inside template literals. + * + * @returns The equivalent of `${await apiOrigin()}/pathEtc` + */ +export const apiURL = async (pathEtc: string) => { + const origin = await apiOrigin(); + return `${origin}/${pathEtc}`; +}; + /** * Return the overridden API origin, if one is defined by either (in priority * order): From 94f4dcb9beebdb03c12d49958db3ff47323c2449 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 15:42:07 +0530 Subject: [PATCH 05/20] more --- web/apps/accounts/src/services/passkey.ts | 14 +++++------ web/packages/accounts/api/srp.ts | 14 +++++------ web/packages/accounts/api/user.ts | 23 ++++++++++++------- web/packages/accounts/services/passkey.ts | 10 ++++---- web/packages/accounts/services/session.ts | 4 ++-- web/packages/new/photos/services/embedding.ts | 4 ++-- .../new/photos/services/feature-flags.ts | 4 ++-- web/packages/shared/network/cast.ts | 19 +++++++-------- 8 files changed, 48 insertions(+), 44 deletions(-) diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 859dd9abf7..12aeb485df 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -1,5 +1,5 @@ import { isDevBuild } from "@/next/env"; -import { apiOrigin, apiURL } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import { clientPackageName } from "@/next/types/app"; import { TwoFactorAuthorizationResponse } from "@/next/types/credentials"; import { ensure } from "@/utils/ensure"; @@ -82,7 +82,7 @@ export const renamePasskey = async ( name: string, ) => { const params = new URLSearchParams({ friendlyName: name }); - const url = `${apiOrigin()}/passkeys/${id}`; + const url = await apiURL(`passkeys/${id}`); const res = await fetch(`${url}?${params.toString()}`, { method: "PATCH", headers: accountsAuthenticatedRequestHeaders(token), @@ -98,7 +98,7 @@ export const renamePasskey = async ( * @param id The `id` of the existing passkey to delete. */ export const deletePasskey = async (token: string, id: string) => { - const url = `${apiOrigin()}/passkeys/${id}`; + const url = await apiURL(`passkeys/${id}`); const res = await fetch(url, { method: "DELETE", headers: accountsAuthenticatedRequestHeaders(token), @@ -149,7 +149,7 @@ interface BeginPasskeyRegistrationResponse { } const beginPasskeyRegistration = async (token: string) => { - const url = `${apiOrigin()}/passkeys/registration/begin`; + const url = await apiURL("passkeys/registration/begin"); const res = await fetch(url, { method: "POST", headers: accountsAuthenticatedRequestHeaders(token), @@ -293,7 +293,7 @@ const finishPasskeyRegistration = async ({ const transports = attestationResponse.getTransports(); const params = new URLSearchParams({ friendlyName, sessionID }); - const url = `${apiOrigin()}/passkeys/registration/finish`; + const url = await apiURL("passkeys/registration/finish"); const res = await fetch(`${url}?${params.toString()}`, { method: "POST", headers: accountsAuthenticatedRequestHeaders(token), @@ -414,7 +414,7 @@ export const passkeySessionAlreadyClaimedErrorMessage = export const beginPasskeyAuthentication = async ( passkeySessionID: string, ): Promise => { - const url = `${apiOrigin()}/users/two-factor/passkeys/begin`; + const url = await apiURL("users/two-factor/passkeys/begin"); const res = await fetch(url, { method: "POST", body: JSON.stringify({ sessionID: passkeySessionID }), @@ -504,7 +504,7 @@ export const finishPasskeyAuthentication = async ({ ceremonySessionID, clientPackage, }); - const url = `${apiOrigin()}/users/two-factor/passkeys/finish`; + const url = await apiURL("users/two-factor/passkeys/finish"); const res = await fetch(`${url}?${params.toString()}`, { method: "POST", headers: { diff --git a/web/packages/accounts/api/srp.ts b/web/packages/accounts/api/srp.ts index d221309f05..068f1732fd 100644 --- a/web/packages/accounts/api/srp.ts +++ b/web/packages/accounts/api/srp.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import type { CompleteSRPSetupRequest, CompleteSRPSetupResponse, @@ -21,7 +21,7 @@ export const getSRPAttributes = async ( ): Promise => { try { const resp = await HTTPService.get( - `${apiOrigin()}/users/srp/attributes`, + await apiURL("users/srp/attributes"), { email, }, @@ -39,7 +39,7 @@ export const startSRPSetup = async ( ): Promise => { try { const resp = await HTTPService.post( - `${apiOrigin()}/users/srp/setup`, + await apiURL("users/srp/setup"), setupSRPRequest, undefined, { @@ -60,7 +60,7 @@ export const completeSRPSetup = async ( ) => { try { const resp = await HTTPService.post( - `${apiOrigin()}/users/srp/complete`, + await apiURL("users/srp/complete"), completeSRPSetupRequest, undefined, { @@ -77,7 +77,7 @@ export const completeSRPSetup = async ( export const createSRPSession = async (srpUserID: string, srpA: string) => { try { const resp = await HTTPService.post( - `${apiOrigin()}/users/srp/create-session`, + await apiURL("users/srp/create-session"), { srpUserID, srpA, @@ -97,7 +97,7 @@ export const verifySRPSession = async ( ) => { try { const resp = await HTTPService.post( - `${apiOrigin()}/users/srp/verify-session`, + await apiURL("users/srp/verify-session"), { sessionID, srpUserID, @@ -125,7 +125,7 @@ export const updateSRPAndKeys = async ( ): Promise => { try { const resp = await HTTPService.post( - `${apiOrigin()}/users/srp/update`, + await apiURL("users/srp/update"), updateSRPAndKeyRequest, undefined, { diff --git a/web/packages/accounts/api/user.ts b/web/packages/accounts/api/user.ts index 3303778730..ec04dd28d3 100644 --- a/web/packages/accounts/api/user.ts +++ b/web/packages/accounts/api/user.ts @@ -1,4 +1,4 @@ -import { apiOrigin } from "@/next/origins"; +import { apiOrigin, apiURL } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import type { RecoveryKey, @@ -14,25 +14,32 @@ import { getToken } from "@ente/shared/storage/localStorage/helpers"; import type { KeyAttributes } from "@ente/shared/user/types"; import { HttpStatusCode } from "axios"; -export const sendOtt = (appName: AppName, email: string) => { - return HTTPService.post(`${apiOrigin()}/users/ott`, { +export const sendOtt = async (appName: AppName, email: string) => { + return HTTPService.post(await apiURL("users/ott"), { email, client: appName == "auth" ? "totp" : "web", }); }; -export const verifyOtt = (email: string, ott: string, referral: string) => { +export const verifyOtt = async ( + email: string, + ott: string, + referral: string, +) => { const cleanedReferral = `web:${referral?.trim() || ""}`; - return HTTPService.post(`${apiOrigin()}/users/verify-email`, { + return HTTPService.post(await apiURL("users/verify-email"), { email, ott, source: cleanedReferral, }); }; -export const putAttributes = (token: string, keyAttributes: KeyAttributes) => +export const putAttributes = async ( + token: string, + keyAttributes: KeyAttributes, +) => HTTPService.put( - `${apiOrigin()}/users/attributes`, + await apiURL("users/attributes"), { keyAttributes }, undefined, { @@ -43,7 +50,7 @@ export const putAttributes = (token: string, keyAttributes: KeyAttributes) => export const logout = async () => { try { const token = getToken(); - await HTTPService.post(`${apiOrigin()}/users/logout`, null, undefined, { + await HTTPService.post(await apiURL("users/logout"), null, undefined, { "X-Auth-Token": token, }); } catch (e) { diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 56b0e78ba4..84453b6b47 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,6 +1,6 @@ import { clientPackageHeaderIfPresent } from "@/next/http"; import log from "@/next/log"; -import { accountsAppOrigin, apiOrigin } from "@/next/origins"; +import { accountsAppOrigin, apiURL } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import { clientPackageName } from "@/next/types/app"; import { TwoFactorAuthorizationResponse } from "@/next/types/credentials"; @@ -139,7 +139,7 @@ export const isPasskeyRecoveryEnabled = async () => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/two-factor/recovery-status`, + await apiURL("users/two-factor/recovery-status"), {}, { "X-Auth-Token": token, @@ -166,7 +166,7 @@ const configurePasskeyRecovery = async ( const token = getToken(); const resp = await HTTPService.post( - `${apiOrigin()}/users/two-factor/passkeys/configure-recovery`, + await apiURL("users/two-factor/passkeys/configure-recovery"), { secret, userSecretCipher, @@ -196,7 +196,7 @@ const getAccountsToken = async () => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/accounts-token`, + await apiURL("users/accounts-token"), undefined, { "X-Auth-Token": token, @@ -234,7 +234,7 @@ export const passkeySessionExpiredErrorMessage = "Passkey session has expired"; export const checkPasskeyVerificationStatus = async ( sessionID: string, ): Promise => { - const url = `${apiOrigin()}/users/two-factor/passkeys/get-token`; + const url = await apiURL("users/two-factor/passkeys/get-token"); const params = new URLSearchParams({ sessionID }); const res = await fetch(`${url}?${params.toString()}`, { headers: clientPackageHeaderIfPresent(), diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index 3780f8b1e0..65493cdb26 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -1,6 +1,6 @@ import { authenticatedRequestHeaders } from "@/next/http"; import { ensureLocalUser } from "@/next/local-user"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import { ensure } from "@/utils/ensure"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import type { KeyAttributes } from "@ente/shared/user/types"; @@ -62,7 +62,7 @@ type SessionValidity = * subsequently. */ export const checkSessionValidity = async (): Promise => { - const url = `${apiOrigin()}/users/session-validity/v2`; + const url = await apiURL("users/session-validity/v2"); const res = await fetch(url, { headers: authenticatedRequestHeaders(), }); diff --git a/web/packages/new/photos/services/embedding.ts b/web/packages/new/photos/services/embedding.ts index e20515e0a9..64f21268c0 100644 --- a/web/packages/new/photos/services/embedding.ts +++ b/web/packages/new/photos/services/embedding.ts @@ -1,5 +1,5 @@ import { authenticatedRequestHeaders } from "@/next/http"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import { nullToUndefined } from "@/utils/transform"; // import ComlinkCryptoWorker from "@ente/shared/crypto"; import { z } from "zod"; @@ -166,7 +166,7 @@ const getEmbeddingsDiff = async ( sinceTime: `${sinceTime}`, limit: `${diffLimit}`, }); - const url = `${apiOrigin()}/embeddings/diff`; + const url = await apiURL("embeddings/diff"); const res = await fetch(`${url}?${params.toString()}`, { headers: authenticatedRequestHeaders(), }); diff --git a/web/packages/new/photos/services/feature-flags.ts b/web/packages/new/photos/services/feature-flags.ts index 8bef610f88..a29dc87506 100644 --- a/web/packages/new/photos/services/feature-flags.ts +++ b/web/packages/new/photos/services/feature-flags.ts @@ -2,7 +2,7 @@ import { isDevBuild } from "@/next/env"; import { authenticatedRequestHeaders } from "@/next/http"; import { localUser } from "@/next/local-user"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import { nullToUndefined } from "@/utils/transform"; import { z } from "zod"; @@ -65,7 +65,7 @@ const fetchAndSaveFeatureFlags = () => .then(saveFlagJSONString); const fetchFeatureFlags = async () => { - const url = `${apiOrigin()}/remote-store/feature-flags`; + const url = await apiURL("remote-store/feature-flags"); const res = await fetch(url, { headers: authenticatedRequestHeaders(), }); diff --git a/web/packages/shared/network/cast.ts b/web/packages/shared/network/cast.ts index 7b2af75907..551d2ad3e3 100644 --- a/web/packages/shared/network/cast.ts +++ b/web/packages/shared/network/cast.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import { ApiError } from "../error"; import { getToken } from "../storage/localStorage/helpers"; import HTTPService from "./HTTPService"; @@ -11,7 +11,7 @@ class CastGateway { let resp; try { resp = await HTTPService.get( - `${apiOrigin()}/cast/cast-data/${code}`, + await apiURL(`cast/cast-data/${code}`), ); } catch (e) { log.error("failed to getCastData", e); @@ -24,7 +24,7 @@ class CastGateway { try { const token = getToken(); await HTTPService.delete( - apiOrigin() + "/cast/revoke-all-tokens/", + await apiURL("cast/revoke-all-tokens"), undefined, undefined, { @@ -42,7 +42,7 @@ class CastGateway { try { const token = getToken(); resp = await HTTPService.get( - `${apiOrigin()}/cast/device-info/${code}`, + await apiURL(`cast/device-info/${code}`), undefined, { "X-Auth-Token": token, @@ -59,12 +59,9 @@ class CastGateway { } public async registerDevice(publicKey: string): Promise { - const resp = await HTTPService.post( - apiOrigin() + "/cast/device-info/", - { - publicKey: publicKey, - }, - ); + const resp = await HTTPService.post(await apiURL("cast/device-info"), { + publicKey: publicKey, + }); return resp.data.deviceCode; } @@ -76,7 +73,7 @@ class CastGateway { ) { const token = getToken(); await HTTPService.post( - apiOrigin() + "/cast/cast-data/", + await apiURL("cast/cast-data"), { deviceCode: `${code}`, encPayload: castPayload, From 9241c2e59558025c5867b5e6f2a62a851c70f02c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:03:19 +0530 Subject: [PATCH 06/20] Retain leading slash Looks pathy-ier --- web/apps/accounts/src/services/passkey.ts | 14 +++++++------- web/packages/accounts/api/srp.ts | 12 ++++++------ web/packages/accounts/api/user.ts | 8 ++++---- web/packages/accounts/services/passkey.ts | 8 ++++---- web/packages/accounts/services/session.ts | 2 +- web/packages/new/photos/services/embedding.ts | 2 +- web/packages/new/photos/services/feature-flags.ts | 2 +- web/packages/next/origins.ts | 10 +++++----- web/packages/shared/network/cast.ts | 10 +++++----- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 12aeb485df..b8c1ec3e56 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -58,7 +58,7 @@ const GetPasskeysResponse = z.object({ * has no passkeys. */ export const getPasskeys = async (token: string) => { - const url = await apiURL("passkeys"); + const url = await apiURL("/passkeys"); const res = await fetch(url, { headers: accountsAuthenticatedRequestHeaders(token), }); @@ -82,7 +82,7 @@ export const renamePasskey = async ( name: string, ) => { const params = new URLSearchParams({ friendlyName: name }); - const url = await apiURL(`passkeys/${id}`); + const url = await apiURL(`/passkeys/${id}`); const res = await fetch(`${url}?${params.toString()}`, { method: "PATCH", headers: accountsAuthenticatedRequestHeaders(token), @@ -98,7 +98,7 @@ export const renamePasskey = async ( * @param id The `id` of the existing passkey to delete. */ export const deletePasskey = async (token: string, id: string) => { - const url = await apiURL(`passkeys/${id}`); + const url = await apiURL(`/passkeys/${id}`); const res = await fetch(url, { method: "DELETE", headers: accountsAuthenticatedRequestHeaders(token), @@ -149,7 +149,7 @@ interface BeginPasskeyRegistrationResponse { } const beginPasskeyRegistration = async (token: string) => { - const url = await apiURL("passkeys/registration/begin"); + const url = await apiURL("/passkeys/registration/begin"); const res = await fetch(url, { method: "POST", headers: accountsAuthenticatedRequestHeaders(token), @@ -293,7 +293,7 @@ const finishPasskeyRegistration = async ({ const transports = attestationResponse.getTransports(); const params = new URLSearchParams({ friendlyName, sessionID }); - const url = await apiURL("passkeys/registration/finish"); + const url = await apiURL("/passkeys/registration/finish"); const res = await fetch(`${url}?${params.toString()}`, { method: "POST", headers: accountsAuthenticatedRequestHeaders(token), @@ -414,7 +414,7 @@ export const passkeySessionAlreadyClaimedErrorMessage = export const beginPasskeyAuthentication = async ( passkeySessionID: string, ): Promise => { - const url = await apiURL("users/two-factor/passkeys/begin"); + const url = await apiURL("/users/two-factor/passkeys/begin"); const res = await fetch(url, { method: "POST", body: JSON.stringify({ sessionID: passkeySessionID }), @@ -504,7 +504,7 @@ export const finishPasskeyAuthentication = async ({ ceremonySessionID, clientPackage, }); - const url = await apiURL("users/two-factor/passkeys/finish"); + const url = await apiURL("/users/two-factor/passkeys/finish"); const res = await fetch(`${url}?${params.toString()}`, { method: "POST", headers: { diff --git a/web/packages/accounts/api/srp.ts b/web/packages/accounts/api/srp.ts index 068f1732fd..f6c773d9cc 100644 --- a/web/packages/accounts/api/srp.ts +++ b/web/packages/accounts/api/srp.ts @@ -21,7 +21,7 @@ export const getSRPAttributes = async ( ): Promise => { try { const resp = await HTTPService.get( - await apiURL("users/srp/attributes"), + await apiURL("/users/srp/attributes"), { email, }, @@ -39,7 +39,7 @@ export const startSRPSetup = async ( ): Promise => { try { const resp = await HTTPService.post( - await apiURL("users/srp/setup"), + await apiURL("/users/srp/setup"), setupSRPRequest, undefined, { @@ -60,7 +60,7 @@ export const completeSRPSetup = async ( ) => { try { const resp = await HTTPService.post( - await apiURL("users/srp/complete"), + await apiURL("/users/srp/complete"), completeSRPSetupRequest, undefined, { @@ -77,7 +77,7 @@ export const completeSRPSetup = async ( export const createSRPSession = async (srpUserID: string, srpA: string) => { try { const resp = await HTTPService.post( - await apiURL("users/srp/create-session"), + await apiURL("/users/srp/create-session"), { srpUserID, srpA, @@ -97,7 +97,7 @@ export const verifySRPSession = async ( ) => { try { const resp = await HTTPService.post( - await apiURL("users/srp/verify-session"), + await apiURL("/users/srp/verify-session"), { sessionID, srpUserID, @@ -125,7 +125,7 @@ export const updateSRPAndKeys = async ( ): Promise => { try { const resp = await HTTPService.post( - await apiURL("users/srp/update"), + await apiURL("/users/srp/update"), updateSRPAndKeyRequest, undefined, { diff --git a/web/packages/accounts/api/user.ts b/web/packages/accounts/api/user.ts index ec04dd28d3..30106ad873 100644 --- a/web/packages/accounts/api/user.ts +++ b/web/packages/accounts/api/user.ts @@ -15,7 +15,7 @@ import type { KeyAttributes } from "@ente/shared/user/types"; import { HttpStatusCode } from "axios"; export const sendOtt = async (appName: AppName, email: string) => { - return HTTPService.post(await apiURL("users/ott"), { + return HTTPService.post(await apiURL("/users/ott"), { email, client: appName == "auth" ? "totp" : "web", }); @@ -27,7 +27,7 @@ export const verifyOtt = async ( referral: string, ) => { const cleanedReferral = `web:${referral?.trim() || ""}`; - return HTTPService.post(await apiURL("users/verify-email"), { + return HTTPService.post(await apiURL("/users/verify-email"), { email, ott, source: cleanedReferral, @@ -39,7 +39,7 @@ export const putAttributes = async ( keyAttributes: KeyAttributes, ) => HTTPService.put( - await apiURL("users/attributes"), + await apiURL("/users/attributes"), { keyAttributes }, undefined, { @@ -50,7 +50,7 @@ export const putAttributes = async ( export const logout = async () => { try { const token = getToken(); - await HTTPService.post(await apiURL("users/logout"), null, undefined, { + await HTTPService.post(await apiURL("/users/logout"), null, undefined, { "X-Auth-Token": token, }); } catch (e) { diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 84453b6b47..772b463772 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -139,7 +139,7 @@ export const isPasskeyRecoveryEnabled = async () => { const token = getToken(); const resp = await HTTPService.get( - await apiURL("users/two-factor/recovery-status"), + await apiURL("/users/two-factor/recovery-status"), {}, { "X-Auth-Token": token, @@ -166,7 +166,7 @@ const configurePasskeyRecovery = async ( const token = getToken(); const resp = await HTTPService.post( - await apiURL("users/two-factor/passkeys/configure-recovery"), + await apiURL("/users/two-factor/passkeys/configure-recovery"), { secret, userSecretCipher, @@ -196,7 +196,7 @@ const getAccountsToken = async () => { const token = getToken(); const resp = await HTTPService.get( - await apiURL("users/accounts-token"), + await apiURL("/users/accounts-token"), undefined, { "X-Auth-Token": token, @@ -234,7 +234,7 @@ export const passkeySessionExpiredErrorMessage = "Passkey session has expired"; export const checkPasskeyVerificationStatus = async ( sessionID: string, ): Promise => { - const url = await apiURL("users/two-factor/passkeys/get-token"); + const url = await apiURL("/users/two-factor/passkeys/get-token"); const params = new URLSearchParams({ sessionID }); const res = await fetch(`${url}?${params.toString()}`, { headers: clientPackageHeaderIfPresent(), diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index 65493cdb26..d82f12ebd9 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -62,7 +62,7 @@ type SessionValidity = * subsequently. */ export const checkSessionValidity = async (): Promise => { - const url = await apiURL("users/session-validity/v2"); + const url = await apiURL("/users/session-validity/v2"); const res = await fetch(url, { headers: authenticatedRequestHeaders(), }); diff --git a/web/packages/new/photos/services/embedding.ts b/web/packages/new/photos/services/embedding.ts index 64f21268c0..9492a47e11 100644 --- a/web/packages/new/photos/services/embedding.ts +++ b/web/packages/new/photos/services/embedding.ts @@ -166,7 +166,7 @@ const getEmbeddingsDiff = async ( sinceTime: `${sinceTime}`, limit: `${diffLimit}`, }); - const url = await apiURL("embeddings/diff"); + const url = await apiURL("/embeddings/diff"); const res = await fetch(`${url}?${params.toString()}`, { headers: authenticatedRequestHeaders(), }); diff --git a/web/packages/new/photos/services/feature-flags.ts b/web/packages/new/photos/services/feature-flags.ts index a29dc87506..97969d1a41 100644 --- a/web/packages/new/photos/services/feature-flags.ts +++ b/web/packages/new/photos/services/feature-flags.ts @@ -65,7 +65,7 @@ const fetchAndSaveFeatureFlags = () => .then(saveFlagJSONString); const fetchFeatureFlags = async () => { - const url = await apiURL("remote-store/feature-flags"); + const url = await apiURL("/remote-store/feature-flags"); const res = await fetch(url, { headers: authenticatedRequestHeaders(), }); diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts index 45b8149890..c4b860c641 100644 --- a/web/packages/next/origins.ts +++ b/web/packages/next/origins.ts @@ -17,12 +17,12 @@ export const apiOrigin = async () => * This avoids us having to create a temporary variable or otherwise complicate * the call sites since async functions cannot be used inside template literals. * - * @returns The equivalent of `${await apiOrigin()}/pathEtc` + * @param path The URL path usually, but can be anything that needs to be + * suffixed to the origin. It must begin with a "/". + * + * @returns path prefixed by {@link apiOrigin}. */ -export const apiURL = async (pathEtc: string) => { - const origin = await apiOrigin(); - return `${origin}/${pathEtc}`; -}; +export const apiURL = async (path: string) => (await apiOrigin()) + path; /** * Return the overridden API origin, if one is defined by either (in priority diff --git a/web/packages/shared/network/cast.ts b/web/packages/shared/network/cast.ts index 551d2ad3e3..c5d0ddef90 100644 --- a/web/packages/shared/network/cast.ts +++ b/web/packages/shared/network/cast.ts @@ -11,7 +11,7 @@ class CastGateway { let resp; try { resp = await HTTPService.get( - await apiURL(`cast/cast-data/${code}`), + await apiURL(`/cast/cast-data/${code}`), ); } catch (e) { log.error("failed to getCastData", e); @@ -24,7 +24,7 @@ class CastGateway { try { const token = getToken(); await HTTPService.delete( - await apiURL("cast/revoke-all-tokens"), + await apiURL("/cast/revoke-all-tokens"), undefined, undefined, { @@ -42,7 +42,7 @@ class CastGateway { try { const token = getToken(); resp = await HTTPService.get( - await apiURL(`cast/device-info/${code}`), + await apiURL(`/cast/device-info/${code}`), undefined, { "X-Auth-Token": token, @@ -59,7 +59,7 @@ class CastGateway { } public async registerDevice(publicKey: string): Promise { - const resp = await HTTPService.post(await apiURL("cast/device-info"), { + const resp = await HTTPService.post(await apiURL("/cast/device-info"), { publicKey: publicKey, }); return resp.data.deviceCode; @@ -73,7 +73,7 @@ class CastGateway { ) { const token = getToken(); await HTTPService.post( - await apiURL("cast/cast-data"), + await apiURL("/cast/cast-data"), { deviceCode: `${code}`, encPayload: castPayload, From 3f96209dbb8969a492867cc9949ab022a3ff1449 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:05:35 +0530 Subject: [PATCH 07/20] Continue --- web/apps/auth/src/services/remote.ts | 6 ++-- web/apps/cast/src/services/render.ts | 4 +-- .../photos/src/services/billingService.ts | 18 +++++------ .../src/services/deduplicationService.ts | 4 +-- .../photos/src/services/embeddingService.ts | 6 ++-- .../src/services/publicCollectionService.ts | 8 ++--- web/apps/photos/src/services/trashService.ts | 6 ++-- .../services/upload/publicUploadHttpClient.ts | 24 +++++++-------- web/apps/photos/src/services/userService.ts | 30 +++++++++---------- web/packages/accounts/api/user.ts | 22 +++++++------- 10 files changed, 62 insertions(+), 66 deletions(-) diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index 0202d997e3..93429be259 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { ApiError, CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -81,7 +81,7 @@ interface AuthKey { export const getAuthKey = async (): Promise => { try { const resp = await HTTPService.get( - `${apiOrigin()}/authenticator/key`, + await apiURL("/authenticator/key"), {}, { "X-Auth-Token": getToken(), @@ -108,7 +108,7 @@ export const getDiff = async ( ): Promise => { try { const resp = await HTTPService.get( - `${apiOrigin()}/authenticator/entity/diff`, + await apiURL("/authenticator/entity/diff"), { sinceTime, limit, diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 750b9bac85..5e86b3b8bc 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -17,7 +17,7 @@ import type { } from "@/new/photos/types/file"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; -import { apiOrigin, customAPIOrigin } from "@/next/origins"; +import { apiURL, customAPIOrigin } from "@/next/origins"; import { shuffled } from "@/utils/array"; import { ensure } from "@/utils/ensure"; import { wait } from "@/utils/promise"; @@ -164,7 +164,7 @@ const getEncryptedCollectionFiles = async ( let resp: AxiosResponse; do { resp = await HTTPService.get( - `${apiOrigin()}/cast/diff`, + await apiURL("/cast/diff"), { sinceTime }, { "Cache-Control": "no-cache", diff --git a/web/apps/photos/src/services/billingService.ts b/web/apps/photos/src/services/billingService.ts index 67004874a0..b25a10c872 100644 --- a/web/apps/photos/src/services/billingService.ts +++ b/web/apps/photos/src/services/billingService.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { apiOrigin, paymentsAppOrigin } from "@/next/origins"; +import { apiURL, paymentsAppOrigin } from "@/next/origins"; import HTTPService from "@ente/shared/network/HTTPService"; import { LS_KEYS, @@ -33,11 +33,11 @@ class billingService { let response; if (!token) { response = await HTTPService.get( - `${apiOrigin()}/billing/plans/v2`, + await apiURL("/billing/plans/v2"), ); } else { response = await HTTPService.get( - `${apiOrigin()}/billing/user-plans`, + await apiURL("/billing/user-plans"), null, { "X-Auth-Token": getToken(), @@ -53,7 +53,7 @@ class billingService { public async syncSubscription() { try { const response = await HTTPService.get( - `${apiOrigin()}/billing/subscription`, + await apiURL("/billing/subscription"), null, { "X-Auth-Token": getToken(), @@ -97,7 +97,7 @@ class billingService { public async cancelSubscription() { try { const response = await HTTPService.post( - `${apiOrigin()}/billing/stripe/cancel-subscription`, + await apiURL("/billing/stripe/cancel-subscription"), null, null, { @@ -115,7 +115,7 @@ class billingService { public async activateSubscription() { try { const response = await HTTPService.post( - `${apiOrigin()}/billing/stripe/activate-subscription`, + await apiURL("/billing/stripe/activate-subscription"), null, null, { @@ -139,7 +139,7 @@ class billingService { return; } const response = await HTTPService.post( - `${apiOrigin()}/billing/verify-subscription`, + await apiURL("/billing/verify-subscription"), { paymentProvider: "stripe", productID: null, @@ -165,7 +165,7 @@ class billingService { } try { await HTTPService.delete( - `${apiOrigin()}/family/leave`, + await apiURL("/family/leave"), null, null, { @@ -197,7 +197,7 @@ class billingService { try { const redirectURL = this.getRedirectURL(); const response = await HTTPService.get( - `${apiOrigin()}/billing/stripe/customer-portal`, + await apiURL("/billing/stripe/customer-portal"), { redirectURL }, { "X-Auth-Token": getToken(), diff --git a/web/apps/photos/src/services/deduplicationService.ts b/web/apps/photos/src/services/deduplicationService.ts index d1edc4f642..613289c883 100644 --- a/web/apps/photos/src/services/deduplicationService.ts +++ b/web/apps/photos/src/services/deduplicationService.ts @@ -3,7 +3,7 @@ import { FILE_TYPE } from "@/media/file-type"; import type { Metadata } from "@/media/types/file"; import { EnteFile } from "@/new/photos/types/file"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import HTTPService from "@ente/shared/network/HTTPService"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; @@ -146,7 +146,7 @@ function groupDupesByFileHashes(dupe: Duplicate) { async function fetchDuplicateFileIDs() { try { const response = await HTTPService.get( - `${apiOrigin()}/files/duplicates`, + await apiURL("/files/duplicates"), null, { "X-Auth-Token": getToken(), diff --git a/web/apps/photos/src/services/embeddingService.ts b/web/apps/photos/src/services/embeddingService.ts index 2c91ca01c9..4aa6c39878 100644 --- a/web/apps/photos/src/services/embeddingService.ts +++ b/web/apps/photos/src/services/embeddingService.ts @@ -3,7 +3,7 @@ import { getAllLocalFiles } from "@/new/photos/services/files"; import { EnteFile } from "@/new/photos/types/file"; import { inWorker } from "@/next/env"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import { workerBridge } from "@/next/worker/worker-bridge"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError } from "@ente/shared/error"; @@ -285,7 +285,7 @@ export const getEmbeddingsDiff = async ( return; } const response = await HTTPService.get( - `${apiOrigin()}/embeddings/diff`, + await apiURL("/embeddings/diff"), { sinceTime, limit: DIFF_LIMIT, @@ -314,7 +314,7 @@ export const putEmbedding = async ( throw Error(CustomError.TOKEN_MISSING); } const resp = await HTTPService.put( - `${apiOrigin()}/embeddings`, + await apiURL("/embeddings"), putEmbeddingReq, null, { diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index d33d89fc41..0528094d3f 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -1,6 +1,6 @@ import { EncryptedEnteFile, EnteFile } from "@/new/photos/types/file"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -252,7 +252,7 @@ const getPublicFiles = async ( break; } resp = await HTTPService.get( - `${apiOrigin()}/public-collection/diff`, + await apiURL("/public-collection/diff"), { sinceTime: time, }, @@ -307,7 +307,7 @@ export const getPublicCollection = async ( return; } const resp = await HTTPService.get( - `${apiOrigin()}/public-collection/info`, + await apiURL("/public-collection/info"), null, { "Cache-Control": "no-cache", "X-Auth-Access-Token": token }, ); @@ -357,7 +357,7 @@ export const verifyPublicCollectionPassword = async ( ): Promise => { try { const resp = await HTTPService.post( - `${apiOrigin()}/public-collection/verify-password`, + await apiURL("/public-collection/verify-password"), { passHash: passwordHash }, null, { "Cache-Control": "no-cache", "X-Auth-Access-Token": token }, diff --git a/web/apps/photos/src/services/trashService.ts b/web/apps/photos/src/services/trashService.ts index cd5b2aa9b9..b4920ed1ea 100644 --- a/web/apps/photos/src/services/trashService.ts +++ b/web/apps/photos/src/services/trashService.ts @@ -1,6 +1,6 @@ import { EnteFile } from "@/new/photos/types/file"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import HTTPService from "@ente/shared/network/HTTPService"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; @@ -89,7 +89,7 @@ export const updateTrash = async ( break; } resp = await HTTPService.get( - `${apiOrigin()}/trash/v2/diff`, + await apiURL("/trash/v2/diff"), { sinceTime: time, }, @@ -158,7 +158,7 @@ export const emptyTrash = async () => { const lastUpdatedAt = await getLastSyncTime(); await HTTPService.post( - `${apiOrigin()}/trash/empty`, + await apiURL("/trash/empty"), { lastUpdatedAt }, null, { diff --git a/web/apps/photos/src/services/upload/publicUploadHttpClient.ts b/web/apps/photos/src/services/upload/publicUploadHttpClient.ts index e0f0bac3ea..063d645173 100644 --- a/web/apps/photos/src/services/upload/publicUploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/publicUploadHttpClient.ts @@ -1,6 +1,6 @@ import { EnteFile } from "@/new/photos/types/file"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import { CustomError, handleUploadError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; import { retryHTTPCall } from "./uploadHttpClient"; @@ -20,19 +20,15 @@ class PublicUploadHttpClient { if (!token) { throw Error(CustomError.TOKEN_MISSING); } + const url = await apiURL("/public-collection/file"); const response = await retryHTTPCall( () => - HTTPService.post( - `${apiOrigin()}/public-collection/file`, - uploadFile, - null, - { - "X-Auth-Access-Token": token, - ...(passwordToken && { - "X-Auth-Access-Token-JWT": passwordToken, - }), - }, - ), + HTTPService.post(url, uploadFile, null, { + "X-Auth-Access-Token": token, + ...(passwordToken && { + "X-Auth-Access-Token-JWT": passwordToken, + }), + }), handleUploadError, ); return response.data; @@ -55,7 +51,7 @@ class PublicUploadHttpClient { throw Error(CustomError.TOKEN_MISSING); } this.uploadURLFetchInProgress = HTTPService.get( - `${apiOrigin()}/public-collection/upload-urls`, + await apiURL("/public-collection/upload-urls"), { count: Math.min(MAX_URL_REQUESTS, count * 2), }, @@ -91,7 +87,7 @@ class PublicUploadHttpClient { throw Error(CustomError.TOKEN_MISSING); } const response = await HTTPService.get( - `${apiOrigin()}/public-collection/multipart-upload-urls`, + await apiURL("/public-collection/multipart-upload-urls"), { count, }, diff --git a/web/apps/photos/src/services/userService.ts b/web/apps/photos/src/services/userService.ts index 6ae112635a..03c6ced55e 100644 --- a/web/apps/photos/src/services/userService.ts +++ b/web/apps/photos/src/services/userService.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { apiOrigin, customAPIOrigin, familyAppOrigin } from "@/next/origins"; +import { apiURL, customAPIOrigin, familyAppOrigin } from "@/next/origins"; import { putAttributes } from "@ente/accounts/api/user"; import { ApiError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -23,7 +23,7 @@ export const getPublicKey = async (email: string) => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/public-key`, + await apiURL("/users/public-key"), { email }, { "X-Auth-Token": token, @@ -36,7 +36,7 @@ export const getPaymentToken = async () => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/payment-token`, + await apiURL("/users/payment-token"), null, { "X-Auth-Token": token, @@ -50,7 +50,7 @@ export const getFamiliesToken = async () => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/families-token`, + await apiURL("/users/families-token"), null, { "X-Auth-Token": token, @@ -68,7 +68,7 @@ export const getRoadmapRedirectURL = async () => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/roadmap/v2`, + await apiURL("/users/roadmap/v2"), null, { "X-Auth-Token": token, @@ -84,7 +84,7 @@ export const getRoadmapRedirectURL = async () => { export const isTokenValid = async (token: string) => { try { const resp = await HTTPService.get( - `${apiOrigin()}/users/session-validity/v2`, + await apiURL("/users/session-validity/v2"), null, { "X-Auth-Token": token, @@ -123,7 +123,7 @@ export const isTokenValid = async (token: string) => { export const getTwoFactorStatus = async () => { const resp = await HTTPService.get( - `${apiOrigin()}/users/two-factor/status`, + await apiURL("/users/two-factor/status"), null, { "X-Auth-Token": getToken(), @@ -137,7 +137,7 @@ export const getUserDetailsV2 = async (): Promise => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/details/v2`, + await apiURL("/users/details/v2"), null, { "X-Auth-Token": token, @@ -168,7 +168,7 @@ export const getAccountDeleteChallenge = async () => { const token = getToken(); const resp = await HTTPService.get( - `${apiOrigin()}/users/delete-challenge`, + await apiURL("/users/delete-challenge"), null, { "X-Auth-Token": token, @@ -193,7 +193,7 @@ export const deleteAccount = async ( } await HTTPService.delete( - `${apiOrigin()}/users/delete`, + await apiURL("/users/delete"), { challenge, reason, feedback }, null, { @@ -211,7 +211,7 @@ export const getFaceSearchEnabledStatus = async () => { const token = getToken(); const resp: AxiosResponse = await HTTPService.get( - `${apiOrigin()}/remote-store`, + await apiURL("/remote-store"), { key: "faceSearchEnabled", defaultValue: false, @@ -231,7 +231,7 @@ export const updateFaceSearchEnabledStatus = async (newStatus: boolean) => { try { const token = getToken(); await HTTPService.post( - `${apiOrigin()}/remote-store/update`, + await apiURL("/remote-store/update"), { key: "faceSearchEnabled", value: newStatus.toString(), @@ -262,7 +262,7 @@ export const getMapEnabledStatus = async () => { const token = getToken(); const resp: AxiosResponse = await HTTPService.get( - `${apiOrigin()}/remote-store`, + await apiURL("/remote-store"), { key: "mapEnabled", defaultValue: false, @@ -282,7 +282,7 @@ export const updateMapEnabledStatus = async (newStatus: boolean) => { try { const token = getToken(); await HTTPService.post( - `${apiOrigin()}/remote-store/update`, + await apiURL("/remote-store/update"), { key: "mapEnabled", value: newStatus.toString(), @@ -318,7 +318,7 @@ export async function getDisableCFUploadProxyFlag(): Promise { // In such cases, disable the Cloudflare upload proxy (which won't work for // self-hosters), and instead just directly use the upload URLs that museum // gives us. - if (customAPIOrigin()) return true; + if (await customAPIOrigin()) return true; try { const featureFlags = ( diff --git a/web/packages/accounts/api/user.ts b/web/packages/accounts/api/user.ts index 30106ad873..131ae73ef7 100644 --- a/web/packages/accounts/api/user.ts +++ b/web/packages/accounts/api/user.ts @@ -1,4 +1,4 @@ -import { apiOrigin, apiURL } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import type { RecoveryKey, @@ -71,7 +71,7 @@ export const logout = async () => { export const verifyTwoFactor = async (code: string, sessionID: string) => { const resp = await HTTPService.post( - `${apiOrigin()}/users/two-factor/verify`, + await apiURL("/users/two-factor/verify"), { code, sessionID, @@ -88,7 +88,7 @@ export const recoverTwoFactor = async ( twoFactorType: TwoFactorType, ) => { const resp = await HTTPService.get( - `${apiOrigin()}/users/two-factor/recover`, + await apiURL("/users/two-factor/recover"), { sessionID, twoFactorType, @@ -103,7 +103,7 @@ export const removeTwoFactor = async ( twoFactorType: TwoFactorType, ) => { const resp = await HTTPService.post( - `${apiOrigin()}/users/two-factor/remove`, + await apiURL("/users/two-factor/remove"), { sessionID, secret, @@ -115,7 +115,7 @@ export const removeTwoFactor = async ( export const changeEmail = async (email: string, ott: string) => { await HTTPService.post( - `${apiOrigin()}/users/change-email`, + await apiURL("/users/change-email"), { email, ott, @@ -128,7 +128,7 @@ export const changeEmail = async (email: string, ott: string) => { }; export const sendOTTForEmailChange = async (email: string) => { - await HTTPService.post(`${apiOrigin()}/users/ott`, { + await HTTPService.post(await apiURL("/users/ott"), { email, client: "web", purpose: "change", @@ -137,7 +137,7 @@ export const sendOTTForEmailChange = async (email: string) => { export const setupTwoFactor = async () => { const resp = await HTTPService.post( - `${apiOrigin()}/users/two-factor/setup`, + await apiURL("/users/two-factor/setup"), null, undefined, { @@ -152,7 +152,7 @@ export const enableTwoFactor = async ( recoveryEncryptedTwoFactorSecret: B64EncryptionResult, ) => { await HTTPService.post( - `${apiOrigin()}/users/two-factor/enable`, + await apiURL("/users/two-factor/enable"), { code, encryptedTwoFactorSecret: @@ -167,9 +167,9 @@ export const enableTwoFactor = async ( ); }; -export const setRecoveryKey = (token: string, recoveryKey: RecoveryKey) => +export const setRecoveryKey = async (token: string, recoveryKey: RecoveryKey) => HTTPService.put( - `${apiOrigin()}/users/recovery-key`, + await apiURL("/users/recovery-key"), recoveryKey, undefined, { @@ -179,7 +179,7 @@ export const setRecoveryKey = (token: string, recoveryKey: RecoveryKey) => export const disableTwoFactor = async () => { await HTTPService.post( - `${apiOrigin()}/users/two-factor/disable`, + await apiURL("/users/two-factor/disable"), null, undefined, { From 55b2934c6250e4981d734468adacb9e5af1c519f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:17:19 +0530 Subject: [PATCH 08/20] Continue --- .../photos/src/services/collectionService.ts | 38 +++++++++---------- web/apps/photos/src/services/entityService.ts | 6 +-- .../src/services/upload/uploadHttpClient.ts | 8 ++-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index 17b9da68dc..ebb50c3299 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -7,7 +7,7 @@ import { VISIBILITY_STATE, } from "@/new/photos/types/magicMetadata"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -181,7 +181,7 @@ const getCollections = async ( ): Promise => { try { const resp = await HTTPService.get( - `${apiOrigin()}/collections/v2`, + await apiURL("/collections/v2"), { sinceTime, }, @@ -328,7 +328,7 @@ export const getCollection = async ( return; } const resp = await HTTPService.get( - `${apiOrigin()}/collections/${collectionID}`, + await apiURL(`/collections/${collectionID}`), null, { "X-Auth-Token": token }, ); @@ -472,7 +472,7 @@ const postCollection = async ( ): Promise => { try { const response = await HTTPService.post( - `${apiOrigin()}/collections`, + await apiURL("/collections"), collectionData, null, { "X-Auth-Token": token }, @@ -527,7 +527,7 @@ export const addToCollection = async ( files: fileKeysEncryptedWithNewCollection, }; await HTTPService.post( - `${apiOrigin()}/collections/add-files`, + await apiURL("/collections/add-files"), requestBody, null, { @@ -557,7 +557,7 @@ export const restoreToCollection = async ( files: fileKeysEncryptedWithNewCollection, }; await HTTPService.post( - `${apiOrigin()}/collections/restore-files`, + await apiURL("/collections/restore-files"), requestBody, null, { @@ -588,7 +588,7 @@ export const moveToCollection = async ( files: fileKeysEncryptedWithNewCollection, }; await HTTPService.post( - `${apiOrigin()}/collections/move-files`, + await apiURL("/collections/move-files"), requestBody, null, { @@ -734,7 +734,7 @@ export const removeNonUserFiles = async ( }; await HTTPService.post( - `${apiOrigin()}/collections/v3/remove-files`, + await apiURL("/collections/v3/remove-files"), request, null, { "X-Auth-Token": token }, @@ -761,7 +761,7 @@ export const deleteCollection = async ( const token = getToken(); await HTTPService.delete( - `${apiOrigin()}/collections/v3/${collectionID}`, + await apiURL(`/collections/v3/${collectionID}`), null, { collectionID, keepFiles }, { "X-Auth-Token": token }, @@ -777,7 +777,7 @@ export const leaveSharedAlbum = async (collectionID: number) => { const token = getToken(); await HTTPService.post( - `${apiOrigin()}/collections/leave/${collectionID}`, + await apiURL(`/collections/leave/${collectionID}`), null, null, { "X-Auth-Token": token }, @@ -815,7 +815,7 @@ export const updateCollectionMagicMetadata = async ( }; await HTTPService.put( - `${apiOrigin()}/collections/magic-metadata`, + await apiURL("/collections/magic-metadata"), reqBody, null, { @@ -859,7 +859,7 @@ export const updateSharedCollectionMagicMetadata = async ( }; await HTTPService.put( - `${apiOrigin()}/collections/sharee-magic-metadata`, + await apiURL("/collections/sharee-magic-metadata"), reqBody, null, { @@ -903,7 +903,7 @@ export const updatePublicCollectionMagicMetadata = async ( }; await HTTPService.put( - `${apiOrigin()}/collections/public-magic-metadata`, + await apiURL("/collections/public-magic-metadata"), reqBody, null, { @@ -938,7 +938,7 @@ export const renameCollection = async ( nameDecryptionNonce, }; await HTTPService.post( - `${apiOrigin()}/collections/rename`, + await apiURL("/collections/rename"), collectionRenameRequest, null, { @@ -967,7 +967,7 @@ export const shareCollection = async ( encryptedKey, }; await HTTPService.post( - `${apiOrigin()}/collections/share`, + await apiURL("/collections/share"), shareCollectionRequest, null, { @@ -991,7 +991,7 @@ export const unshareCollection = async ( email: withUserEmail, }; await HTTPService.post( - `${apiOrigin()}/collections/unshare`, + await apiURL("/collections/unshare"), shareCollectionRequest, null, { @@ -1013,7 +1013,7 @@ export const createShareableURL = async (collection: Collection) => { collectionID: collection.id, }; const resp = await HTTPService.post( - `${apiOrigin()}/collections/share-url`, + await apiURL("/collections/share-url"), createPublicAccessTokenRequest, null, { @@ -1034,7 +1034,7 @@ export const deleteShareableURL = async (collection: Collection) => { return null; } await HTTPService.delete( - `${apiOrigin()}/collections/share-url/${collection.id}`, + await apiURL(`/collections/share-url/${collection.id}`), null, null, { @@ -1056,7 +1056,7 @@ export const updateShareableURL = async ( return null; } const res = await HTTPService.put( - `${apiOrigin()}/collections/share-url`, + await apiURL("/collections/share-url"), request, null, { diff --git a/web/apps/photos/src/services/entityService.ts b/web/apps/photos/src/services/entityService.ts index 67f6275a03..ea95da6a62 100644 --- a/web/apps/photos/src/services/entityService.ts +++ b/web/apps/photos/src/services/entityService.ts @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import HTTPService from "@ente/shared/network/HTTPService"; import localForage from "@ente/shared/storage/localForage"; @@ -58,7 +58,7 @@ const getEntityKey = async (type: EntityType) => { return; } const resp = await HTTPService.get( - `${apiOrigin()}/user-entity/key`, + await apiURL("/user-entity/key"), { type, }, @@ -173,7 +173,7 @@ const getEntityDiff = async ( return; } const resp = await HTTPService.get( - `${apiOrigin()}/user-entity/entity/diff`, + await apiURL("/user-entity/entity/diff"), { sinceTime: time, type, diff --git a/web/apps/photos/src/services/upload/uploadHttpClient.ts b/web/apps/photos/src/services/upload/uploadHttpClient.ts index badf03aa0d..ec414852a0 100644 --- a/web/apps/photos/src/services/upload/uploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/uploadHttpClient.ts @@ -1,6 +1,6 @@ import { EnteFile } from "@/new/photos/types/file"; import log from "@/next/log"; -import { apiOrigin, uploaderOrigin } from "@/next/origins"; +import { apiURL, uploaderOrigin } from "@/next/origins"; import { wait } from "@/utils/promise"; import { CustomError, handleUploadError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -20,7 +20,7 @@ class UploadHttpClient { } const response = await retryHTTPCall( () => - HTTPService.post(`${apiOrigin()}/files`, uploadFile, null, { + HTTPService.post(await apiURL("/files"), uploadFile, null, { "X-Auth-Token": token, }), handleUploadError, @@ -41,7 +41,7 @@ class UploadHttpClient { return; } this.uploadURLFetchInProgress = HTTPService.get( - `${apiOrigin()}/files/upload-urls`, + await apiURL("/files/upload-urls"), { count: Math.min(MAX_URL_REQUESTS, count * 2), }, @@ -71,7 +71,7 @@ class UploadHttpClient { return; } const response = await HTTPService.get( - `${apiOrigin()}/files/multipart-upload-urls`, + await apiURL("/files/multipart-upload-urls"), { count, }, From e86b09548070983146f70525466673912741ffec Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:21:10 +0530 Subject: [PATCH 09/20] Fin --- web/apps/photos/src/services/fileService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/apps/photos/src/services/fileService.ts b/web/apps/photos/src/services/fileService.ts index 2e8621d693..968fdf4df0 100644 --- a/web/apps/photos/src/services/fileService.ts +++ b/web/apps/photos/src/services/fileService.ts @@ -8,7 +8,7 @@ import { } from "@/new/photos/types/file"; import { BulkUpdateMagicMetadataRequest } from "@/new/photos/types/magicMetadata"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { apiURL } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import HTTPService from "@ente/shared/network/HTTPService"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; @@ -70,7 +70,7 @@ export const getFiles = async ( break; } resp = await HTTPService.get( - `${apiOrigin()}/collections/v2/diff`, + await apiURL("/collections/v2/diff"), { collectionID: collection.id, sinceTime: time, @@ -139,7 +139,7 @@ export const trashFiles = async (filesToTrash: EnteFile[]) => { })), }; await HTTPService.post( - `${apiOrigin()}/files/trash`, + await apiURL("/files/trash"), trashRequest, null, { @@ -163,7 +163,7 @@ export const deleteFromTrash = async (filesToDelete: number[]) => { for (const batch of batchedFilesToDelete) { await HTTPService.post( - `${apiOrigin()}/trash/delete`, + await apiURL("/trash/delete"), { fileIDs: batch }, null, { @@ -206,7 +206,7 @@ export const updateFileMagicMetadata = async ( }); } await HTTPService.put( - `${apiOrigin()}/files/magic-metadata`, + await apiURL("/files/magic-metadata"), reqBody, null, { @@ -253,7 +253,7 @@ export const updateFilePublicMagicMetadata = async ( }); } await HTTPService.put( - `${apiOrigin()}/files/public-magic-metadata`, + await apiURL("/files/public-magic-metadata"), reqBody, null, { From c4c53cd59f713c056109c8cf69b386aa1ea4017b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:22:46 +0530 Subject: [PATCH 10/20] Touchups --- web/packages/next/origins.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts index c4b860c641..4e604445b6 100644 --- a/web/packages/next/origins.ts +++ b/web/packages/next/origins.ts @@ -1,4 +1,3 @@ -import { nullToUndefined } from "@/utils/transform"; import { get, set } from "idb-keyval"; /** @@ -42,7 +41,7 @@ export const customAPIOrigin = async () => { // Remove me after a bit (27 June 2024). const legacyOrigin = localStorage.getItem("apiOrigin"); if (legacyOrigin !== null) { - origin = nullToUndefined(legacyOrigin); + origin = legacyOrigin; if (origin) await set("apiOrigin", origin); localStorage.removeItem("apiOrigin"); } From ab63ed53df47923be1b2728c192698aeb5ff0570 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:25:11 +0530 Subject: [PATCH 11/20] Fix --- web/apps/photos/src/pages/index.tsx | 2 +- web/apps/photos/src/services/upload/uploadHttpClient.ts | 3 ++- web/packages/new/photos/components/DevSettings.tsx | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index ec6a10b911..197906cdbd 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -22,7 +22,7 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel"; import "pure-react-carousel/dist/react-carousel.es.css"; -import { useEffect, useState, useCallback } from "react"; +import { useCallback, useEffect, useState } from "react"; import { Trans } from "react-i18next"; import { useAppContext } from "./_app"; diff --git a/web/apps/photos/src/services/upload/uploadHttpClient.ts b/web/apps/photos/src/services/upload/uploadHttpClient.ts index ec414852a0..3f9bafb6a7 100644 --- a/web/apps/photos/src/services/upload/uploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/uploadHttpClient.ts @@ -18,9 +18,10 @@ class UploadHttpClient { if (!token) { return; } + const url = await apiURL("/files"); const response = await retryHTTPCall( () => - HTTPService.post(await apiURL("/files"), uploadFile, null, { + HTTPService.post(url, uploadFile, null, { "X-Auth-Token": token, }), handleUploadError, diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 67e95a5d3e..e8a4f197fb 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -72,7 +72,7 @@ const Contents: React.FC = (props) => { setInitialAPIOrigin( // TODO: Migration of apiOrigin from local storage to indexed DB // Remove me after a bit (27 June 2024). - o ?? localStorage.getItem("apiOrigin") ?? undefined, + o ?? localStorage.getItem("apiOrigin") ?? "", ), ), [], @@ -80,20 +80,20 @@ const Contents: React.FC = (props) => { // Even though this is async, this should be instantanous, we're just // reading the value from the local IndexedDB. - if (!initialAPIOrigin) return <>; + if (initialAPIOrigin === undefined) return <>; return
; }; type FormProps = ContentsProps & { /** The initial value of API origin to prefill in the text input field. */ - initialAPIOrigin: string | undefined; + initialAPIOrigin: string; }; const Form: React.FC = ({ initialAPIOrigin, onClose }) => { const form = useFormik({ initialValues: { - apiOrigin: initialAPIOrigin ?? "", + apiOrigin: initialAPIOrigin, }, validate: ({ apiOrigin }) => { try { From 3555adae09335d5bf2d2558f92252fab0bdea256 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:34:19 +0530 Subject: [PATCH 12/20] Move to correct place --- web/packages/new/package.json | 1 - web/packages/next/package.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/new/package.json b/web/packages/new/package.json index e1107100ff..eeb6507c45 100644 --- a/web/packages/new/package.json +++ b/web/packages/new/package.json @@ -8,7 +8,6 @@ "@ente/shared": "*", "formik": "^2.4", "idb": "^8", - "idb-keyval": "^6", "zod": "^3" }, "devDependencies": {} diff --git a/web/packages/next/package.json b/web/packages/next/package.json index 00392569bb..311b02870e 100644 --- a/web/packages/next/package.json +++ b/web/packages/next/package.json @@ -11,6 +11,7 @@ "get-user-locale": "^2.3", "i18next": "^23.10", "i18next-resources-to-backend": "^1.2.0", + "idb-keyval": "^6", "is-electron": "^2.2", "next": "^14.1", "react": "^18", From 554a90eec5accc3dda5f2a9870a7eea03eb6370c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 16:36:30 +0530 Subject: [PATCH 13/20] Clear on logout --- web/packages/accounts/services/logout.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/packages/accounts/services/logout.ts b/web/packages/accounts/services/logout.ts index 59371e1e7c..3e9beeeb15 100644 --- a/web/packages/accounts/services/logout.ts +++ b/web/packages/accounts/services/logout.ts @@ -5,6 +5,7 @@ import InMemoryStore from "@ente/shared/storage/InMemoryStore"; import localForage from "@ente/shared/storage/localForage"; import { clearData } from "@ente/shared/storage/localStorage"; import { clearKeys } from "@ente/shared/storage/sessionStorage"; +import { clear as clearKV } from "idb-keyval"; import { logout as remoteLogout } from "../api/user"; /** @@ -56,4 +57,9 @@ export const accountLogout = async () => { } catch (e) { ignoreError("http", e); } + try { + await clearKV(); + } catch (e) { + ignoreError("kv", e); + } }; From 52d7914ad0e0af75c82cea395bb432d837e6dc94 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 19:58:55 +0530 Subject: [PATCH 14/20] Ergonomic kv --- web/packages/next/kv.ts | 125 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 web/packages/next/kv.ts diff --git a/web/packages/next/kv.ts b/web/packages/next/kv.ts new file mode 100644 index 0000000000..219b5faa0b --- /dev/null +++ b/web/packages/next/kv.ts @@ -0,0 +1,125 @@ +import { deleteDB, openDB, type DBSchema } from "idb"; +import log from "./log"; + +/** + * Key value store schema. + * + * The use IndexedDB to store arbitrary key-value pairs. The functional + * motivation is to allow these to also be accessed from Web Workers (local + * storage is limited to the main thread). + * + * The "kv" database consists of one object store, "kv". Each entry is a string. + * The key is also a string. + */ +interface KVDBSchema extends DBSchema { + kv: { + key: string; + value: string; + }; +} + +/** + * A lazily-created, cached promise for KV DB. + * + * [Note: Caching IDB instances in separate execution contexts] + * + * We open the database once (on access), and thereafter save and reuse this + * promise each time something wants to connect to it. + * + * This promise can subsequently get cleared if we need to relinquish our + * connection (e.g. if another client wants to open the face DB with a newer + * version of the schema). + * + * It can also get cleared on logout. In all such cases, it'll automatically get + * recreated on next access. + * + * Note that this is module specific state, so each execution context (main + * thread, web worker) that calls the functions in this module will its own + * promise to the database. To ensure that all connections get torn down + * correctly, we need to perform the following logout sequence: + * + * 1. Terminate all the workers which might have one of the instances in + * memory. This closes their connections. + * + * 2. Delete the database on the main thread. + */ +let _kvDB: ReturnType | undefined; + +const openKVDB = async () => { + const db = await openDB("kv", 1, { + upgrade(db) { + db.createObjectStore("kv", { keyPath: "key" }); + }, + blocking() { + log.info( + "Another client is attempting to open a new version of KV DB", + ); + db.close(); + _kvDB = undefined; + }, + blocked() { + log.warn( + "Waiting for an existing client to close their connection so that we can update the KV DB version", + ); + }, + terminated() { + log.warn("Our connection to KV DB was unexpectedly terminated"); + _kvDB = undefined; + }, + }); + + return db; +}; + +/** + * @returns a lazily created, cached connection to the KV DB. + */ +const kvDB = () => (_kvDB ??= openKVDB()); + +/** + * Clear all key values stored in the KV db. + * + * This is meant to be called during logout in the main thread. + */ +export const clearKVDB = async () => { + try { + if (_kvDB) (await _kvDB).close(); + } catch (e) { + log.warn("Ignoring error when trying to close KV DB", e); + } + _kvDB = undefined; + + return deleteDB("kv", { + blocked() { + log.warn( + "Waiting for an existing client to close their connection so that we can delete the KV DB", + ); + }, + }); +}; + +/** + * Return the value stored corresponding to {@link key}, or `undefined` if there + * is no such entry. + */ +export const getKV = async (key: string) => { + const db = await kvDB(); + return db.get("kv", key); +}; + +/** + * Save the given {@link value} corresponding to {@link key}, overwriting any + * existing value. + */ +export const setKV = async (key: string, value: string) => { + const db = await kvDB(); + return db.put("kv", value, key); +}; + +/** + * Remove the entry corresponding to {@link key} (if any). + */ +export const removeKV = async (key: string) => { + const db = await kvDB(); + return db.delete("kv", key); +}; From 640fd48e70ad20db4ec8f41c61fddcaa759d47ab Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 20:00:17 +0530 Subject: [PATCH 15/20] Hobgoblin --- web/apps/photos/src/services/face/indexer.worker.ts | 2 +- web/docs/dependencies.md | 2 +- web/docs/storage.md | 2 +- web/packages/next/env.ts | 2 +- web/packages/next/kv.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/apps/photos/src/services/face/indexer.worker.ts b/web/apps/photos/src/services/face/indexer.worker.ts index 8de8b61d16..a31db7550d 100644 --- a/web/apps/photos/src/services/face/indexer.worker.ts +++ b/web/apps/photos/src/services/face/indexer.worker.ts @@ -14,7 +14,7 @@ import type { FaceIndex } from "./types"; * Index faces in a file, save the persist the results locally, and put them on * remote. * - * This class is instantiated in a Web Worker so as to not get in the way of the + * This class is instantiated in a web worker so as to not get in the way of the * main thread. It could've been a bunch of free standing functions too, it is * just a class for convenience of compatibility with how the rest of our * comlink workers are structured. diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index c4d4e9479e..74e0076daf 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -169,7 +169,7 @@ For more details, see [translations.md](translations.md). ## Infrastructure - [comlink](https://github.com/GoogleChromeLabs/comlink) provides a minimal - layer on top of Web Workers to make them more easier to use. + layer on top of web workers to make them more easier to use. - [idb](https://github.com/jakearchibald/idb) provides a promise API over the browser-native IndexedDB APIs, and is used as our primary tabular database. diff --git a/web/docs/storage.md b/web/docs/storage.md index 8f1ca8bba8..729c6fd663 100644 --- a/web/docs/storage.md +++ b/web/docs/storage.md @@ -28,7 +28,7 @@ IndexedDB is a transactional NoSQL store provided by browsers. It has quite large storage limits, and data is stored per origin (and remains persistent across tab restarts). -Unlike local storage, IndexedDB is also accessible from Web Workers. +Unlike local storage, IndexedDB is also accessible from web workers. Older code used the LocalForage library for storing things in Indexed DB. This library falls back to localStorage in case Indexed DB storage is not available. diff --git a/web/packages/next/env.ts b/web/packages/next/env.ts index ea145d892d..29468ac02c 100644 --- a/web/packages/next/env.ts +++ b/web/packages/next/env.ts @@ -15,7 +15,7 @@ export const isDevBuild = process.env.NODE_ENV === "development"; * `true` if we're running in the default global context (aka the main thread) * of a web browser. * - * In particular, this is `false` when we're running in a Web Worker, + * In particular, this is `false` when we're running in a web worker, * irrespecitve of whether the worker is running in a Node.js context or a web * browser context. * diff --git a/web/packages/next/kv.ts b/web/packages/next/kv.ts index 219b5faa0b..8eafebe268 100644 --- a/web/packages/next/kv.ts +++ b/web/packages/next/kv.ts @@ -5,7 +5,7 @@ import log from "./log"; * Key value store schema. * * The use IndexedDB to store arbitrary key-value pairs. The functional - * motivation is to allow these to also be accessed from Web Workers (local + * motivation is to allow these to also be accessed from web workers (local * storage is limited to the main thread). * * The "kv" database consists of one object store, "kv". Each entry is a string. From d33ba285a6f37eebe36fc9a01d22a325b88d3022 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 20:04:02 +0530 Subject: [PATCH 16/20] Replace --- web/docs/dependencies.md | 4 +--- web/docs/storage.md | 10 ++-------- web/packages/accounts/services/logout.ts | 20 +++++++++---------- .../new/photos/components/DevSettings.tsx | 8 ++++---- web/packages/next/origins.ts | 6 +++--- web/packages/next/package.json | 1 - web/yarn.lock | 5 ----- 7 files changed, 20 insertions(+), 34 deletions(-) diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index 74e0076daf..280da4aea8 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -172,9 +172,7 @@ For more details, see [translations.md](translations.md). layer on top of web workers to make them more easier to use. - [idb](https://github.com/jakearchibald/idb) provides a promise API over the - browser-native IndexedDB APIs, and is used as our primary tabular database. - [idb-keyval](https://github.com/jakearchibald/idb-keyval) is its sibling - library that we use for ad-hoc key value storage. + browser-native IndexedDB APIs. > For more details about IDB and its role, see [storage.md](storage.md). diff --git a/web/docs/storage.md b/web/docs/storage.md index 729c6fd663..8be709f222 100644 --- a/web/docs/storage.md +++ b/web/docs/storage.md @@ -28,7 +28,8 @@ IndexedDB is a transactional NoSQL store provided by browsers. It has quite large storage limits, and data is stored per origin (and remains persistent across tab restarts). -Unlike local storage, IndexedDB is also accessible from web workers. +Unlike local storage, IndexedDB is also accessible from web workers and so we +also use IndexedDB for storing ad-hoc key value pairs. Older code used the LocalForage library for storing things in Indexed DB. This library falls back to localStorage in case Indexed DB storage is not available. @@ -41,13 +42,6 @@ For more details, see: - https://web.dev/articles/indexeddb - https://github.com/jakearchibald/idb -## IndexedDB KV - -We earlier used local storage for ad-hoc key value storage, but local storage is -not accessible from web workers which we use quite a bit. So now we use _idb_'s -sibling libary, idb-keyval, for storing key value pairs that need to be accessed -from both the main thread and web workers. - ## OPFS OPFS is used for caching entire files when we're running under Electron (the Web diff --git a/web/packages/accounts/services/logout.ts b/web/packages/accounts/services/logout.ts index 3e9beeeb15..079366524e 100644 --- a/web/packages/accounts/services/logout.ts +++ b/web/packages/accounts/services/logout.ts @@ -1,11 +1,11 @@ import { clearBlobCaches } from "@/next/blob-cache"; import { clearHTTPState } from "@/next/http"; +import { clearKVDB } from "@/next/kv"; import log from "@/next/log"; import InMemoryStore from "@ente/shared/storage/InMemoryStore"; import localForage from "@ente/shared/storage/localForage"; import { clearData } from "@ente/shared/storage/localStorage"; import { clearKeys } from "@ente/shared/storage/sessionStorage"; -import { clear as clearKV } from "idb-keyval"; import { logout as remoteLogout } from "../api/user"; /** @@ -25,41 +25,41 @@ export const accountLogout = async () => { try { await remoteLogout(); } catch (e) { - ignoreError("remote", e); + ignoreError("Remote", e); } try { InMemoryStore.clear(); } catch (e) { - ignoreError("in-memory store", e); + ignoreError("In-memory store", e); } try { clearKeys(); } catch (e) { - ignoreError("session store", e); + ignoreError("Session storage", e); } try { clearData(); } catch (e) { - ignoreError("local storage", e); + ignoreError("Local storage", e); } try { await localForage.clear(); } catch (e) { - ignoreError("local forage", e); + ignoreError("Local forage", e); } try { await clearBlobCaches(); } catch (e) { - ignoreError("cache", e); + ignoreError("Blob cache", e); } try { clearHTTPState(); } catch (e) { - ignoreError("http", e); + ignoreError("HTTP", e); } try { - await clearKV(); + await clearKVDB(); } catch (e) { - ignoreError("kv", e); + ignoreError("KV DB", e); } }; diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index e8a4f197fb..62408f4d81 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,3 +1,4 @@ +import { getKV, removeKV, setKV } from "@/next/kv"; import log from "@/next/log"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { @@ -14,7 +15,6 @@ import { } from "@mui/material"; import { useFormik } from "formik"; import { t } from "i18next"; -import { del, get, set } from "idb-keyval"; import React, { useEffect, useState } from "react"; import { z } from "zod"; import { FocusVisibleButton } from "./FocusVisibleButton"; @@ -68,7 +68,7 @@ const Contents: React.FC = (props) => { useEffect( () => - void get("apiOrigin").then((o) => + void getKV("apiOrigin").then((o) => setInitialAPIOrigin( // TODO: Migration of apiOrigin from local storage to indexed DB // Remove me after a bit (27 June 2024). @@ -213,7 +213,7 @@ const Form: React.FC = ({ initialAPIOrigin, onClose }) => { */ const updateAPIOrigin = async (origin: string) => { if (!origin) { - await del("apiOrigin"); + await removeKV("apiOrigin"); // TODO: Migration of apiOrigin from local storage to indexed DB // Remove me after a bit (27 June 2024). localStorage.removeItem("apiOrigin"); @@ -230,7 +230,7 @@ const updateAPIOrigin = async (origin: string) => { throw new Error("Invalid response"); } - await set("apiOrigin", origin); + await setKV("apiOrigin", origin); }; const PingResponse = z.object({ diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts index 4e604445b6..8200df95a3 100644 --- a/web/packages/next/origins.ts +++ b/web/packages/next/origins.ts @@ -1,4 +1,4 @@ -import { get, set } from "idb-keyval"; +import { getKV, setKV } from "@/next/kv"; /** * Return the origin (scheme, host, port triple) that should be used for making @@ -35,14 +35,14 @@ export const apiURL = async (path: string) => (await apiOrigin()) + path; * Otherwise return undefined. */ export const customAPIOrigin = async () => { - let origin = await get("apiOrigin"); + let origin = await getKV("apiOrigin"); if (!origin) { // TODO: Migration of apiOrigin from local storage to indexed DB // Remove me after a bit (27 June 2024). const legacyOrigin = localStorage.getItem("apiOrigin"); if (legacyOrigin !== null) { origin = legacyOrigin; - if (origin) await set("apiOrigin", origin); + if (origin) await setKV("apiOrigin", origin); localStorage.removeItem("apiOrigin"); } } diff --git a/web/packages/next/package.json b/web/packages/next/package.json index 311b02870e..00392569bb 100644 --- a/web/packages/next/package.json +++ b/web/packages/next/package.json @@ -11,7 +11,6 @@ "get-user-locale": "^2.3", "i18next": "^23.10", "i18next-resources-to-backend": "^1.2.0", - "idb-keyval": "^6", "is-electron": "^2.2", "next": "^14.1", "react": "^18", diff --git a/web/yarn.lock b/web/yarn.lock index 1a89c87063..be1c37c63d 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2922,11 +2922,6 @@ i18next@^23.10: dependencies: "@babel/runtime" "^7.23.2" -idb-keyval@^6: - version "6.2.1" - resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" - integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== - idb@^8: version "8.0.0" resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.0.tgz#33d7ed894ed36e23bcb542fb701ad579bfaad41f" From 9f9038ff97a229ba78bdfab8306f3a582d8a7c29 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 20:08:47 +0530 Subject: [PATCH 17/20] Note --- web/apps/photos/src/services/logout.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/apps/photos/src/services/logout.ts b/web/apps/photos/src/services/logout.ts index 98f1d5fb38..6d335e83df 100644 --- a/web/apps/photos/src/services/logout.ts +++ b/web/apps/photos/src/services/logout.ts @@ -18,6 +18,9 @@ export const photosLogout = async () => { const ignoreError = (label: string, e: unknown) => log.error(`Ignoring error during logout (${label})`, e); + // Terminate any workers before clearing persistent state. + // See: [Note: Caching IDB instances in separate execution contexts]. + await accountLogout(); try { From 29a496c039517dfcf813124a51d4f34248525cbd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 20:11:30 +0530 Subject: [PATCH 18/20] Fix --- web/packages/next/kv.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web/packages/next/kv.ts b/web/packages/next/kv.ts index 8eafebe268..eab1412a5e 100644 --- a/web/packages/next/kv.ts +++ b/web/packages/next/kv.ts @@ -14,10 +14,15 @@ import log from "./log"; interface KVDBSchema extends DBSchema { kv: { key: string; - value: string; + value: KV; }; } +interface KV { + key: string; + value: string; +} + /** * A lazily-created, cached promise for KV DB. * @@ -104,7 +109,8 @@ export const clearKVDB = async () => { */ export const getKV = async (key: string) => { const db = await kvDB(); - return db.get("kv", key); + const kv = await db.get("kv", key); + return kv?.value; }; /** @@ -113,7 +119,7 @@ export const getKV = async (key: string) => { */ export const setKV = async (key: string, value: string) => { const db = await kvDB(); - return db.put("kv", value, key); + await db.put("kv", { key, value }, key); }; /** @@ -121,5 +127,5 @@ export const setKV = async (key: string, value: string) => { */ export const removeKV = async (key: string) => { const db = await kvDB(); - return db.delete("kv", key); + await db.delete("kv", key); }; From b84470f574da2ce88156e0d16b9fd5cc40e9cf83 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 20:13:31 +0530 Subject: [PATCH 19/20] Fix --- web/packages/next/kv.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/web/packages/next/kv.ts b/web/packages/next/kv.ts index eab1412a5e..847968beb0 100644 --- a/web/packages/next/kv.ts +++ b/web/packages/next/kv.ts @@ -14,15 +14,10 @@ import log from "./log"; interface KVDBSchema extends DBSchema { kv: { key: string; - value: KV; + value: string; }; } -interface KV { - key: string; - value: string; -} - /** * A lazily-created, cached promise for KV DB. * @@ -53,7 +48,7 @@ let _kvDB: ReturnType | undefined; const openKVDB = async () => { const db = await openDB("kv", 1, { upgrade(db) { - db.createObjectStore("kv", { keyPath: "key" }); + db.createObjectStore("kv"); }, blocking() { log.info( @@ -109,8 +104,7 @@ export const clearKVDB = async () => { */ export const getKV = async (key: string) => { const db = await kvDB(); - const kv = await db.get("kv", key); - return kv?.value; + return await db.get("kv", key); }; /** @@ -119,7 +113,7 @@ export const getKV = async (key: string) => { */ export const setKV = async (key: string, value: string) => { const db = await kvDB(); - await db.put("kv", { key, value }, key); + await db.put("kv", value, key); }; /** From c7d5dde9f73eecc4ce3103c45d1e74a218cd8dce Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 27 Jun 2024 20:30:53 +0530 Subject: [PATCH 20/20] Sigh --- web/packages/shared/network/cast.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/web/packages/shared/network/cast.ts b/web/packages/shared/network/cast.ts index c5d0ddef90..6f9c1d54b6 100644 --- a/web/packages/shared/network/cast.ts +++ b/web/packages/shared/network/cast.ts @@ -24,7 +24,7 @@ class CastGateway { try { const token = getToken(); await HTTPService.delete( - await apiURL("/cast/revoke-all-tokens"), + await apiURL("/cast/revoke-all-tokens/"), undefined, undefined, { @@ -59,9 +59,12 @@ class CastGateway { } public async registerDevice(publicKey: string): Promise { - const resp = await HTTPService.post(await apiURL("/cast/device-info"), { - publicKey: publicKey, - }); + const resp = await HTTPService.post( + await apiURL("/cast/device-info/"), + { + publicKey: publicKey, + }, + ); return resp.data.deviceCode; } @@ -73,7 +76,7 @@ class CastGateway { ) { const token = getToken(); await HTTPService.post( - await apiURL("/cast/cast-data"), + await apiURL("/cast/cast-data/"), { deviceCode: `${code}`, encPayload: castPayload,