This commit is contained in:
Manav Rathi 2024-06-27 15:20:50 +05:30
parent 1aef9cf179
commit 1b77c899da
No known key found for this signature in database
10 changed files with 62 additions and 37 deletions

View File

@ -317,8 +317,9 @@ const downloadFile = async (
if (!isImageOrLivePhoto(file)) if (!isImageOrLivePhoto(file))
throw new Error("Can only cast images and live photos"); throw new Error("Can only cast images and live photos");
const customOrigin = await customAPIOrigin();
const getFile = () => { const getFile = () => {
const customOrigin = customAPIOrigin();
if (customOrigin) { if (customOrigin) {
// See: [Note: Passing credentials for self-hosted file fetches] // See: [Note: Passing credentials for self-hosted file fetches]
const params = new URLSearchParams({ castToken }); const params = new URLSearchParams({ castToken });

View File

@ -679,15 +679,15 @@ const ExitSection: React.FC = () => {
const DebugSection: React.FC = () => { const DebugSection: React.FC = () => {
const appContext = useContext(AppContext); const appContext = useContext(AppContext);
const [appVersion, setAppVersion] = useState<string | undefined>(); const [appVersion, setAppVersion] = useState<string | undefined>();
const [host, setHost] = useState<string | undefined>();
const electron = globalThis.electron; const electron = globalThis.electron;
useEffect(() => { useEffect(() => {
electron?.appVersion().then((v) => setAppVersion(v)); void electron?.appVersion().then(setAppVersion);
void customAPIHost().then(setHost);
}); });
const host = customAPIHost();
const confirmLogDownload = () => const confirmLogDownload = () =>
appContext.setDialogMessage({ appContext.setDialogMessage({
title: t("DOWNLOAD_LOGS"), title: t("DOWNLOAD_LOGS"),

View File

@ -22,7 +22,7 @@ import { t } from "i18next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel"; import { CarouselProvider, DotGroup, Slide, Slider } from "pure-react-carousel";
import "pure-react-carousel/dist/react-carousel.es.css"; 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 { Trans } from "react-i18next";
import { useAppContext } from "./_app"; import { useAppContext } from "./_app";
@ -31,14 +31,17 @@ export default function LandingPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [showLogin, setShowLogin] = useState(true); const [showLogin, setShowLogin] = useState(true);
// This is kept as state because it can change as a result of user action const [host, setHost] = useState<string | undefined>();
// 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 router = useRouter(); const router = useRouter();
const refreshHost = useCallback(
() => void customAPIHost().then(setHost),
[],
);
useEffect(() => { useEffect(() => {
refreshHost();
showNavBar(false); showNavBar(false);
const currentURL = new URL(window.location.href); const currentURL = new URL(window.location.href);
const albumsURL = new URL(albumsAppOrigin()); const albumsURL = new URL(albumsAppOrigin());
@ -51,9 +54,7 @@ export default function LandingPage() {
} else { } else {
handleNormalRedirect(); handleNormalRedirect();
} }
}, []); }, [refreshHost]);
const handleMaybeChangeHost = () => setHost(customAPIHost());
const handleAlbumsRedirect = async (currentURL: URL) => { const handleAlbumsRedirect = async (currentURL: URL) => {
const end = currentURL.hash.lastIndexOf("&"); const end = currentURL.hash.lastIndexOf("&");
@ -117,7 +118,7 @@ export default function LandingPage() {
const redirectToLoginPage = () => router.push(PAGES.LOGIN); const redirectToLoginPage = () => router.push(PAGES.LOGIN);
return ( return (
<TappableContainer onMaybeChangeHost={handleMaybeChangeHost}> <TappableContainer onMaybeChangeHost={refreshHost}>
{loading ? ( {loading ? (
<EnteSpinner /> <EnteSpinner />
) : ( ) : (

View File

@ -19,10 +19,11 @@ export class PhotosDownloadClient implements DownloadClient {
const token = this.token; const token = this.token;
if (!token) throw Error(CustomError.TOKEN_MISSING); if (!token) throw Error(CustomError.TOKEN_MISSING);
const customOrigin = await customAPIOrigin();
// See: [Note: Passing credentials for self-hosted file fetches] // See: [Note: Passing credentials for self-hosted file fetches]
const getThumbnail = () => { const getThumbnail = () => {
const opts = { responseType: "arraybuffer", timeout: this.timeout }; const opts = { responseType: "arraybuffer", timeout: this.timeout };
const customOrigin = customAPIOrigin();
if (customOrigin) { if (customOrigin) {
const params = new URLSearchParams({ token }); const params = new URLSearchParams({ token });
return HTTPService.get( return HTTPService.get(
@ -53,6 +54,8 @@ export class PhotosDownloadClient implements DownloadClient {
const token = this.token; const token = this.token;
if (!token) throw Error(CustomError.TOKEN_MISSING); if (!token) throw Error(CustomError.TOKEN_MISSING);
const customOrigin = await customAPIOrigin();
// See: [Note: Passing credentials for self-hosted file fetches] // See: [Note: Passing credentials for self-hosted file fetches]
const getFile = () => { const getFile = () => {
const opts = { const opts = {
@ -61,7 +64,6 @@ export class PhotosDownloadClient implements DownloadClient {
onDownloadProgress, onDownloadProgress,
}; };
const customOrigin = customAPIOrigin();
if (customOrigin) { if (customOrigin) {
const params = new URLSearchParams({ token }); const params = new URLSearchParams({ token });
return HTTPService.get( return HTTPService.get(
@ -89,6 +91,8 @@ export class PhotosDownloadClient implements DownloadClient {
const token = this.token; const token = this.token;
if (!token) throw Error(CustomError.TOKEN_MISSING); if (!token) throw Error(CustomError.TOKEN_MISSING);
const customOrigin = await customAPIOrigin();
// [Note: Passing credentials for self-hosted file fetches] // [Note: Passing credentials for self-hosted file fetches]
// //
// Fetching files (or thumbnails) in the default self-hosted Ente // 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. // signed URL and stream back the response.
const getFile = () => { const getFile = () => {
const customOrigin = customAPIOrigin();
if (customOrigin) { if (customOrigin) {
const params = new URLSearchParams({ token }); const params = new URLSearchParams({ token });
return fetch( return fetch(

View File

@ -20,6 +20,7 @@ export class PublicAlbumsDownloadClient implements DownloadClient {
const accessToken = this.token; const accessToken = this.token;
const accessTokenJWT = this.passwordToken; const accessTokenJWT = this.passwordToken;
if (!accessToken) throw Error(CustomError.TOKEN_MISSING); if (!accessToken) throw Error(CustomError.TOKEN_MISSING);
const customOrigin = await customAPIOrigin();
// See: [Note: Passing credentials for self-hosted file fetches] // See: [Note: Passing credentials for self-hosted file fetches]
const getThumbnail = () => { const getThumbnail = () => {
@ -27,7 +28,6 @@ export class PublicAlbumsDownloadClient implements DownloadClient {
responseType: "arraybuffer", responseType: "arraybuffer",
}; };
const customOrigin = customAPIOrigin();
if (customOrigin) { if (customOrigin) {
const params = new URLSearchParams({ const params = new URLSearchParams({
accessToken, accessToken,
@ -67,6 +67,8 @@ export class PublicAlbumsDownloadClient implements DownloadClient {
const accessTokenJWT = this.passwordToken; const accessTokenJWT = this.passwordToken;
if (!accessToken) throw Error(CustomError.TOKEN_MISSING); if (!accessToken) throw Error(CustomError.TOKEN_MISSING);
const customOrigin = await customAPIOrigin();
// See: [Note: Passing credentials for self-hosted file fetches] // See: [Note: Passing credentials for self-hosted file fetches]
const getFile = () => { const getFile = () => {
const opts = { const opts = {
@ -75,7 +77,6 @@ export class PublicAlbumsDownloadClient implements DownloadClient {
onDownloadProgress, onDownloadProgress,
}; };
const customOrigin = customAPIOrigin();
if (customOrigin) { if (customOrigin) {
const params = new URLSearchParams({ const params = new URLSearchParams({
accessToken, accessToken,
@ -112,9 +113,10 @@ export class PublicAlbumsDownloadClient implements DownloadClient {
const accessTokenJWT = this.passwordToken; const accessTokenJWT = this.passwordToken;
if (!accessToken) throw Error(CustomError.TOKEN_MISSING); if (!accessToken) throw Error(CustomError.TOKEN_MISSING);
const customOrigin = await customAPIOrigin();
// See: [Note: Passing credentials for self-hosted file fetches] // See: [Note: Passing credentials for self-hosted file fetches]
const getFile = () => { const getFile = () => {
const customOrigin = customAPIOrigin();
if (customOrigin) { if (customOrigin) {
const params = new URLSearchParams({ const params = new URLSearchParams({
accessToken, accessToken,

View File

@ -117,9 +117,10 @@ class UploadHttpClient {
progressTracker, progressTracker,
): Promise<string> { ): Promise<string> {
try { try {
const origin = await uploaderOrigin();
await retryHTTPCall(() => await retryHTTPCall(() =>
HTTPService.put( HTTPService.put(
`${uploaderOrigin()}/file-upload`, `${origin}/file-upload`,
file, file,
null, null,
{ {
@ -173,9 +174,10 @@ class UploadHttpClient {
progressTracker, progressTracker,
) { ) {
try { try {
const origin = await uploaderOrigin();
const response = await retryHTTPCall(async () => { const response = await retryHTTPCall(async () => {
const resp = await HTTPService.put( const resp = await HTTPService.put(
`${uploaderOrigin()}/multipart-upload`, `${origin}/multipart-upload`,
filePart, filePart,
null, null,
{ {
@ -214,9 +216,10 @@ class UploadHttpClient {
async completeMultipartUploadV2(completeURL: string, reqBody: any) { async completeMultipartUploadV2(completeURL: string, reqBody: any) {
try { try {
const origin = await uploaderOrigin();
await retryHTTPCall(() => await retryHTTPCall(() =>
HTTPService.post( HTTPService.post(
`${uploaderOrigin()}/multipart-complete`, `${origin}/multipart-complete`,
reqBody, reqBody,
null, null,
{ {

View File

@ -13,12 +13,12 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName, showNavBar } = appContext; const { appName, showNavBar } = appContext;
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [host, setHost] = useState<string | undefined>();
const router = useRouter(); const router = useRouter();
const host = customAPIHost();
useEffect(() => { useEffect(() => {
void customAPIHost().then(setHost);
const user = getData(LS_KEYS.USER); const user = getData(LS_KEYS.USER);
if (user?.email) { if (user?.email) {
router.push(PAGES.VERIFY); router.push(PAGES.VERIFY);

View File

@ -13,12 +13,12 @@ const Page: React.FC<PageProps> = ({ appContext }) => {
const { appName } = appContext; const { appName } = appContext;
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [host, setHost] = useState<string | undefined>();
const router = useRouter(); const router = useRouter();
const host = customAPIHost();
useEffect(() => { useEffect(() => {
void customAPIHost().then(setHost);
const user = getData(LS_KEYS.USER); const user = getData(LS_KEYS.USER);
if (user?.email) { if (user?.email) {
router.push(PAGES.VERIFY); router.push(PAGES.VERIFY);

View File

@ -1,4 +1,5 @@
import { nullToUndefined } from "@/utils/transform"; import { nullToUndefined } from "@/utils/transform";
import { get, set } from "idb-keyval";
/** /**
* Return the origin (scheme, host, port triple) that should be used for making * 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 * This defaults "https://api.ente.io", Ente's production API servers. but can
* be overridden when self hosting or developing (see {@link customAPIOrigin}). * 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 * 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. * Otherwise return undefined.
*/ */
export const customAPIOrigin = () => export const customAPIOrigin = async () => {
nullToUndefined(localStorage.getItem("apiOrigin")) ?? let origin = await get<string>("apiOrigin");
process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? if (!origin) {
undefined; // 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 * 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. * This is useful in places where we indicate the custom origin in the UI.
*/ */
export const customAPIHost = () => { export const customAPIHost = async () => {
const origin = customAPIOrigin(); const origin = await customAPIOrigin();
return origin ? new URL(origin).host : undefined; return origin ? new URL(origin).host : undefined;
}; };
@ -44,8 +57,8 @@ export const customAPIHost = () => {
* this value is set to the {@link customAPIOrigin} itself, effectively * this value is set to the {@link customAPIOrigin} itself, effectively
* bypassing the Cloudflare worker for non-Ente deployments. * bypassing the Cloudflare worker for non-Ente deployments.
*/ */
export const uploaderOrigin = () => export const uploaderOrigin = async () =>
customAPIOrigin() ?? "https://uploader.ente.io"; (await customAPIOrigin()) ?? "https://uploader.ente.io";
/** /**
* Return the origin that serves the accounts app. * Return the origin that serves the accounts app.

View File

@ -10,7 +10,7 @@ import EnteButton from "@ente/shared/components/EnteButton";
import { CircularProgress, Stack, Typography, styled } from "@mui/material"; import { CircularProgress, Stack, Typography, styled } from "@mui/material";
import { t } from "i18next"; import { t } from "i18next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { VerticallyCentered } from "./Container"; import { VerticallyCentered } from "./Container";
import type { DialogBoxAttributesV2 } from "./DialogBoxV2/types"; import type { DialogBoxAttributesV2 } from "./DialogBoxV2/types";
import { genericErrorAttributes } from "./ErrorComponents"; import { genericErrorAttributes } from "./ErrorComponents";
@ -48,7 +48,9 @@ const Header_ = styled("div")`
export const LoginFlowFormFooter: React.FC<React.PropsWithChildren> = ({ export const LoginFlowFormFooter: React.FC<React.PropsWithChildren> = ({
children, children,
}) => { }) => {
const host = customAPIHost(); const [host, setHost] = useState<string | undefined>();
useEffect(() => void customAPIHost().then(setHost), []);
return ( return (
<FormPaperFooter> <FormPaperFooter>