This commit is contained in:
Manav Rathi
2025-07-03 11:54:42 +05:30
parent fd39c78e5d
commit 67c65657a4
15 changed files with 220 additions and 169 deletions

View File

@@ -1,9 +1,8 @@
import "@fontsource-variable/inter";
import { CssBaseline } from "@mui/material";
import { ThemeProvider } from "@mui/material/styles";
import { getData } from "ente-accounts/services/accounts-db";
import { savedLocalUser } from "ente-accounts/services/accounts-db";
import { accountLogout } from "ente-accounts/services/logout";
import type { User } from "ente-accounts/services/user";
import { staticAppTitle } from "ente-base/app";
import { CustomHead } from "ente-base/components/Head";
import {
@@ -32,8 +31,7 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog();
useEffect(() => {
const user = getData("user") as User | undefined | null;
logStartupBanner(user?.id);
logStartupBanner(savedLocalUser()?.id);
}, []);
const logout = useCallback(() => {

View File

@@ -6,8 +6,8 @@ import { useNotification } from "components/utils/hooks-app";
import {
getData,
isLocalStorageAndIndexedDBMismatch,
savedLocalUser,
} from "ente-accounts/services/accounts-db";
import type { User } from "ente-accounts/services/user";
import { isDesktop, staticAppTitle } from "ente-base/app";
import { CenteredRow } from "ente-base/components/containers";
import { CustomHead } from "ente-base/components/Head";
@@ -68,8 +68,7 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const logout = useCallback(() => void photosLogout(), []);
useEffect(() => {
const user = getData("user") as User | undefined | null;
logStartupBanner(user?.id);
logStartupBanner(savedLocalUser()?.id);
void isLocalStorageAndIndexedDBMismatch().then((mismatch) => {
if (mismatch) {
log.error("Logging out (IndexedDB and local storage mismatch)");

View File

@@ -1,5 +1,5 @@
import { getData } from "ente-accounts/services/accounts-db";
import type { LocalUser, User } from "ente-accounts/services/user";
import type { LocalUser, PartialLocalUser } from "ente-accounts/services/user";
import { joinPath } from "ente-base/file-name";
import log from "ente-base/log";
import { type Electron } from "ente-base/types/ipc";
@@ -284,7 +284,7 @@ export const getArchivedFiles = (files: EnteFile[]) => {
};
export const getUserOwnedFiles = (files: EnteFile[]) => {
const user: User = getData("user");
const user: PartialLocalUser = getData("user");
if (!user?.id) {
throw Error("user missing");
}

View File

@@ -1,8 +1,11 @@
import { Input, Stack, TextField, Typography } from "@mui/material";
import { AccountsPageFooter } from "ente-accounts/components/layouts/centered-paper";
import { saveSRPAttributes } from "ente-accounts/services/accounts-db";
import {
savePartialLocalUser,
saveSRPAttributes,
} from "ente-accounts/services/accounts-db";
import { getSRPAttributes } from "ente-accounts/services/srp";
import { savePartialLocalUser, sendOTT } from "ente-accounts/services/user";
import { sendOTT } from "ente-accounts/services/user";
import { LinkButton } from "ente-base/components/LinkButton";
import { LoadingButton } from "ente-base/components/mui/LoadingButton";
import { isMuseumHTTPError } from "ente-base/http";

View File

@@ -16,6 +16,7 @@ import {
import {
saveJustSignedUp,
saveOriginalKeyAttributes,
savePartialLocalUser,
stashReferralSource,
stashSRPSetupAttributes,
} from "ente-accounts/services/accounts-db";
@@ -23,7 +24,6 @@ import { generateSRPSetupAttributes } from "ente-accounts/services/srp";
import {
generateAndSaveInteractiveKeyAttributes,
generateKeysAndAttributes,
savePartialLocalUser,
sendOTT,
type GenerateKeysAndAttributesResult,
} from "ente-accounts/services/user";

View File

@@ -5,11 +5,7 @@ import {
AccountsPageTitle,
} from "ente-accounts/components/layouts/centered-paper";
import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect";
import {
changePassword,
localUser,
type LocalUser,
} from "ente-accounts/services/user";
import { changePassword, type LocalUser } from "ente-accounts/services/user";
import { LinkButton } from "ente-base/components/LinkButton";
import { LoadingIndicator } from "ente-base/components/loaders";
import { deriveKeyInsufficientMemoryErrorMessage } from "ente-base/crypto/types";
@@ -21,12 +17,13 @@ import {
NewPasswordForm,
type NewPasswordFormProps,
} from "../components/NewPasswordForm";
import { savedLocalUser } from "../services/accounts-db";
/**
* A page that allows a user to reset or change their password.
*/
const Page: React.FC = () => {
const [user, setUser] = useState<LocalUser>();
const [user, setUser] = useState<LocalUser | undefined>(undefined);
const router = useRouter();
@@ -34,7 +31,7 @@ const Page: React.FC = () => {
const isReset = router.query.op == "reset";
useEffect(() => {
const user = localUser();
const user = savedLocalUser();
if (user) {
setUser(user);
} else {

View File

@@ -45,7 +45,7 @@ import {
import {
generateAndSaveInteractiveKeyAttributes,
type KeyAttributes,
type User,
type PartialLocalUser,
} from "ente-accounts/services/user";
import { decryptAndStoreToken } from "ente-accounts/utils/helpers";
import { LinkButton } from "ente-base/components/LinkButton";
@@ -81,7 +81,7 @@ const Page: React.FC = () => {
const [srpAttributes, setSrpAttributes] = useState<SRPAttributes>();
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
const [user, setUser] = useState<User>();
const [user, setUser] = useState<PartialLocalUser>();
const [passkeyVerificationData, setPasskeyVerificationData] = useState<
{ passkeySessionID: string; url: string } | undefined
>();
@@ -127,7 +127,7 @@ const Page: React.FC = () => {
useEffect(() => {
const main = async () => {
const user: User = getData("user");
const user: PartialLocalUser = getData("user");
if (!user?.email) {
void router.push("/");
return;
@@ -280,7 +280,7 @@ const Page: React.FC = () => {
await decryptAndStoreToken(keyAttributes, masterKey);
try {
let srpAttributes = savedSRPAttributes();
if (!srpAttributes && user) {
if (!srpAttributes && user?.email) {
srpAttributes = await getSRPAttributes(user.email);
if (srpAttributes) {
saveSRPAttributes(srpAttributes);

View File

@@ -16,7 +16,7 @@ import {
generateSRPSetupAttributes,
setupSRP,
} from "ente-accounts/services/srp";
import type { User } from "ente-accounts/services/user";
import type { PartialLocalUser } from "ente-accounts/services/user";
import {
generateAndSaveInteractiveKeyAttributes,
generateKeysAndAttributes,
@@ -42,14 +42,14 @@ import {
const Page: React.FC = () => {
const { logout, showMiniDialog } = useBaseContext();
const [user, setUser] = useState<User>();
const [user, setUser] = useState<PartialLocalUser>();
const [openRecoveryKey, setOpenRecoveryKey] = useState(false);
const [loading, setLoading] = useState(true);
const router = useRouter();
useEffect(() => {
const user: User = getData("user");
const user: PartialLocalUser = getData("user");
setUser(user);
if (!user?.token) {
void router.push("/");
@@ -109,7 +109,7 @@ const Page: React.FC = () => {
<AccountsPageContents>
<AccountsPageTitle>{t("set_password")}</AccountsPageTitle>
<NewPasswordForm
userEmail={user.email}
userEmail={user.email! /* TODO(RE):*/}
submitButtonTitle={t("set_password")}
onSubmit={handleSubmit}
/>

View File

@@ -9,7 +9,10 @@ import {
} from "ente-accounts/services/accounts-db";
import { recoveryKeyFromMnemonic } from "ente-accounts/services/recovery-key";
import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect";
import type { KeyAttributes, User } from "ente-accounts/services/user";
import type {
KeyAttributes,
PartialLocalUser,
} from "ente-accounts/services/user";
import { sendOTT } from "ente-accounts/services/user";
import { decryptAndStoreToken } from "ente-accounts/utils/helpers";
import { LinkButton } from "ente-base/components/LinkButton";
@@ -38,7 +41,7 @@ const Page: React.FC = () => {
const router = useRouter();
useEffect(() => {
const user: User = getData("user");
const user: PartialLocalUser = getData("user");
if (!user?.email) {
void router.push("/");
return;

View File

@@ -4,7 +4,7 @@ import {
saveKeyAttributes,
setLSUser,
} from "ente-accounts/services/accounts-db";
import type { User } from "ente-accounts/services/user";
import type { PartialLocalUser } from "ente-accounts/services/user";
import { verifyTwoFactor } from "ente-accounts/services/user";
import { LinkButton } from "ente-base/components/LinkButton";
import { useBaseContext } from "ente-base/context";
@@ -27,7 +27,7 @@ const Page: React.FC = () => {
const router = useRouter();
useEffect(() => {
const user: User = getData("user");
const user: PartialLocalUser = getData("user");
if (!user?.email || !user.twoFactorSessionID) {
void router.push("/");
} else if (

View File

@@ -28,7 +28,7 @@ import {
unstashRedirect,
} from "ente-accounts/services/redirect";
import { getSRPAttributes, setupSRP } from "ente-accounts/services/srp";
import type { User } from "ente-accounts/services/user";
import type { PartialLocalUser } from "ente-accounts/services/user";
import {
putUserKeyAttributes,
sendOTT,
@@ -69,7 +69,7 @@ const Page: React.FC = () => {
useEffect(() => {
const main = async () => {
const user: User = getData("user");
const user: PartialLocalUser = getData("user");
const redirect = await redirectionIfNeeded(user);
if (redirect) {
@@ -253,7 +253,7 @@ export default Page;
*
* @returns The slug to redirect to, if needed.
*/
const redirectionIfNeeded = async (user: User | undefined) => {
const redirectionIfNeeded = async (user: PartialLocalUser | undefined) => {
const email = user?.email;
if (!email) {
return "/";

View File

@@ -1,18 +1,5 @@
import { getKVS, removeKV, setKV } from "ente-base/kv";
import log from "ente-base/log";
import { nullToUndefined } from "ente-utils/transform";
import { z } from "zod/v4";
import {
RemoteSRPAttributes,
SRPSetupAttributes,
type SRPAttributes,
} from "./srp";
import { RemoteKeyAttributes, type KeyAttributes } from "./user";
export type LocalStorageKey = "user";
/**
* [Note: Accounts DB]
* @file [Note: Accounts DB]
*
* The accounts package stores various state both during the login / signup
* flow, and post login to identify the logged in user.
@@ -29,6 +16,145 @@ export type LocalStorageKey = "user";
* - "originalKeyAttributes"
* - "srpAttributes"
*/
import { getKVS, removeKV, setKV } from "ente-base/kv";
import log from "ente-base/log";
import { getAuthToken } from "ente-base/token";
import { nullToUndefined } from "ente-utils/transform";
import { z } from "zod/v4";
import {
RemoteSRPAttributes,
SRPSetupAttributes,
type SRPAttributes,
} from "./srp";
import {
RemoteKeyAttributes,
type KeyAttributes,
type LocalUser,
} from "./user";
/**
* The local storage data about the user before login or signup is complete.
*
* [Note: Partial local user]
*
* During login or signup, the user object exists in various partial states in
* local storage.
*
* - Initially, there is no user object in local storage.
*
* - When the user enters their email, the email property of the stored object
* is set, but nothing else.
*
* - After they verify their password, we have two cases: if second factor
* verification is not set, and when it is set.
*
* - If second factor verification is not set, then after verifying their
* password their {@link id} and {@link encryptedToken} will get filled in,
* and {@link isTwoFactorEnabled} will be set to false.
*
* - If they have second factor verification set, then after verifying their
* password {@link isTwoFactorEnabled} and {@link twoFactorSessionID} will
* also get filled in. Once they verify their TOTP based second factor, their
* {@link id} and {@link encryptedToken} will also get filled in.
*
* - As the login or signup sequence completes, a {@link token} obtained from
* the {@link encryptedToken} will be written out, and the
* {@link encryptedToken} cleared since it is not needed anymore.
*
* So while the underlying storage is the same, we offer two APIs for code to
* obtain the user:
*
* - Before login is complete, or when it is unknown if login is complete or
* not, then {@link savedPartialLocalUser} can be used to obtain a
* {@link PartialLocalUser} with all of its properties set to be optional (and
* some additional properties not available in the regular user object).
*
* - When we know that the login has completed, we can use either
* {@link savedLocalUser} (which returns `undefined` if our assumption is
* false) or {@link ensureSavedLocalUser} (which throws if our assumption is
* false) to obtain an object with all the properties expected to be present
* for a locally persisted user set to be required.
*/
export interface PartialLocalUser {
id?: number;
email?: string;
token?: string;
encryptedToken?: string;
isTwoFactorEnabled?: boolean;
twoFactorSessionID?: string;
}
const PartialLocalUser = z.object({
id: z.number().nullish().transform(nullToUndefined),
email: z.string().nullish().transform(nullToUndefined),
token: z.string().nullish().transform(nullToUndefined),
encryptedToken: z.string().nullish().transform(nullToUndefined),
isTwoFactorEnabled: z.boolean().nullish().transform(nullToUndefined),
twoFactorSessionID: z.string().nullish().transform(nullToUndefined),
});
/**
* Zod schema for the {@link LocalUser} TypeScript type.
*
* The type itself is in `user.ts`.
*/
const LocalUser = z.object({
id: z.number(),
email: z.string(),
token: z.string(),
});
/**
* Return the local storage value of the user's data.
*
* This function is meant to be called during the login or signup sequence.
* After the user is logged in, use {@link savedLocalUser} or
* {@link ensureLocalUser} instead.
*
* Use {@link savePartialLocalUser} to updated the saved value.
*/
export const savedPartialLocalUser = (): PartialLocalUser | undefined => {
const jsonString = localStorage.getItem("user");
if (!jsonString) return undefined;
return PartialLocalUser.parse(JSON.parse(jsonString));
};
/**
* Save the users data as we accrue it during the signup or login flow.
*
* See: [Note: Partial local user].
*
* TODO: WARNING: This does not update the KV token. The idea is to gradually
* move over uses of setLSUser to this while explicitly setting the KV token
* where needed.
*/
export const savePartialLocalUser = (partialLocalUser: Partial<LocalUser>) =>
localStorage.setItem("user", JSON.stringify(partialLocalUser));
/**
* Return data about the logged-in user, if someone is indeed logged in.
* Otherwise return `undefined`.
*
* The user's data is stored in the browser's localStorage. Thus, this function
* only works from the main thread, not from web workers since local storage is
* not accessible to web workers.
*
* There is no setter corresponding to this function since this is only a view
* on data saved using {@link savePartialLocalUser}.
*
* See: [Note: Partial local user] for more about the whole shebang.
*/
export const savedLocalUser = (): LocalUser | undefined => {
const jsonString = localStorage.getItem("user");
if (!jsonString) return undefined;
// We might have some data, but not all of it. So do a non-throwing parse.
const { success, data } = LocalUser.safeParse(JSON.parse(jsonString));
return success ? data : undefined;
};
export type LocalStorageKey = "user";
export const getData = (key: LocalStorageKey) => {
try {
if (
@@ -110,16 +236,8 @@ export const migrateKVToken = async (user: unknown) => {
* This acts a sanity check on IndexedDB by ensuring that if the user has a
* token in local storage, then it should also be present in IndexedDB.
*/
export const isLocalStorageAndIndexedDBMismatch = async () => {
const oldLSUser = getData("user");
return (
oldLSUser &&
typeof oldLSUser == "object" &&
"token" in oldLSUser &&
typeof oldLSUser.token == "string" &&
!(await getKVS("token"))
);
};
export const isLocalStorageAndIndexedDBMismatch = async () =>
savedPartialLocalUser()?.token && !(await getAuthToken());
/**
* Return the user's {@link KeyAttributes} if they are present in local storage.
@@ -155,6 +273,9 @@ export const saveKeyAttributes = (keyAttributes: KeyAttributes) =>
* either freshly generated (if the user signed up on this client) or were
* fetched from remote (otherwise).
*
* > NOTE: Currently the code does not guarantee that savedOriginalKeyAttributes
* > will always be set when savedKeyAttributes is set.
*
* In contrast, the regular key attributes get overwritten by the local only
* interactive key attributes for the user's convenience. See the documentation
* of {@link generateAndSaveInteractiveKeyAttributes} for more details.

View File

@@ -1,9 +1,11 @@
import {
getData,
savedKeyAttributes,
savedLocalUser,
saveKeyAttributes,
saveSRPAttributes,
setLSUser,
type PartialLocalUser,
} from "ente-accounts/services/accounts-db";
import {
generateSRPSetupAttributes,
@@ -36,121 +38,51 @@ import { nullToUndefined } from "ente-utils/transform";
import { z } from "zod/v4";
import { getUserRecoveryKey, recoveryKeyFromMnemonic } from "./recovery-key";
export interface User {
id: number;
email: string;
token: string;
encryptedToken: string;
isTwoFactorEnabled: boolean;
twoFactorSessionID: string;
}
// TODO(RE): Temporary re-export
export type { PartialLocalUser };
/**
* The local storage data about the user after they've logged in.
* The locally persisted data we have about the user after they've logged in.
*
* This type arguably belongs to accounts-db (since that's what persists it and
* its shadow alias, {@link PartialLocalUser}), but since most code that will
* need this will need this after login has completed, and will be using the
* {@link ensureLocalUser} method below, we keep this in the same file to reduce
* the need to import the type from a separate file.
*/
const LocalUser = z.object({
export interface LocalUser {
/**
* The user's ID.
*/
id: z.number(),
id: number;
/**
* The user's email.
* The email associated with the user's Ente account.
*/
email: z.string(),
email: string;
/**
* The user's (plaintext) auth token.
*
* It is used for making API calls on their behalf, by passing this token as
* the value of the X-Auth-Token header in the HTTP request.
*
* Deprecated, use `getAuthToken()` instead (which fetches it from IDB).
* Usually you shouldn't be needing to access this property; instead use
* {@link getAuthToken()} which is kept in sync with this value, and lives
* in IndexedDB and thus can also be used in web workers.
*/
token: z.string(),
});
token: string;
}
/**
* The local storage data about the user after they've logged in.
*/
export type LocalUser = z.infer<typeof LocalUser>;
/**
* The local storage data about the user before login or signup is complete.
* Return the currently logged in {@link LocalUser}, throwing it the user is not
* logged in.
*
* [Note: Partial local user]
*
* During login or signup, the user object exists in various partial states in
* local storage.
*
* - Initially, there is no user object in local storage.
*
* - When the user enters their email, the email property of the stored object
* is set, but nothing else.
*
* - After they verify their password, we have two cases: if second factor
* verification is not set, and when it is set.
*
* - If second factor verification is not set, then after verifying their
* password their {@link id} and {@link encryptedToken} will get filled in,
* and {@link isTwoFactorEnabled} will be set to false.
*
* - If they have second factor verification set, then after verifying their
* password {@link isTwoFactorEnabled} and {@link twoFactorSessionID} will
* also get filled in. Once they verify their TOTP based second factor, their
* {@link id} and {@link encryptedToken} will also get filled in.
*
* So while the underlying storage is the same, we offer two APIs for code to
* obtain the user:
*
* - Before login is complete, or when it is unknown if login is complete or
* not, then {@link partialLocalUser} can be used to obtain a
* {@link LocalUser} with all of its properties set to be optional.
*
* - When we know that the login has completed, we can use either
* {@link localUser} (which returns `undefined` if our presumption is false)
* or {@link ensureLocalUser} (which throws if our presumption is false) to
* obtain an object with all the properties expected to be present for a
* locally persisted user set to be required.
*/
export const partialLocalUser = (): Partial<LocalUser> | undefined => {
// TODO: duplicate of getData("user")
const s = localStorage.getItem("user");
if (!s) return undefined;
return LocalUser.partial().parse(JSON.parse(s));
};
/**
* Save the users data as we accrue it during the signup or login flow.
*
* See: [Note: Partial local user].
*
* TODO: WARNING: This does not update the KV token. The idea is to gradually
* move over uses of setLSUser to this while explicitly setting the KV token
* where needed.
*/
export const savePartialLocalUser = (partialLocalUser: Partial<LocalUser>) =>
localStorage.setItem("user", JSON.stringify(partialLocalUser));
/**
* Return the logged-in user, if someone is indeed logged in. Otherwise return
* `undefined`.
*
* The user's data is stored in the browser's localStorage. Thus, this function
* only works from the main thread, not from web workers (local storage is not
* accessible to web workers).
*/
export const localUser = (): LocalUser | undefined => {
// TODO: duplicate of getData("user")
const s = localStorage.getItem("user");
if (!s) return undefined;
const { success, data } = LocalUser.safeParse(JSON.parse(s));
return success ? data : undefined;
};
/**
* A wrapper over {@link localUser} with that throws if no one is logged in.
* This is a wrapper over the {@link savedLocalUser} function that throws if no
* one is logged in. A more appropriate name for this function, keeping in line
* with the conventions the other methods follow, would've been
* {@link ensureSavedLocalUser}. The shorter name is for readability.
*/
export const ensureLocalUser = (): LocalUser =>
ensureExpectedLoggedInValue(localUser());
ensureExpectedLoggedInValue(savedLocalUser());
/**
* A function throws an error if a value that is expected to be truthy when the

View File

@@ -1,4 +1,4 @@
import type { User } from "ente-accounts/services/user";
import { type LocalUser } from "ente-accounts/services/user";
import {
groupFilesByCollectionID,
sortFiles,
@@ -108,15 +108,15 @@ export interface GalleryState {
/*--< Mostly static state >--*/
/**
* The logged in {@link User}.
* The logged in {@link LocalUser}.
*
* This is expected to be undefined only for a brief duration until the code
* for the initial "mount" runs (If we're not logged in, then the gallery
* will redirect the user to an appropriate authentication page).
*/
user: User | undefined;
user: LocalUser | undefined;
/**
* Family plan related information for the logged in {@link User}.
* Family plan related information for the logged in {@link LocalUser}.
*/
familyData: FamilyData | undefined;
@@ -449,7 +449,7 @@ export interface GalleryState {
export type GalleryAction =
| {
type: "mount";
user: User;
user: LocalUser;
familyData: FamilyData | undefined;
collections: Collection[];
collectionFiles: EnteFile[];
@@ -1225,7 +1225,7 @@ const deriveArchivedFileIDs = (
* Compute favorite file IDs from their dependencies.
*/
const deriveFavoriteFileIDs = (
user: User,
user: LocalUser,
collections: GalleryState["collections"],
collectionFiles: GalleryState["collectionFiles"],
unsyncedFavoriteUpdates: GalleryState["unsyncedFavoriteUpdates"],
@@ -1278,7 +1278,7 @@ const createFileCollectionIDs = (files: EnteFile[]) =>
*/
const deriveNormalCollectionSummaries = (
normalCollections: Collection[],
user: User,
user: LocalUser,
trashItems: GalleryState["trashItems"],
collectionFiles: GalleryState["collectionFiles"],
hiddenFileIDs: GalleryState["hiddenFileIDs"],
@@ -1378,7 +1378,7 @@ const pseudoCollectionOptionsForLatestFileAndCount = (
*/
const deriveHiddenCollectionSummaries = (
hiddenCollections: Collection[],
user: User,
user: LocalUser,
collectionFiles: GalleryState["collectionFiles"],
) => {
const hiddenCollectionSummaries = createCollectionSummaries(
@@ -1414,7 +1414,7 @@ const deriveUncategorizedCollectionSummaryID = (
PseudoCollectionID.uncategorizedPlaceholder;
const createCollectionSummaries = (
user: User,
user: LocalUser,
collections: Collection[],
collectionFiles: EnteFile[],
) => {
@@ -1968,7 +1968,7 @@ const enqueuePendingSearchSuggestionsIfNeeded = (
* users who have shared a collection with the user.
*/
const constructUserIDToEmailMap = (
user: User,
user: LocalUser,
collections: GalleryState["collections"],
): Map<number, string> => {
const userIDToEmail = new Map<number, string>();
@@ -1990,7 +1990,7 @@ const constructUserIDToEmailMap = (
* are trying to share albums with specific users.
*/
const createShareSuggestionEmails = (
user: User,
user: LocalUser,
familyData: FamilyData | undefined,
collections: Collection[],
): string[] => [

View File

@@ -2,7 +2,7 @@
* @file Storage (in-memory, local, remote) and update of various settings.
*/
import { partialLocalUser } from "ente-accounts/services/user";
import { savedPartialLocalUser } from "ente-accounts/services/accounts-db";
import { isDevBuild } from "ente-base/env";
import log from "ente-base/log";
import { updateShouldDisableCFUploadProxy } from "ente-gallery/services/upload";
@@ -195,10 +195,8 @@ const setSettingsSnapshot = (snapshot: Settings) => {
*/
export const isDevBuildAndUser = () => isDevBuild && isDevUserViaEmail();
const isDevUserViaEmail = () => {
const user = partialLocalUser();
return !!user?.email?.endsWith("@ente.io");
};
const isDevUserViaEmail = () =>
!!savedPartialLocalUser()?.email?.endsWith("@ente.io");
/**
* Persist the user's map enabled preference both locally and on remote.