Switch to prettier defaults (#1656)
This commit is contained in:
commit
8b1d33e83d
669 changed files with 12321 additions and 12054 deletions
3
.github/.prettierrc.json
vendored
3
.github/.prettierrc.json
vendored
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"tabWidth": 4
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
{
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"bracketSameLine": true
|
||||
"plugins": ["prettier-plugin-organize-imports"]
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ module.exports = {
|
|||
// This is required here to ensure desktop picks the right eslint config, where this app is
|
||||
// packaged as a submodule.
|
||||
root: true,
|
||||
extends: ['@ente/eslint-config'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ["@ente/eslint-config"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json',
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js', 'out'],
|
||||
ignorePatterns: [".eslintrc.js", "out"],
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const nextConfigBase = require('@/next/next.config.base.js');
|
||||
const nextConfigBase = require("@/next/next.config.base.js");
|
||||
|
||||
module.exports = {
|
||||
...nextConfigBase,
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { initSentry } from '@ente/shared/sentry/config/sentry.config.base';
|
||||
import { initSentry } from "@ente/shared/sentry/config/sentry.config.base";
|
||||
|
||||
initSentry('https://0f7214c7feb9b1dd2fed5db09b42fa1b@sentry.ente.io/5');
|
||||
initSentry("https://0f7214c7feb9b1dd2fed5db09b42fa1b@sentry.ente.io/5");
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import { CacheProvider } from '@emotion/react';
|
||||
import { APPS, APP_TITLES } from '@ente/shared/apps/constants';
|
||||
import { EnteAppProps } from '@ente/shared/apps/types';
|
||||
import { Overlay } from '@ente/shared/components/Container';
|
||||
import DialogBoxV2 from '@ente/shared/components/DialogBoxV2';
|
||||
import { setupI18n } from "@/ui/i18n";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import { APPS, APP_TITLES } from "@ente/shared/apps/constants";
|
||||
import { EnteAppProps } from "@ente/shared/apps/types";
|
||||
import { Overlay } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import {
|
||||
DialogBoxAttributesV2,
|
||||
SetDialogBoxAttributesV2,
|
||||
} from '@ente/shared/components/DialogBoxV2/types';
|
||||
import EnteSpinner from '@ente/shared/components/EnteSpinner';
|
||||
import AppNavbar from '@ente/shared/components/Navbar/app';
|
||||
import { useLocalState } from '@ente/shared/hooks/useLocalState';
|
||||
import { setupI18n } from '@/ui/i18n';
|
||||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import { LS_KEYS, getData } from '@ente/shared/storage/localStorage';
|
||||
import { getTheme } from '@ente/shared/themes';
|
||||
import { THEME_COLOR } from '@ente/shared/themes/constants';
|
||||
import createEmotionCache from '@ente/shared/themes/createEmotionCache';
|
||||
import { CssBaseline, useMediaQuery } from '@mui/material';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import { createContext, useEffect, useState } from 'react';
|
||||
import 'styles/global.css';
|
||||
} from "@ente/shared/components/DialogBoxV2/types";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import createEmotionCache from "@ente/shared/themes/createEmotionCache";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import "styles/global.css";
|
||||
|
||||
interface AppContextProps {
|
||||
isMobile: boolean;
|
||||
|
@ -50,7 +50,7 @@ export default function App(props: EnteAppProps) {
|
|||
|
||||
const showNavBar = (show: boolean) => setShowNavBar(show);
|
||||
|
||||
const isMobile = useMediaQuery('(max-width:428px)');
|
||||
const isMobile = useMediaQuery("(max-width:428px)");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -70,14 +70,14 @@ export default function App(props: EnteAppProps) {
|
|||
const pkg = getData(LS_KEYS.CLIENT_PACKAGE);
|
||||
if (!pkg) return;
|
||||
HTTPService.setHeaders({
|
||||
'X-Client-Package': pkg.name,
|
||||
"X-Client-Package": pkg.name,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
router.events.on('routeChangeComplete', setupPackageName);
|
||||
router.events.on("routeChangeComplete", setupPackageName);
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', setupPackageName);
|
||||
router.events.off("routeChangeComplete", setupPackageName);
|
||||
};
|
||||
}, [router.events]);
|
||||
|
||||
|
@ -111,17 +111,19 @@ export default function App(props: EnteAppProps) {
|
|||
showNavBar,
|
||||
setDialogBoxAttributesV2:
|
||||
setDialogBoxAttributesV2 as any,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{!isI18nReady && (
|
||||
<Overlay
|
||||
sx={(theme) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 2000,
|
||||
backgroundColor: (theme as any).colors
|
||||
.background.base,
|
||||
})}>
|
||||
})}
|
||||
>
|
||||
<EnteSpinner />
|
||||
</Overlay>
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import DocumentPage, {
|
||||
EnteDocumentProps,
|
||||
} from '@ente/shared/next/pages/_document';
|
||||
} from "@ente/shared/next/pages/_document";
|
||||
|
||||
export default function Document(props: EnteDocumentProps) {
|
||||
return <DocumentPage {...props} />;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { VerticallyCentered } from '@ente/shared/components/Container';
|
||||
import EnteSpinner from '@ente/shared/components/EnteSpinner';
|
||||
import { ACCOUNTS_PAGES } from '@ente/shared/constants/pages';
|
||||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { LS_KEYS, getData, setData } from '@ente/shared/storage/localStorage';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { ACCOUNTS_PAGES } from "@ente/shared/constants/pages";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const AccountHandoff = () => {
|
||||
const router = useRouter();
|
||||
|
@ -16,26 +16,26 @@ const AccountHandoff = () => {
|
|||
|
||||
router.push(ACCOUNTS_PAGES.PASSKEYS);
|
||||
} catch (e) {
|
||||
logError(e, 'Failed to deserialize and set passed user data');
|
||||
logError(e, "Failed to deserialize and set passed user data");
|
||||
router.push(ACCOUNTS_PAGES.LOGIN);
|
||||
}
|
||||
};
|
||||
|
||||
const getClientPackageName = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const pkg = urlParams.get('package');
|
||||
const pkg = urlParams.get("package");
|
||||
if (!pkg) return;
|
||||
setData(LS_KEYS.CLIENT_PACKAGE, { name: pkg });
|
||||
HTTPService.setHeaders({
|
||||
'X-Client-Package': pkg,
|
||||
"X-Client-Package": pkg,
|
||||
});
|
||||
};
|
||||
|
||||
const extractAccountsToken = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get('token');
|
||||
const token = urlParams.get("token");
|
||||
if (!token) {
|
||||
throw new Error('token not found');
|
||||
throw new Error("token not found");
|
||||
}
|
||||
|
||||
const user = getData(LS_KEYS.USER) || {};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import CredentialPage from '@ente/accounts/pages/credentials';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from '../_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import CredentialPage from "@ente/accounts/pages/credentials";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext } from "react";
|
||||
import { AppContext } from "../_app";
|
||||
|
||||
export default function Credential() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import GeneratePage from '@ente/accounts/pages/generate';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import GeneratePage from "@ente/accounts/pages/generate";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Generate() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const Index = () => {
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
router.push('/login');
|
||||
router.push("/login");
|
||||
}, []);
|
||||
return <></>;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import LoginPage from '@ente/accounts/pages/login';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from '../_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import LoginPage from "@ente/accounts/pages/login";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext } from "react";
|
||||
import { AppContext } from "../_app";
|
||||
|
||||
export default function Login() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import DialogBoxV2 from '@ente/shared/components/DialogBoxV2';
|
||||
import EnteButton from '@ente/shared/components/EnteButton';
|
||||
import { Button, Stack, Typography } from '@mui/material';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext, useState } from 'react';
|
||||
import { deletePasskey } from 'services/passkeysService';
|
||||
import { PasskeysContext } from '.';
|
||||
import { t } from 'i18next';
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import EnteButton from "@ente/shared/components/EnteButton";
|
||||
import { Button, Stack, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext, useState } from "react";
|
||||
import { deletePasskey } from "services/passkeysService";
|
||||
import { PasskeysContext } from ".";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
|
@ -41,27 +41,30 @@ const DeletePasskeyModal = (props: IProps) => {
|
|||
onClose={props.onClose}
|
||||
fullScreen={isMobile}
|
||||
attributes={{
|
||||
title: t('DELETE_PASSKEY'),
|
||||
title: t("DELETE_PASSKEY"),
|
||||
secondary: {
|
||||
action: props.onClose,
|
||||
text: t('CANCEL'),
|
||||
text: t("CANCEL"),
|
||||
},
|
||||
}}>
|
||||
<Stack spacing={'8px'}>
|
||||
<Typography>{t('DELETE_PASSKEY_CONFIRMATION')}</Typography>
|
||||
}}
|
||||
>
|
||||
<Stack spacing={"8px"}>
|
||||
<Typography>{t("DELETE_PASSKEY_CONFIRMATION")}</Typography>
|
||||
<EnteButton
|
||||
type="submit"
|
||||
size="large"
|
||||
color="critical"
|
||||
loading={loading}
|
||||
onClick={doDelete}>
|
||||
{t('DELETE')}
|
||||
onClick={doDelete}
|
||||
>
|
||||
{t("DELETE")}
|
||||
</EnteButton>
|
||||
<Button
|
||||
size="large"
|
||||
color={'secondary'}
|
||||
onClick={props.onClose}>
|
||||
{t('CANCEL')}
|
||||
color={"secondary"}
|
||||
onClick={props.onClose}
|
||||
>
|
||||
{t("CANCEL")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</DialogBoxV2>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { EnteDrawer } from '@ente/shared/components/EnteDrawer';
|
||||
import { PasskeysContext } from '.';
|
||||
import { Stack } from '@mui/material';
|
||||
import Titlebar from '@ente/shared/components/Titlebar';
|
||||
import { MenuItemGroup } from '@ente/shared/components/Menu/MenuItemGroup';
|
||||
import { EnteMenuItem } from '@ente/shared/components/Menu/EnteMenuItem';
|
||||
import { useContext, useState } from 'react';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import MenuItemDivider from '@ente/shared/components/Menu/MenuItemDivider';
|
||||
import DeletePasskeyModal from './DeletePasskeyModal';
|
||||
import RenamePasskeyModal from './RenamePasskeyModal';
|
||||
import InfoItem from '@ente/shared/components/Info/InfoItem';
|
||||
import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
|
||||
import { formatDateTimeFull } from '@ente/shared/time/format';
|
||||
import { t } from 'i18next';
|
||||
import { EnteDrawer } from "@ente/shared/components/EnteDrawer";
|
||||
import InfoItem from "@ente/shared/components/Info/InfoItem";
|
||||
import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem";
|
||||
import MenuItemDivider from "@ente/shared/components/Menu/MenuItemDivider";
|
||||
import { MenuItemGroup } from "@ente/shared/components/Menu/MenuItemGroup";
|
||||
import Titlebar from "@ente/shared/components/Titlebar";
|
||||
import { formatDateTimeFull } from "@ente/shared/time/format";
|
||||
import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import { Stack } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { useContext, useState } from "react";
|
||||
import { PasskeysContext } from ".";
|
||||
import DeletePasskeyModal from "./DeletePasskeyModal";
|
||||
import RenamePasskeyModal from "./RenamePasskeyModal";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
|
@ -33,10 +33,11 @@ const ManagePasskeyDrawer = (props: IProps) => {
|
|||
open={props.open}
|
||||
onClose={() => {
|
||||
setShowPasskeyDrawer(false);
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{selectedPasskey && (
|
||||
<>
|
||||
<Stack spacing={'4px'} py={'12px'}>
|
||||
<Stack spacing={"4px"} py={"12px"}>
|
||||
<Titlebar
|
||||
onClose={() => {
|
||||
setShowPasskeyDrawer(false);
|
||||
|
@ -48,11 +49,11 @@ const ManagePasskeyDrawer = (props: IProps) => {
|
|||
/>
|
||||
<InfoItem
|
||||
icon={<CalendarTodayIcon />}
|
||||
title={t('CREATED_AT')}
|
||||
title={t("CREATED_AT")}
|
||||
caption={
|
||||
`${formatDateTimeFull(
|
||||
selectedPasskey.createdAt / 1000
|
||||
)}` || ''
|
||||
selectedPasskey.createdAt / 1000,
|
||||
)}` || ""
|
||||
}
|
||||
loading={!selectedPasskey}
|
||||
hideEditOption
|
||||
|
@ -63,7 +64,7 @@ const ManagePasskeyDrawer = (props: IProps) => {
|
|||
setShowRenamePasskeyModal(true);
|
||||
}}
|
||||
startIcon={<EditIcon />}
|
||||
label={'Rename Passkey'}
|
||||
label={"Rename Passkey"}
|
||||
/>
|
||||
<MenuItemDivider />
|
||||
<EnteMenuItem
|
||||
|
@ -71,7 +72,7 @@ const ManagePasskeyDrawer = (props: IProps) => {
|
|||
setShowDeletePasskeyModal(true);
|
||||
}}
|
||||
startIcon={<DeleteIcon />}
|
||||
label={'Delete Passkey'}
|
||||
label={"Delete Passkey"}
|
||||
color="critical"
|
||||
/>
|
||||
</MenuItemGroup>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { EnteMenuItem } from '@ente/shared/components/Menu/EnteMenuItem';
|
||||
import { Passkey } from 'types/passkey';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import { useContext } from 'react';
|
||||
import { PasskeysContext } from '.';
|
||||
import KeyIcon from '@mui/icons-material/Key';
|
||||
import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import KeyIcon from "@mui/icons-material/Key";
|
||||
import { useContext } from "react";
|
||||
import { Passkey } from "types/passkey";
|
||||
import { PasskeysContext } from ".";
|
||||
|
||||
interface IProps {
|
||||
passkey: Passkey;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { MenuItemGroup } from '@ente/shared/components/Menu/MenuItemGroup';
|
||||
import { Passkey } from 'types/passkey';
|
||||
import PasskeyListItem from './PasskeyListItem';
|
||||
import MenuItemDivider from '@ente/shared/components/Menu/MenuItemDivider';
|
||||
import { Fragment } from 'react';
|
||||
import MenuItemDivider from "@ente/shared/components/Menu/MenuItemDivider";
|
||||
import { MenuItemGroup } from "@ente/shared/components/Menu/MenuItemGroup";
|
||||
import { Fragment } from "react";
|
||||
import { Passkey } from "types/passkey";
|
||||
import PasskeyListItem from "./PasskeyListItem";
|
||||
|
||||
interface IProps {
|
||||
passkeys: Passkey[];
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import DialogBoxV2 from '@ente/shared/components/DialogBoxV2';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { PasskeysContext } from '.';
|
||||
import SingleInputForm from '@ente/shared/components/SingleInputForm';
|
||||
import { t } from 'i18next';
|
||||
import { renamePasskey } from 'services/passkeysService';
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import SingleInputForm from "@ente/shared/components/SingleInputForm";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import { renamePasskey } from "services/passkeysService";
|
||||
import { PasskeysContext } from ".";
|
||||
|
||||
interface IProps {
|
||||
open: boolean;
|
||||
|
@ -34,17 +34,18 @@ const RenamePasskeyModal = (props: IProps) => {
|
|||
onClose={props.onClose}
|
||||
fullScreen={isMobile}
|
||||
attributes={{
|
||||
title: t('RENAME_PASSKEY'),
|
||||
title: t("RENAME_PASSKEY"),
|
||||
secondary: {
|
||||
action: props.onClose,
|
||||
text: t('CANCEL'),
|
||||
text: t("CANCEL"),
|
||||
},
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<SingleInputForm
|
||||
initialValue={selectedPasskey?.friendlyName}
|
||||
callback={onSubmit}
|
||||
placeholder={t('ENTER_PASSKEY_NAME')}
|
||||
buttonText={t('RENAME')}
|
||||
placeholder={t("ENTER_PASSKEY_NAME")}
|
||||
buttonText={t("RENAME")}
|
||||
fieldType="text"
|
||||
secondaryButtonAction={props.onClose}
|
||||
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import PasskeysFinishPage from '@ente/accounts/pages/passkeys/finish';
|
||||
import PasskeysFinishPage from "@ente/accounts/pages/passkeys/finish";
|
||||
const PasskeysFinish = () => {
|
||||
return <PasskeysFinishPage />;
|
||||
};
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { APPS, CLIENT_PACKAGE_NAMES } from '@ente/shared/apps/constants';
|
||||
import { APPS, CLIENT_PACKAGE_NAMES } from "@ente/shared/apps/constants";
|
||||
import {
|
||||
CenteredFlex,
|
||||
VerticallyCentered,
|
||||
} from '@ente/shared/components/Container';
|
||||
import EnteButton from '@ente/shared/components/EnteButton';
|
||||
import EnteSpinner from '@ente/shared/components/EnteSpinner';
|
||||
import FormPaper from '@ente/shared/components/Form/FormPaper';
|
||||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { LS_KEYS, setData } from '@ente/shared/storage/localStorage';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { t } from 'i18next';
|
||||
import _sodium from 'libsodium-wrappers';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
} from "@ente/shared/components/Container";
|
||||
import EnteButton from "@ente/shared/components/EnteButton";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { LS_KEYS, setData } from "@ente/shared/storage/localStorage";
|
||||
import InfoIcon from "@mui/icons-material/Info";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import _sodium from "libsodium-wrappers";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
BeginPasskeyAuthenticationResponse,
|
||||
beginPasskeyAuthentication,
|
||||
finishPasskeyAuthentication,
|
||||
} from 'services/passkeysService';
|
||||
} from "services/passkeysService";
|
||||
|
||||
const PasskeysFlow = () => {
|
||||
const [errored, setErrored] = useState(false);
|
||||
|
@ -32,18 +32,18 @@ const PasskeysFlow = () => {
|
|||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// get redirect from the query params
|
||||
const redirect = searchParams.get('redirect') as string;
|
||||
const redirect = searchParams.get("redirect") as string;
|
||||
|
||||
const redirectURL = new URL(redirect);
|
||||
if (process.env.NEXT_PUBLIC_DISABLE_REDIRECT_CHECK !== 'true') {
|
||||
if (process.env.NEXT_PUBLIC_DISABLE_REDIRECT_CHECK !== "true") {
|
||||
if (
|
||||
redirect !== '' &&
|
||||
redirect !== "" &&
|
||||
!(
|
||||
redirectURL.host.endsWith('.ente.io') ||
|
||||
redirectURL.host.endsWith('bada-frame.pages.dev')
|
||||
redirectURL.host.endsWith(".ente.io") ||
|
||||
redirectURL.host.endsWith("bada-frame.pages.dev")
|
||||
) &&
|
||||
redirectURL.protocol !== 'ente:' &&
|
||||
redirectURL.protocol !== 'enteauth:'
|
||||
redirectURL.protocol !== "ente:" &&
|
||||
redirectURL.protocol !== "enteauth:"
|
||||
) {
|
||||
setInvalidInfo(true);
|
||||
setLoading(false);
|
||||
|
@ -52,19 +52,19 @@ const PasskeysFlow = () => {
|
|||
}
|
||||
|
||||
let pkg = CLIENT_PACKAGE_NAMES.get(APPS.PHOTOS);
|
||||
if (redirectURL.protocol === 'enteauth:') {
|
||||
if (redirectURL.protocol === "enteauth:") {
|
||||
pkg = CLIENT_PACKAGE_NAMES.get(APPS.AUTH);
|
||||
} else if (redirectURL.hostname.startsWith('accounts')) {
|
||||
} else if (redirectURL.hostname.startsWith("accounts")) {
|
||||
pkg = CLIENT_PACKAGE_NAMES.get(APPS.ACCOUNTS);
|
||||
}
|
||||
|
||||
setData(LS_KEYS.CLIENT_PACKAGE, { name: pkg });
|
||||
HTTPService.setHeaders({
|
||||
'X-Client-Package': pkg,
|
||||
"X-Client-Package": pkg,
|
||||
});
|
||||
|
||||
// get passkeySessionID from the query params
|
||||
const passkeySessionID = searchParams.get('passkeySessionID') as string;
|
||||
const passkeySessionID = searchParams.get("passkeySessionID") as string;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
|
@ -100,7 +100,7 @@ const PasskeysFlow = () => {
|
|||
|
||||
if (!credential) {
|
||||
if (!isWebAuthnSupported()) {
|
||||
alert('WebAuthn is not supported in this browser');
|
||||
alert("WebAuthn is not supported in this browser");
|
||||
}
|
||||
setErrored(true);
|
||||
return;
|
||||
|
@ -114,7 +114,7 @@ const PasskeysFlow = () => {
|
|||
finishData = await finishAuthentication(
|
||||
credential,
|
||||
passkeySessionID,
|
||||
beginData.ceremonySessionID
|
||||
beginData.ceremonySessionID,
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, "Couldn't finish passkey authentication");
|
||||
|
@ -142,28 +142,28 @@ const PasskeysFlow = () => {
|
|||
|
||||
const getCredential = async (
|
||||
publicKey: any,
|
||||
timeoutMillis: number = 60000 // Default timeout of 60 seconds
|
||||
timeoutMillis: number = 60000, // Default timeout of 60 seconds
|
||||
): Promise<Credential | null> => {
|
||||
publicKey.challenge = _sodium.from_base64(
|
||||
publicKey.challenge,
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
);
|
||||
publicKey.allowCredentials?.forEach(function (listItem: any) {
|
||||
listItem.id = _sodium.from_base64(
|
||||
listItem.id,
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
);
|
||||
// note: we are orverwriting the transports array with all possible values.
|
||||
// This is because the browser will only prompt the user for the transport that is available.
|
||||
// Warning: In case of invalid transport value, the webauthn will fail on Safari & iOS browsers
|
||||
listItem.transports = ['usb', 'nfc', 'ble', 'internal'];
|
||||
listItem.transports = ["usb", "nfc", "ble", "internal"];
|
||||
});
|
||||
publicKey.timeout = timeoutMillis;
|
||||
const publicKeyCredentialCreationOptions: CredentialRequestOptions = {
|
||||
publicKey: publicKey,
|
||||
};
|
||||
const credential = await navigator.credentials.get(
|
||||
publicKeyCredentialCreationOptions
|
||||
publicKeyCredentialCreationOptions,
|
||||
);
|
||||
return credential;
|
||||
};
|
||||
|
@ -171,12 +171,12 @@ const PasskeysFlow = () => {
|
|||
const finishAuthentication = async (
|
||||
credential: Credential,
|
||||
sessionId: string,
|
||||
ceremonySessionId: string
|
||||
ceremonySessionId: string,
|
||||
) => {
|
||||
const data = await finishPasskeyAuthentication(
|
||||
credential,
|
||||
sessionId,
|
||||
ceremonySessionId
|
||||
ceremonySessionId,
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
@ -199,18 +199,20 @@ const PasskeysFlow = () => {
|
|||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%">
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: '1rem',
|
||||
}}>
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t('PASSKEY_LOGIN_FAILED')}
|
||||
{t("PASSKEY_LOGIN_FAILED")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t('PASSKEY_LOGIN_URL_INVALID')}
|
||||
{t("PASSKEY_LOGIN_URL_INVALID")}
|
||||
</Typography>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
|
@ -224,18 +226,20 @@ const PasskeysFlow = () => {
|
|||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%">
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: '1rem',
|
||||
}}>
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t('PASSKEY_LOGIN_FAILED')}
|
||||
{t("PASSKEY_LOGIN_FAILED")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t('PASSKEY_LOGIN_ERRORED')}
|
||||
{t("PASSKEY_LOGIN_ERRORED")}
|
||||
</Typography>
|
||||
<EnteButton
|
||||
onClick={() => {
|
||||
|
@ -244,12 +248,13 @@ const PasskeysFlow = () => {
|
|||
}}
|
||||
fullWidth
|
||||
style={{
|
||||
marginTop: '1rem',
|
||||
marginTop: "1rem",
|
||||
}}
|
||||
color="primary"
|
||||
type="button"
|
||||
variant="contained">
|
||||
{t('TRY_AGAIN')}
|
||||
variant="contained"
|
||||
>
|
||||
{t("TRY_AGAIN")}
|
||||
</EnteButton>
|
||||
</FormPaper>
|
||||
</Box>
|
||||
|
@ -263,18 +268,20 @@ const PasskeysFlow = () => {
|
|||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
height="100%">
|
||||
height="100%"
|
||||
>
|
||||
<Box maxWidth="30rem">
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: '1rem',
|
||||
}}>
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
<Typography fontWeight="bold" variant="h1">
|
||||
{t('LOGIN_WITH_PASSKEY')}
|
||||
{t("LOGIN_WITH_PASSKEY")}
|
||||
</Typography>
|
||||
<Typography marginTop="1rem">
|
||||
{t('PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER')}
|
||||
{t("PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER")}
|
||||
</Typography>
|
||||
<CenteredFlex marginTop="1rem">
|
||||
<Image
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { CenteredFlex } from '@ente/shared/components/Container';
|
||||
import FormPaper from '@ente/shared/components/Form/FormPaper';
|
||||
import SingleInputForm from '@ente/shared/components/SingleInputForm';
|
||||
import { ACCOUNTS_PAGES } from '@ente/shared/constants/pages';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { getToken } from '@ente/shared/storage/localStorage/helpers';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { t } from 'i18next';
|
||||
import _sodium from 'libsodium-wrappers';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { CenteredFlex } from "@ente/shared/components/Container";
|
||||
import FormPaper from "@ente/shared/components/Form/FormPaper";
|
||||
import SingleInputForm from "@ente/shared/components/SingleInputForm";
|
||||
import { ACCOUNTS_PAGES } from "@ente/shared/constants/pages";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { getToken } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import _sodium from "libsodium-wrappers";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
|
@ -16,15 +16,15 @@ import {
|
|||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Passkey } from 'types/passkey';
|
||||
} from "react";
|
||||
import { Passkey } from "types/passkey";
|
||||
import {
|
||||
finishPasskeyRegistration,
|
||||
getPasskeyRegistrationOptions,
|
||||
getPasskeys,
|
||||
} from '../../services/passkeysService';
|
||||
import ManagePasskeyDrawer from './ManagePasskeyDrawer';
|
||||
import PasskeysList from './PasskeysList';
|
||||
} from "../../services/passkeysService";
|
||||
import ManagePasskeyDrawer from "./ManagePasskeyDrawer";
|
||||
import PasskeysList from "./PasskeysList";
|
||||
|
||||
export const PasskeysContext = createContext(
|
||||
{} as {
|
||||
|
@ -32,14 +32,14 @@ export const PasskeysContext = createContext(
|
|||
setSelectedPasskey: Dispatch<SetStateAction<Passkey | null>>;
|
||||
setShowPasskeyDrawer: Dispatch<SetStateAction<boolean>>;
|
||||
refreshPasskeys: () => void;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const Passkeys = () => {
|
||||
const { showNavBar } = useContext(AppContext);
|
||||
|
||||
const [selectedPasskey, setSelectedPasskey] = useState<Passkey | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
|
||||
const [showPasskeyDrawer, setShowPasskeyDrawer] = useState(false);
|
||||
|
@ -69,7 +69,7 @@ const Passkeys = () => {
|
|||
const handleSubmit = async (
|
||||
inputValue: string,
|
||||
setFieldError: (errorMessage: string) => void,
|
||||
resetForm: (nextState?: unknown) => void
|
||||
resetForm: (nextState?: unknown) => void,
|
||||
) => {
|
||||
let response: {
|
||||
options: {
|
||||
|
@ -81,7 +81,7 @@ const Passkeys = () => {
|
|||
try {
|
||||
response = await getPasskeyRegistrationOptions();
|
||||
} catch {
|
||||
setFieldError('Failed to begin registration');
|
||||
setFieldError("Failed to begin registration");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -90,12 +90,12 @@ const Passkeys = () => {
|
|||
options.publicKey.challenge = _sodium.from_base64(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
options.publicKey.challenge
|
||||
options.publicKey.challenge,
|
||||
);
|
||||
options.publicKey.user.id = _sodium.from_base64(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
options.publicKey.user.id
|
||||
options.publicKey.user.id,
|
||||
);
|
||||
|
||||
// create new credential
|
||||
|
@ -104,8 +104,8 @@ const Passkeys = () => {
|
|||
try {
|
||||
newCredential = await navigator.credentials.create(options);
|
||||
} catch (e) {
|
||||
logError(e, 'Error creating credential');
|
||||
setFieldError('Failed to create credential');
|
||||
logError(e, "Error creating credential");
|
||||
setFieldError("Failed to create credential");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -113,10 +113,10 @@ const Passkeys = () => {
|
|||
await finishPasskeyRegistration(
|
||||
inputValue,
|
||||
newCredential,
|
||||
response.sessionID
|
||||
response.sessionID,
|
||||
);
|
||||
} catch {
|
||||
setFieldError('Failed to finish registration');
|
||||
setFieldError("Failed to finish registration");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -132,21 +132,23 @@ const Passkeys = () => {
|
|||
setSelectedPasskey,
|
||||
setShowPasskeyDrawer,
|
||||
refreshPasskeys: init,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<CenteredFlex>
|
||||
<Box maxWidth="20rem">
|
||||
<Box marginBottom="1rem">
|
||||
<Typography>{t('PASSKEYS_DESCRIPTION')}</Typography>
|
||||
<Typography>{t("PASSKEYS_DESCRIPTION")}</Typography>
|
||||
</Box>
|
||||
<FormPaper
|
||||
style={{
|
||||
padding: '1rem',
|
||||
}}>
|
||||
padding: "1rem",
|
||||
}}
|
||||
>
|
||||
<SingleInputForm
|
||||
fieldType="text"
|
||||
placeholder={t('ENTER_PASSKEY_NAME')}
|
||||
buttonText={t('ADD_PASSKEY')}
|
||||
initialValue={''}
|
||||
placeholder={t("ENTER_PASSKEY_NAME")}
|
||||
buttonText={t("ADD_PASSKEY")}
|
||||
initialValue={""}
|
||||
callback={handleSubmit}
|
||||
submitButtonProps={{
|
||||
sx: {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import RecoverPage from '@ente/accounts/pages/recover';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import RecoverPage from "@ente/accounts/pages/recover";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Recover() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import SignupPage from '@ente/accounts/pages/signup';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import SignupPage from "@ente/accounts/pages/signup";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Sigup() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import TwoFactorRecoverPage from '@ente/accounts/pages/two-factor/recover';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import TwoFactorRecoverPage from "@ente/accounts/pages/two-factor/recover";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function TwoFactorRecover() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import TwoFactorSetupPage from '@ente/accounts/pages/two-factor/setup';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import TwoFactorSetupPage from "@ente/accounts/pages/two-factor/setup";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function TwoFactorSetup() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import TwoFactorVerifyPage from '@ente/accounts/pages/two-factor/verify';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function TwoFactorVerify() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import VerifyPage from '@ente/accounts/pages/verify';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import VerifyPage from "@ente/accounts/pages/verify";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Verify() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import { getEndpoint } from '@ente/shared/network/api';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { getToken } from '@ente/shared/storage/localStorage/helpers';
|
||||
import _sodium from 'libsodium-wrappers';
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { getEndpoint } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { getToken } from "@ente/shared/storage/localStorage/helpers";
|
||||
import _sodium from "libsodium-wrappers";
|
||||
const ENDPOINT = getEndpoint();
|
||||
|
||||
export const getPasskeys = async () => {
|
||||
|
@ -12,11 +12,11 @@ export const getPasskeys = async () => {
|
|||
const response = await HTTPService.get(
|
||||
`${ENDPOINT}/passkeys`,
|
||||
{},
|
||||
{ 'X-Auth-Token': token }
|
||||
{ "X-Auth-Token": token },
|
||||
);
|
||||
return await response.data;
|
||||
} catch (e) {
|
||||
logError(e, 'get passkeys failed');
|
||||
logError(e, "get passkeys failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -29,11 +29,11 @@ export const renamePasskey = async (id: string, name: string) => {
|
|||
`${ENDPOINT}/passkeys/${id}`,
|
||||
{},
|
||||
{ friendlyName: name },
|
||||
{ 'X-Auth-Token': token }
|
||||
{ "X-Auth-Token": token },
|
||||
);
|
||||
return await response.data;
|
||||
} catch (e) {
|
||||
logError(e, 'rename passkey failed');
|
||||
logError(e, "rename passkey failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -46,11 +46,11 @@ export const deletePasskey = async (id: string) => {
|
|||
`${ENDPOINT}/passkeys/${id}`,
|
||||
{},
|
||||
{},
|
||||
{ 'X-Auth-Token': token }
|
||||
{ "X-Auth-Token": token },
|
||||
);
|
||||
return await response.data;
|
||||
} catch (e) {
|
||||
logError(e, 'delete passkey failed');
|
||||
logError(e, "delete passkey failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -63,12 +63,12 @@ export const getPasskeyRegistrationOptions = async () => {
|
|||
`${ENDPOINT}/passkeys/registration/begin`,
|
||||
{},
|
||||
{
|
||||
'X-Auth-Token': token,
|
||||
}
|
||||
"X-Auth-Token": token,
|
||||
},
|
||||
);
|
||||
return await response.data;
|
||||
} catch (e) {
|
||||
logError(e, 'get passkey registration options failed');
|
||||
logError(e, "get passkey registration options failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -76,20 +76,20 @@ export const getPasskeyRegistrationOptions = async () => {
|
|||
export const finishPasskeyRegistration = async (
|
||||
friendlyName: string,
|
||||
credential: Credential,
|
||||
sessionId: string
|
||||
sessionId: string,
|
||||
) => {
|
||||
try {
|
||||
const attestationObjectB64 = _sodium.to_base64(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
new Uint8Array(credential.response.attestationObject),
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
);
|
||||
const clientDataJSONB64 = _sodium.to_base64(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
new Uint8Array(credential.response.clientDataJSON),
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
);
|
||||
|
||||
const token = getToken();
|
||||
|
@ -111,12 +111,12 @@ export const finishPasskeyRegistration = async (
|
|||
sessionID: sessionId,
|
||||
},
|
||||
{
|
||||
'X-Auth-Token': token,
|
||||
}
|
||||
"X-Auth-Token": token,
|
||||
},
|
||||
);
|
||||
return await response.data;
|
||||
} catch (e) {
|
||||
logError(e, 'finish passkey registration failed');
|
||||
logError(e, "finish passkey registration failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -130,19 +130,19 @@ interface Options {
|
|||
}
|
||||
|
||||
export const beginPasskeyAuthentication = async (
|
||||
sessionId: string
|
||||
sessionId: string,
|
||||
): Promise<BeginPasskeyAuthenticationResponse> => {
|
||||
try {
|
||||
const data = await HTTPService.post(
|
||||
`${ENDPOINT}/users/two-factor/passkeys/begin`,
|
||||
{
|
||||
sessionID: sessionId,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return data.data;
|
||||
} catch (e) {
|
||||
logError(e, 'begin passkey authentication failed');
|
||||
logError(e, "begin passkey authentication failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -150,7 +150,7 @@ export const beginPasskeyAuthentication = async (
|
|||
export const finishPasskeyAuthentication = async (
|
||||
credential: Credential,
|
||||
sessionId: string,
|
||||
ceremonySessionId: string
|
||||
ceremonySessionId: string,
|
||||
) => {
|
||||
try {
|
||||
const data = await HTTPService.post(
|
||||
|
@ -164,37 +164,37 @@ export const finishPasskeyAuthentication = async (
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
new Uint8Array(credential.response.authenticatorData),
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
),
|
||||
clientDataJSON: _sodium.to_base64(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
new Uint8Array(credential.response.clientDataJSON),
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
),
|
||||
signature: _sodium.to_base64(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
new Uint8Array(credential.response.signature),
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
),
|
||||
userHandle: _sodium.to_base64(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
new Uint8Array(credential.response.userHandle),
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING
|
||||
_sodium.base64_variants.URLSAFE_NO_PADDING,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
sessionID: sessionId,
|
||||
ceremonySessionID: ceremonySessionId,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return data.data;
|
||||
} catch (e) {
|
||||
logError(e, 'finish passkey authentication failed');
|
||||
logError(e, "finish passkey authentication failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
/* inter-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local(''),
|
||||
url('/fonts/inter-v11-latin-500.woff2') format('woff2'),
|
||||
local(""),
|
||||
url("/fonts/inter-v11-latin-500.woff2") format("woff2"),
|
||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url('/fonts/inter-v11-latin-500.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
url("/fonts/inter-v11-latin-500.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
font-display: swap; /*https://web.dev/font-display/?utm_source=lighthouse&utm_medium=devtools#how-to-avoid-showing-invisible-text*/
|
||||
}
|
||||
|
||||
/* inter-600 - latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src:
|
||||
local(''),
|
||||
url('/fonts/inter-v11-latin-600.woff2') format('woff2'),
|
||||
local(""),
|
||||
url("/fonts/inter-v11-latin-600.woff2") format("woff2"),
|
||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url('/fonts/inter-v11-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
url("/fonts/inter-v11-latin-600.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* inter-800 - latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src:
|
||||
local(''),
|
||||
url('/fonts/inter-v11-latin-800.woff2') format('woff2'),
|
||||
local(""),
|
||||
url("/fonts/inter-v11-latin-800.woff2") format("woff2"),
|
||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url('/fonts/inter-v11-latin-800.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
url("/fonts/inter-v11-latin-800.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ module.exports = {
|
|||
// This is required here to ensure desktop picks the right eslint config, where this app is
|
||||
// packaged as a submodule.
|
||||
root: true,
|
||||
extends: ['@ente/eslint-config'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ["@ente/eslint-config"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json',
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js', 'out'],
|
||||
ignorePatterns: [".eslintrc.js", "out"],
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
const nextConfigBase = require('@/next/next.config.base.js');
|
||||
const nextConfigBase = require("@/next/next.config.base.js");
|
||||
|
||||
module.exports = nextConfigBase;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { initSentry } from '@ente/shared/sentry/config/sentry.config.base';
|
||||
import { initSentry } from "@ente/shared/sentry/config/sentry.config.base";
|
||||
|
||||
initSentry('https://5d344112b570b1a368b6f5c1d0bb798b@sentry.ente.io/8');
|
||||
initSentry("https://5d344112b570b1a368b6f5c1d0bb798b@sentry.ente.io/8");
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
import { Button } from '@mui/material';
|
||||
import { t } from 'i18next';
|
||||
import { Button } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const AuthFooter = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<p>{t('AUTH_DOWNLOAD_MOBILE_APP')}</p>
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<p>{t("AUTH_DOWNLOAD_MOBILE_APP")}</p>
|
||||
<a href="https://github.com/ente-io/auth#-download" download>
|
||||
<Button color="accent">{t('DOWNLOAD')}</Button>
|
||||
<Button color="accent">{t("DOWNLOAD")}</Button>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
import { HorizontalFlex } from '@ente/shared/components/Container';
|
||||
import NavbarBase from '@ente/shared/components/Navbar/base';
|
||||
import React from 'react';
|
||||
import { t } from 'i18next';
|
||||
import { logoutUser } from '@ente/accounts/services/user';
|
||||
import { EnteLogo } from '@ente/shared/components/EnteLogo';
|
||||
import OverflowMenu from '@ente/shared/components/OverflowMenu/menu';
|
||||
import { OverflowMenuOption } from '@ente/shared/components/OverflowMenu/option';
|
||||
import MoreHoriz from '@mui/icons-material/MoreHoriz';
|
||||
import LogoutOutlined from '@mui/icons-material/LogoutOutlined';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { logoutUser } from "@ente/accounts/services/user";
|
||||
import { HorizontalFlex } from "@ente/shared/components/Container";
|
||||
import { EnteLogo } from "@ente/shared/components/EnteLogo";
|
||||
import NavbarBase from "@ente/shared/components/Navbar/base";
|
||||
import OverflowMenu from "@ente/shared/components/OverflowMenu/menu";
|
||||
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
|
||||
import LogoutOutlined from "@mui/icons-material/LogoutOutlined";
|
||||
import MoreHoriz from "@mui/icons-material/MoreHoriz";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import React from "react";
|
||||
|
||||
export default function AuthNavbar() {
|
||||
const { isMobile } = React.useContext(AppContext);
|
||||
return (
|
||||
<NavbarBase isMobile={isMobile}>
|
||||
<HorizontalFlex flex={1} justifyContent={'center'}>
|
||||
<HorizontalFlex flex={1} justifyContent={"center"}>
|
||||
<EnteLogo />
|
||||
</HorizontalFlex>
|
||||
<HorizontalFlex position={'absolute'} right="24px">
|
||||
<HorizontalFlex position={"absolute"} right="24px">
|
||||
<OverflowMenu
|
||||
ariaControls={'auth-options'}
|
||||
triggerButtonIcon={<MoreHoriz />}>
|
||||
ariaControls={"auth-options"}
|
||||
triggerButtonIcon={<MoreHoriz />}
|
||||
>
|
||||
<OverflowMenuOption
|
||||
color="critical"
|
||||
startIcon={<LogoutOutlined />}
|
||||
onClick={logoutUser}>
|
||||
{t('LOGOUT')}
|
||||
onClick={logoutUser}
|
||||
>
|
||||
{t("LOGOUT")}
|
||||
</OverflowMenuOption>
|
||||
</OverflowMenu>
|
||||
</HorizontalFlex>
|
||||
|
|
|
@ -1,98 +1,107 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { TOTP, HOTP } from 'otpauth';
|
||||
import { Code } from 'types/code';
|
||||
import TimerProgress from './TimerProgress';
|
||||
import { t } from 'i18next';
|
||||
import { ButtonBase, Snackbar } from '@mui/material';
|
||||
import { ButtonBase, Snackbar } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { HOTP, TOTP } from "otpauth";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Code } from "types/code";
|
||||
import TimerProgress from "./TimerProgress";
|
||||
|
||||
const TOTPDisplay = ({ issuer, account, code, nextCode, period }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'rgba(40, 40, 40, 0.6)',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
backgroundColor: "rgba(40, 40, 40, 0.6)",
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<TimerProgress period={period ?? Code.defaultPeriod} />
|
||||
<div
|
||||
style={{
|
||||
padding: '12px 20px 0px 20px',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
minWidth: '320px',
|
||||
minHeight: '120px',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
padding: "12px 20px 0px 20px",
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
minWidth: "320px",
|
||||
minHeight: "120px",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
minWidth: '200px',
|
||||
}}>
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
minWidth: "200px",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
margin: '0px',
|
||||
fontSize: '14px',
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
fontWeight: "bold",
|
||||
margin: "0px",
|
||||
fontSize: "14px",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{issuer}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
marginTop: '0px',
|
||||
marginBottom: '8px',
|
||||
textAlign: 'left',
|
||||
fontSize: '12px',
|
||||
maxWidth: '200px',
|
||||
minHeight: '16px',
|
||||
color: 'grey',
|
||||
}}>
|
||||
marginTop: "0px",
|
||||
marginBottom: "8px",
|
||||
textAlign: "left",
|
||||
fontSize: "12px",
|
||||
maxWidth: "200px",
|
||||
minHeight: "16px",
|
||||
color: "grey",
|
||||
}}
|
||||
>
|
||||
{account}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
margin: '0px',
|
||||
marginBottom: '1rem',
|
||||
fontSize: '24px',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
margin: "0px",
|
||||
marginBottom: "1rem",
|
||||
fontSize: "24px",
|
||||
fontWeight: "bold",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{code}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ flex: 1 }} />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
minWidth: '120px',
|
||||
textAlign: 'right',
|
||||
marginTop: 'auto',
|
||||
marginBottom: '1rem',
|
||||
}}>
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-end",
|
||||
minWidth: "120px",
|
||||
textAlign: "right",
|
||||
marginTop: "auto",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '0px',
|
||||
fontSize: '10px',
|
||||
marginTop: 'auto',
|
||||
textAlign: 'right',
|
||||
color: 'grey',
|
||||
}}>
|
||||
{t('AUTH_NEXT')}
|
||||
fontWeight: "bold",
|
||||
marginBottom: "0px",
|
||||
fontSize: "10px",
|
||||
marginTop: "auto",
|
||||
textAlign: "right",
|
||||
color: "grey",
|
||||
}}
|
||||
>
|
||||
{t("AUTH_NEXT")}
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '0px',
|
||||
marginTop: 'auto',
|
||||
textAlign: 'right',
|
||||
color: 'grey',
|
||||
}}>
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
marginBottom: "0px",
|
||||
marginTop: "auto",
|
||||
textAlign: "right",
|
||||
color: "grey",
|
||||
}}
|
||||
>
|
||||
{nextCode}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -111,7 +120,7 @@ function BadCodeInfo({ codeInfo, codeErr }) {
|
|||
<div>
|
||||
{showRawData ? (
|
||||
<div onClick={() => setShowRawData(false)}>
|
||||
{codeInfo.rawData ?? 'no raw data'}
|
||||
{codeInfo.rawData ?? "no raw data"}
|
||||
</div>
|
||||
) : (
|
||||
<div onClick={() => setShowRawData(true)}>Show rawData</div>
|
||||
|
@ -127,15 +136,15 @@ interface OTPDisplayProps {
|
|||
|
||||
const OTPDisplay = (props: OTPDisplayProps) => {
|
||||
const { codeInfo } = props;
|
||||
const [code, setCode] = useState('');
|
||||
const [nextCode, setNextCode] = useState('');
|
||||
const [codeErr, setCodeErr] = useState('');
|
||||
const [code, setCode] = useState("");
|
||||
const [nextCode, setNextCode] = useState("");
|
||||
const [codeErr, setCodeErr] = useState("");
|
||||
const [hasCopied, setHasCopied] = useState(false);
|
||||
|
||||
const generateCodes = () => {
|
||||
try {
|
||||
const currentTime = new Date().getTime();
|
||||
if (codeInfo.type.toLowerCase() === 'totp') {
|
||||
if (codeInfo.type.toLowerCase() === "totp") {
|
||||
const totp = new TOTP({
|
||||
secret: codeInfo.secret,
|
||||
algorithm: codeInfo.algorithm ?? Code.defaultAlgo,
|
||||
|
@ -146,9 +155,9 @@ const OTPDisplay = (props: OTPDisplayProps) => {
|
|||
setNextCode(
|
||||
totp.generate({
|
||||
timestamp: currentTime + codeInfo.period * 1000,
|
||||
})
|
||||
}),
|
||||
);
|
||||
} else if (codeInfo.type.toLowerCase() === 'hotp') {
|
||||
} else if (codeInfo.type.toLowerCase() === "hotp") {
|
||||
const hotp = new HOTP({
|
||||
secret: codeInfo.secret,
|
||||
counter: 0,
|
||||
|
@ -184,8 +193,8 @@ const OTPDisplay = (props: OTPDisplayProps) => {
|
|||
// we need to call generateCodes() once before the interval loop
|
||||
// to set the initial code and nextCode
|
||||
generateCodes();
|
||||
codeType.toLowerCase() === 'totp' ||
|
||||
codeType.toLowerCase() === 'hotp'
|
||||
codeType.toLowerCase() === "totp" ||
|
||||
codeType.toLowerCase() === "hotp"
|
||||
? setInterval(() => {
|
||||
generateCodes();
|
||||
}, codePeriodInMs)
|
||||
|
@ -198,13 +207,14 @@ const OTPDisplay = (props: OTPDisplayProps) => {
|
|||
}, [codeInfo]);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '8px' }}>
|
||||
{codeErr === '' ? (
|
||||
<div style={{ padding: "8px" }}>
|
||||
{codeErr === "" ? (
|
||||
<ButtonBase
|
||||
component="div"
|
||||
onClick={() => {
|
||||
copyCode();
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<TOTPDisplay
|
||||
period={codeInfo.period}
|
||||
issuer={codeInfo.issuer}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const TimerProgress = ({ period }) => {
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
@ -24,14 +24,14 @@ const TimerProgress = ({ period }) => {
|
|||
return () => clearInterval(ticker);
|
||||
}, []);
|
||||
|
||||
const color = progress > 0.4 ? 'green' : 'orange';
|
||||
const color = progress > 0.4 ? "green" : "orange";
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
borderTopLeftRadius: '3px',
|
||||
borderTopLeftRadius: "3px",
|
||||
width: `${progress * 100}%`,
|
||||
height: '3px',
|
||||
height: "3px",
|
||||
backgroundColor: color,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import NotFoundPage from '@ente/shared/next/pages/404';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import NotFoundPage from "@ente/shared/next/pages/404";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function NotFound() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
import React, { createContext, useEffect, useRef, useState } from 'react';
|
||||
import AppNavbar from '@ente/shared/components/Navbar/app';
|
||||
import { t } from 'i18next';
|
||||
import AppNavbar from "@ente/shared/components/Navbar/app";
|
||||
import { t } from "i18next";
|
||||
import { createContext, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { useRouter } from 'next/router';
|
||||
import { Overlay } from '@ente/shared/components/Container';
|
||||
import EnteSpinner from '@ente/shared/components/EnteSpinner';
|
||||
import { LS_KEYS } from '@ente/shared/storage/localStorage';
|
||||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import Head from 'next/head';
|
||||
import LoadingBar from 'react-top-loading-bar';
|
||||
import DialogBoxV2 from '@ente/shared/components/DialogBoxV2';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { MessageContainer } from '@ente/shared/components/MessageContainer';
|
||||
import { CssBaseline, useMediaQuery } from '@mui/material';
|
||||
import { Overlay } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import {
|
||||
DialogBoxAttributesV2,
|
||||
SetDialogBoxAttributesV2,
|
||||
} from '@ente/shared/components/DialogBoxV2/types';
|
||||
} from "@ente/shared/components/DialogBoxV2/types";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { MessageContainer } from "@ente/shared/components/MessageContainer";
|
||||
import {
|
||||
clearLogsIfLocalStorageLimitExceeded,
|
||||
logStartupMessage,
|
||||
} from '@ente/shared/logging/web';
|
||||
} from "@ente/shared/logging/web";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { LS_KEYS } from "@ente/shared/storage/localStorage";
|
||||
import { CssBaseline, useMediaQuery } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import LoadingBar from "react-top-loading-bar";
|
||||
|
||||
import { CacheProvider } from '@emotion/react';
|
||||
import { setupI18n } from "@/ui/i18n";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import {
|
||||
APP_TITLES,
|
||||
APPS,
|
||||
CLIENT_PACKAGE_NAMES,
|
||||
} from '@ente/shared/apps/constants';
|
||||
import { EnteAppProps } from '@ente/shared/apps/types';
|
||||
import createEmotionCache from '@ente/shared/themes/createEmotionCache';
|
||||
import { THEME_COLOR } from '@ente/shared/themes/constants';
|
||||
import { SetTheme } from '@ente/shared/themes/types';
|
||||
import { setupI18n } from '@/ui/i18n';
|
||||
import { useLocalState } from '@ente/shared/hooks/useLocalState';
|
||||
import { PHOTOS_PAGES as PAGES } from '@ente/shared/constants/pages';
|
||||
import { getTheme } from '@ente/shared/themes';
|
||||
import '../../public/css/global.css';
|
||||
} from "@ente/shared/apps/constants";
|
||||
import { EnteAppProps } from "@ente/shared/apps/types";
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useLocalState } from "@ente/shared/hooks/useLocalState";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import createEmotionCache from "@ente/shared/themes/createEmotionCache";
|
||||
import { SetTheme } from "@ente/shared/themes/types";
|
||||
import "../../public/css/global.css";
|
||||
|
||||
type AppContextType = {
|
||||
showNavBar: (show: boolean) => void;
|
||||
|
@ -64,7 +64,7 @@ export default function App(props: EnteAppProps) {
|
|||
const [isI18nReady, setIsI18nReady] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [offline, setOffline] = useState(
|
||||
typeof window !== 'undefined' && !window.navigator.onLine
|
||||
typeof window !== "undefined" && !window.navigator.onLine,
|
||||
);
|
||||
const [showNavbar, setShowNavBar] = useState(false);
|
||||
const isLoadingBarRunning = useRef(false);
|
||||
|
@ -72,10 +72,10 @@ export default function App(props: EnteAppProps) {
|
|||
const [dialogBoxAttributeV2, setDialogBoxAttributesV2] =
|
||||
useState<DialogBoxAttributesV2>();
|
||||
const [dialogBoxV2View, setDialogBoxV2View] = useState(false);
|
||||
const isMobile = useMediaQuery('(max-width:428px)');
|
||||
const isMobile = useMediaQuery("(max-width:428px)");
|
||||
const [themeColor, setThemeColor] = useLocalState(
|
||||
LS_KEYS.THEME,
|
||||
THEME_COLOR.DARK
|
||||
THEME_COLOR.DARK,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -83,7 +83,7 @@ export default function App(props: EnteAppProps) {
|
|||
setupI18n().finally(() => setIsI18nReady(true));
|
||||
// set client package name in headers
|
||||
HTTPService.setHeaders({
|
||||
'X-Client-Package': CLIENT_PACKAGE_NAMES.get(APPS.AUTH),
|
||||
"X-Client-Package": CLIENT_PACKAGE_NAMES.get(APPS.AUTH),
|
||||
});
|
||||
// setup logging
|
||||
clearLogsIfLocalStorageLimitExceeded();
|
||||
|
@ -96,31 +96,31 @@ export default function App(props: EnteAppProps) {
|
|||
useEffect(() => {
|
||||
if (isI18nReady) {
|
||||
console.log(
|
||||
`%c${t('CONSOLE_WARNING_STOP')}`,
|
||||
'color: red; font-size: 52px;'
|
||||
`%c${t("CONSOLE_WARNING_STOP")}`,
|
||||
"color: red; font-size: 52px;",
|
||||
);
|
||||
console.log(`%c${t('CONSOLE_WARNING_DESC')}`, 'font-size: 20px;');
|
||||
console.log(`%c${t("CONSOLE_WARNING_DESC")}`, "font-size: 20px;");
|
||||
}
|
||||
}, [isI18nReady]);
|
||||
|
||||
useEffect(() => {
|
||||
router.events.on('routeChangeStart', (url: string) => {
|
||||
const newPathname = url.split('?')[0] as PAGES;
|
||||
router.events.on("routeChangeStart", (url: string) => {
|
||||
const newPathname = url.split("?")[0] as PAGES;
|
||||
if (window.location.pathname !== newPathname) {
|
||||
setLoading(true);
|
||||
}
|
||||
});
|
||||
|
||||
router.events.on('routeChangeComplete', () => {
|
||||
router.events.on("routeChangeComplete", () => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
window.addEventListener('online', setUserOnline);
|
||||
window.addEventListener('offline', setUserOffline);
|
||||
window.addEventListener("online", setUserOnline);
|
||||
window.addEventListener("offline", setUserOffline);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', setUserOnline);
|
||||
window.removeEventListener('offline', setUserOffline);
|
||||
window.removeEventListener("online", setUserOnline);
|
||||
window.removeEventListener("offline", setUserOffline);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
@ -145,9 +145,9 @@ export default function App(props: EnteAppProps) {
|
|||
|
||||
const somethingWentWrong = () =>
|
||||
setDialogBoxAttributesV2({
|
||||
title: t('ERROR'),
|
||||
close: { variant: 'critical' },
|
||||
content: t('UNKNOWN_ERROR'),
|
||||
title: t("ERROR"),
|
||||
close: { variant: "critical" },
|
||||
content: t("UNKNOWN_ERROR"),
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -155,7 +155,7 @@ export default function App(props: EnteAppProps) {
|
|||
<Head>
|
||||
<title>
|
||||
{isI18nReady
|
||||
? t('TITLE', { context: APPS.AUTH })
|
||||
? t("TITLE", { context: APPS.AUTH })
|
||||
: APP_TITLES.get(APPS.AUTH)}
|
||||
</title>
|
||||
<meta
|
||||
|
@ -168,7 +168,7 @@ export default function App(props: EnteAppProps) {
|
|||
<CssBaseline enableColorScheme />
|
||||
{showNavbar && <AppNavbar isMobile={isMobile} />}
|
||||
<MessageContainer>
|
||||
{offline && t('OFFLINE_MSG')}
|
||||
{offline && t("OFFLINE_MSG")}
|
||||
</MessageContainer>
|
||||
|
||||
<LoadingBar color="#51cd7c" ref={loadingBar} />
|
||||
|
@ -190,16 +190,18 @@ export default function App(props: EnteAppProps) {
|
|||
setThemeColor,
|
||||
somethingWentWrong,
|
||||
setDialogBoxAttributesV2,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{(loading || !isI18nReady) && (
|
||||
<Overlay
|
||||
sx={(theme) => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 2000,
|
||||
backgroundColor: theme.colors.background.base,
|
||||
})}>
|
||||
})}
|
||||
>
|
||||
<EnteSpinner />
|
||||
</Overlay>
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import DocumentPage, {
|
||||
EnteDocumentProps,
|
||||
} from '@ente/shared/next/pages/_document';
|
||||
} from "@ente/shared/next/pages/_document";
|
||||
|
||||
export default function Document(props: EnteDocumentProps) {
|
||||
return <DocumentPage {...props} />;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import ErrorPage from '@ente/shared/next/pages/_error';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import ErrorPage from "@ente/shared/next/pages/_error";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Error() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import OTPDisplay from 'components/OTPDisplay';
|
||||
import { getAuthCodes } from 'services';
|
||||
import { CustomError } from '@ente/shared/error';
|
||||
import { AUTH_PAGES as PAGES } from '@ente/shared/constants/pages';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AuthFooter } from 'components/AuthFooter';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { TextField } from '@mui/material';
|
||||
import AuthNavbar from 'components/Navbar';
|
||||
import { t } from 'i18next';
|
||||
import EnteSpinner from '@ente/shared/components/EnteSpinner';
|
||||
import { VerticallyCentered } from '@ente/shared/components/Container';
|
||||
import InMemoryStore, { MS_KEYS } from '@ente/shared/storage/InMemoryStore';
|
||||
import { VerticallyCentered } from "@ente/shared/components/Container";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { AUTH_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore";
|
||||
import { TextField } from "@mui/material";
|
||||
import { AuthFooter } from "components/AuthFooter";
|
||||
import AuthNavbar from "components/Navbar";
|
||||
import OTPDisplay from "components/OTPDisplay";
|
||||
import { t } from "i18next";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { getAuthCodes } from "services";
|
||||
|
||||
const AuthenticatorCodesPage = () => {
|
||||
const appContext = useContext(AppContext);
|
||||
const router = useRouter();
|
||||
const [codes, setCodes] = useState([]);
|
||||
const [hasFetched, setHasFetched] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCodes = async () => {
|
||||
|
@ -41,12 +41,12 @@ const AuthenticatorCodesPage = () => {
|
|||
|
||||
const filteredCodes = codes.filter(
|
||||
(secret) =>
|
||||
(secret.issuer ?? '')
|
||||
(secret.issuer ?? "")
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()) ||
|
||||
(secret.account ?? '')
|
||||
(secret.account ?? "")
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase())
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
|
||||
if (!hasFetched) {
|
||||
|
@ -64,47 +64,50 @@ const AuthenticatorCodesPage = () => {
|
|||
<AuthNavbar />
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '800px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto',
|
||||
}}>
|
||||
<div style={{ marginBottom: '1rem' }} />
|
||||
maxWidth: "800px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: "1rem" }} />
|
||||
{filteredCodes.length === 0 && searchTerm.length === 0 ? (
|
||||
<></>
|
||||
) : (
|
||||
<TextField
|
||||
id="search"
|
||||
name="search"
|
||||
label={t('SEARCH')}
|
||||
label={t("SEARCH")}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
variant="filled"
|
||||
style={{ width: '350px' }}
|
||||
style={{ width: "350px" }}
|
||||
value={searchTerm}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: '1rem' }} />
|
||||
<div style={{ marginBottom: "1rem" }} />
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{filteredCodes.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
textAlign: 'center',
|
||||
marginTop: '32px',
|
||||
}}>
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
textAlign: "center",
|
||||
marginTop: "32px",
|
||||
}}
|
||||
>
|
||||
{searchTerm.length !== 0 ? (
|
||||
<p>{t('NO_RESULTS')}</p>
|
||||
<p>{t("NO_RESULTS")}</p>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
@ -115,9 +118,9 @@ const AuthenticatorCodesPage = () => {
|
|||
))
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginBottom: '2rem' }} />
|
||||
<div style={{ marginBottom: "2rem" }} />
|
||||
<AuthFooter />
|
||||
<div style={{ marginBottom: '4rem' }} />
|
||||
<div style={{ marginBottom: "4rem" }} />
|
||||
</div>
|
||||
<style jsx>{`
|
||||
@media (min-width: 800px) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import ChangeEmailPage from '@ente/accounts/pages/change-email';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import ChangeEmailPage from "@ente/accounts/pages/change-email";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function ChangeEmail() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import ChangePasswordPage from '@ente/accounts/pages/change-password';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import ChangePasswordPage from "@ente/accounts/pages/change-password";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function ChangePassword() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import CredentialPage from '@ente/accounts/pages/credentials';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import CredentialPage from "@ente/accounts/pages/credentials";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Credential() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import GeneratePage from '@ente/accounts/pages/generate';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import GeneratePage from "@ente/accounts/pages/generate";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Generate() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PHOTOS_PAGES as PAGES } from '@ente/shared/constants/pages';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const IndexPage = () => {
|
||||
const router = useRouter();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import LoginPage from '@ente/accounts/pages/login';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import LoginPage from "@ente/accounts/pages/login";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Login() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import RecoverPage from '@ente/accounts/pages/recover';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import RecoverPage from "@ente/accounts/pages/recover";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Recover() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import SignupPage from '@ente/accounts/pages/signup';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import SignupPage from "@ente/accounts/pages/signup";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Sigup() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import TwoFactorRecoverPage from '@ente/accounts/pages/two-factor/recover';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import TwoFactorRecoverPage from "@ente/accounts/pages/two-factor/recover";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function TwoFactorRecover() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import TwoFactorSetupPage from '@ente/accounts/pages/two-factor/setup';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import TwoFactorSetupPage from "@ente/accounts/pages/two-factor/setup";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function TwoFactorSetup() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import TwoFactorVerifyPage from '@ente/accounts/pages/two-factor/verify';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import TwoFactorVerifyPage from "@ente/accounts/pages/two-factor/verify";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function TwoFactorVerify() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import VerifyPage from '@ente/accounts/pages/verify';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { useContext } from 'react';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import VerifyPage from "@ente/accounts/pages/verify";
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function Verify() {
|
||||
const appContext = useContext(AppContext);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { HttpStatusCode } from 'axios';
|
||||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import { AuthEntity, AuthKey } from 'types/api';
|
||||
import { Code } from 'types/code';
|
||||
import ComlinkCryptoWorker from '@ente/shared/crypto';
|
||||
import { getEndpoint } from '@ente/shared/network/api';
|
||||
import { getActualKey } from '@ente/shared/user';
|
||||
import { getToken } from '@ente/shared/storage/localStorage/helpers';
|
||||
import { ApiError, CustomError } from '@ente/shared/error';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { ApiError, CustomError } from "@ente/shared/error";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { getEndpoint } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { getToken } from "@ente/shared/storage/localStorage/helpers";
|
||||
import { getActualKey } from "@ente/shared/user";
|
||||
import { HttpStatusCode } from "axios";
|
||||
import { AuthEntity, AuthKey } from "types/api";
|
||||
import { Code } from "types/code";
|
||||
|
||||
const ENDPOINT = getEndpoint();
|
||||
export const getAuthCodes = async (): Promise<Code[]> => {
|
||||
|
@ -18,7 +18,7 @@ export const getAuthCodes = async (): Promise<Code[]> => {
|
|||
const authenticatorKey = await cryptoWorker.decryptB64(
|
||||
authKeyData.encryptedKey,
|
||||
authKeyData.header,
|
||||
masterKey
|
||||
masterKey,
|
||||
);
|
||||
// always fetch all data from server for now
|
||||
const authEntity: AuthEntity[] = await getDiff(0);
|
||||
|
@ -31,21 +31,21 @@ export const getAuthCodes = async (): Promise<Code[]> => {
|
|||
await cryptoWorker.decryptMetadata(
|
||||
entity.encryptedData,
|
||||
entity.header,
|
||||
authenticatorKey
|
||||
authenticatorKey,
|
||||
);
|
||||
return Code.fromRawData(entity.id, decryptedCode);
|
||||
} catch (e) {
|
||||
logError(
|
||||
Error('failed to parse code'),
|
||||
'codeId = ' + entity.id
|
||||
Error("failed to parse code"),
|
||||
"codeId = " + entity.id,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
// Remove null and undefined values
|
||||
const filteredAuthCodes = authCodes.filter(
|
||||
(f) => f !== null && f !== undefined
|
||||
(f) => f !== null && f !== undefined,
|
||||
);
|
||||
filteredAuthCodes.sort((a, b) => {
|
||||
if (a.issuer && b.issuer) {
|
||||
|
@ -62,7 +62,7 @@ export const getAuthCodes = async (): Promise<Code[]> => {
|
|||
return filteredAuthCodes;
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.AUTH_KEY_NOT_FOUND) {
|
||||
logError(e, 'get authenticator entities failed');
|
||||
logError(e, "get authenticator entities failed");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
@ -74,8 +74,8 @@ export const getAuthKey = async (): Promise<AuthKey> => {
|
|||
`${ENDPOINT}/authenticator/key`,
|
||||
{},
|
||||
{
|
||||
'X-Auth-Token': getToken(),
|
||||
}
|
||||
"X-Auth-Token": getToken(),
|
||||
},
|
||||
);
|
||||
return resp.data;
|
||||
} catch (e) {
|
||||
|
@ -85,7 +85,7 @@ export const getAuthKey = async (): Promise<AuthKey> => {
|
|||
) {
|
||||
throw Error(CustomError.AUTH_KEY_NOT_FOUND);
|
||||
} else {
|
||||
logError(e, 'Get key failed');
|
||||
logError(e, "Get key failed");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ export const getAuthKey = async (): Promise<AuthKey> => {
|
|||
// return a promise which resolves to list of AuthEnitity
|
||||
export const getDiff = async (
|
||||
sinceTime: number,
|
||||
limit = 2500
|
||||
limit = 2500,
|
||||
): Promise<AuthEntity[]> => {
|
||||
try {
|
||||
const resp = await HTTPService.get(
|
||||
|
@ -104,12 +104,12 @@ export const getDiff = async (
|
|||
limit,
|
||||
},
|
||||
{
|
||||
'X-Auth-Token': getToken(),
|
||||
}
|
||||
"X-Auth-Token": getToken(),
|
||||
},
|
||||
);
|
||||
return resp.data.diff;
|
||||
} catch (e) {
|
||||
logError(e, 'Get diff failed');
|
||||
logError(e, "Get diff failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { URI } from 'vscode-uri';
|
||||
import { URI } from "vscode-uri";
|
||||
|
||||
type Type = 'totp' | 'TOTP' | 'hotp' | 'HOTP';
|
||||
type Type = "totp" | "TOTP" | "hotp" | "HOTP";
|
||||
|
||||
type AlgorithmType =
|
||||
| 'sha1'
|
||||
| 'SHA1'
|
||||
| 'sha256'
|
||||
| 'SHA256'
|
||||
| 'sha512'
|
||||
| 'SHA512';
|
||||
| "sha1"
|
||||
| "SHA1"
|
||||
| "sha256"
|
||||
| "SHA256"
|
||||
| "sha512"
|
||||
| "SHA512";
|
||||
|
||||
export class Code {
|
||||
static readonly defaultDigits = 6;
|
||||
static readonly defaultAlgo = 'sha1';
|
||||
static readonly defaultAlgo = "sha1";
|
||||
static readonly defaultPeriod = 30;
|
||||
|
||||
// id for the corresponding auth entity
|
||||
|
@ -35,7 +35,7 @@ export class Code {
|
|||
algorithm: AlgorithmType,
|
||||
type: Type,
|
||||
rawData?: string,
|
||||
id?: string
|
||||
id?: string,
|
||||
) {
|
||||
this.account = account;
|
||||
this.issuer = issuer;
|
||||
|
@ -50,36 +50,36 @@ export class Code {
|
|||
|
||||
static fromRawData(id: string, rawData: string): Code {
|
||||
let santizedRawData = rawData
|
||||
.replace(/\+/g, '%2B')
|
||||
.replace(/:/g, '%3A')
|
||||
.replaceAll('\r', '');
|
||||
.replace(/\+/g, "%2B")
|
||||
.replace(/:/g, "%3A")
|
||||
.replaceAll("\r", "");
|
||||
if (santizedRawData.startsWith('"')) {
|
||||
santizedRawData = santizedRawData.substring(1);
|
||||
}
|
||||
if (santizedRawData.endsWith('"')) {
|
||||
santizedRawData = santizedRawData.substring(
|
||||
0,
|
||||
santizedRawData.length - 1
|
||||
santizedRawData.length - 1,
|
||||
);
|
||||
}
|
||||
|
||||
const uriParams = {};
|
||||
const searchParamsString =
|
||||
decodeURIComponent(santizedRawData).split('?')[1];
|
||||
searchParamsString.split('&').forEach((pair) => {
|
||||
const [key, value] = pair.split('=');
|
||||
decodeURIComponent(santizedRawData).split("?")[1];
|
||||
searchParamsString.split("&").forEach((pair) => {
|
||||
const [key, value] = pair.split("=");
|
||||
uriParams[key] = value;
|
||||
});
|
||||
|
||||
const uri = URI.parse(santizedRawData);
|
||||
let uriPath = decodeURIComponent(uri.path);
|
||||
if (
|
||||
uriPath.startsWith('/otpauth://') ||
|
||||
uriPath.startsWith('otpauth://')
|
||||
uriPath.startsWith("/otpauth://") ||
|
||||
uriPath.startsWith("otpauth://")
|
||||
) {
|
||||
uriPath = uriPath.split('otpauth://')[1];
|
||||
} else if (uriPath.startsWith('otpauth%3A//')) {
|
||||
uriPath = uriPath.split('otpauth%3A//')[1];
|
||||
uriPath = uriPath.split("otpauth://")[1];
|
||||
} else if (uriPath.startsWith("otpauth%3A//")) {
|
||||
uriPath = uriPath.split("otpauth%3A//")[1];
|
||||
}
|
||||
|
||||
return new Code(
|
||||
|
@ -91,54 +91,54 @@ export class Code {
|
|||
Code._getAlgorithm(uriParams),
|
||||
Code._getType(uriPath),
|
||||
rawData,
|
||||
id
|
||||
id,
|
||||
);
|
||||
}
|
||||
|
||||
private static _getAccount(uriPath: string): string {
|
||||
try {
|
||||
const path = decodeURIComponent(uriPath);
|
||||
if (path.includes(':')) {
|
||||
return path.split(':')[1];
|
||||
} else if (path.includes('/')) {
|
||||
return path.split('/')[1];
|
||||
if (path.includes(":")) {
|
||||
return path.split(":")[1];
|
||||
} else if (path.includes("/")) {
|
||||
return path.split("/")[1];
|
||||
}
|
||||
} catch (e) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static _getIssuer(
|
||||
uriPath: string,
|
||||
uriParams: { get?: any }
|
||||
uriParams: { get?: any },
|
||||
): string {
|
||||
try {
|
||||
if (uriParams['issuer'] !== undefined) {
|
||||
let issuer = uriParams['issuer'];
|
||||
if (uriParams["issuer"] !== undefined) {
|
||||
let issuer = uriParams["issuer"];
|
||||
// This is to handle bug in the ente auth app
|
||||
if (issuer.endsWith('period')) {
|
||||
if (issuer.endsWith("period")) {
|
||||
issuer = issuer.substring(0, issuer.length - 6);
|
||||
}
|
||||
return issuer;
|
||||
}
|
||||
let path = decodeURIComponent(uriPath);
|
||||
if (path.startsWith('totp/') || path.startsWith('hotp/')) {
|
||||
if (path.startsWith("totp/") || path.startsWith("hotp/")) {
|
||||
path = path.substring(5);
|
||||
}
|
||||
if (path.includes(':')) {
|
||||
return path.split(':')[0];
|
||||
} else if (path.includes('-')) {
|
||||
return path.split('-')[0];
|
||||
if (path.includes(":")) {
|
||||
return path.split(":")[0];
|
||||
} else if (path.includes("-")) {
|
||||
return path.split("-")[0];
|
||||
}
|
||||
return path;
|
||||
} catch (e) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static _getDigits(uriParams): number {
|
||||
try {
|
||||
return parseInt(uriParams['digits'], 10) || Code.defaultDigits;
|
||||
return parseInt(uriParams["digits"], 10) || Code.defaultDigits;
|
||||
} catch (e) {
|
||||
return Code.defaultDigits;
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ export class Code {
|
|||
|
||||
private static _getPeriod(uriParams): number {
|
||||
try {
|
||||
return parseInt(uriParams['period'], 10) || Code.defaultPeriod;
|
||||
return parseInt(uriParams["period"], 10) || Code.defaultPeriod;
|
||||
} catch (e) {
|
||||
return Code.defaultPeriod;
|
||||
}
|
||||
|
@ -154,29 +154,29 @@ export class Code {
|
|||
|
||||
private static _getAlgorithm(uriParams): AlgorithmType {
|
||||
try {
|
||||
const algorithm = uriParams['algorithm'].toLowerCase();
|
||||
if (algorithm === 'sha256') {
|
||||
const algorithm = uriParams["algorithm"].toLowerCase();
|
||||
if (algorithm === "sha256") {
|
||||
return algorithm;
|
||||
} else if (algorithm === 'sha512') {
|
||||
} else if (algorithm === "sha512") {
|
||||
return algorithm;
|
||||
}
|
||||
} catch (e) {
|
||||
// nothing
|
||||
}
|
||||
return 'sha1';
|
||||
return "sha1";
|
||||
}
|
||||
|
||||
private static _getType(uriPath: string): Type {
|
||||
const oauthType = uriPath.split('/')[0].substring(0);
|
||||
if (oauthType.toLowerCase() === 'totp') {
|
||||
return 'totp';
|
||||
} else if (oauthType.toLowerCase() === 'hotp') {
|
||||
return 'hotp';
|
||||
const oauthType = uriPath.split("/")[0].substring(0);
|
||||
if (oauthType.toLowerCase() === "totp") {
|
||||
return "totp";
|
||||
} else if (oauthType.toLowerCase() === "hotp") {
|
||||
return "hotp";
|
||||
}
|
||||
throw new Error(`Unsupported format with host ${oauthType}`);
|
||||
}
|
||||
|
||||
static getSanitizedSecret(uriParams): string {
|
||||
return uriParams['secret'].replace(/ /g, '').toUpperCase();
|
||||
return uriParams["secret"].replace(/ /g, "").toUpperCase();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ module.exports = {
|
|||
// This is required here to ensure desktop picks the right eslint config, where this app is
|
||||
// packaged as a submodule.
|
||||
root: true,
|
||||
extends: ['@ente/eslint-config'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ["@ente/eslint-config"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json',
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js', 'out'],
|
||||
ignorePatterns: [".eslintrc.js", "out"],
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
const nextConfigBase = require('@/next/next.config.base.js');
|
||||
const nextConfigBase = require("@/next/next.config.base.js");
|
||||
|
||||
module.exports = nextConfigBase;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { initSentry } from '@ente/shared/sentry/config/sentry.config.base';
|
||||
import { initSentry } from "@ente/shared/sentry/config/sentry.config.base";
|
||||
|
||||
initSentry('https://0f7214c7feb9b1dd2fed5db09b42fa1b@sentry.ente.io/5');
|
||||
initSentry("https://0f7214c7feb9b1dd2fed5db09b42fa1b@sentry.ente.io/5");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import styles from './FilledCircleCheck.module.scss'; // Import our CSS module
|
||||
import { useEffect, useState } from "react";
|
||||
import styles from "./FilledCircleCheck.module.scss"; // Import our CSS module
|
||||
|
||||
const FilledCircleCheck = () => {
|
||||
const [animate, setAnimate] = useState(false);
|
||||
|
@ -9,11 +9,12 @@ const FilledCircleCheck = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`${styles.circle} ${animate ? styles.animate : ''}`}>
|
||||
<div className={`${styles.circle} ${animate ? styles.animate : ""}`}>
|
||||
<svg
|
||||
className={styles.checkmark}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 52 52">
|
||||
viewBox="0 0 52 52"
|
||||
>
|
||||
<circle
|
||||
className={styles.checkmark__circle}
|
||||
cx="26"
|
||||
|
|
|
@ -1,58 +1,62 @@
|
|||
const colourPool = [
|
||||
'#87CEFA', // Light Blue
|
||||
'#90EE90', // Light Green
|
||||
'#F08080', // Light Coral
|
||||
'#FFFFE0', // Light Yellow
|
||||
'#FFB6C1', // Light Pink
|
||||
'#E0FFFF', // Light Cyan
|
||||
'#FAFAD2', // Light Goldenrod
|
||||
'#87CEFA', // Light Sky Blue
|
||||
'#D3D3D3', // Light Gray
|
||||
'#B0C4DE', // Light Steel Blue
|
||||
'#FFA07A', // Light Salmon
|
||||
'#20B2AA', // Light Sea Green
|
||||
'#778899', // Light Slate Gray
|
||||
'#AFEEEE', // Light Turquoise
|
||||
'#7A58C1', // Light Violet
|
||||
'#FFA500', // Light Orange
|
||||
'#A0522D', // Light Brown
|
||||
'#9370DB', // Light Purple
|
||||
'#008080', // Light Teal
|
||||
'#808000', // Light Olive
|
||||
"#87CEFA", // Light Blue
|
||||
"#90EE90", // Light Green
|
||||
"#F08080", // Light Coral
|
||||
"#FFFFE0", // Light Yellow
|
||||
"#FFB6C1", // Light Pink
|
||||
"#E0FFFF", // Light Cyan
|
||||
"#FAFAD2", // Light Goldenrod
|
||||
"#87CEFA", // Light Sky Blue
|
||||
"#D3D3D3", // Light Gray
|
||||
"#B0C4DE", // Light Steel Blue
|
||||
"#FFA07A", // Light Salmon
|
||||
"#20B2AA", // Light Sea Green
|
||||
"#778899", // Light Slate Gray
|
||||
"#AFEEEE", // Light Turquoise
|
||||
"#7A58C1", // Light Violet
|
||||
"#FFA500", // Light Orange
|
||||
"#A0522D", // Light Brown
|
||||
"#9370DB", // Light Purple
|
||||
"#008080", // Light Teal
|
||||
"#808000", // Light Olive
|
||||
];
|
||||
|
||||
export default function LargeType({ chars }: { chars: string[] }) {
|
||||
return (
|
||||
<table
|
||||
style={{
|
||||
fontSize: '4rem',
|
||||
fontWeight: 'bold',
|
||||
fontFamily: 'monospace',
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
}}>
|
||||
fontSize: "4rem",
|
||||
fontWeight: "bold",
|
||||
fontFamily: "monospace",
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{chars.map((char, i) => (
|
||||
<tr
|
||||
key={i}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '0.5rem',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
padding: "0.5rem",
|
||||
// alternating background
|
||||
backgroundColor: i % 2 === 0 ? '#2e2e2e' : '#5e5e5e',
|
||||
}}>
|
||||
backgroundColor: i % 2 === 0 ? "#2e2e2e" : "#5e5e5e",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: colourPool[i % colourPool.length],
|
||||
lineHeight: 1.2,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{char}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
fontSize: '1rem',
|
||||
}}>
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
{i + 1}
|
||||
</span>
|
||||
</tr>
|
||||
|
|
|
@ -1,38 +1,42 @@
|
|||
import FilledCircleCheck from './FilledCircleCheck';
|
||||
import FilledCircleCheck from "./FilledCircleCheck";
|
||||
|
||||
export default function PairedSuccessfullyOverlay() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
right: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 100,
|
||||
backgroundColor: 'black',
|
||||
}}>
|
||||
backgroundColor: "black",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<FilledCircleCheck />
|
||||
<h2
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Pairing Complete
|
||||
</h2>
|
||||
<p
|
||||
style={{
|
||||
lineHeight: '1.5rem',
|
||||
}}>
|
||||
lineHeight: "1.5rem",
|
||||
}}
|
||||
>
|
||||
We're preparing your album.
|
||||
<br /> This should only take a few seconds.
|
||||
</p>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { SlideshowContext } from 'pages/slideshow';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { SlideshowContext } from "pages/slideshow";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export default function PhotoAuditorium({
|
||||
url,
|
||||
|
@ -49,38 +49,40 @@ export default function PhotoAuditorium({
|
|||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
backgroundImage: `url(${url})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundBlendMode: 'multiply',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
}}>
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundBlendMode: "multiply",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backdropFilter: 'blur(10px)',
|
||||
}}>
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backdropFilter: "blur(10px)",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={url}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
display: showPreloadedNextSlide ? 'none' : 'block',
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
display: showPreloadedNextSlide ? "none" : "block",
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
src={nextSlideUrl}
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
display: showPreloadedNextSlide ? 'block' : 'none',
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
display: showPreloadedNextSlide ? "block" : "none",
|
||||
}}
|
||||
onLoad={() => {
|
||||
setNextSlidePrerendered(true);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import mime from 'mime-types';
|
||||
import { SlideshowContext } from 'pages/slideshow';
|
||||
import { useContext, useEffect, useRef } from 'react';
|
||||
import mime from "mime-types";
|
||||
import { SlideshowContext } from "pages/slideshow";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
|
||||
export default function VideoAuditorium({
|
||||
name,
|
||||
|
@ -30,22 +30,24 @@ export default function VideoAuditorium({
|
|||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<video
|
||||
ref={videoRef}
|
||||
autoPlay
|
||||
controls
|
||||
style={{
|
||||
maxWidth: '100vw',
|
||||
maxHeight: '100vh',
|
||||
maxWidth: "100vw",
|
||||
maxHeight: "100vh",
|
||||
}}
|
||||
onError={showNextSlide}
|
||||
onEnded={showNextSlide}>
|
||||
onEnded={showNextSlide}
|
||||
>
|
||||
<source src={url} type={mime.lookup(name)} />
|
||||
</video>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FILE_TYPE } from 'constants/file';
|
||||
import PhotoAuditorium from './PhotoAuditorium';
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import PhotoAuditorium from "./PhotoAuditorium";
|
||||
// import VideoAuditorium from './VideoAuditorium';
|
||||
|
||||
interface fileProp {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function TimerBar({ percentage }: { percentage: number }) {
|
||||
const okColor = '#75C157';
|
||||
const warningColor = '#FFC000';
|
||||
const lateColor = '#FF0000';
|
||||
const okColor = "#75C157";
|
||||
const warningColor = "#FFC000";
|
||||
const lateColor = "#FF0000";
|
||||
|
||||
const [backgroundColor, setBackgroundColor] = useState(okColor);
|
||||
|
||||
|
@ -21,9 +21,9 @@ export default function TimerBar({ percentage }: { percentage: number }) {
|
|||
<div
|
||||
style={{
|
||||
width: `${percentage}%`, // Set the width based on the time left
|
||||
height: '10px', // Same as the border thickness
|
||||
height: "10px", // Same as the border thickness
|
||||
backgroundColor, // The color of the moving border
|
||||
transition: 'width 1s linear', // Smooth transition for the width change
|
||||
transition: "width 1s linear", // Smooth transition for the width change
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { getAlbumsURL } from '@ente/shared/network/api';
|
||||
import { runningInBrowser } from '@ente/shared/platform';
|
||||
import { PAGES } from 'constants/pages';
|
||||
import { getAlbumsURL } from "@ente/shared/network/api";
|
||||
import { runningInBrowser } from "@ente/shared/platform";
|
||||
import { PAGES } from "constants/pages";
|
||||
|
||||
export enum APPS {
|
||||
PHOTOS = 'PHOTOS',
|
||||
AUTH = 'AUTH',
|
||||
ALBUMS = 'ALBUMS',
|
||||
PHOTOS = "PHOTOS",
|
||||
AUTH = "AUTH",
|
||||
ALBUMS = "ALBUMS",
|
||||
}
|
||||
|
||||
export const ALLOWED_APP_PAGES = new Map([
|
||||
|
@ -29,9 +29,9 @@ export const ALLOWED_APP_PAGES = new Map([
|
|||
]);
|
||||
|
||||
export const CLIENT_PACKAGE_NAMES = new Map([
|
||||
[APPS.ALBUMS, 'io.ente.albums.web'],
|
||||
[APPS.PHOTOS, 'io.ente.photos.web'],
|
||||
[APPS.AUTH, 'io.ente.auth.web'],
|
||||
[APPS.ALBUMS, "io.ente.albums.web"],
|
||||
[APPS.PHOTOS, "io.ente.photos.web"],
|
||||
[APPS.AUTH, "io.ente.auth.web"],
|
||||
]);
|
||||
|
||||
export const getAppNameAndTitle = () => {
|
||||
|
@ -41,9 +41,9 @@ export const getAppNameAndTitle = () => {
|
|||
const currentURL = new URL(window.location.href);
|
||||
const albumsURL = new URL(getAlbumsURL());
|
||||
if (currentURL.origin === albumsURL.origin) {
|
||||
return { name: APPS.ALBUMS, title: 'ente Photos' };
|
||||
return { name: APPS.ALBUMS, title: "ente Photos" };
|
||||
} else {
|
||||
return { name: APPS.PHOTOS, title: 'ente Photos' };
|
||||
return { name: APPS.PHOTOS, title: "ente Photos" };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export enum CACHES {
|
||||
THUMBS = 'thumbs',
|
||||
FACE_CROPS = 'face-crops',
|
||||
FILES = 'files',
|
||||
THUMBS = "thumbs",
|
||||
FACE_CROPS = "face-crops",
|
||||
FILES = "files",
|
||||
}
|
||||
|
|
|
@ -3,31 +3,31 @@ export const TRASH_SECTION = -2;
|
|||
export const DUMMY_UNCATEGORIZED_COLLECTION = -3;
|
||||
export const HIDDEN_ITEMS_SECTION = -4;
|
||||
export const ALL_SECTION = 0;
|
||||
export const DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME = 'Hidden';
|
||||
export const DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME = "Hidden";
|
||||
|
||||
export enum CollectionType {
|
||||
folder = 'folder',
|
||||
favorites = 'favorites',
|
||||
album = 'album',
|
||||
uncategorized = 'uncategorized',
|
||||
folder = "folder",
|
||||
favorites = "favorites",
|
||||
album = "album",
|
||||
uncategorized = "uncategorized",
|
||||
}
|
||||
|
||||
export enum CollectionSummaryType {
|
||||
folder = 'folder',
|
||||
favorites = 'favorites',
|
||||
album = 'album',
|
||||
archive = 'archive',
|
||||
trash = 'trash',
|
||||
uncategorized = 'uncategorized',
|
||||
all = 'all',
|
||||
outgoingShare = 'outgoingShare',
|
||||
incomingShareViewer = 'incomingShareViewer',
|
||||
incomingShareCollaborator = 'incomingShareCollaborator',
|
||||
sharedOnlyViaLink = 'sharedOnlyViaLink',
|
||||
archived = 'archived',
|
||||
defaultHidden = 'defaultHidden',
|
||||
hiddenItems = 'hiddenItems',
|
||||
pinned = 'pinned',
|
||||
folder = "folder",
|
||||
favorites = "favorites",
|
||||
album = "album",
|
||||
archive = "archive",
|
||||
trash = "trash",
|
||||
uncategorized = "uncategorized",
|
||||
all = "all",
|
||||
outgoingShare = "outgoingShare",
|
||||
incomingShareViewer = "incomingShareViewer",
|
||||
incomingShareCollaborator = "incomingShareCollaborator",
|
||||
sharedOnlyViaLink = "sharedOnlyViaLink",
|
||||
archived = "archived",
|
||||
defaultHidden = "defaultHidden",
|
||||
hiddenItems = "hiddenItems",
|
||||
pinned = "pinned",
|
||||
}
|
||||
export enum COLLECTION_LIST_SORT_BY {
|
||||
NAME,
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export const INPUT_PATH_PLACEHOLDER = 'INPUT';
|
||||
export const FFMPEG_PLACEHOLDER = 'FFMPEG';
|
||||
export const OUTPUT_PATH_PLACEHOLDER = 'OUTPUT';
|
||||
export const INPUT_PATH_PLACEHOLDER = "INPUT";
|
||||
export const FFMPEG_PLACEHOLDER = "FFMPEG";
|
||||
export const OUTPUT_PATH_PLACEHOLDER = "OUTPUT";
|
||||
|
|
|
@ -4,10 +4,10 @@ export const MAX_EDITED_CREATION_TIME = new Date();
|
|||
export const MAX_EDITED_FILE_NAME_LENGTH = 100;
|
||||
export const MAX_CAPTION_SIZE = 5000;
|
||||
|
||||
export const TYPE_HEIC = 'heic';
|
||||
export const TYPE_HEIF = 'heif';
|
||||
export const TYPE_JPEG = 'jpeg';
|
||||
export const TYPE_JPG = 'jpg';
|
||||
export const TYPE_HEIC = "heic";
|
||||
export const TYPE_HEIF = "heif";
|
||||
export const TYPE_JPEG = "jpeg";
|
||||
export const TYPE_JPG = "jpg";
|
||||
|
||||
export enum FILE_TYPE {
|
||||
IMAGE,
|
||||
|
@ -17,27 +17,27 @@ export enum FILE_TYPE {
|
|||
}
|
||||
|
||||
export const RAW_FORMATS = [
|
||||
'heic',
|
||||
'rw2',
|
||||
'tiff',
|
||||
'arw',
|
||||
'cr3',
|
||||
'cr2',
|
||||
'raf',
|
||||
'nef',
|
||||
'psd',
|
||||
'dng',
|
||||
'tif',
|
||||
"heic",
|
||||
"rw2",
|
||||
"tiff",
|
||||
"arw",
|
||||
"cr3",
|
||||
"cr2",
|
||||
"raf",
|
||||
"nef",
|
||||
"psd",
|
||||
"dng",
|
||||
"tif",
|
||||
];
|
||||
export const SUPPORTED_RAW_FORMATS = [
|
||||
'heic',
|
||||
'rw2',
|
||||
'tiff',
|
||||
'arw',
|
||||
'cr3',
|
||||
'cr2',
|
||||
'nef',
|
||||
'psd',
|
||||
'dng',
|
||||
'tif',
|
||||
"heic",
|
||||
"rw2",
|
||||
"tiff",
|
||||
"arw",
|
||||
"cr3",
|
||||
"cr2",
|
||||
"nef",
|
||||
"psd",
|
||||
"dng",
|
||||
"tif",
|
||||
];
|
||||
|
|
|
@ -8,8 +8,8 @@ export const SPACE_BTW_DATES = 44;
|
|||
export const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244;
|
||||
|
||||
export enum PLAN_PERIOD {
|
||||
MONTH = 'month',
|
||||
YEAR = 'year',
|
||||
MONTH = "month",
|
||||
YEAR = "year",
|
||||
}
|
||||
|
||||
export const SYNC_INTERVAL_IN_MICROSECONDS = 1000 * 60 * 5; // 5 minutes
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
export enum PAGES {
|
||||
CHANGE_EMAIL = '/change-email',
|
||||
CHANGE_PASSWORD = '/change-password',
|
||||
CREDENTIALS = '/credentials',
|
||||
GALLERY = '/gallery',
|
||||
GENERATE = '/generate',
|
||||
LOGIN = '/login',
|
||||
RECOVER = '/recover',
|
||||
SIGNUP = '/signup',
|
||||
TWO_FACTOR_SETUP = '/two-factor/setup',
|
||||
TWO_FACTOR_VERIFY = '/two-factor/verify',
|
||||
TWO_FACTOR_RECOVER = '/two-factor/recover',
|
||||
VERIFY = '/verify',
|
||||
ROOT = '/',
|
||||
SHARED_ALBUMS = '/shared-albums',
|
||||
CHANGE_EMAIL = "/change-email",
|
||||
CHANGE_PASSWORD = "/change-password",
|
||||
CREDENTIALS = "/credentials",
|
||||
GALLERY = "/gallery",
|
||||
GENERATE = "/generate",
|
||||
LOGIN = "/login",
|
||||
RECOVER = "/recover",
|
||||
SIGNUP = "/signup",
|
||||
TWO_FACTOR_SETUP = "/two-factor/setup",
|
||||
TWO_FACTOR_VERIFY = "/two-factor/verify",
|
||||
TWO_FACTOR_RECOVER = "/two-factor/recover",
|
||||
VERIFY = "/verify",
|
||||
ROOT = "/",
|
||||
SHARED_ALBUMS = "/shared-albums",
|
||||
// ML_DEBUG = '/ml-debug',
|
||||
DEDUPLICATE = '/deduplicate',
|
||||
DEDUPLICATE = "/deduplicate",
|
||||
// AUTH page is used to show (auth)enticator codes
|
||||
AUTH = '/auth',
|
||||
AUTH = "/auth",
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
import { ENCRYPTION_CHUNK_SIZE } from '@ente/shared/crypto/constants';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { ENCRYPTION_CHUNK_SIZE } from "@ente/shared/crypto/constants";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import {
|
||||
FileTypeInfo,
|
||||
ImportSuggestion,
|
||||
Location,
|
||||
ParsedExtractedMetadata,
|
||||
} from 'types/upload';
|
||||
} from "types/upload";
|
||||
|
||||
// list of format that were missed by type-detection for some files.
|
||||
export const WHITELISTED_FILE_FORMATS: FileTypeInfo[] = [
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: 'jpeg', mimeType: 'image/jpeg' },
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: 'jpg', mimeType: 'image/jpeg' },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: 'webm', mimeType: 'video/webm' },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: 'mod', mimeType: 'video/mpeg' },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: 'mp4', mimeType: 'video/mp4' },
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: 'gif', mimeType: 'image/gif' },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: 'dv', mimeType: 'video/x-dv' },
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: "jpeg", mimeType: "image/jpeg" },
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: "jpg", mimeType: "image/jpeg" },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: "webm", mimeType: "video/webm" },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: "mod", mimeType: "video/mpeg" },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: "mp4", mimeType: "video/mp4" },
|
||||
{ fileType: FILE_TYPE.IMAGE, exactType: "gif", mimeType: "image/gif" },
|
||||
{ fileType: FILE_TYPE.VIDEO, exactType: "dv", mimeType: "video/x-dv" },
|
||||
{
|
||||
fileType: FILE_TYPE.VIDEO,
|
||||
exactType: 'wmv',
|
||||
mimeType: 'video/x-ms-asf',
|
||||
exactType: "wmv",
|
||||
mimeType: "video/x-ms-asf",
|
||||
},
|
||||
{
|
||||
fileType: FILE_TYPE.VIDEO,
|
||||
exactType: 'hevc',
|
||||
mimeType: 'video/hevc',
|
||||
exactType: "hevc",
|
||||
mimeType: "video/hevc",
|
||||
},
|
||||
{
|
||||
fileType: FILE_TYPE.IMAGE,
|
||||
exactType: 'raf',
|
||||
mimeType: 'image/x-fuji-raf',
|
||||
exactType: "raf",
|
||||
mimeType: "image/x-fuji-raf",
|
||||
},
|
||||
{
|
||||
fileType: FILE_TYPE.IMAGE,
|
||||
exactType: 'orf',
|
||||
mimeType: 'image/x-olympus-orf',
|
||||
exactType: "orf",
|
||||
mimeType: "image/x-olympus-orf",
|
||||
},
|
||||
|
||||
{
|
||||
fileType: FILE_TYPE.IMAGE,
|
||||
exactType: 'crw',
|
||||
mimeType: 'image/x-canon-crw',
|
||||
exactType: "crw",
|
||||
mimeType: "image/x-canon-crw",
|
||||
},
|
||||
];
|
||||
|
||||
export const KNOWN_NON_MEDIA_FORMATS = ['xmp', 'html', 'txt'];
|
||||
export const KNOWN_NON_MEDIA_FORMATS = ["xmp", "html", "txt"];
|
||||
|
||||
export const EXIFLESS_FORMATS = ['gif', 'bmp'];
|
||||
export const EXIFLESS_FORMATS = ["gif", "bmp"];
|
||||
|
||||
// this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part.
|
||||
export const MULTIPART_PART_SIZE = 20 * 1024 * 1024;
|
||||
|
@ -54,7 +54,7 @@ export const MULTIPART_PART_SIZE = 20 * 1024 * 1024;
|
|||
export const FILE_READER_CHUNK_SIZE = ENCRYPTION_CHUNK_SIZE;
|
||||
|
||||
export const FILE_CHUNKS_COMBINED_FOR_A_UPLOAD_PART = Math.floor(
|
||||
MULTIPART_PART_SIZE / FILE_READER_CHUNK_SIZE
|
||||
MULTIPART_PART_SIZE / FILE_READER_CHUNK_SIZE,
|
||||
);
|
||||
|
||||
export const RANDOM_PERCENTAGE_PROGRESS_FOR_PUT = () => 90 + 10 * Math.random();
|
||||
|
@ -88,9 +88,9 @@ export enum UPLOAD_RESULT {
|
|||
}
|
||||
|
||||
export enum PICKED_UPLOAD_TYPE {
|
||||
FILES = 'files',
|
||||
FOLDERS = 'folders',
|
||||
ZIPS = 'zips',
|
||||
FILES = "files",
|
||||
FOLDERS = "folders",
|
||||
ZIPS = "zips",
|
||||
}
|
||||
|
||||
export const MAX_FILE_SIZE_SUPPORTED = 4 * 1024 * 1024 * 1024; // 4 GB
|
||||
|
@ -107,36 +107,36 @@ export const NULL_EXTRACTED_METADATA: ParsedExtractedMetadata = {
|
|||
export const A_SEC_IN_MICROSECONDS = 1e6;
|
||||
|
||||
export const DEFAULT_IMPORT_SUGGESTION: ImportSuggestion = {
|
||||
rootFolderName: '',
|
||||
rootFolderName: "",
|
||||
hasNestedFolders: false,
|
||||
hasRootLevelFileWithFolder: false,
|
||||
};
|
||||
|
||||
export const BLACK_THUMBNAIL_BASE64 =
|
||||
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB' +
|
||||
'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ' +
|
||||
'EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARC' +
|
||||
'ACWASwDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF' +
|
||||
'BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk' +
|
||||
'6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztL' +
|
||||
'W2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAA' +
|
||||
'AAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVY' +
|
||||
'nLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImK' +
|
||||
'kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oAD' +
|
||||
'AMBAAIRAxEAPwD/AD/6ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAC' +
|
||||
'gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' +
|
||||
'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAK' +
|
||||
'ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' +
|
||||
'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k=';
|
||||
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB" +
|
||||
"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ" +
|
||||
"EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARC" +
|
||||
"ACWASwDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF" +
|
||||
"BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk" +
|
||||
"6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztL" +
|
||||
"W2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAA" +
|
||||
"AAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVY" +
|
||||
"nLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImK" +
|
||||
"kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oAD" +
|
||||
"AMBAAIRAxEAPwD/AD/6ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" +
|
||||
"CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" +
|
||||
"AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAC" +
|
||||
"gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" +
|
||||
"AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" +
|
||||
"AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" +
|
||||
"AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" +
|
||||
"CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" +
|
||||
"CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA" +
|
||||
"KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg" +
|
||||
"AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" +
|
||||
"AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA" +
|
||||
"CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAK" +
|
||||
"ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA" +
|
||||
"KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" +
|
||||
"AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo" +
|
||||
"AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k=";
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
export const ENTE_WEBSITE_LINK = 'https://ente.io';
|
||||
export const ENTE_WEBSITE_LINK = "https://ente.io";
|
||||
|
||||
export const ML_BLOG_LINK = 'https://ente.io/blog/desktop-ml-beta';
|
||||
export const ML_BLOG_LINK = "https://ente.io/blog/desktop-ml-beta";
|
||||
|
||||
export const FACE_SEARCH_PRIVACY_POLICY_LINK =
|
||||
'https://ente.io/privacy#8-biometric-information-privacy-policy';
|
||||
"https://ente.io/privacy#8-biometric-information-privacy-policy";
|
||||
|
||||
export const SUPPORT_EMAIL = 'support@ente.io';
|
||||
export const SUPPORT_EMAIL = "support@ente.io";
|
||||
|
||||
export const APP_DOWNLOAD_URL = 'https://ente.io/download/desktop';
|
||||
export const APP_DOWNLOAD_URL = "https://ente.io/download/desktop";
|
||||
|
||||
export const FEEDBACK_EMAIL = 'feedback@ente.io';
|
||||
export const FEEDBACK_EMAIL = "feedback@ente.io";
|
||||
|
||||
export const DELETE_ACCOUNT_EMAIL = 'account-deletion@ente.io';
|
||||
export const DELETE_ACCOUNT_EMAIL = "account-deletion@ente.io";
|
||||
|
||||
export const WEB_ROADMAP_URL = 'https://github.com/ente-io/photos-web/issues';
|
||||
export const WEB_ROADMAP_URL = "https://github.com/ente-io/photos-web/issues";
|
||||
|
||||
export const DESKTOP_ROADMAP_URL =
|
||||
'https://github.com/ente-io/photos-desktop/issues';
|
||||
"https://github.com/ente-io/photos-desktop/issues";
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { AppProps } from 'next/app';
|
||||
import 'styles/global.css';
|
||||
import { ThemeProvider, CssBaseline } from '@mui/material';
|
||||
import { getTheme } from '@ente/shared/themes';
|
||||
import { THEME_COLOR } from '@ente/shared/themes/constants';
|
||||
import { APPS } from '@ente/shared/apps/constants';
|
||||
import { APPS } from "@ente/shared/apps/constants";
|
||||
import { getTheme } from "@ente/shared/themes";
|
||||
import { THEME_COLOR } from "@ente/shared/themes/constants";
|
||||
import { CssBaseline, ThemeProvider } from "@mui/material";
|
||||
import type { AppProps } from "next/app";
|
||||
import "styles/global.css";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
|
@ -12,8 +12,9 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||
|
||||
<main
|
||||
style={{
|
||||
display: 'contents',
|
||||
}}>
|
||||
display: "contents",
|
||||
}}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
import { Html, Head, Main, NextScript } from 'next/document';
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html
|
||||
lang="en"
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}>
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Head />
|
||||
<body
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
margin: 0,
|
||||
backgroundColor: 'black',
|
||||
color: 'white',
|
||||
}}>
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import EnteSpinner from '@ente/shared/components/EnteSpinner';
|
||||
import { boxSealOpen, toB64 } from '@ente/shared/crypto/internal/libsodium';
|
||||
import { useCastReceiver } from '@ente/shared/hooks/useCastReceiver';
|
||||
import { addLogLine } from '@ente/shared/logging';
|
||||
import castGateway from '@ente/shared/network/cast';
|
||||
import LargeType from 'components/LargeType';
|
||||
import _sodium from 'libsodium-wrappers';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { storeCastData } from 'services/cast/castService';
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { boxSealOpen, toB64 } from "@ente/shared/crypto/internal/libsodium";
|
||||
import { useCastReceiver } from "@ente/shared/hooks/useCastReceiver";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import castGateway from "@ente/shared/network/cast";
|
||||
import LargeType from "components/LargeType";
|
||||
import _sodium from "libsodium-wrappers";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { storeCastData } from "services/cast/castService";
|
||||
|
||||
// Function to generate cryptographically secure digits
|
||||
const generateSecureData = (length: number): Uint8Array => {
|
||||
|
@ -21,7 +21,7 @@ const generateSecureData = (length: number): Uint8Array => {
|
|||
};
|
||||
|
||||
const convertDataToDecimalString = (data: Uint8Array): string => {
|
||||
let decimalString = '';
|
||||
let decimalString = "";
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
decimalString += data[i].toString(); // No need to pad, as each value is a single digit
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ const convertDataToDecimalString = (data: Uint8Array): string => {
|
|||
|
||||
export default function PairingMode() {
|
||||
const [digits, setDigits] = useState<string[]>([]);
|
||||
const [publicKeyB64, setPublicKeyB64] = useState('');
|
||||
const [privateKeyB64, setPrivateKeyB64] = useState('');
|
||||
const [publicKeyB64, setPublicKeyB64] = useState("");
|
||||
const [privateKeyB64, setPrivateKeyB64] = useState("");
|
||||
const [codePending, setCodePending] = useState(true);
|
||||
const [isCastReady, setIsCastReady] = useState(false);
|
||||
|
||||
|
@ -49,18 +49,18 @@ export default function PairingMode() {
|
|||
try {
|
||||
const options = new cast.framework.CastReceiverOptions();
|
||||
options.customNamespaces = Object.assign({});
|
||||
options.customNamespaces['urn:x-cast:pair-request'] =
|
||||
options.customNamespaces["urn:x-cast:pair-request"] =
|
||||
cast.framework.system.MessageType.JSON;
|
||||
|
||||
options.disableIdleTimeout = true;
|
||||
|
||||
context.addCustomMessageListener(
|
||||
'urn:x-cast:pair-request',
|
||||
messageReceiveHandler
|
||||
"urn:x-cast:pair-request",
|
||||
messageReceiveHandler,
|
||||
);
|
||||
context.start(options);
|
||||
} catch (e) {
|
||||
addLogLine(e, 'failed to create cast context');
|
||||
addLogLine(e, "failed to create cast context");
|
||||
}
|
||||
setIsCastReady(true);
|
||||
return () => {
|
||||
|
@ -74,17 +74,17 @@ export default function PairingMode() {
|
|||
data: any;
|
||||
}) => {
|
||||
cast.framework.CastReceiverContext.getInstance().sendCustomMessage(
|
||||
'urn:x-cast:pair-request',
|
||||
"urn:x-cast:pair-request",
|
||||
message.senderId,
|
||||
{
|
||||
code: digits.join(''),
|
||||
}
|
||||
code: digits.join(""),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
const data = generateSecureData(6);
|
||||
setDigits(convertDataToDecimalString(data).split(''));
|
||||
setDigits(convertDataToDecimalString(data).split(""));
|
||||
const keypair = await generateKeyPair();
|
||||
setPublicKeyB64(await toB64(keypair.publicKey));
|
||||
setPrivateKeyB64(await toB64(keypair.privateKey));
|
||||
|
@ -105,10 +105,10 @@ export default function PairingMode() {
|
|||
// see if we were acknowledged on the client.
|
||||
// the client will send us the encrypted payload using our public key that we advertised.
|
||||
// then, we can decrypt this and store all the necessary info locally so we can play the collection slideshow.
|
||||
let devicePayload = '';
|
||||
let devicePayload = "";
|
||||
try {
|
||||
const encDastData = await castGateway.getCastData(
|
||||
`${digits.join('')}`
|
||||
`${digits.join("")}`,
|
||||
);
|
||||
if (!encDastData) return;
|
||||
devicePayload = encDastData;
|
||||
|
@ -121,7 +121,7 @@ export default function PairingMode() {
|
|||
const decryptedPayload = await boxSealOpen(
|
||||
devicePayload,
|
||||
publicKeyB64,
|
||||
privateKeyB64
|
||||
privateKeyB64,
|
||||
);
|
||||
|
||||
const decryptedPayloadObj = JSON.parse(atob(decryptedPayload));
|
||||
|
@ -133,8 +133,8 @@ export default function PairingMode() {
|
|||
// hey client, we exist!
|
||||
try {
|
||||
await castGateway.registerDevice(
|
||||
`${digits.join('')}`,
|
||||
publicKeyB64
|
||||
`${digits.join("")}`,
|
||||
publicKeyB64,
|
||||
);
|
||||
setCodePending(false);
|
||||
} catch (e) {
|
||||
|
@ -155,7 +155,7 @@ export default function PairingMode() {
|
|||
const data = await pollForCastData();
|
||||
if (!data) return;
|
||||
storeCastData(data);
|
||||
await router.push('/slideshow');
|
||||
await router.push("/slideshow");
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
|
@ -172,30 +172,34 @@ export default function PairingMode() {
|
|||
<>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<img width={150} src="/images/ente.svg" />
|
||||
<h1
|
||||
style={{
|
||||
fontWeight: 'normal',
|
||||
}}>
|
||||
fontWeight: "normal",
|
||||
}}
|
||||
>
|
||||
Enter this code on <b>ente</b> to pair this TV
|
||||
</h1>
|
||||
<div
|
||||
style={{
|
||||
borderRadius: '10px',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
borderRadius: "10px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{codePending ? (
|
||||
<EnteSpinner />
|
||||
) : (
|
||||
|
@ -206,33 +210,36 @@ export default function PairingMode() {
|
|||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '1.2rem',
|
||||
}}>
|
||||
Visit{' '}
|
||||
fontSize: "1.2rem",
|
||||
}}
|
||||
>
|
||||
Visit{" "}
|
||||
<a
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
color: '#87CEFA',
|
||||
fontWeight: 'bold',
|
||||
textDecoration: "none",
|
||||
color: "#87CEFA",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
href="https://ente.io/cast"
|
||||
target="_blank">
|
||||
target="_blank"
|
||||
>
|
||||
ente.io/cast
|
||||
</a>{' '}
|
||||
</a>{" "}
|
||||
for help
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
right: '20px',
|
||||
backgroundColor: 'white',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '10px',
|
||||
borderRadius: '10px',
|
||||
}}>
|
||||
position: "fixed",
|
||||
bottom: "20px",
|
||||
right: "20px",
|
||||
backgroundColor: "white",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
borderRadius: "10px",
|
||||
}}
|
||||
>
|
||||
<img src="/images/help-qrcode.webp" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { logError } from '@ente/shared/sentry';
|
||||
import PairedSuccessfullyOverlay from 'components/PairedSuccessfullyOverlay';
|
||||
import Theatre from 'components/Theatre';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { useRouter } from 'next/router';
|
||||
import { createContext, useEffect, useState } from 'react';
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay";
|
||||
import Theatre from "components/Theatre";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import { useRouter } from "next/router";
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import {
|
||||
getCastCollection,
|
||||
getLocalFiles,
|
||||
syncPublicFiles,
|
||||
} from 'services/cast/castService';
|
||||
import { Collection } from 'types/collection';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { getPreviewableImage, isRawFileFromFileName } from 'utils/file';
|
||||
} from "services/cast/castService";
|
||||
import { Collection } from "types/collection";
|
||||
import { EnteFile } from "types/file";
|
||||
import { getPreviewableImage, isRawFileFromFileName } from "utils/file";
|
||||
|
||||
export const SlideshowContext = createContext<{
|
||||
showNextSlide: () => void;
|
||||
|
@ -23,24 +23,24 @@ export default function Slideshow() {
|
|||
const [collectionFiles, setCollectionFiles] = useState<EnteFile[]>([]);
|
||||
|
||||
const [currentFile, setCurrentFile] = useState<EnteFile | undefined>(
|
||||
undefined
|
||||
undefined,
|
||||
);
|
||||
const [nextFile, setNextFile] = useState<EnteFile | undefined>(undefined);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [castToken, setCastToken] = useState<string>('');
|
||||
const [castToken, setCastToken] = useState<string>("");
|
||||
const [castCollection, setCastCollection] = useState<
|
||||
Collection | undefined
|
||||
>(undefined);
|
||||
|
||||
const syncCastFiles = async (token: string) => {
|
||||
try {
|
||||
const castToken = window.localStorage.getItem('castToken');
|
||||
const castToken = window.localStorage.getItem("castToken");
|
||||
const requestedCollectionKey =
|
||||
window.localStorage.getItem('collectionKey');
|
||||
window.localStorage.getItem("collectionKey");
|
||||
const collection = await getCastCollection(
|
||||
castToken,
|
||||
requestedCollectionKey
|
||||
requestedCollectionKey,
|
||||
);
|
||||
if (
|
||||
castCollection === undefined ||
|
||||
|
@ -50,22 +50,22 @@ export default function Slideshow() {
|
|||
await syncPublicFiles(token, collection, () => {});
|
||||
const files = await getLocalFiles(String(collection.id));
|
||||
setCollectionFiles(
|
||||
files.filter((file) => isFileEligibleForCast(file))
|
||||
files.filter((file) => isFileEligibleForCast(file)),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e, 'error during sync');
|
||||
router.push('/');
|
||||
logError(e, "error during sync");
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
const castToken = window.localStorage.getItem('castToken');
|
||||
const castToken = window.localStorage.getItem("castToken");
|
||||
setCastToken(castToken);
|
||||
} catch (e) {
|
||||
logError(e, 'error during sync');
|
||||
router.push('/');
|
||||
logError(e, "error during sync");
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -115,7 +115,7 @@ export default function Slideshow() {
|
|||
|
||||
const showNextSlide = () => {
|
||||
const currentIndex = collectionFiles.findIndex(
|
||||
(file) => file.id === currentFile?.id
|
||||
(file) => file.id === currentFile?.id,
|
||||
);
|
||||
|
||||
const nextIndex = (currentIndex + 1) % collectionFiles.length;
|
||||
|
@ -128,7 +128,7 @@ export default function Slideshow() {
|
|||
setNextFile(nextNextFile);
|
||||
};
|
||||
|
||||
const [renderableFileURL, setRenderableFileURL] = useState<string>('');
|
||||
const [renderableFileURL, setRenderableFileURL] = useState<string>("");
|
||||
|
||||
const getRenderableFileURL = async () => {
|
||||
if (!currentFile) return;
|
||||
|
@ -143,7 +143,7 @@ export default function Slideshow() {
|
|||
try {
|
||||
const blob = await getPreviewableImage(
|
||||
currentFile as EnteFile,
|
||||
castToken
|
||||
castToken,
|
||||
);
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export enum MS_KEYS {
|
||||
OPT_OUT_OF_CRASH_REPORTS = 'optOutOfCrashReports',
|
||||
SRP_CONFIGURE_IN_PROGRESS = 'srpConfigureInProgress',
|
||||
REDIRECT_URL = 'redirectUrl',
|
||||
OPT_OUT_OF_CRASH_REPORTS = "optOutOfCrashReports",
|
||||
SRP_CONFIGURE_IN_PROGRESS = "srpConfigureInProgress",
|
||||
REDIRECT_URL = "redirectUrl",
|
||||
}
|
||||
|
||||
type StoreType = Map<Partial<MS_KEYS>, any>;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { LimitedCacheStorage } from 'types/cache/index';
|
||||
import { LimitedCacheStorage } from "types/cache/index";
|
||||
// import { ElectronCacheStorage } from 'services/electron/cache';
|
||||
// import { runningInElectron, runningInWorker } from 'utils/common';
|
||||
// import { WorkerElectronCacheStorageService } from 'services/workerElectronCache/service';
|
||||
|
@ -25,7 +25,7 @@ class cacheStorageFactory {
|
|||
export const CacheStorageFactory = new cacheStorageFactory();
|
||||
|
||||
function transformBrowserCacheStorageToLimitedCacheStorage(
|
||||
caches: CacheStorage
|
||||
caches: CacheStorage,
|
||||
): LimitedCacheStorage {
|
||||
return {
|
||||
async open(cacheName) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { logError } from '@ente/shared/sentry';
|
||||
import { CacheStorageFactory } from './cacheStorageFactory';
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { CacheStorageFactory } from "./cacheStorageFactory";
|
||||
|
||||
const SecurityError = 'SecurityError';
|
||||
const INSECURE_OPERATION = 'The operation is insecure.';
|
||||
const SecurityError = "SecurityError";
|
||||
const INSECURE_OPERATION = "The operation is insecure.";
|
||||
async function openCache(cacheName: string) {
|
||||
try {
|
||||
return await CacheStorageFactory.getCacheStorage().open(cacheName);
|
||||
|
@ -12,7 +12,7 @@ async function openCache(cacheName: string) {
|
|||
// no-op
|
||||
} else {
|
||||
// log and ignore, we don't want to break the caller flow, when cache is not available
|
||||
logError(e, 'openCache failed');
|
||||
logError(e, "openCache failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ async function deleteCache(cacheName: string) {
|
|||
// no-op
|
||||
} else {
|
||||
// log and ignore, we don't want to break the caller flow, when cache is not available
|
||||
logError(e, 'deleteCache failed');
|
||||
logError(e, "deleteCache failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,51 +1,51 @@
|
|||
import { getEndpoint } from '@ente/shared/network/api';
|
||||
import localForage from '@ente/shared/storage/localForage';
|
||||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { CustomError, parseSharingErrorCodes } from '@ente/shared/error';
|
||||
import ComlinkCryptoWorker from '@ente/shared/crypto';
|
||||
import ComlinkCryptoWorker from "@ente/shared/crypto";
|
||||
import { CustomError, parseSharingErrorCodes } from "@ente/shared/error";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { getEndpoint } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import localForage from "@ente/shared/storage/localForage";
|
||||
|
||||
import { Collection, CollectionPublicMagicMetadata } from 'types/collection';
|
||||
import { EncryptedEnteFile, EnteFile } from 'types/file';
|
||||
import { decryptFile, mergeMetadata, sortFiles } from 'utils/file';
|
||||
import { Collection, CollectionPublicMagicMetadata } from "types/collection";
|
||||
import { EncryptedEnteFile, EnteFile } from "types/file";
|
||||
import { decryptFile, mergeMetadata, sortFiles } from "utils/file";
|
||||
|
||||
export interface SavedCollectionFiles {
|
||||
collectionLocalID: string;
|
||||
files: EnteFile[];
|
||||
}
|
||||
const ENDPOINT = getEndpoint();
|
||||
const COLLECTION_FILES_TABLE = 'collection-files';
|
||||
const COLLECTIONS_TABLE = 'collections';
|
||||
const COLLECTION_FILES_TABLE = "collection-files";
|
||||
const COLLECTIONS_TABLE = "collections";
|
||||
|
||||
const getLastSyncKey = (collectionUID: string) => `${collectionUID}-time`;
|
||||
|
||||
export const getLocalFiles = async (
|
||||
collectionUID: string
|
||||
collectionUID: string,
|
||||
): Promise<EnteFile[]> => {
|
||||
const localSavedcollectionFiles =
|
||||
(await localForage.getItem<SavedCollectionFiles[]>(
|
||||
COLLECTION_FILES_TABLE
|
||||
COLLECTION_FILES_TABLE,
|
||||
)) || [];
|
||||
const matchedCollection = localSavedcollectionFiles.find(
|
||||
(item) => item.collectionLocalID === collectionUID
|
||||
(item) => item.collectionLocalID === collectionUID,
|
||||
);
|
||||
return matchedCollection?.files || [];
|
||||
};
|
||||
|
||||
const savecollectionFiles = async (
|
||||
collectionUID: string,
|
||||
files: EnteFile[]
|
||||
files: EnteFile[],
|
||||
) => {
|
||||
const collectionFiles =
|
||||
(await localForage.getItem<SavedCollectionFiles[]>(
|
||||
COLLECTION_FILES_TABLE
|
||||
COLLECTION_FILES_TABLE,
|
||||
)) || [];
|
||||
await localForage.setItem(
|
||||
COLLECTION_FILES_TABLE,
|
||||
dedupeCollectionFiles([
|
||||
{ collectionLocalID: collectionUID, files },
|
||||
...collectionFiles,
|
||||
])
|
||||
]),
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -55,7 +55,7 @@ export const getLocalCollections = async (collectionKey: string) => {
|
|||
const collection =
|
||||
localCollections.find(
|
||||
(localSavedPublicCollection) =>
|
||||
localSavedPublicCollection.key === collectionKey
|
||||
localSavedPublicCollection.key === collectionKey,
|
||||
) || null;
|
||||
return collection;
|
||||
};
|
||||
|
@ -65,7 +65,7 @@ const saveCollection = async (collection: Collection) => {
|
|||
(await localForage.getItem<Collection[]>(COLLECTIONS_TABLE)) ?? [];
|
||||
await localForage.setItem(
|
||||
COLLECTIONS_TABLE,
|
||||
dedupeCollections([collection, ...collections])
|
||||
dedupeCollections([collection, ...collections]),
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -105,7 +105,7 @@ const updateSyncTime = async (collectionUID: string, time: number) =>
|
|||
export const syncPublicFiles = async (
|
||||
token: string,
|
||||
collection: Collection,
|
||||
setPublicFiles: (files: EnteFile[]) => void
|
||||
setPublicFiles: (files: EnteFile[]) => void,
|
||||
) => {
|
||||
try {
|
||||
let files: EnteFile[] = [];
|
||||
|
@ -123,7 +123,7 @@ export const syncPublicFiles = async (
|
|||
collection,
|
||||
lastSyncTime,
|
||||
files,
|
||||
setPublicFiles
|
||||
setPublicFiles,
|
||||
);
|
||||
|
||||
files = [...files, ...fetchedFiles];
|
||||
|
@ -150,14 +150,14 @@ export const syncPublicFiles = async (
|
|||
setPublicFiles([...sortFiles(mergeMetadata(files), sortAsc)]);
|
||||
} catch (e) {
|
||||
const parsedError = parseSharingErrorCodes(e);
|
||||
logError(e, 'failed to sync shared collection files');
|
||||
logError(e, "failed to sync shared collection files");
|
||||
if (parsedError.message === CustomError.TOKEN_EXPIRED) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return [...sortFiles(mergeMetadata(files), sortAsc)];
|
||||
} catch (e) {
|
||||
logError(e, 'failed to get local or sync shared collection files');
|
||||
logError(e, "failed to get local or sync shared collection files");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -167,7 +167,7 @@ const fetchFiles = async (
|
|||
collection: Collection,
|
||||
sinceTime: number,
|
||||
files: EnteFile[],
|
||||
setPublicFiles: (files: EnteFile[]) => void
|
||||
setPublicFiles: (files: EnteFile[]) => void,
|
||||
): Promise<EnteFile[]> => {
|
||||
try {
|
||||
let decryptedFiles: EnteFile[] = [];
|
||||
|
@ -184,9 +184,9 @@ const fetchFiles = async (
|
|||
sinceTime: time,
|
||||
},
|
||||
{
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Cast-Access-Token': castToken,
|
||||
}
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Cast-Access-Token": castToken,
|
||||
},
|
||||
);
|
||||
decryptedFiles = [
|
||||
...decryptedFiles,
|
||||
|
@ -197,7 +197,7 @@ const fetchFiles = async (
|
|||
} else {
|
||||
return file;
|
||||
}
|
||||
}) as Promise<EnteFile>[]
|
||||
}) as Promise<EnteFile>[],
|
||||
)),
|
||||
];
|
||||
|
||||
|
@ -208,28 +208,28 @@ const fetchFiles = async (
|
|||
sortFiles(
|
||||
mergeMetadata(
|
||||
[...(files || []), ...decryptedFiles].filter(
|
||||
(item) => !item.isDeleted
|
||||
)
|
||||
(item) => !item.isDeleted,
|
||||
),
|
||||
),
|
||||
sortAsc
|
||||
)
|
||||
sortAsc,
|
||||
),
|
||||
);
|
||||
} while (resp.data.hasMore);
|
||||
return decryptedFiles;
|
||||
} catch (e) {
|
||||
logError(e, 'Get cast files failed');
|
||||
logError(e, "Get cast files failed");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const getCastCollection = async (
|
||||
castToken: string,
|
||||
collectionKey: string
|
||||
collectionKey: string,
|
||||
): Promise<Collection> => {
|
||||
try {
|
||||
const resp = await HTTPService.get(`${ENDPOINT}/cast/info`, null, {
|
||||
'Cache-Control': 'no-cache',
|
||||
'X-Cast-Access-Token': castToken,
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Cast-Access-Token": castToken,
|
||||
});
|
||||
const fetchedCollection = resp.data.collection;
|
||||
|
||||
|
@ -240,7 +240,7 @@ export const getCastCollection = async (
|
|||
(await cryptoWorker.decryptToUTF8(
|
||||
fetchedCollection.encryptedName,
|
||||
fetchedCollection.nameDecryptionNonce,
|
||||
collectionKey
|
||||
collectionKey,
|
||||
)));
|
||||
|
||||
let collectionPublicMagicMetadata: CollectionPublicMagicMetadata;
|
||||
|
@ -250,7 +250,7 @@ export const getCastCollection = async (
|
|||
data: await cryptoWorker.decryptMetadata(
|
||||
fetchedCollection.pubMagicMetadata.data,
|
||||
fetchedCollection.pubMagicMetadata.header,
|
||||
collectionKey
|
||||
collectionKey,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -264,20 +264,20 @@ export const getCastCollection = async (
|
|||
await saveCollection(collection);
|
||||
return collection;
|
||||
} catch (e) {
|
||||
logError(e, 'failed to get cast collection');
|
||||
logError(e, "failed to get cast collection");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export const removeCollection = async (
|
||||
collectionUID: string,
|
||||
collectionKey: string
|
||||
collectionKey: string,
|
||||
) => {
|
||||
const collections =
|
||||
(await localForage.getItem<Collection[]>(COLLECTIONS_TABLE)) || [];
|
||||
await localForage.setItem(
|
||||
COLLECTIONS_TABLE,
|
||||
collections.filter((collection) => collection.key !== collectionKey)
|
||||
collections.filter((collection) => collection.key !== collectionKey),
|
||||
);
|
||||
await removeCollectionFiles(collectionUID);
|
||||
};
|
||||
|
@ -286,14 +286,14 @@ export const removeCollectionFiles = async (collectionUID: string) => {
|
|||
await localForage.removeItem(getLastSyncKey(collectionUID));
|
||||
const collectionFiles =
|
||||
(await localForage.getItem<SavedCollectionFiles[]>(
|
||||
COLLECTION_FILES_TABLE
|
||||
COLLECTION_FILES_TABLE,
|
||||
)) ?? [];
|
||||
await localForage.setItem(
|
||||
COLLECTION_FILES_TABLE,
|
||||
collectionFiles.filter(
|
||||
(collectionFiles) =>
|
||||
collectionFiles.collectionLocalID !== collectionUID
|
||||
)
|
||||
collectionFiles.collectionLocalID !== collectionUID,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { EnteFile } from "types/file";
|
||||
import {
|
||||
createTypedObjectURL,
|
||||
generateStreamFromArrayBuffer,
|
||||
getRenderableFileURL,
|
||||
createTypedObjectURL,
|
||||
} from 'utils/file';
|
||||
import { EnteFile } from 'types/file';
|
||||
} from "utils/file";
|
||||
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { CustomError } from '@ente/shared/error';
|
||||
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
|
||||
import { CACHES } from 'constants/cache';
|
||||
import { CacheStorageService } from './cache/cacheStorageService';
|
||||
import { LimitedCache } from 'types/cache';
|
||||
import { getCastFileURL, getCastThumbnailURL } from '@ente/shared/network/api';
|
||||
import HTTPService from '@ente/shared/network/HTTPService';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import { getCastFileURL, getCastThumbnailURL } from "@ente/shared/network/api";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { CACHES } from "constants/cache";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import { LimitedCache } from "types/cache";
|
||||
import ComlinkCryptoWorker from "utils/comlink/ComlinkCryptoWorker";
|
||||
import { CacheStorageService } from "./cache/cacheStorageService";
|
||||
|
||||
class CastDownloadManager {
|
||||
private fileObjectURLPromise = new Map<
|
||||
|
@ -33,7 +33,7 @@ class CastDownloadManager {
|
|||
private async getThumbnailCache() {
|
||||
try {
|
||||
const thumbnailCache = await CacheStorageService.open(
|
||||
CACHES.THUMBS
|
||||
CACHES.THUMBS,
|
||||
);
|
||||
return thumbnailCache;
|
||||
} catch (e) {
|
||||
|
@ -44,14 +44,14 @@ class CastDownloadManager {
|
|||
|
||||
public async getCachedThumbnail(
|
||||
file: EnteFile,
|
||||
thumbnailCache?: LimitedCache
|
||||
thumbnailCache?: LimitedCache,
|
||||
) {
|
||||
try {
|
||||
if (!thumbnailCache) {
|
||||
thumbnailCache = await this.getThumbnailCache();
|
||||
}
|
||||
const cacheResp: Response = await thumbnailCache?.match(
|
||||
file.id.toString()
|
||||
file.id.toString(),
|
||||
);
|
||||
|
||||
if (cacheResp) {
|
||||
|
@ -59,7 +59,7 @@ class CastDownloadManager {
|
|||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
logError(e, 'failed to get cached thumbnail');
|
||||
logError(e, "failed to get cached thumbnail");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ class CastDownloadManager {
|
|||
const thumbnailCache = await this.getThumbnailCache();
|
||||
const cachedThumb = await this.getCachedThumbnail(
|
||||
file,
|
||||
thumbnailCache
|
||||
thumbnailCache,
|
||||
);
|
||||
if (cachedThumb) {
|
||||
return cachedThumb;
|
||||
|
@ -82,7 +82,7 @@ class CastDownloadManager {
|
|||
try {
|
||||
await thumbnailCache?.put(
|
||||
file.id.toString(),
|
||||
new Response(thumbBlob)
|
||||
new Response(thumbBlob),
|
||||
);
|
||||
} catch (e) {
|
||||
// TODO: handle storage full exception.
|
||||
|
@ -95,7 +95,7 @@ class CastDownloadManager {
|
|||
return await this.thumbnailObjectURLPromise.get(file.id);
|
||||
} catch (e) {
|
||||
this.thumbnailObjectURLPromise.delete(file.id);
|
||||
logError(e, 'get castDownloadManager preview Failed');
|
||||
logError(e, "get castDownloadManager preview Failed");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -105,18 +105,18 @@ class CastDownloadManager {
|
|||
getCastThumbnailURL(file.id),
|
||||
null,
|
||||
{
|
||||
'X-Cast-Access-Token': castToken,
|
||||
"X-Cast-Access-Token": castToken,
|
||||
},
|
||||
{ responseType: 'arraybuffer' }
|
||||
{ responseType: "arraybuffer" },
|
||||
);
|
||||
if (typeof resp.data === 'undefined') {
|
||||
if (typeof resp.data === "undefined") {
|
||||
throw Error(CustomError.REQUEST_FAILED);
|
||||
}
|
||||
const cryptoWorker = await ComlinkCryptoWorker.getInstance();
|
||||
const decrypted = await cryptoWorker.decryptThumbnail(
|
||||
new Uint8Array(resp.data),
|
||||
await cryptoWorker.fromB64(file.thumbnail.decryptionHeader),
|
||||
file.key
|
||||
file.key,
|
||||
);
|
||||
return decrypted;
|
||||
};
|
||||
|
@ -132,7 +132,7 @@ class CastDownloadManager {
|
|||
} else {
|
||||
const fileURL = await createTypedObjectURL(
|
||||
fileBlob,
|
||||
file.metadata.title
|
||||
file.metadata.title,
|
||||
);
|
||||
return { converted: [fileURL], original: [fileURL] };
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ class CastDownloadManager {
|
|||
return fileURLs;
|
||||
} catch (e) {
|
||||
this.fileObjectURLPromise.delete(fileKey);
|
||||
logError(e, 'castDownloadManager failed to get file');
|
||||
logError(e, "castDownloadManager failed to get file");
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
@ -166,40 +166,40 @@ class CastDownloadManager {
|
|||
getCastFileURL(file.id),
|
||||
null,
|
||||
{
|
||||
'X-Cast-Access-Token': castToken,
|
||||
"X-Cast-Access-Token": castToken,
|
||||
},
|
||||
{ responseType: 'arraybuffer' }
|
||||
{ responseType: "arraybuffer" },
|
||||
);
|
||||
if (typeof resp.data === 'undefined') {
|
||||
if (typeof resp.data === "undefined") {
|
||||
throw Error(CustomError.REQUEST_FAILED);
|
||||
}
|
||||
const decrypted = await cryptoWorker.decryptFile(
|
||||
new Uint8Array(resp.data),
|
||||
await cryptoWorker.fromB64(file.file.decryptionHeader),
|
||||
file.key
|
||||
file.key,
|
||||
);
|
||||
return generateStreamFromArrayBuffer(decrypted);
|
||||
}
|
||||
const resp = await fetch(getCastFileURL(file.id), {
|
||||
headers: {
|
||||
'X-Cast-Access-Token': castToken,
|
||||
"X-Cast-Access-Token": castToken,
|
||||
},
|
||||
});
|
||||
const reader = resp.body.getReader();
|
||||
|
||||
const contentLength = +resp.headers.get('Content-Length');
|
||||
const contentLength = +resp.headers.get("Content-Length");
|
||||
let downloadedBytes = 0;
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const decryptionHeader = await cryptoWorker.fromB64(
|
||||
file.file.decryptionHeader
|
||||
file.file.decryptionHeader,
|
||||
);
|
||||
const fileKey = await cryptoWorker.fromB64(file.key);
|
||||
const { pullState, decryptionChunkSize } =
|
||||
await cryptoWorker.initChunkDecryption(
|
||||
decryptionHeader,
|
||||
fileKey
|
||||
fileKey,
|
||||
);
|
||||
let data = new Uint8Array();
|
||||
// The following function handles each data chunk
|
||||
|
@ -214,19 +214,19 @@ class CastDownloadManager {
|
|||
total: contentLength,
|
||||
});
|
||||
const buffer = new Uint8Array(
|
||||
data.byteLength + value.byteLength
|
||||
data.byteLength + value.byteLength,
|
||||
);
|
||||
buffer.set(new Uint8Array(data), 0);
|
||||
buffer.set(new Uint8Array(value), data.byteLength);
|
||||
if (buffer.length > decryptionChunkSize) {
|
||||
const fileData = buffer.slice(
|
||||
0,
|
||||
decryptionChunkSize
|
||||
decryptionChunkSize,
|
||||
);
|
||||
const { decryptedData } =
|
||||
await cryptoWorker.decryptFileChunk(
|
||||
fileData,
|
||||
pullState
|
||||
pullState,
|
||||
);
|
||||
controller.enqueue(decryptedData);
|
||||
data = buffer.slice(decryptionChunkSize);
|
||||
|
@ -239,7 +239,7 @@ class CastDownloadManager {
|
|||
const { decryptedData } =
|
||||
await cryptoWorker.decryptFileChunk(
|
||||
data,
|
||||
pullState
|
||||
pullState,
|
||||
);
|
||||
controller.enqueue(decryptedData);
|
||||
data = null;
|
||||
|
@ -262,7 +262,7 @@ class CastDownloadManager {
|
|||
} else {
|
||||
this.fileDownloadProgress.set(
|
||||
fileID,
|
||||
Math.round((event.loaded * 100) / event.total)
|
||||
Math.round((event.loaded * 100) / event.total),
|
||||
);
|
||||
}
|
||||
this.progressUpdater(new Map(this.fileDownloadProgress));
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { EventEmitter } from 'eventemitter3';
|
||||
import { EventEmitter } from "eventemitter3";
|
||||
|
||||
// When registering event handlers,
|
||||
// handle errors to avoid unhandled rejection or propagation to emit call
|
||||
|
||||
export enum Events {
|
||||
LOGOUT = 'logout',
|
||||
FILE_UPLOADED = 'fileUploaded',
|
||||
LOCAL_FILES_UPDATED = 'localFilesUpdated',
|
||||
LOGOUT = "logout",
|
||||
FILE_UPLOADED = "fileUploaded",
|
||||
LOCAL_FILES_UPDATED = "localFilesUpdated",
|
||||
}
|
||||
|
||||
export const eventBus = new EventEmitter<Events>();
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
// import isElectron from 'is-electron';
|
||||
// import { ElectronFFmpeg } from 'services/electron/ffmpeg';
|
||||
import { ElectronFile } from 'types/upload';
|
||||
import ComlinkFFmpegWorker from 'utils/comlink/ComlinkFFmpegWorker';
|
||||
import { ElectronFile } from "types/upload";
|
||||
import ComlinkFFmpegWorker from "utils/comlink/ComlinkFFmpegWorker";
|
||||
|
||||
export interface IFFmpeg {
|
||||
run: (
|
||||
cmd: string[],
|
||||
inputFile: File | ElectronFile,
|
||||
outputFilename: string,
|
||||
dontTimeout?: boolean
|
||||
dontTimeout?: boolean,
|
||||
) => Promise<File | ElectronFile>;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { logError } from "@ente/shared/sentry";
|
||||
import {
|
||||
FFMPEG_PLACEHOLDER,
|
||||
INPUT_PATH_PLACEHOLDER,
|
||||
OUTPUT_PATH_PLACEHOLDER,
|
||||
} from 'constants/ffmpeg';
|
||||
import { ElectronFile } from 'types/upload';
|
||||
import ffmpegFactory from './ffmpegFactory';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
} from "constants/ffmpeg";
|
||||
import { ElectronFile } from "types/upload";
|
||||
import ffmpegFactory from "./ffmpegFactory";
|
||||
|
||||
export async function convertToMP4(file: File | ElectronFile) {
|
||||
try {
|
||||
|
@ -13,18 +13,18 @@ export async function convertToMP4(file: File | ElectronFile) {
|
|||
return await ffmpegClient.run(
|
||||
[
|
||||
FFMPEG_PLACEHOLDER,
|
||||
'-i',
|
||||
"-i",
|
||||
INPUT_PATH_PLACEHOLDER,
|
||||
'-preset',
|
||||
'ultrafast',
|
||||
"-preset",
|
||||
"ultrafast",
|
||||
OUTPUT_PATH_PLACEHOLDER,
|
||||
],
|
||||
file,
|
||||
'output.mp4',
|
||||
true
|
||||
"output.mp4",
|
||||
true,
|
||||
);
|
||||
} catch (e) {
|
||||
logError(e, 'ffmpeg convertToMP4 failed');
|
||||
logError(e, "ffmpeg convertToMP4 failed");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { logError } from '@ente/shared/sentry';
|
||||
import WasmHEICConverterService from './wasmHeicConverter/wasmHEICConverterService';
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import WasmHEICConverterService from "./wasmHeicConverter/wasmHEICConverterService";
|
||||
|
||||
class HeicConversionService {
|
||||
async convert(heicFileData: Blob): Promise<Blob> {
|
||||
try {
|
||||
return await WasmHEICConverterService.convert(heicFileData);
|
||||
} catch (e) {
|
||||
logError(e, 'failed to convert heic file');
|
||||
logError(e, "failed to convert heic file");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import JSZip from 'jszip';
|
||||
import { EnteFile } from 'types/file';
|
||||
import JSZip from "jszip";
|
||||
import { EnteFile } from "types/file";
|
||||
import {
|
||||
getFileExtensionWithDot,
|
||||
getFileNameWithoutExtension,
|
||||
} from 'utils/file';
|
||||
} from "utils/file";
|
||||
|
||||
class LivePhoto {
|
||||
image: Uint8Array;
|
||||
|
@ -18,14 +18,14 @@ export const decodeLivePhoto = async (file: EnteFile, zipBlob: Blob) => {
|
|||
|
||||
const livePhoto = new LivePhoto();
|
||||
for (const zipFilename in zip.files) {
|
||||
if (zipFilename.startsWith('image')) {
|
||||
if (zipFilename.startsWith("image")) {
|
||||
livePhoto.imageNameTitle =
|
||||
originalName + getFileExtensionWithDot(zipFilename);
|
||||
livePhoto.image = await zip.files[zipFilename].async('uint8array');
|
||||
} else if (zipFilename.startsWith('video')) {
|
||||
livePhoto.image = await zip.files[zipFilename].async("uint8array");
|
||||
} else if (zipFilename.startsWith("video")) {
|
||||
livePhoto.videoNameTitle =
|
||||
originalName + getFileExtensionWithDot(zipFilename);
|
||||
livePhoto.video = await zip.files[zipFilename].async('uint8array');
|
||||
livePhoto.video = await zip.files[zipFilename].async("uint8array");
|
||||
}
|
||||
}
|
||||
return livePhoto;
|
||||
|
@ -34,12 +34,12 @@ export const decodeLivePhoto = async (file: EnteFile, zipBlob: Blob) => {
|
|||
export const encodeLivePhoto = async (livePhoto: LivePhoto) => {
|
||||
const zip = new JSZip();
|
||||
zip.file(
|
||||
'image' + getFileExtensionWithDot(livePhoto.imageNameTitle),
|
||||
livePhoto.image
|
||||
"image" + getFileExtensionWithDot(livePhoto.imageNameTitle),
|
||||
livePhoto.image,
|
||||
);
|
||||
zip.file(
|
||||
'video' + getFileExtensionWithDot(livePhoto.videoNameTitle),
|
||||
livePhoto.video
|
||||
"video" + getFileExtensionWithDot(livePhoto.videoNameTitle),
|
||||
livePhoto.video,
|
||||
);
|
||||
return await zip.generateAsync({ type: 'uint8array' });
|
||||
return await zip.generateAsync({ type: "uint8array" });
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CustomError } from '@ente/shared/error';
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
|
||||
interface RequestQueueItem {
|
||||
request: (canceller?: RequestCanceller) => Promise<any>;
|
||||
|
@ -28,11 +28,11 @@ export default class QueueProcessor<T> {
|
|||
|
||||
constructor(
|
||||
private maxParallelProcesses: number,
|
||||
private processingStrategy = PROCESSING_STRATEGY.FIFO
|
||||
private processingStrategy = PROCESSING_STRATEGY.FIFO,
|
||||
) {}
|
||||
|
||||
public queueUpRequest(
|
||||
request: (canceller?: RequestCanceller) => Promise<T>
|
||||
request: (canceller?: RequestCanceller) => Promise<T>,
|
||||
) {
|
||||
const isCanceled: CancellationStatus = { status: false };
|
||||
const canceller: RequestCanceller = {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { logError } from '@ente/shared/sentry';
|
||||
import { convertBytesToHumanReadable } from '@ente/shared/utils/size';
|
||||
import { ElectronFile } from 'types/upload';
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { ElectronFile } from "types/upload";
|
||||
|
||||
export async function getUint8ArrayView(
|
||||
file: Blob | ElectronFile
|
||||
file: Blob | ElectronFile,
|
||||
): Promise<Uint8Array> {
|
||||
try {
|
||||
return new Uint8Array(await file.arrayBuffer());
|
||||
} catch (e) {
|
||||
logError(e, 'reading file blob failed', {
|
||||
logError(e, "reading file blob failed", {
|
||||
fileSize: convertBytesToHumanReadable(file.size),
|
||||
});
|
||||
throw e;
|
||||
|
@ -37,7 +37,7 @@ export function getFileStream(file: File, chunkSize: number) {
|
|||
|
||||
export async function getElectronFileStream(
|
||||
file: ElectronFile,
|
||||
chunkSize: number
|
||||
chunkSize: number,
|
||||
) {
|
||||
const chunkCount = Math.ceil(file.size / chunkSize);
|
||||
return {
|
||||
|
@ -61,29 +61,29 @@ async function* fileChunkReaderMaker(file: File, chunkSize: number) {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function getUint8ArrayViewOld(
|
||||
reader: FileReader,
|
||||
file: Blob
|
||||
file: Blob,
|
||||
): Promise<Uint8Array> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
reader.onabort = () =>
|
||||
reject(
|
||||
Error(
|
||||
`file reading was aborted, file size= ${convertBytesToHumanReadable(
|
||||
file.size
|
||||
)}`
|
||||
)
|
||||
file.size,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
reader.onerror = () =>
|
||||
reject(
|
||||
Error(
|
||||
`file reading has failed, file size= ${convertBytesToHumanReadable(
|
||||
file.size
|
||||
)} , reason= ${reader.error}`
|
||||
)
|
||||
file.size,
|
||||
)} , reason= ${reader.error}`,
|
||||
),
|
||||
);
|
||||
reader.onload = () => {
|
||||
// Do whatever you want with the file contents
|
||||
const result =
|
||||
typeof reader.result === 'string'
|
||||
typeof reader.result === "string"
|
||||
? new TextEncoder().encode(reader.result)
|
||||
: new Uint8Array(reader.result);
|
||||
resolve(result);
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import { FILE_TYPE } from 'constants/file';
|
||||
import { ElectronFile, FileTypeInfo } from 'types/upload';
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import {
|
||||
WHITELISTED_FILE_FORMATS,
|
||||
KNOWN_NON_MEDIA_FORMATS,
|
||||
} from 'constants/upload';
|
||||
import { CustomError } from '@ente/shared/error';
|
||||
import { getFileExtension } from 'utils/file';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { getUint8ArrayView } from './readerService';
|
||||
import FileType, { FileTypeResult } from 'file-type';
|
||||
import { convertBytesToHumanReadable } from '@ente/shared/utils/size';
|
||||
WHITELISTED_FILE_FORMATS,
|
||||
} from "constants/upload";
|
||||
import FileType, { FileTypeResult } from "file-type";
|
||||
import { ElectronFile, FileTypeInfo } from "types/upload";
|
||||
import { getFileExtension } from "utils/file";
|
||||
import { getUint8ArrayView } from "./readerService";
|
||||
|
||||
function getFileSize(file: File | ElectronFile) {
|
||||
return file.size;
|
||||
}
|
||||
|
||||
const TYPE_VIDEO = 'video';
|
||||
const TYPE_IMAGE = 'image';
|
||||
const TYPE_VIDEO = "video";
|
||||
const TYPE_IMAGE = "image";
|
||||
const CHUNK_SIZE_FOR_TYPE_DETECTION = 4100;
|
||||
|
||||
export async function getFileType(
|
||||
receivedFile: File | ElectronFile
|
||||
receivedFile: File | ElectronFile,
|
||||
): Promise<FileTypeInfo> {
|
||||
try {
|
||||
let fileType: FILE_TYPE;
|
||||
|
@ -32,7 +32,7 @@ export async function getFileType(
|
|||
typeResult = await extractElectronFileType(receivedFile);
|
||||
}
|
||||
|
||||
const mimTypeParts: string[] = typeResult.mime?.split('/');
|
||||
const mimTypeParts: string[] = typeResult.mime?.split("/");
|
||||
|
||||
if (mimTypeParts?.length !== 2) {
|
||||
throw Error(CustomError.INVALID_MIME_TYPE(typeResult.mime));
|
||||
|
@ -56,7 +56,7 @@ export async function getFileType(
|
|||
const fileFormat = getFileExtension(receivedFile.name);
|
||||
const fileSize = convertBytesToHumanReadable(getFileSize(receivedFile));
|
||||
const whiteListedFormat = WHITELISTED_FILE_FORMATS.find(
|
||||
(a) => a.exactType === fileFormat
|
||||
(a) => a.exactType === fileFormat,
|
||||
);
|
||||
if (whiteListedFormat) {
|
||||
return whiteListedFormat;
|
||||
|
@ -65,13 +65,13 @@ export async function getFileType(
|
|||
throw Error(CustomError.UNSUPPORTED_FILE_FORMAT);
|
||||
}
|
||||
if (e.message === CustomError.NON_MEDIA_FILE) {
|
||||
logError(e, 'unsupported file format', {
|
||||
logError(e, "unsupported file format", {
|
||||
fileFormat,
|
||||
fileSize,
|
||||
});
|
||||
throw Error(CustomError.UNSUPPORTED_FILE_FORMAT);
|
||||
}
|
||||
logError(e, 'type detection failed', {
|
||||
logError(e, "type detection failed", {
|
||||
fileFormat,
|
||||
fileSize,
|
||||
});
|
||||
|
@ -96,11 +96,11 @@ async function extractElectronFileType(file: ElectronFile) {
|
|||
async function getFileTypeFromBuffer(buffer: Uint8Array) {
|
||||
const result = await FileType.fromBuffer(buffer);
|
||||
if (!result?.mime) {
|
||||
let logableInfo = '';
|
||||
let logableInfo = "";
|
||||
try {
|
||||
logableInfo = `result: ${JSON.stringify(result)}`;
|
||||
} catch (e) {
|
||||
logableInfo = 'failed to stringify result';
|
||||
logableInfo = "failed to stringify result";
|
||||
}
|
||||
throw Error(`mimetype missing from file type result - ${logableInfo}`);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { promiseWithTimeout } from '@ente/shared/promise';
|
||||
import { createFFmpeg, FFmpeg } from 'ffmpeg-wasm';
|
||||
import QueueProcessor from 'services/queueProcessor';
|
||||
import { getUint8ArrayView } from 'services/readerService';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { generateTempName } from 'utils/temp';
|
||||
import { addLogLine } from '@ente/shared/logging';
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { promiseWithTimeout } from "@ente/shared/promise";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { createFFmpeg, FFmpeg } from "ffmpeg-wasm";
|
||||
import QueueProcessor from "services/queueProcessor";
|
||||
import { getUint8ArrayView } from "services/readerService";
|
||||
import { generateTempName } from "utils/temp";
|
||||
|
||||
const INPUT_PATH_PLACEHOLDER = 'INPUT';
|
||||
const FFMPEG_PLACEHOLDER = 'FFMPEG';
|
||||
const OUTPUT_PATH_PLACEHOLDER = 'OUTPUT';
|
||||
const INPUT_PATH_PLACEHOLDER = "INPUT";
|
||||
const FFMPEG_PLACEHOLDER = "FFMPEG";
|
||||
const OUTPUT_PATH_PLACEHOLDER = "OUTPUT";
|
||||
|
||||
const FFMPEG_EXECUTION_WAIT_TIME = 30 * 1000;
|
||||
|
||||
|
@ -19,7 +19,7 @@ export class WasmFFmpeg {
|
|||
|
||||
constructor() {
|
||||
this.ffmpeg = createFFmpeg({
|
||||
corePath: '/js/ffmpeg/ffmpeg-core.js',
|
||||
corePath: "/js/ffmpeg/ffmpeg-core.js",
|
||||
mt: false,
|
||||
});
|
||||
|
||||
|
@ -36,7 +36,7 @@ export class WasmFFmpeg {
|
|||
cmd: string[],
|
||||
inputFile: File,
|
||||
outputFileName: string,
|
||||
dontTimeout = false
|
||||
dontTimeout = false,
|
||||
) {
|
||||
const response = this.ffmpegTaskQueue.queueUpRequest(() => {
|
||||
if (dontTimeout) {
|
||||
|
@ -44,14 +44,14 @@ export class WasmFFmpeg {
|
|||
} else {
|
||||
return promiseWithTimeout<File>(
|
||||
this.execute(cmd, inputFile, outputFileName),
|
||||
FFMPEG_EXECUTION_WAIT_TIME
|
||||
FFMPEG_EXECUTION_WAIT_TIME,
|
||||
);
|
||||
}
|
||||
});
|
||||
try {
|
||||
return await response.promise;
|
||||
} catch (e) {
|
||||
logError(e, 'ffmpeg run failed');
|
||||
logError(e, "ffmpeg run failed");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -59,25 +59,25 @@ export class WasmFFmpeg {
|
|||
private async execute(
|
||||
cmd: string[],
|
||||
inputFile: File,
|
||||
outputFileName: string
|
||||
outputFileName: string,
|
||||
) {
|
||||
let tempInputFilePath: string;
|
||||
let tempOutputFilePath: string;
|
||||
try {
|
||||
await this.ready;
|
||||
const extension = getFileExtension(inputFile.name);
|
||||
const tempNameSuffix = extension ? `input.${extension}` : 'input';
|
||||
const tempNameSuffix = extension ? `input.${extension}` : "input";
|
||||
tempInputFilePath = `${generateTempName(10, tempNameSuffix)}`;
|
||||
this.ffmpeg.FS(
|
||||
'writeFile',
|
||||
"writeFile",
|
||||
tempInputFilePath,
|
||||
await getUint8ArrayView(inputFile)
|
||||
await getUint8ArrayView(inputFile),
|
||||
);
|
||||
tempOutputFilePath = `${generateTempName(10, outputFileName)}`;
|
||||
|
||||
cmd = cmd.map((cmdPart) => {
|
||||
if (cmdPart === FFMPEG_PLACEHOLDER) {
|
||||
return '';
|
||||
return "";
|
||||
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
|
||||
return tempInputFilePath;
|
||||
} else if (cmdPart === OUTPUT_PATH_PLACEHOLDER) {
|
||||
|
@ -89,26 +89,26 @@ export class WasmFFmpeg {
|
|||
addLogLine(`${cmd}`);
|
||||
await this.ffmpeg.run(...cmd);
|
||||
return new File(
|
||||
[this.ffmpeg.FS('readFile', tempOutputFilePath)],
|
||||
outputFileName
|
||||
[this.ffmpeg.FS("readFile", tempOutputFilePath)],
|
||||
outputFileName,
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
this.ffmpeg.FS('unlink', tempInputFilePath);
|
||||
this.ffmpeg.FS("unlink", tempInputFilePath);
|
||||
} catch (e) {
|
||||
logError(e, 'unlink input file failed');
|
||||
logError(e, "unlink input file failed");
|
||||
}
|
||||
try {
|
||||
this.ffmpeg.FS('unlink', tempOutputFilePath);
|
||||
this.ffmpeg.FS("unlink", tempOutputFilePath);
|
||||
} catch (e) {
|
||||
logError(e, 'unlink output file failed');
|
||||
logError(e, "unlink output file failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getFileExtension(filename: string) {
|
||||
const lastDotPosition = filename.lastIndexOf('.');
|
||||
const lastDotPosition = filename.lastIndexOf(".");
|
||||
if (lastDotPosition === -1) return null;
|
||||
else {
|
||||
return filename.slice(lastDotPosition + 1);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as HeicConvert from 'heic-convert';
|
||||
import { getUint8ArrayView } from 'services/readerService';
|
||||
import * as HeicConvert from "heic-convert";
|
||||
import { getUint8ArrayView } from "services/readerService";
|
||||
|
||||
export async function convertHEIC(
|
||||
fileBlob: Blob,
|
||||
format: string
|
||||
format: string,
|
||||
): Promise<Blob> {
|
||||
const filedata = await getUint8ArrayView(fileBlob);
|
||||
const result = await HeicConvert({ buffer: filedata, format });
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import QueueProcessor from 'services/queueProcessor';
|
||||
import { retryAsyncFunction } from 'utils/network';
|
||||
import { DedicatedConvertWorker } from 'worker/convert.worker';
|
||||
import { ComlinkWorker } from 'utils/comlink/comlinkWorker';
|
||||
import { getDedicatedConvertWorker } from 'utils/comlink/ComlinkConvertWorker';
|
||||
import { logError } from '@ente/shared/sentry';
|
||||
import { addLogLine } from '@ente/shared/logging';
|
||||
import { convertBytesToHumanReadable } from '@ente/shared/utils/size';
|
||||
import { CustomError } from '@ente/shared/error';
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import { addLogLine } from "@ente/shared/logging";
|
||||
import { logError } from "@ente/shared/sentry";
|
||||
import { convertBytesToHumanReadable } from "@ente/shared/utils/size";
|
||||
import QueueProcessor from "services/queueProcessor";
|
||||
import { getDedicatedConvertWorker } from "utils/comlink/ComlinkConvertWorker";
|
||||
import { ComlinkWorker } from "utils/comlink/comlinkWorker";
|
||||
import { retryAsyncFunction } from "utils/network";
|
||||
import { DedicatedConvertWorker } from "worker/convert.worker";
|
||||
|
||||
const WORKER_POOL_SIZE = 2;
|
||||
const MAX_CONVERSION_IN_PARALLEL = 1;
|
||||
const WAIT_TIME_BEFORE_NEXT_ATTEMPT_IN_MICROSECONDS = [100, 100];
|
||||
const WAIT_TIME_IN_MICROSECONDS = 30 * 1000;
|
||||
const BREATH_TIME_IN_MICROSECONDS = 1000;
|
||||
const CONVERT_FORMAT = 'JPEG';
|
||||
const CONVERT_FORMAT = "JPEG";
|
||||
|
||||
class HEICConverter {
|
||||
private convertProcessor = new QueueProcessor<Blob>(
|
||||
MAX_CONVERSION_IN_PARALLEL
|
||||
MAX_CONVERSION_IN_PARALLEL,
|
||||
);
|
||||
private workerPool: ComlinkWorker<typeof DedicatedConvertWorker>[] = [];
|
||||
private ready: Promise<void>;
|
||||
|
@ -43,22 +43,22 @@ class HEICConverter {
|
|||
const main = async () => {
|
||||
try {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(Error('wait time exceeded'));
|
||||
reject(Error("wait time exceeded"));
|
||||
}, WAIT_TIME_IN_MICROSECONDS);
|
||||
const startTime = Date.now();
|
||||
const convertedHEIC =
|
||||
await worker.convertHEIC(
|
||||
fileBlob,
|
||||
CONVERT_FORMAT
|
||||
CONVERT_FORMAT,
|
||||
);
|
||||
addLogLine(
|
||||
`originalFileSize:${convertBytesToHumanReadable(
|
||||
fileBlob?.size
|
||||
fileBlob?.size,
|
||||
)},convertedFileSize:${convertBytesToHumanReadable(
|
||||
convertedHEIC?.size
|
||||
convertedHEIC?.size,
|
||||
)}, heic conversion time: ${
|
||||
Date.now() - startTime
|
||||
}ms `
|
||||
}ms `,
|
||||
);
|
||||
clearTimeout(timeout);
|
||||
resolve(convertedHEIC);
|
||||
|
@ -67,37 +67,37 @@ class HEICConverter {
|
|||
}
|
||||
};
|
||||
main();
|
||||
}
|
||||
},
|
||||
);
|
||||
if (!convertedHEIC || convertedHEIC?.size === 0) {
|
||||
logError(
|
||||
Error(`converted heic fileSize is Zero`),
|
||||
'converted heic fileSize is Zero',
|
||||
"converted heic fileSize is Zero",
|
||||
{
|
||||
originalFileSize: convertBytesToHumanReadable(
|
||||
fileBlob?.size ?? 0
|
||||
fileBlob?.size ?? 0,
|
||||
),
|
||||
convertedFileSize: convertBytesToHumanReadable(
|
||||
convertedHEIC?.size ?? 0
|
||||
convertedHEIC?.size ?? 0,
|
||||
),
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(
|
||||
() => resolve(null),
|
||||
BREATH_TIME_IN_MICROSECONDS
|
||||
BREATH_TIME_IN_MICROSECONDS,
|
||||
);
|
||||
});
|
||||
this.workerPool.push(convertWorker);
|
||||
return convertedHEIC;
|
||||
} catch (e) {
|
||||
logError(e, 'heic conversion failed');
|
||||
logError(e, "heic conversion failed");
|
||||
convertWorker.terminate();
|
||||
this.workerPool.push(getDedicatedConvertWorker());
|
||||
throw e;
|
||||
}
|
||||
}, WAIT_TIME_BEFORE_NEXT_ATTEMPT_IN_MICROSECONDS)
|
||||
}, WAIT_TIME_BEFORE_NEXT_ATTEMPT_IN_MICROSECONDS),
|
||||
);
|
||||
try {
|
||||
return await response.promise;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { EnteFile } from 'types/file';
|
||||
import { CollectionSummaryType, CollectionType } from 'constants/collection';
|
||||
import { CollectionSummaryType, CollectionType } from "constants/collection";
|
||||
import { EnteFile } from "types/file";
|
||||
import {
|
||||
EncryptedMagicMetadata,
|
||||
MagicMetadataCore,
|
||||
SUB_TYPE,
|
||||
VISIBILITY_STATE,
|
||||
} from 'types/magicMetadata';
|
||||
} from "types/magicMetadata";
|
||||
|
||||
export enum COLLECTION_ROLE {
|
||||
VIEWER = 'VIEWER',
|
||||
OWNER = 'OWNER',
|
||||
COLLABORATOR = 'COLLABORATOR',
|
||||
UNKNOWN = 'UNKNOWN',
|
||||
VIEWER = "VIEWER",
|
||||
OWNER = "OWNER",
|
||||
COLLABORATOR = "COLLABORATOR",
|
||||
UNKNOWN = "UNKNOWN",
|
||||
}
|
||||
|
||||
export interface CollectionUser {
|
||||
|
@ -43,13 +43,13 @@ export interface EncryptedCollection {
|
|||
export interface Collection
|
||||
extends Omit<
|
||||
EncryptedCollection,
|
||||
| 'encryptedKey'
|
||||
| 'keyDecryptionNonce'
|
||||
| 'encryptedName'
|
||||
| 'nameDecryptionNonce'
|
||||
| 'magicMetadata'
|
||||
| 'pubMagicMetadata'
|
||||
| 'sharedMagicMetadata'
|
||||
| "encryptedKey"
|
||||
| "keyDecryptionNonce"
|
||||
| "encryptedName"
|
||||
| "nameDecryptionNonce"
|
||||
| "magicMetadata"
|
||||
| "pubMagicMetadata"
|
||||
| "sharedMagicMetadata"
|
||||
> {
|
||||
key: string;
|
||||
name: string;
|
||||
|
|
|
@ -2,8 +2,8 @@ import {
|
|||
EncryptedMagicMetadata,
|
||||
MagicMetadataCore,
|
||||
VISIBILITY_STATE,
|
||||
} from 'types/magicMetadata';
|
||||
import { Metadata } from 'types/upload';
|
||||
} from "types/magicMetadata";
|
||||
import { Metadata } from "types/upload";
|
||||
|
||||
export interface MetadataFileAttributes {
|
||||
encryptedData: string;
|
||||
|
@ -38,11 +38,11 @@ export interface EncryptedEnteFile {
|
|||
export interface EnteFile
|
||||
extends Omit<
|
||||
EncryptedEnteFile,
|
||||
| 'metadata'
|
||||
| 'pubMagicMetadata'
|
||||
| 'magicMetadata'
|
||||
| 'encryptedKey'
|
||||
| 'keyDecryptionNonce'
|
||||
| "metadata"
|
||||
| "pubMagicMetadata"
|
||||
| "magicMetadata"
|
||||
| "encryptedKey"
|
||||
| "keyDecryptionNonce"
|
||||
> {
|
||||
metadata: Metadata;
|
||||
magicMetadata: FileMagicMetadata;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// import { CollectionDownloadProgressAttributes } from 'components/Collections/CollectionDownloadProgress';
|
||||
// import { CollectionSelectorAttributes } from 'components/Collections/CollectionSelector';
|
||||
// import { TimeStampListItem } from 'components/PhotoList';
|
||||
import { User } from '@ente/shared/user/types';
|
||||
import { Collection } from 'types/collection';
|
||||
import { EnteFile } from 'types/file';
|
||||
import { User } from "@ente/shared/user/types";
|
||||
import { Collection } from "types/collection";
|
||||
import { EnteFile } from "types/file";
|
||||
|
||||
export type SelectedState = {
|
||||
[k: number]: boolean;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import {
|
||||
B64EncryptionResult,
|
||||
LocalFileAttributes,
|
||||
} from '@ente/shared/crypto/types';
|
||||
import { FILE_TYPE } from 'constants/file';
|
||||
import { Collection } from 'types/collection';
|
||||
} from "@ente/shared/crypto/types";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
import { Collection } from "types/collection";
|
||||
import {
|
||||
MetadataFileAttributes,
|
||||
S3FileAttributes,
|
||||
FilePublicMagicMetadata,
|
||||
FilePublicMagicMetadataProps,
|
||||
} from 'types/file';
|
||||
import { EncryptedMagicMetadata } from 'types/magicMetadata';
|
||||
MetadataFileAttributes,
|
||||
S3FileAttributes,
|
||||
} from "types/file";
|
||||
import { EncryptedMagicMetadata } from "types/magicMetadata";
|
||||
|
||||
export interface DataStream {
|
||||
stream: ReadableStream<Uint8Array>;
|
||||
|
@ -18,7 +18,7 @@ export interface DataStream {
|
|||
}
|
||||
|
||||
export function isDataStream(object: any): object is DataStream {
|
||||
return 'stream' in object;
|
||||
return "stream" in object;
|
||||
}
|
||||
|
||||
export type Logger = (message: string) => void;
|
||||
|
@ -114,7 +114,7 @@ export interface FileInMemory {
|
|||
}
|
||||
|
||||
export interface FileWithMetadata
|
||||
extends Omit<FileInMemory, 'hasStaticThumbnail'> {
|
||||
extends Omit<FileInMemory, "hasStaticThumbnail"> {
|
||||
metadata: Metadata;
|
||||
localID: number;
|
||||
pubMagicMetadata: FilePublicMagicMetadata;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UPLOAD_RESULT, UPLOAD_STAGES } from 'constants/upload';
|
||||
import { UPLOAD_RESULT, UPLOAD_STAGES } from "constants/upload";
|
||||
|
||||
export type FileID = number;
|
||||
export type FileName = string;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { COLLECTION_ROLE, Collection } from 'types/collection';
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import { User } from "@ente/shared/user/types";
|
||||
import {
|
||||
CollectionSummaryType,
|
||||
CollectionType,
|
||||
HIDE_FROM_COLLECTION_BAR_TYPES,
|
||||
OPTIONS_NOT_HAVING_COLLECTION_TYPES,
|
||||
} from 'constants/collection';
|
||||
import { SUB_TYPE, VISIBILITY_STATE } from 'types/magicMetadata';
|
||||
import { User } from '@ente/shared/user/types';
|
||||
import { getData, LS_KEYS } from '@ente/shared/storage/localStorage';
|
||||
} from "constants/collection";
|
||||
import { COLLECTION_ROLE, Collection } from "types/collection";
|
||||
import { SUB_TYPE, VISIBILITY_STATE } from "types/magicMetadata";
|
||||
|
||||
export enum COLLECTION_OPS_TYPE {
|
||||
ADD,
|
||||
|
@ -19,7 +19,7 @@ export enum COLLECTION_OPS_TYPE {
|
|||
|
||||
export function getSelectedCollection(
|
||||
collectionID: number,
|
||||
collections: Collection[]
|
||||
collections: Collection[],
|
||||
) {
|
||||
return collections.find((collection) => collection.id === collectionID);
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export const shouldBeShownOnCollectionBar = (type: CollectionSummaryType) => {
|
|||
export const getUserOwnedCollections = (collections: Collection[]) => {
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
if (!user?.id) {
|
||||
throw Error('user missing');
|
||||
throw Error("user missing");
|
||||
}
|
||||
return collections.filter((collection) => collection.owner.id === user.id);
|
||||
};
|
||||
|
@ -103,7 +103,7 @@ export function isSharedOnlyViaLink(collection: Collection) {
|
|||
export function isValidMoveTarget(
|
||||
sourceCollectionID: number,
|
||||
targetCollection: Collection,
|
||||
user: User
|
||||
user: User,
|
||||
) {
|
||||
return (
|
||||
sourceCollectionID !== targetCollection.id &&
|
||||
|
@ -116,7 +116,7 @@ export function isValidMoveTarget(
|
|||
export function isValidReplacementAlbum(
|
||||
collection: Collection,
|
||||
user: User,
|
||||
wantedCollectionName: string
|
||||
wantedCollectionName: string,
|
||||
) {
|
||||
return (
|
||||
collection.name === wantedCollectionName &&
|
||||
|
@ -129,15 +129,15 @@ export function isValidReplacementAlbum(
|
|||
}
|
||||
|
||||
export function getCollectionNameMap(
|
||||
collections: Collection[]
|
||||
collections: Collection[],
|
||||
): Map<number, string> {
|
||||
return new Map<number, string>(
|
||||
collections.map((collection) => [collection.id, collection.name])
|
||||
collections.map((collection) => [collection.id, collection.name]),
|
||||
);
|
||||
}
|
||||
|
||||
export function getNonHiddenCollections(
|
||||
collections: Collection[]
|
||||
collections: Collection[],
|
||||
): Collection[] {
|
||||
return collections.filter((collection) => !isHiddenCollection(collection));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Remote } from 'comlink';
|
||||
import { DedicatedConvertWorker } from 'worker/convert.worker';
|
||||
import { ComlinkWorker } from './comlinkWorker';
|
||||
import { runningInBrowser } from '@ente/shared/platform';
|
||||
import { runningInBrowser } from "@ente/shared/platform";
|
||||
import { Remote } from "comlink";
|
||||
import { DedicatedConvertWorker } from "worker/convert.worker";
|
||||
import { ComlinkWorker } from "./comlinkWorker";
|
||||
|
||||
class ComlinkConvertWorker {
|
||||
private comlinkWorkerInstance: Remote<DedicatedConvertWorker>;
|
||||
|
@ -20,8 +20,8 @@ export const getDedicatedConvertWorker = () => {
|
|||
const cryptoComlinkWorker = new ComlinkWorker<
|
||||
typeof DedicatedConvertWorker
|
||||
>(
|
||||
'ente-convert-worker',
|
||||
new Worker(new URL('worker/convert.worker.ts', import.meta.url))
|
||||
"ente-convert-worker",
|
||||
new Worker(new URL("worker/convert.worker.ts", import.meta.url)),
|
||||
);
|
||||
return cryptoComlinkWorker;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue