added braces everywhere and formated all files

This commit is contained in:
Abhinav-grd 2021-02-09 15:03:54 +05:30
parent 829e59635d
commit 1d5473f3dd
42 changed files with 1183 additions and 710 deletions

View file

@ -1,7 +1,5 @@
{
"presets": [
"next/babel"
],
"presets": ["next/babel"],
"plugins": [
[
"styled-components",
@ -12,4 +10,4 @@
}
]
]
}
}

View file

@ -12,9 +12,8 @@ const Container = styled.div`
export default Container;
export const DisclaimerContainer = styled.div`
margin: 16px 0;
margin: 16px 0;
color: rgb(158, 150, 137);
font-size: 14px;
`;

View file

@ -1,23 +1,24 @@
import React from "react";
import styled from "styled-components";
import React from 'react';
import styled from 'styled-components';
const HeartUI = styled.button<{
isClick: boolean,
size: number,
isClick: boolean;
size: number;
}>`
width: ${props => props.size}px;
height: ${props => props.size}px;
float:right;
background: url("/fav-button.png") no-repeat;
cursor: pointer;
background-size: cover;
border: none;
${({ isClick, size }) => isClick && `background-position: -${28 * size}px;transition: background 1s steps(28);`}
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
float: right;
background: url('/fav-button.png') no-repeat;
cursor: pointer;
background-size: cover;
border: none;
${({ isClick, size }) =>
isClick &&
`background-position: -${
28 * size
}px;transition: background 1s steps(28);`}
`;
export default function FavButton({ isClick, onClick, size }) {
return (
<HeartUI isClick={isClick} onClick={onClick} size={size}/>
);
}
return <HeartUI isClick={isClick} onClick={onClick} size={size} />;
}

View file

@ -2,9 +2,9 @@ import React, { useRef } from 'react';
import styled from 'styled-components';
const DropDiv = styled.div`
flex: 1;
display: flex;
flex-direction: column;
flex: 1;
display: flex;
flex-direction: column;
`;
type Props = React.PropsWithChildren<{
@ -12,30 +12,34 @@ type Props = React.PropsWithChildren<{
closeModal: () => void;
}>;
export default function FullScreenDropZone({ children, showModal, closeModal }: Props) {
export default function FullScreenDropZone({
children,
showModal,
closeModal,
}: Props) {
const closeTimer = useRef<number>();
const clearTimer = () => {
if (closeTimer.current) {
clearTimeout(closeTimer.current);
}
}
};
const onDragOver = (e) => {
e.preventDefault();
clearTimer();
showModal();
}
};
const onDragLeave = (e) => {
e.preventDefault();
clearTimer();
closeTimer.current = setTimeout(closeModal, 1000);
}
};
return (
<DropDiv onDragOver={onDragOver} onDragLeave={onDragLeave}>
{children}
</DropDiv>
);
};
}

View file

@ -4,11 +4,14 @@ import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default';
import classnames from 'classnames';
import events from './events';
import FavButton from 'components/FavButton';
import { addToFavorites, removeFromFavorites } from 'services/collectionService';
import {
addToFavorites,
removeFromFavorites,
} from 'services/collectionService';
import { file } from 'services/fileService';
interface Iprops {
isOpen: boolean
isOpen: boolean;
items: any[];
options?: Object;
onClose?: () => void;
@ -17,28 +20,26 @@ interface Iprops {
className?: string;
favItemIds: Set<number>;
setFavItemIds: (favItemIds: Set<number>) => void;
};
}
function PhotoSwipe(props: Iprops) {
let pswpElement;
const [photoSwipe, setPhotoSwipe] = useState<Photoswipe<any>>();
const { isOpen } = props;
const [isFav, setIsFav] = useState(false)
const [isFav, setIsFav] = useState(false);
useEffect(() => {
if (!pswpElement)
if (!pswpElement) {
return;
if (isOpen)
}
if (isOpen) {
openPhotoSwipe();
}
}, [pswpElement]);
useEffect(() => {
if (!pswpElement)
return;
if (!pswpElement) return;
if (isOpen) {
openPhotoSwipe();
}
@ -47,17 +48,21 @@ function PhotoSwipe(props: Iprops) {
}
return () => {
closePhotoSwipe();
}
};
}, [isOpen]);
function updateFavButton() {
setIsFav(isInFav(this?.currItem));
}
const openPhotoSwipe = () => {
const { items, options } = props;
let photoSwipe = new Photoswipe(pswpElement, PhotoswipeUIDefault, items, options);
let photoSwipe = new Photoswipe(
pswpElement,
PhotoswipeUIDefault,
items,
options
);
events.forEach((event) => {
const callback = props[event];
if (callback || event === 'destroy') {
@ -75,7 +80,6 @@ function PhotoSwipe(props: Iprops) {
photoSwipe.listen('beforeChange', updateFavButton);
photoSwipe.init();
setPhotoSwipe(photoSwipe);
};
const updateItems = (items = []) => {
@ -88,8 +92,7 @@ function PhotoSwipe(props: Iprops) {
};
const closePhotoSwipe = () => {
if (photoSwipe)
photoSwipe.close();
if (photoSwipe) photoSwipe.close();
};
const handleClose = () => {
@ -102,10 +105,8 @@ function PhotoSwipe(props: Iprops) {
const { favItemIds } = props;
if (favItemIds && file) {
return favItemIds.has(file.id);
}
else
return false;
}
} else return false;
};
const onFavClick = async (file) => {
const { favItemIds, setFavItemIds } = props;
@ -114,15 +115,13 @@ function PhotoSwipe(props: Iprops) {
await addToFavorites(file);
setIsFav(true);
setFavItemIds(favItemIds);
}
else {
} else {
favItemIds.delete(file.id);
await removeFromFavorites(file)
await removeFromFavorites(file);
setIsFav(false);
setFavItemIds(favItemIds);
}
}
};
const { id } = props;
let { className } = props;
className = classnames(['pswp', className]).trim();
@ -130,7 +129,7 @@ function PhotoSwipe(props: Iprops) {
<div
id={id}
className={className}
tabIndex={Number("-1")}
tabIndex={Number('-1')}
role="dialog"
aria-hidden="true"
ref={(node) => {
@ -160,8 +159,17 @@ function PhotoSwipe(props: Iprops) {
className="pswp__button pswp__button--fs"
title="Toggle fullscreen"
/>
<button className="pswp__button pswp__button--zoom" title="Zoom in/out" />
<FavButton size={44} isClick={isFav} onClick={() => { onFavClick(photoSwipe?.currItem) }} />
<button
className="pswp__button pswp__button--zoom"
title="Zoom in/out"
/>
<FavButton
size={44}
isClick={isFav}
onClick={() => {
onFavClick(photoSwipe?.currItem);
}}
/>
<div className="pswp__preloader">
<div className="pswp__preloader__icn">
<div className="pswp__preloader__cut">
@ -190,4 +198,4 @@ function PhotoSwipe(props: Iprops) {
);
}
export default PhotoSwipe;
export default PhotoSwipe;

View file

@ -15,5 +15,5 @@ export default [
'destroy',
'updateScrollOffset',
'preventDragEvent',
'shareLinkClick'
];
'shareLinkClick',
];

View file

@ -8,8 +8,8 @@ export default function PlayCircleOutline(props) {
viewBox={props.viewBox}
width={props.width}
>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</svg>
);
}
@ -18,4 +18,4 @@ PlayCircleOutline.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
}
};

View file

@ -8,10 +8,10 @@ export default function SadFace(props) {
viewBox={props.viewBox}
width={props.width}
>
<path d="M0 0h24v24H0V0z" fill="none"/>
<circle cx="15.5" cy="9.5" r="1.5"/>
<circle cx="8.5" cy="9.5" r="1.5"/>
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-6c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5z"/>
<path d="M0 0h24v24H0V0z" fill="none" />
<circle cx="15.5" cy="9.5" r="1.5" />
<circle cx="8.5" cy="9.5" r="1.5" />
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-6c-2.33 0-4.32 1.45-5.12 3.5h1.67c.69-1.19 1.97-2 3.45-2s2.75.81 3.45 2h1.67c-.8-2.05-2.79-3.5-5.12-3.5z" />
</svg>
);
}
@ -20,4 +20,4 @@ SadFace.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
}
};

View file

@ -8,8 +8,8 @@ export default function PowerSettings(props) {
viewBox={props.viewBox}
width={props.width}
>
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z"/>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z" />
</svg>
);
}
@ -18,4 +18,4 @@ PowerSettings.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
}
};

View file

@ -108,12 +108,12 @@ const GlobalStyles = createGlobalStyle`
`;
const Image = styled.img`
max-height: 28px;
margin-right: 5px;
max-height: 28px;
margin-right: 5px;
`;
const FlexContainer = styled.div`
flex: 1;
flex: 1;
`;
export default function App({ Component, pageProps }) {
@ -163,35 +163,39 @@ export default function App({ Component, pageProps }) {
showModal={showUploadModal}
>
<Head>
<title>ente.io | Privacy friendly alternative to Google Photos</title>
<title>
ente.io | Privacy friendly alternative to Google Photos
</title>
</Head>
<GlobalStyles />
<Navbar>
<FlexContainer>
<Image alt='logo' src='/icon.png' />
<Image alt="logo" src="/icon.png" />
{constants.COMPANY_NAME}
</FlexContainer>
{uploadButtonView && <UploadButton showModal={showUploadModal} />}
{user &&
<Button variant='link' onClick={logout}>
{uploadButtonView && (
<UploadButton showModal={showUploadModal} />
)}
{user && (
<Button variant="link" onClick={logout}>
<PowerSettings />
</Button>
}
)}
</Navbar>
{loading ? (
<Container>
<Spinner animation='border' role='status' variant='primary'>
<span className='sr-only'>Loading...</span>
<Spinner animation="border" role="status" variant="primary">
<span className="sr-only">Loading...</span>
</Spinner>
</Container>
) : (
<Component
uploadModalView={uploadModalView}
showUploadModal={showUploadModal}
closeUploadModal={closeUploadModal}
setUploadButtonView={setUploadButtonView}
/>
)}
<Component
uploadModalView={uploadModalView}
showUploadModal={showUploadModal}
closeUploadModal={closeUploadModal}
setUploadButtonView={setUploadButtonView}
/>
)}
</FullScreenDropZone>
);
}

View file

@ -1,21 +1,19 @@
import Document, {
Html, Head, Main, NextScript,
} from 'next/document';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
});
const initialProps = await Document.getInitialProps(ctx)
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
@ -24,21 +22,21 @@ export default class MyDocument extends Document {
{sheet.getStyleElement()}
</>
),
}
};
} finally {
sheet.seal()
sheet.seal();
}
}
render() {
return (
<Html lang='en'>
<Html lang="en">
<Head>
<meta
name="description"
content="ente is a privacy friendly alternative to Google Photos that supports end-to-end encryption. Because memories are precious."
/>
<link rel="icon" href="/icon.png" type="image/png"/>
<link rel="icon" href="/icon.png" type="image/png" />
</Head>
<body>
<Main />
@ -47,4 +45,4 @@ export default class MyDocument extends Document {
</Html>
);
}
}
}

View file

@ -2,11 +2,12 @@ import { createProxyMiddleware } from 'http-proxy-middleware';
export const config = {
api: {
bodyParser: false,
bodyParser: false,
},
};
const API_ENDPOINT = process.env.NEXT_PUBLIC_ENTE_ENDPOINT || "https://api.staging.ente.io";
const API_ENDPOINT =
process.env.NEXT_PUBLIC_ENTE_ENDPOINT || 'https://api.staging.ente.io';
export default createProxyMiddleware({
target: API_ENDPOINT,

View file

@ -11,10 +11,11 @@ import { useRouter } from 'next/router';
import * as Yup from 'yup';
import { keyAttributes } from 'types';
import { setKey, SESSION_KEYS, getKey } from 'utils/storage/sessionStorage';
import * as Comlink from "comlink";
import * as Comlink from 'comlink';
const CryptoWorker: any = typeof window !== 'undefined'
&& Comlink.wrap(new Worker("worker/crypto.worker.js", { type: 'module' }));
const CryptoWorker: any =
typeof window !== 'undefined' &&
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
const Image = styled.img`
width: 200px;
@ -41,22 +42,34 @@ export default function Credentials() {
} else if (!keyAttributes) {
router.push('/generate');
} else if (key) {
router.push('/gallery')
router.push('/gallery');
} else {
setKeyAttributes(keyAttributes);
}
}, []);
const verifyPassphrase = async (values: formValues, { setFieldError }: FormikHelpers<formValues>) => {
const verifyPassphrase = async (
values: formValues,
{ setFieldError }: FormikHelpers<formValues>
) => {
setLoading(true);
try {
const cryptoWorker = await new CryptoWorker();
const { passphrase } = values;
const kek: string = await cryptoWorker.deriveKey(passphrase, keyAttributes.kekSalt);
const kek: string = await cryptoWorker.deriveKey(
passphrase,
keyAttributes.kekSalt
);
if (await cryptoWorker.verifyHash(keyAttributes.kekHash, kek)) {
const key: string = await cryptoWorker.decryptB64(keyAttributes.encryptedKey, keyAttributes.keyDecryptionNonce, kek);
const sessionKeyAttributes = await cryptoWorker.encryptToB64(key);
const key: string = await cryptoWorker.decryptB64(
keyAttributes.encryptedKey,
keyAttributes.keyDecryptionNonce,
kek
);
const sessionKeyAttributes = await cryptoWorker.encryptToB64(
key
);
const sessionKey = sessionKeyAttributes.key;
const sessionNonce = sessionKeyAttributes.nonce;
const encryptionKey = sessionKeyAttributes.encryptedData;
@ -67,44 +80,65 @@ export default function Credentials() {
setFieldError('passphrase', constants.INCORRECT_PASSPHRASE);
}
} catch (e) {
setFieldError('passphrase', `${constants.UNKNOWN_ERROR} ${e.message}`);
setFieldError(
'passphrase',
`${constants.UNKNOWN_ERROR} ${e.message}`
);
}
setLoading(false);
}
};
return (<Container>
<Image alt='vault' src='/vault.svg' />
<Card style={{ minWidth: '300px' }}>
<Card.Body>
<p className="text-center">{constants.ENTER_PASSPHRASE}</p>
<Formik<formValues>
initialValues={{ passphrase: '' }}
onSubmit={verifyPassphrase}
validationSchema={Yup.object().shape({
passphrase: Yup.string().required(constants.REQUIRED),
})}
>
{({ values, touched, errors, handleChange, handleBlur, handleSubmit }) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
<Form.Control
type="password"
placeholder={constants.RETURN_PASSPHRASE_HINT}
value={values.passphrase}
onChange={handleChange('passphrase')}
onBlur={handleBlur('passphrase')}
isInvalid={Boolean(touched.passphrase && errors.passphrase)}
disabled={loading}
/>
<Form.Control.Feedback type="invalid">
{errors.passphrase}
</Form.Control.Feedback>
</Form.Group>
<Button block type='submit' disabled={loading}>{constants.VERIFY_PASSPHRASE}</Button>
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>)
return (
<Container>
<Image alt="vault" src="/vault.svg" />
<Card style={{ minWidth: '300px' }}>
<Card.Body>
<p className="text-center">{constants.ENTER_PASSPHRASE}</p>
<Formik<formValues>
initialValues={{ passphrase: '' }}
onSubmit={verifyPassphrase}
validationSchema={Yup.object().shape({
passphrase: Yup.string().required(
constants.REQUIRED
),
})}
>
{({
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
}) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
<Form.Control
type="password"
placeholder={
constants.RETURN_PASSPHRASE_HINT
}
value={values.passphrase}
onChange={handleChange('passphrase')}
onBlur={handleBlur('passphrase')}
isInvalid={Boolean(
touched.passphrase &&
errors.passphrase
)}
disabled={loading}
/>
<Form.Control.Feedback type="invalid">
{errors.passphrase}
</Form.Control.Feedback>
</Form.Group>
<Button block type="submit" disabled={loading}>
{constants.VERIFY_PASSPHRASE}
</Button>
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>
);
}

View file

@ -1,8 +1,8 @@
import React, { useState } from "react";
import { Card } from "react-bootstrap";
import styled from "styled-components";
import CreateCollection from "./CreateCollection";
import DropzoneWrapper from "./DropzoneWrapper";
import React, { useState } from 'react';
import { Card } from 'react-bootstrap';
import styled from 'styled-components';
import CreateCollection from './CreateCollection';
import DropzoneWrapper from './DropzoneWrapper';
const ImageContainer = styled.div`
min-height: 192px;
@ -19,7 +19,6 @@ const StyledCard = styled(Card)`
`;
export default function AddCollection(props) {
const [acceptedFiles, setAcceptedFiles] = useState<File[]>();
const [createCollectionView, setCreateCollectionView] = useState(false);
@ -32,7 +31,9 @@ export default function AddCollection(props) {
const children = (
<StyledCard>
<ImageContainer>+</ImageContainer>
<Card.Text style={{ textAlign: "center" }}>Create New Album</Card.Text>
<Card.Text style={{ textAlign: 'center' }}>
Create New Album
</Card.Text>
</StyledCard>
);
return (
@ -51,5 +52,5 @@ export default function AddCollection(props) {
acceptedFiles={acceptedFiles}
/>
</>
)
);
}

View file

@ -3,7 +3,6 @@ import UploadService from 'services/uploadService';
import { getToken } from 'utils/common/key';
import DropzoneWrapper from './DropzoneWrapper';
function CollectionDropZone({
children,
closeModal,
@ -12,9 +11,8 @@ function CollectionDropZone({
collectionAndItsLatestFile,
setProgressView,
progressBarProps,
setErrorCode
setErrorCode,
}) {
const upload = async (acceptedFiles) => {
try {
const token = getToken();
@ -22,16 +20,21 @@ function CollectionDropZone({
progressBarProps.setPercentComplete(0);
setProgressView(true);
await UploadService.uploadFiles(acceptedFiles, collectionAndItsLatestFile, token, progressBarProps);
await UploadService.uploadFiles(
acceptedFiles,
collectionAndItsLatestFile,
token,
progressBarProps
);
refetchData();
} catch (err) {
if (err.response)
if (err.response) {
setErrorCode(err.response.status);
}
finally {
}
} finally {
setProgressView(false);
}
}
};
return (
<DropzoneWrapper
children={children}
@ -40,6 +43,6 @@ function CollectionDropZone({
onDropRejected={closeModal}
/>
);
};
}
export default CollectionDropZone;

View file

@ -15,17 +15,23 @@ function CollectionSelector(props) {
} = props;
const CollectionIcons = collectionAndItsLatestFile?.map((item) => (
<CollectionDropZone key={item.collection.id}
<CollectionDropZone
key={item.collection.id}
{...rest}
closeModal={closeUploadModal}
showModal={showUploadModal}
collectionAndItsLatestFile={item}
>
<Card>
<PreviewCard data={item.file} updateUrl={() => { }} forcedEnable />
<Card.Text className="text-center">{item.collection.name}</Card.Text>
<PreviewCard
data={item.file}
updateUrl={() => {}}
forcedEnable
/>
<Card.Text className="text-center">
{item.collection.name}
</Card.Text>
</Card>
</CollectionDropZone>
));
@ -36,11 +42,15 @@ function CollectionSelector(props) {
dialogClassName="modal-90w"
>
<Modal.Header closeButton>
<Modal.Title >
{constants.SELECT_COLLECTION}
</Modal.Title>
<Modal.Title>{constants.SELECT_COLLECTION}</Modal.Title>
</Modal.Header>
<Modal.Body style={{ display: "flex", justifyContent: "flex-start", flexWrap: "wrap" }}>
<Modal.Body
style={{
display: 'flex',
justifyContent: 'flex-start',
flexWrap: 'wrap',
}}
>
<AddCollection
{...rest}
showUploadModal={showUploadModal}

View file

@ -34,17 +34,18 @@ const Wrapper = styled.div`
white-space: nowrap;
overflow: auto;
max-width: 100%;
`
`;
const Chip = styled.button<{ active: boolean }>`
border-radius: 20px;
padding: 2px 10px;
margin: 2px 5px 2px 2px;
border: none;
background-color: ${props => props.active ? '#fff' : 'rgba(255, 255, 255, 0.3)'};
background-color: ${(props) =>
props.active ? '#fff' : 'rgba(255, 255, 255, 0.3)'};
outline: none !important;
&:focus {
box-shadow : 0 0 0 2px #2666cc;
box-shadow: 0 0 0 2px #2666cc;
background-color: #eee;
}
`;
@ -53,14 +54,22 @@ export default function Collections(props: CollectionProps) {
const { selected, collections, selectCollection } = props;
const clickHandler = (id?: number) => () => selectCollection(id);
return <Container>
<Wrapper>
<Chip active={!selected} onClick={clickHandler()}>All</Chip>
{collections?.map(item => <Chip
key={item.id}
active={selected === item.id.toString()}
onClick={clickHandler(item.id)}
>{item.name}</Chip>)}
</Wrapper>
</Container>;
return (
<Container>
<Wrapper>
<Chip active={!selected} onClick={clickHandler()}>
All
</Chip>
{collections?.map((item) => (
<Chip
key={item.id}
active={selected === item.id.toString()}
onClick={clickHandler(item.id)}
>
{item.name}
</Chip>
))}
</Wrapper>
</Container>
);
}

View file

@ -2,28 +2,46 @@ import React, { useEffect, useState } from 'react';
import { Button, Form, Modal } from 'react-bootstrap';
import { createAlbum } from 'services/collectionService';
import UploadService from 'services/uploadService';
import { CollectionAndItsLatestFile } from 'services/collectionService'
import { CollectionAndItsLatestFile } from 'services/collectionService';
import { getToken } from 'utils/common/key';
export default function CreateCollection(props) {
const {
acceptedFiles,
setProgressView,
progressBarProps,
refetchData,
modalView,
closeModal,
closeUploadModal,
setErrorCode,
} = props;
const [albumName, setAlbumName] = useState('');
const { acceptedFiles, setProgressView, progressBarProps, refetchData, modalView, closeModal, closeUploadModal, setErrorCode } = props;
const [albumName, setAlbumName] = useState("");
const handleChange = (event) => { setAlbumName(event.target.value); }
const handleChange = (event) => {
setAlbumName(event.target.value);
};
useEffect(() => {
if (acceptedFiles == null)
if (acceptedFiles == null) {
return;
}
let commonPathPrefix: string = (() => {
const paths: string[] = acceptedFiles.map(files => files.path);
const paths: string[] = acceptedFiles.map((files) => files.path);
paths.sort();
let firstPath = paths[0], lastPath = paths[paths.length - 1], L = firstPath.length, i = 0;
let firstPath = paths[0],
lastPath = paths[paths.length - 1],
L = firstPath.length,
i = 0;
while (i < L && firstPath.charAt(i) === lastPath.charAt(i)) i++;
return firstPath.substring(0, i);
})();
if (commonPathPrefix)
commonPathPrefix = commonPathPrefix.substr(1, commonPathPrefix.lastIndexOf('/') - 1);
if (commonPathPrefix) {
commonPathPrefix = commonPathPrefix.substr(
1,
commonPathPrefix.lastIndexOf('/') - 1
);
}
setAlbumName(commonPathPrefix);
}, [acceptedFiles]);
const handleSubmit = async (event) => {
@ -36,46 +54,54 @@ export default function CreateCollection(props) {
const collection = await createAlbum(albumName);
const collectionAndItsLatestFile: CollectionAndItsLatestFile = { collection, file: null }
const collectionAndItsLatestFile: CollectionAndItsLatestFile = {
collection,
file: null,
};
progressBarProps.setPercentComplete(0);
setProgressView(true);
await UploadService.uploadFiles(acceptedFiles, collectionAndItsLatestFile, token, progressBarProps);
await UploadService.uploadFiles(
acceptedFiles,
collectionAndItsLatestFile,
token,
progressBarProps
);
refetchData();
}
catch (err) {
if (err.response)
} catch (err) {
if (err.response) {
setErrorCode(err.response.status);
}
finally {
}
} finally {
setProgressView(false);
}
}
};
return (
<Modal
show={modalView}
onHide={closeModal}
centered
backdrop="static"
>
<Modal show={modalView} onHide={closeModal} centered backdrop="static">
<Modal.Header closeButton>
<Modal.Title>
Create Collection
</Modal.Title>
<Modal.Title>Create Collection</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit}>
<Form.Group controlId="formBasicEmail">
<Form.Label>Album Name:</Form.Label>
<Form.Control type="text" placeholder="Enter Album Name" value={albumName} onChange={handleChange} />
<Form.Control
type="text"
placeholder="Enter Album Name"
value={albumName}
onChange={handleChange}
/>
</Form.Group>
<Button variant="primary" type="submit" style={{ width: "100%" }}>
<Button
variant="primary"
type="submit"
style={{ width: '100%' }}
>
Submit
</Button>
</Button>
</Form>
</Modal.Body>
</Modal>
);
}
}

View file

@ -17,20 +17,20 @@ export const getColor = (props) => {
export const enableBorder = (props) => (props.isDragActive ? 'dashed' : 'none');
export const DropDiv = styled.div`
width:200px;
margin:5px;
height:230px;
color:black;
border-width: 2px;
border-radius: 2px;
border-color: ${(props) => getColor(props)};
border-style: ${(props) => enableBorder(props)};
outline: none;
transition: border 0.24s ease-in-out;
width: 200px;
margin: 5px;
height: 230px;
color: black;
border-width: 2px;
border-radius: 2px;
border-color: ${(props) => getColor(props)};
border-style: ${(props) => enableBorder(props)};
outline: none;
transition: border 0.24s ease-in-out;
`;
export function DropzoneWrapper(props) {
const { children, ...callbackProps } = props
const { children, ...callbackProps } = props;
return (
<Dropzone
noDragEventsBubbling
@ -59,6 +59,6 @@ export function DropzoneWrapper(props) {
}}
</Dropzone>
);
};
}
export default DropzoneWrapper;

View file

@ -19,4 +19,4 @@ export default function ErrorAlert({ errorCode }) {
{errorMessage}
</Alert>
)
}
}

View file

@ -5,10 +5,10 @@ import styled from 'styled-components';
import PlayCircleOutline from 'components/PlayCircleOutline';
interface IProps {
data: file,
updateUrl: (url: string) => void,
onClick?: () => void,
forcedEnable?: boolean,
data: file;
updateUrl: (url: string) => void;
onClick?: () => void;
forcedEnable?: boolean;
}
const Cont = styled.div<{ disabled: boolean }>`
@ -19,7 +19,7 @@ const Cont = styled.div<{ disabled: boolean }>`
min-width: 100%;
overflow: hidden;
position: relative;
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
& > img {
object-fit: cover;
@ -36,7 +36,7 @@ const Cont = styled.div<{ disabled: boolean }>`
margin-top: 50%;
top: -25px;
left: -25px;
filter: drop-shadow( 3px 3px 2px rgba(0, 0, 0, .7));
filter: drop-shadow(3px 3px 2px rgba(0, 0, 0, 0.7));
}
`;
@ -52,7 +52,7 @@ export default function PreviewCard(props: IProps) {
setImgSrc(url);
data.msrc = url;
updateUrl(url);
}
};
main();
}
}, [data]);
@ -61,10 +61,15 @@ export default function PreviewCard(props: IProps) {
if (data?.msrc || imgSrc) {
onClick?.();
}
}
};
return <Cont onClick={handleClick} disabled={!forcedEnable && !data?.msrc && !imgSrc}>
<img src={data?.msrc || imgSrc} />
{data?.metadata.fileType === 1 && <PlayCircleOutline />}
</Cont>;
return (
<Cont
onClick={handleClick}
disabled={!forcedEnable && !data?.msrc && !imgSrc}
>
<img src={data?.msrc || imgSrc} />
{data?.metadata.fileType === 1 && <PlayCircleOutline />}
</Cont>
);
}

View file

@ -1,11 +1,13 @@
import React, { useState } from "react"
import { UPLOAD_STAGES } from "services/uploadService";
import CollectionSelector from "./CollectionSelector"
import UploadProgress from "./UploadProgress"
import React, { useState } from 'react';
import { UPLOAD_STAGES } from 'services/uploadService';
import CollectionSelector from './CollectionSelector';
import UploadProgress from './UploadProgress';
export default function Upload(props) {
const [progressView, setProgressView] = useState(false);
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>(UPLOAD_STAGES.START);
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>(
UPLOAD_STAGES.START
);
const [fileCounter, setFileCounter] = useState({ current: 0, total: 0 });
const [percentComplete, setPercentComplete] = useState(0);
const init = () => {
@ -13,20 +15,25 @@ export default function Upload(props) {
setUploadStage(UPLOAD_STAGES.START);
setFileCounter({ current: 0, total: 0 });
setPercentComplete(0);
}
return (<>
<CollectionSelector
{...props}
setProgressView={setProgressView}
progressBarProps={{ setPercentComplete, setFileCounter, setUploadStage }}
/>
<UploadProgress
now={percentComplete}
fileCounter={fileCounter}
uploadStage={uploadStage}
show={progressView}
onHide={init}
/>
</>
)
}
};
return (
<>
<CollectionSelector
{...props}
setProgressView={setProgressView}
progressBarProps={{
setPercentComplete,
setFileCounter,
setUploadStage,
}}
/>
<UploadProgress
now={percentComplete}
fileCounter={fileCounter}
uploadStage={uploadStage}
show={progressView}
onHide={init}
/>
</>
);
}

View file

@ -3,10 +3,10 @@ import { Button } from 'react-bootstrap';
function UploadButton({ showModal }) {
return (
<Button variant='primary' onClick={showModal}>
<Button variant="primary" onClick={showModal}>
Upload New Photos
</Button>
);
};
}
export default UploadButton;

View file

@ -1,30 +1,40 @@
import React from 'react';
import { Alert, Modal, ProgressBar } from 'react-bootstrap';
import constants from 'utils/strings/constants'
import constants from 'utils/strings/constants';
export default function UploadProgress({ fileCounter, uploadStage, now, ...props }) {
export default function UploadProgress({
fileCounter,
uploadStage,
now,
...props
}) {
return (
<Modal
{...props}
size='lg'
aria-labelledby='contained-modal-title-vcenter'
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
backdrop="static"
>
<Modal.Header>
<Modal.Title id='contained-modal-title-vcenter'>
<Modal.Title id="contained-modal-title-vcenter">
Uploading Files
</Modal.Title>
</Modal.Title>
</Modal.Header>
<Modal.Body>
{now === 100 ? (
<Alert variant='success'>{constants.UPLOAD[3]}</Alert>
<Alert variant="success">{constants.UPLOAD[3]}</Alert>
) : (
<>
<Alert variant='info'>{constants.UPLOAD[uploadStage]} {fileCounter?.total != 0 ? `${fileCounter?.current} ${constants.OF} ${fileCounter?.total}` : ''}</Alert>
<ProgressBar animated now={now} />
</>
)}
<>
<Alert variant="info">
{constants.UPLOAD[uploadStage]}{' '}
{fileCounter?.total != 0
? `${fileCounter?.current} ${constants.OF} ${fileCounter?.total}`
: ''}
</Alert>
<ProgressBar animated now={now} />
</>
)}
</Modal.Body>
</Modal>
);

View file

@ -29,7 +29,7 @@ import {
getLocalCollections,
} from 'services/collectionService';
import constants from 'utils/strings/constants';
import ErrorAlert from './components/ErrorAlert';
import ErrorAlert from './components/ErrorAlert';
const DATE_CONTAINER_HEIGHT = 45;
const IMAGE_CONTAINER_HEIGHT = 200;

View file

@ -11,11 +11,12 @@ import { putAttributes } from 'services/userService';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { useRouter } from 'next/router';
import { getKey, SESSION_KEYS, setKey } from 'utils/storage/sessionStorage';
import * as Comlink from "comlink";
import * as Comlink from 'comlink';
import { keyEncryptionResult } from 'services/uploadService';
const CryptoWorker: any = typeof window !== 'undefined'
&& Comlink.wrap(new Worker("worker/crypto.worker.js", { type: 'module' }));
const CryptoWorker: any =
typeof window !== 'undefined' &&
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
const Image = styled.img`
width: 200px;
@ -38,7 +39,7 @@ export default function Generate() {
router.prefetch('/gallery');
const user = getData(LS_KEYS.USER);
if (!user?.token) {
router.push("/");
router.push('/');
} else if (key) {
router.push('/gallery');
} else {
@ -46,7 +47,10 @@ export default function Generate() {
}
}, []);
const onSubmit = async (values: formValues, { setFieldError }: FormikHelpers<formValues>) => {
const onSubmit = async (
values: formValues,
{ setFieldError }: FormikHelpers<formValues>
) => {
setLoading(true);
try {
const { passphrase, confirm } = values;
@ -54,11 +58,20 @@ export default function Generate() {
const cryptoWorker = await new CryptoWorker();
const key: string = await cryptoWorker.generateMasterKey();
const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
const kek: string = await cryptoWorker.deriveKey(passphrase, kekSalt);
const kek: string = await cryptoWorker.deriveKey(
passphrase,
kekSalt
);
const kekHash: string = await cryptoWorker.hash(kek);
const encryptedKeyAttributes: keyEncryptionResult = await cryptoWorker.encryptToB64(key, kek);
const encryptedKeyAttributes: keyEncryptionResult = await cryptoWorker.encryptToB64(
key,
kek
);
const keyPair = await cryptoWorker.generateKeyPair();
const encryptedKeyPairAttributes: keyEncryptionResult = await cryptoWorker.encryptToB64(keyPair.privateKey, key);
const encryptedKeyPairAttributes: keyEncryptionResult = await cryptoWorker.encryptToB64(
keyPair.privateKey,
key
);
const keyAttributes = {
kekSalt,
@ -66,13 +79,20 @@ export default function Generate() {
encryptedKey: encryptedKeyAttributes.encryptedData,
keyDecryptionNonce: encryptedKeyAttributes.nonce,
publicKey: keyPair.publicKey,
encryptedSecretKey: encryptedKeyPairAttributes.encryptedData,
secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce
encryptedSecretKey:
encryptedKeyPairAttributes.encryptedData,
secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce,
};
await putAttributes(token, getData(LS_KEYS.USER).name, keyAttributes);
await putAttributes(
token,
getData(LS_KEYS.USER).name,
keyAttributes
);
setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes);
const sessionKeyAttributes = await cryptoWorker.encryptToB64(key);
const sessionKeyAttributes = await cryptoWorker.encryptToB64(
key
);
const sessionKey = sessionKeyAttributes.key;
const sessionNonce = sessionKeyAttributes.nonce;
const encryptionKey = sessionKeyAttributes.encryptedData;
@ -83,62 +103,85 @@ export default function Generate() {
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
}
} catch (e) {
setFieldError('passphrase', `${constants.UNKNOWN_ERROR} ${e.message}`);
setFieldError(
'passphrase',
`${constants.UNKNOWN_ERROR} ${e.message}`
);
}
setLoading(false);
}
};
return (<Container>
<Image alt='vault' src='/vault.svg' />
<Card>
<Card.Body>
<div className="text-center">
<p>{constants.ENTER_ENC_PASSPHRASE}</p>
<p>{constants.PASSPHRASE_DISCLAIMER()}</p>
</div>
<Formik<formValues>
initialValues={{ passphrase: '', confirm: '' }}
validationSchema={Yup.object().shape({
passphrase: Yup.string().required(constants.REQUIRED),
confirm: Yup.string().required(constants.REQUIRED),
})}
onSubmit={onSubmit}
>
{({ values, touched, errors, handleChange, handleBlur, handleSubmit }) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
<Form.Control
type="text"
placeholder={constants.PASSPHRASE_HINT}
value={values.passphrase}
onChange={handleChange('passphrase')}
onBlur={handleBlur('passphrase')}
isInvalid={Boolean(touched.passphrase && errors.passphrase)}
disabled={loading}
/>
<Form.Control.Feedback type='invalid'>
{errors.passphrase}
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Control
type="text"
placeholder={constants.PASSPHRASE_CONFIRM}
value={values.confirm}
onChange={handleChange('confirm')}
onBlur={handleBlur('confirm')}
isInvalid={Boolean(touched.confirm && errors.confirm)}
disabled={loading}
/>
<Form.Control.Feedback type='invalid'>
{errors.confirm}
</Form.Control.Feedback>
</Form.Group>
<Button type="submit" block disabled={loading}>{constants.SET_PASSPHRASE}</Button>
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>)
return (
<Container>
<Image alt="vault" src="/vault.svg" />
<Card>
<Card.Body>
<div className="text-center">
<p>{constants.ENTER_ENC_PASSPHRASE}</p>
<p>{constants.PASSPHRASE_DISCLAIMER()}</p>
</div>
<Formik<formValues>
initialValues={{ passphrase: '', confirm: '' }}
validationSchema={Yup.object().shape({
passphrase: Yup.string().required(
constants.REQUIRED
),
confirm: Yup.string().required(constants.REQUIRED),
})}
onSubmit={onSubmit}
>
{({
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
}) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
<Form.Control
type="text"
placeholder={constants.PASSPHRASE_HINT}
value={values.passphrase}
onChange={handleChange('passphrase')}
onBlur={handleBlur('passphrase')}
isInvalid={Boolean(
touched.passphrase &&
errors.passphrase
)}
disabled={loading}
/>
<Form.Control.Feedback type="invalid">
{errors.passphrase}
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Control
type="text"
placeholder={
constants.PASSPHRASE_CONFIRM
}
value={values.confirm}
onChange={handleChange('confirm')}
onBlur={handleBlur('confirm')}
isInvalid={Boolean(
touched.confirm && errors.confirm
)}
disabled={loading}
/>
<Form.Control.Feedback type="invalid">
{errors.confirm}
</Form.Control.Feedback>
</Form.Group>
<Button type="submit" block disabled={loading}>
{constants.SET_PASSPHRASE}
</Button>
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>
);
}

View file

@ -28,7 +28,10 @@ export default function Home() {
}
}, []);
const loginUser = async ({ email }: formValues, { setFieldError }: FormikHelpers<formValues>) => {
const loginUser = async (
{ email }: formValues,
{ setFieldError }: FormikHelpers<formValues>
) => {
try {
setLoading(true);
await getOtt(email);
@ -38,27 +41,36 @@ export default function Home() {
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
}
setLoading(false);
}
};
const register = () => {
router.push('/signup');
}
};
return (
<Container>
<Card style={{ minWidth: '300px' }} className="text-center">
<Card.Body>
<Card.Title style={{ marginBottom: '20px' }}>{constants.LOGIN}</Card.Title>
<Card.Title style={{ marginBottom: '20px' }}>
{constants.LOGIN}
</Card.Title>
<Formik<formValues>
initialValues={{ email: '' }}
validationSchema={Yup.object().shape({
email: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED)
.required(constants.REQUIRED),
})}
onSubmit={loginUser}
>
{({ values, errors, touched, handleChange, handleBlur, handleSubmit }) => (
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
}) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="formBasicEmail">
<Form.Control
@ -67,7 +79,9 @@ export default function Home() {
value={values.email}
onChange={handleChange('email')}
onBlur={handleBlur('email')}
isInvalid={Boolean(touched.email && errors.email)}
isInvalid={Boolean(
touched.email && errors.email
)}
disabled={loading}
/>
<FormControl.Feedback type="invalid">
@ -75,7 +89,9 @@ export default function Home() {
</FormControl.Feedback>
</Form.Group>
<Button
variant="primary" type="submit" block
variant="primary"
type="submit"
block
disabled={loading}
style={{ marginBottom: '12px' }}
>
@ -84,9 +100,15 @@ export default function Home() {
</Form>
)}
</Formik>
<Card.Link href="#" onClick={register} style={{ fontSize: '14px' }}>Don't have an account?</Card.Link>
<Card.Link
href="#"
onClick={register}
style={{ fontSize: '14px' }}
>
Don't have an account?
</Card.Link>
</Card.Body>
</Card>
</Container>
)
);
}

View file

@ -17,7 +17,6 @@ interface FormValues {
email: string;
}
export default function Home() {
const [loading, setLoading] = useState(false);
const router = useRouter();
@ -30,7 +29,10 @@ export default function Home() {
}
}, []);
const registerUser = async ({ name, email }: FormValues, { setFieldError }: FormikHelpers<FormValues>) => {
const registerUser = async (
{ name, email }: FormValues,
{ setFieldError }: FormikHelpers<FormValues>
) => {
try {
setLoading(true);
setData(LS_KEYS.USER, { name, email });
@ -40,25 +42,33 @@ export default function Home() {
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
}
setLoading(false);
}
};
return (
<Container>
<Card style={{ minWidth: '300px' }} className="text-center" >
<Card style={{ minWidth: '300px' }} className="text-center">
<Card.Body>
<Card.Title style={{ marginBottom: '20px' }}>{constants.SIGN_UP}</Card.Title>
<Card.Title style={{ marginBottom: '20px' }}>
{constants.SIGN_UP}
</Card.Title>
<Formik<FormValues>
initialValues={{ name: '', email: '' }}
validationSchema={Yup.object().shape({
name: Yup.string()
.required(constants.REQUIRED),
name: Yup.string().required(constants.REQUIRED),
email: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED)
.required(constants.REQUIRED),
})}
onSubmit={registerUser}
>
{({ values, errors, touched, handleChange, handleBlur, handleSubmit }): JSX.Element => (
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
}): JSX.Element => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="registrationForm.name">
<Form.Control
@ -67,7 +77,9 @@ export default function Home() {
value={values.name}
onChange={handleChange('name')}
onBlur={handleBlur('name')}
isInvalid={Boolean(touched.name && errors.name)}
isInvalid={Boolean(
touched.name && errors.name
)}
disabled={loading}
/>
<FormControl.Feedback type="invalid">
@ -81,7 +93,9 @@ export default function Home() {
value={values.email}
onChange={handleChange('email')}
onBlur={handleBlur('email')}
isInvalid={Boolean(touched.email && errors.email)}
isInvalid={Boolean(
touched.email && errors.email
)}
disabled={loading}
/>
<FormControl.Feedback type="invalid">
@ -92,7 +106,9 @@ export default function Home() {
{constants.DATA_DISCLAIMER}
</DisclaimerContainer>
<Button
variant="primary" type="submit" block
variant="primary"
type="submit"
block
disabled={loading}
>
{constants.SUBMIT}
@ -103,5 +119,5 @@ export default function Home() {
</Card.Body>
</Card>
</Container>
)
);
}

View file

@ -32,15 +32,18 @@ export default function Verify() {
router.prefetch('/generate');
const user = getData(LS_KEYS.USER);
if (!user?.email) {
router.push("/");
router.push('/');
} else if (user.token) {
router.push("/credentials")
router.push('/credentials');
} else {
setEmail(user.email);
}
}, []);
const onSubmit = async ({ ott }: formValues, { setFieldError }: FormikHelpers<formValues>) => {
const onSubmit = async (
{ ott }: formValues,
{ setFieldError }: FormikHelpers<formValues>
) => {
try {
setLoading(true);
const resp = await verifyOtt(email, ott);
@ -52,9 +55,9 @@ export default function Verify() {
});
setData(LS_KEYS.KEY_ATTRIBUTES, resp.data.keyAttributes);
if (resp.data.keyAttributes?.encryptedKey) {
router.push("/credentials");
router.push('/credentials');
} else {
router.push("/generate");
router.push('/generate');
}
} catch (e) {
if (e?.response?.status === 401) {
@ -64,60 +67,80 @@ export default function Verify() {
}
}
setLoading(false);
}
};
const resendEmail = async () => {
setResend(1);
const resp = await getOtt(email);
setResend(2);
setTimeout(() => setResend(0), 3000);
}
};
if (!email) {
return null;
}
return (<Container>
<Image alt='Email Sent' src='/email_sent.svg' />
<Card style={{ minWidth: '300px' }} className="text-center">
<Card.Body>
<Card.Title>{constants.VERIFY_EMAIL}</Card.Title>
{constants.EMAIL_SENT({ email })}
{constants.CHECK_INBOX}<br />
<br />
<Formik<formValues>
initialValues={{ ott: '' }}
validationSchema={Yup.object().shape({
ott: Yup.string().required(constants.REQUIRED),
})}
onSubmit={onSubmit}
>
{({ values, touched, errors, handleChange, handleBlur, handleSubmit }) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
<Form.Control
className="text-center"
type='text'
value={values.ott}
onChange={handleChange('ott')}
onBlur={handleBlur('ott')}
isInvalid={Boolean(touched.ott && errors.ott)}
placeholder={constants.ENTER_OTT}
disabled={loading}
/>
<Form.Control.Feedback type='invalid'>
{errors.ott}
</Form.Control.Feedback>
</Form.Group>
<Button type="submit" block disabled={loading}>{constants.VERIFY}</Button>
<br />
{resend === 0 && <a href="#" onClick={resendEmail}>{constants.RESEND_MAIL}</a>}
{resend === 1 && <span>{constants.SENDING}</span>}
{resend === 2 && <span>{constants.SENT}</span>}
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>)
}
return (
<Container>
<Image alt="Email Sent" src="/email_sent.svg" />
<Card style={{ minWidth: '300px' }} className="text-center">
<Card.Body>
<Card.Title>{constants.VERIFY_EMAIL}</Card.Title>
{constants.EMAIL_SENT({ email })}
{constants.CHECK_INBOX}
<br />
<br />
<Formik<formValues>
initialValues={{ ott: '' }}
validationSchema={Yup.object().shape({
ott: Yup.string().required(constants.REQUIRED),
})}
onSubmit={onSubmit}
>
{({
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
}) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
<Form.Control
className="text-center"
type="text"
value={values.ott}
onChange={handleChange('ott')}
onBlur={handleBlur('ott')}
isInvalid={Boolean(
touched.ott && errors.ott
)}
placeholder={constants.ENTER_OTT}
disabled={loading}
/>
<Form.Control.Feedback type="invalid">
{errors.ott}
</Form.Control.Feedback>
</Form.Group>
<Button type="submit" block disabled={loading}>
{constants.VERIFY}
</Button>
<br />
{resend === 0 && (
<a href="#" onClick={resendEmail}>
{constants.RESEND_MAIL}
</a>
)}
{resend === 1 && (
<span>{constants.SENDING}</span>
)}
{resend === 2 && <span>{constants.SENT}</span>}
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>
);
}

View file

@ -69,7 +69,12 @@ class HTTPService {
/**
* Get request.
*/
public get(url: string, params?: IQueryPrams, headers?: IHTTPHeaders, customConfig?: any) {
public get(
url: string,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
headers,
@ -77,15 +82,20 @@ class HTTPService {
params,
url,
},
customConfig,
customConfig
);
}
/**
* Post request
*/
public post(url: string, data?: any, params?: IQueryPrams,
headers?: IHTTPHeaders, customConfig?: any) {
public post(
url: string,
data?: any,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
data,
@ -94,15 +104,20 @@ class HTTPService {
params,
url,
},
customConfig,
customConfig
);
}
/**
* Put request
*/
public put(url: string, data: any, params?: IQueryPrams,
headers?: IHTTPHeaders, customConfig?: any) {
public put(
url: string,
data: any,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
data,
@ -111,15 +126,20 @@ class HTTPService {
params,
url,
},
customConfig,
customConfig
);
}
/**
* Delete request
*/
public delete(url: string, data: any, params?: IQueryPrams,
headers?: IHTTPHeaders, customConfig?: any) {
public delete(
url: string,
data: any,
params?: IQueryPrams,
headers?: IHTTPHeaders,
customConfig?: any
) {
return this.request(
{
data,
@ -128,7 +148,7 @@ class HTTPService {
params,
url,
},
customConfig,
customConfig
);
}
}

View file

@ -1,38 +1,36 @@
import { getEndpoint } from 'utils/common/apiUtil';
import HTTPService from './HTTPService';
import * as Comlink from 'comlink';
import EXIF from "exif-js";
import EXIF from 'exif-js';
import { fileAttribute } from './fileService';
import { collection, CollectionAndItsLatestFile } from "./collectionService"
import { collection, CollectionAndItsLatestFile } from './collectionService';
import { FILE_TYPE } from 'pages/gallery';
const CryptoWorker: any =
typeof window !== 'undefined' &&
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
const ENDPOINT = getEndpoint();
interface encryptionResult {
file: fileAttribute,
key: string
file: fileAttribute;
key: string;
}
export interface keyEncryptionResult {
encryptedData: string,
key: string,
nonce: string,
encryptedData: string;
key: string;
nonce: string;
}
interface uploadURL {
url: string,
objectKey: string
url: string;
objectKey: string;
}
interface FileinMemory {
filedata: Uint8Array,
thumbnail: Uint8Array,
filename: string
filedata: Uint8Array;
thumbnail: Uint8Array;
filename: string;
}
interface encryptedFile {
filedata: fileAttribute;
thumbnail: fileAttribute;
@ -40,47 +38,51 @@ interface encryptedFile {
}
interface objectKey {
objectKey: string,
decryptionHeader: string
objectKey: string;
decryptionHeader: string;
}
interface objectKeys {
file: objectKey
thumbnail: objectKey
file: objectKey;
thumbnail: objectKey;
}
interface uploadFile extends objectKeys {
collectionID: number,
collectionID: number;
encryptedKey: string;
keyDecryptionNonce: string;
metadata?: {
encryptedData: string | Uint8Array,
decryptionHeader: string
}
encryptedData: string | Uint8Array;
decryptionHeader: string;
};
}
interface UploadFileWithoutMetaData {
tempUploadFile: uploadFile,
encryptedFileKey: keyEncryptionResult,
fileName: string
tempUploadFile: uploadFile;
encryptedFileKey: keyEncryptionResult;
fileName: string;
}
export enum UPLOAD_STAGES {
START,
ENCRYPTION,
UPLOAD,
FINISH
FINISH,
}
class UploadService {
private uploadURLs: uploadURL[];
private uploadURLFetchInProgress: Promise<any>;
private perStepProgress: number
private stepsCompleted: number
private totalFilesCount: number
private perStepProgress: number;
private stepsCompleted: number;
private totalFilesCount: number;
private metadataMap: Map<string, Object>;
public async uploadFiles(recievedFiles: File[], collectionAndItsLatestFile: CollectionAndItsLatestFile, token: string, progressBarProps) {
public async uploadFiles(
recievedFiles: File[],
collectionAndItsLatestFile: CollectionAndItsLatestFile,
token: string,
progressBarProps
) {
try {
const worker = await new CryptoWorker();
this.stepsCompleted = 0;
@ -90,11 +92,16 @@ class UploadService {
let metadataFiles: File[] = [];
let actualFiles: File[] = [];
recievedFiles.forEach(file => {
if (file.type.substr(0, 5) === "image" || file.type.substr(0, 5) === "video")
recievedFiles.forEach((file) => {
if (
file.type.substr(0, 5) === 'image' ||
file.type.substr(0, 5) === 'video'
) {
actualFiles.push(file);
if (file.name.slice(-4) == "json")
}
if (file.name.slice(-4) == 'json') {
metadataFiles.push(file);
}
});
this.totalFilesCount = actualFiles.length;
this.perStepProgress = 100 / (3 * actualFiles.length);
@ -107,38 +114,58 @@ class UploadService {
while (actualFiles.length > 0) {
var promises = [];
for (var i = 0; i < 5 && actualFiles.length > 0; i++)
promises.push(this.uploadHelper(progressBarProps, actualFiles.pop(), collectionAndItsLatestFile.collection, token));
uploadFilesWithoutMetaData.push(...await Promise.all(promises));
promises.push(
this.uploadHelper(
progressBarProps,
actualFiles.pop(),
collectionAndItsLatestFile.collection,
token
)
);
uploadFilesWithoutMetaData.push(
...(await Promise.all(promises))
);
}
for await (const rawFile of metadataFiles) {
await this.updateMetadata(rawFile)
};
await this.updateMetadata(rawFile);
}
progressBarProps.setUploadStage(UPLOAD_STAGES.ENCRYPTION);
const completeUploadFiles: uploadFile[] = await Promise.all(uploadFilesWithoutMetaData.map(async (file: UploadFileWithoutMetaData) => {
const { file: encryptedMetaData } = await this.encryptMetadata(worker, file.fileName, file.encryptedFileKey);
const completeUploadFile = {
...file.tempUploadFile,
metadata: {
encryptedData: encryptedMetaData.encryptedData,
decryptionHeader: encryptedMetaData.decryptionHeader
const completeUploadFiles: uploadFile[] = await Promise.all(
uploadFilesWithoutMetaData.map(
async (file: UploadFileWithoutMetaData) => {
const {
file: encryptedMetaData,
} = await this.encryptMetadata(
worker,
file.fileName,
file.encryptedFileKey
);
const completeUploadFile = {
...file.tempUploadFile,
metadata: {
encryptedData: encryptedMetaData.encryptedData,
decryptionHeader:
encryptedMetaData.decryptionHeader,
},
};
this.changeProgressBarProps(progressBarProps);
return completeUploadFile;
}
}
this.changeProgressBarProps(progressBarProps);
return completeUploadFile;
}));
)
);
progressBarProps.setUploadStage(UPLOAD_STAGES.UPLOAD);
await Promise.all(completeUploadFiles.map(async (uploadFile: uploadFile) => {
await this.uploadFile(uploadFile, token);
this.changeProgressBarProps(progressBarProps);
}));
await Promise.all(
completeUploadFiles.map(async (uploadFile: uploadFile) => {
await this.uploadFile(uploadFile, token);
this.changeProgressBarProps(progressBarProps);
})
);
progressBarProps.setUploadStage(UPLOAD_STAGES.FINISH);
progressBarProps.setPercentComplete(100);
} catch (e) {
console.log(e);
throw e;
@ -148,18 +175,29 @@ class UploadService {
try {
const worker = await new CryptoWorker();
let file: FileinMemory = await this.readFile(rawFile);
let encryptedFile: encryptedFile = await this.encryptFile(worker, file, collection.key);
let objectKeys = await this.uploadtoBucket(encryptedFile, token, 2 * this.totalFilesCount);
let uploadFileWithoutMetaData: uploadFile = this.getuploadFile(collection, encryptedFile.fileKey, objectKeys);
let encryptedFile: encryptedFile = await this.encryptFile(
worker,
file,
collection.key
);
let objectKeys = await this.uploadtoBucket(
encryptedFile,
token,
2 * this.totalFilesCount
);
let uploadFileWithoutMetaData: uploadFile = this.getuploadFile(
collection,
encryptedFile.fileKey,
objectKeys
);
this.changeProgressBarProps(progressBarProps);
return {
tempUploadFile: uploadFileWithoutMetaData,
encryptedFileKey: encryptedFile.fileKey,
fileName: file.filename
fileName: file.filename,
};
}
catch (e) {
} catch (e) {
console.log(e);
throw e;
}
@ -174,24 +212,28 @@ class UploadService {
private async readFile(recievedFile: File) {
try {
const filedata: Uint8Array = await this.getUint8ArrayView(recievedFile);
const filedata: Uint8Array = await this.getUint8ArrayView(
recievedFile
);
let fileType;
switch (recievedFile.type.split('/')[0]) {
case "image":
case 'image':
fileType = FILE_TYPE.IMAGE;
break;
case "video":
case 'video':
fileType = FILE_TYPE.VIDEO;
break;
default:
fileType = FILE_TYPE.OTHERS;
}
const { location, creationTime } = await this.getExifData(recievedFile);
const { location, creationTime } = await this.getExifData(
recievedFile
);
this.metadataMap.set(recievedFile.name, {
title: recievedFile.name,
creationTime: creationTime || (recievedFile.lastModified) * 1000,
modificationTime: (recievedFile.lastModified) * 1000,
creationTime: creationTime || recievedFile.lastModified * 1000,
modificationTime: recievedFile.lastModified * 1000,
latitude: location?.latitude,
longitude: location?.latitude,
fileType,
@ -199,102 +241,167 @@ class UploadService {
return {
filedata,
filename: recievedFile.name,
thumbnail: await this.generateThumbnail(recievedFile)
}
thumbnail: await this.generateThumbnail(recievedFile),
};
} catch (e) {
console.log("error reading files " + e);
console.log('error reading files ' + e);
}
}
private async encryptFile(worker, file: FileinMemory, encryptionKey: string): Promise<encryptedFile> {
private async encryptFile(
worker,
file: FileinMemory,
encryptionKey: string
): Promise<encryptedFile> {
try {
const {
key: fileKey,
file: encryptedFiledata,
}: encryptionResult = await worker.encryptFile(file.filedata);
const { key: fileKey, file: encryptedFiledata }: encryptionResult = await worker.encryptFile(file.filedata);
const { file: encryptedThumbnail }: encryptionResult = await worker.encryptThumbnail(file.thumbnail, fileKey);
const encryptedKey: keyEncryptionResult = await worker.encryptToB64(fileKey, encryptionKey);
const {
file: encryptedThumbnail,
}: encryptionResult = await worker.encryptThumbnail(
file.thumbnail,
fileKey
);
const encryptedKey: keyEncryptionResult = await worker.encryptToB64(
fileKey,
encryptionKey
);
const result: encryptedFile = {
filedata: encryptedFiledata,
thumbnail: encryptedThumbnail,
fileKey: encryptedKey
fileKey: encryptedKey,
};
return result;
}
catch (e) {
console.log("Error encrypting files " + e);
} catch (e) {
console.log('Error encrypting files ' + e);
}
}
private async encryptMetadata(worker: any, fileName: string, encryptedFileKey: keyEncryptionResult) {
private async encryptMetadata(
worker: any,
fileName: string,
encryptedFileKey: keyEncryptionResult
) {
const metaData = this.metadataMap.get(fileName);
const fileKey = await worker.decryptB64(encryptedFileKey.encryptedData, encryptedFileKey.nonce, encryptedFileKey.key);
const encryptedMetaData = await worker.encryptMetadata(metaData, fileKey);
const fileKey = await worker.decryptB64(
encryptedFileKey.encryptedData,
encryptedFileKey.nonce,
encryptedFileKey.key
);
const encryptedMetaData = await worker.encryptMetadata(
metaData,
fileKey
);
return encryptedMetaData;
}
private async uploadtoBucket(file: encryptedFile, token, count: number): Promise<objectKeys> {
private async uploadtoBucket(
file: encryptedFile,
token,
count: number
): Promise<objectKeys> {
try {
const fileUploadURL = await this.getUploadURL(token, count);
const fileObjectKey = await this.putFile(fileUploadURL, file.filedata.encryptedData)
const fileObjectKey = await this.putFile(
fileUploadURL,
file.filedata.encryptedData
);
const thumbnailUploadURL = await this.getUploadURL(token, count);
const thumbnailObjectKey = await this.putFile(thumbnailUploadURL, file.thumbnail.encryptedData)
const thumbnailObjectKey = await this.putFile(
thumbnailUploadURL,
file.thumbnail.encryptedData
);
return {
file: { objectKey: fileObjectKey, decryptionHeader: file.filedata.decryptionHeader },
thumbnail: { objectKey: thumbnailObjectKey, decryptionHeader: file.thumbnail.decryptionHeader }
file: {
objectKey: fileObjectKey,
decryptionHeader: file.filedata.decryptionHeader,
},
thumbnail: {
objectKey: thumbnailObjectKey,
decryptionHeader: file.thumbnail.decryptionHeader,
},
};
} catch (e) {
console.log("error uploading to bucket " + e);
console.log('error uploading to bucket ' + e);
throw e;
}
}
private getuploadFile(collection: collection, encryptedKey: keyEncryptionResult, objectKeys: objectKeys): uploadFile {
private getuploadFile(
collection: collection,
encryptedKey: keyEncryptionResult,
objectKeys: objectKeys
): uploadFile {
const uploadFile: uploadFile = {
collectionID: collection.id,
encryptedKey: encryptedKey.encryptedData,
keyDecryptionNonce: encryptedKey.nonce,
...objectKeys
}
...objectKeys,
};
return uploadFile;
}
private async uploadFile(uploadFile: uploadFile, token) {
try {
const response = await HTTPService.post(`${ENDPOINT}/files`, uploadFile, null, { 'X-Auth-Token': token });
const response = await HTTPService.post(
`${ENDPOINT}/files`,
uploadFile,
null,
{ 'X-Auth-Token': token }
);
return response.data;
} catch (e) {
console.log("upload Files Failed " + e);
console.log('upload Files Failed ' + e);
}
}
private async updateMetadata(recievedFile: File) {
try {
const metadataJSON: object = await new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
var result = typeof reader.result !== "string" ? new TextDecoder().decode(reader.result) : reader.result
resolve(JSON.parse(result));
const metadataJSON: object = await new Promise(
(resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
var result =
typeof reader.result !== 'string'
? new TextDecoder().decode(reader.result)
: reader.result;
resolve(JSON.parse(result));
};
reader.readAsText(recievedFile);
}
reader.readAsText(recievedFile)
});
if (!this.metadataMap.has(metadataJSON['title']))
);
if (!this.metadataMap.has(metadataJSON['title'])) {
return;
}
const metaDataObject = this.metadataMap.get(metadataJSON['title']);
metaDataObject['creationTime'] = metadataJSON['photoTakenTime']['timestamp'] * 1000000;
metaDataObject['modificationTime'] = metadataJSON['modificationTime']['timestamp'] * 1000000;
metaDataObject['creationTime'] =
metadataJSON['photoTakenTime']['timestamp'] * 1000000;
metaDataObject['modificationTime'] =
metadataJSON['modificationTime']['timestamp'] * 1000000;
if (metaDataObject['latitude'] == null || (metaDataObject['latitude'] == 0.0 && metaDataObject['longitude'] == 0.0)) {
if (
metaDataObject['latitude'] == null ||
(metaDataObject['latitude'] == 0.0 &&
metaDataObject['longitude'] == 0.0)
) {
var locationData = null;
if (metadataJSON['geoData']['latitude'] != 0.0 || metadataJSON['geoData']['longitude'] != 0.0) {
if (
metadataJSON['geoData']['latitude'] != 0.0 ||
metadataJSON['geoData']['longitude'] != 0.0
) {
locationData = metadataJSON['geoData'];
}
else if (metadataJSON['geoDataExif']['latitude'] != 0.0 || metadataJSON['geoDataExif']['longitude'] != 0.0) {
} else if (
metadataJSON['geoDataExif']['latitude'] != 0.0 ||
metadataJSON['geoDataExif']['longitude'] != 0.0
) {
locationData = metadataJSON['geoDataExif'];
}
if (locationData != null) {
@ -303,31 +410,34 @@ class UploadService {
}
}
} catch (e) {
console.log("error reading metaData Files " + e);
console.log('error reading metaData Files ' + e);
}
}
private async generateThumbnail(file: File): Promise<Uint8Array> {
try {
let canvas = document.createElement("canvas");
let canvas_CTX = canvas.getContext("2d");
let canvas = document.createElement('canvas');
let canvas_CTX = canvas.getContext('2d');
let imageURL = null;
if (file.type.match("image")) {
if (file.type.match('image')) {
let image = new Image();
imageURL = URL.createObjectURL(file);
image.setAttribute("src", imageURL);
image.setAttribute('src', imageURL);
await new Promise((resolve) => {
image.onload = () => {
canvas.width = image.width;
canvas.height = image.height;
canvas_CTX.drawImage(image, 0, 0, image.width, image.height);
canvas_CTX.drawImage(
image,
0,
0,
image.width,
image.height
);
image = undefined;
resolve(null);
}
};
});
}
else {
} else {
await new Promise(async (resolve) => {
let video = document.createElement('video');
imageURL = URL.createObjectURL(file);
@ -347,7 +457,13 @@ class UploadService {
var snapImage = function () {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas_CTX.drawImage(video, 0, 0, canvas.width, canvas.height);
canvas_CTX.drawImage(
video,
0,
0,
canvas.width,
canvas.height
);
var image = canvas.toDataURL();
var success = image.length > 100000;
return success;
@ -362,34 +478,39 @@ class UploadService {
});
}
URL.revokeObjectURL(imageURL);
var thumbnailBlob = await new Promise(resolve => {
var thumbnailBlob = await new Promise((resolve) => {
canvas.toBlob(function (blob) {
resolve(blob);
}), 'image/jpeg', 0.4
}),
'image/jpeg',
0.4;
});
const thumbnail = this.getUint8ArrayView(thumbnailBlob);
return thumbnail;
} catch (e) {
console.log("Error generatin thumbnail " + e);
console.log('Error generatin thumbnail ' + e);
}
}
private async getUint8ArrayView(file): Promise<Uint8Array> {
try {
return await new Promise((resolve, reject) => {
const reader = new FileReader()
const reader = new FileReader();
reader.onabort = () => reject('file reading was aborted')
reader.onerror = () => reject('file reading has failed')
reader.onabort = () => reject('file reading was aborted');
reader.onerror = () => reject('file reading has failed');
reader.onload = () => {
// Do whatever you want with the file contents
const result = typeof reader.result === "string" ? new TextEncoder().encode(reader.result) : new Uint8Array(reader.result);
const result =
typeof reader.result === 'string'
? new TextEncoder().encode(reader.result)
: new Uint8Array(reader.result);
resolve(result);
}
reader.readAsArrayBuffer(file)
};
reader.readAsArrayBuffer(file);
});
} catch (e) {
console.log("error readinf file to bytearray " + e);
console.log('error readinf file to bytearray ' + e);
throw e;
}
}
@ -404,26 +525,34 @@ class UploadService {
private async fetchUploadURLs(token: string, count: number): Promise<void> {
try {
if (!this.uploadURLFetchInProgress) {
this.uploadURLFetchInProgress = HTTPService.get(`${ENDPOINT}/files/upload-urls`,
this.uploadURLFetchInProgress = HTTPService.get(
`${ENDPOINT}/files/upload-urls`,
{
count: Math.min(50, count).toString() //m4gic number
}, { 'X-Auth-Token': token })
count: Math.min(50, count).toString(), //m4gic number
},
{ 'X-Auth-Token': token }
);
const response = await this.uploadURLFetchInProgress;
this.uploadURLFetchInProgress = null;
this.uploadURLs.push(...response.data["urls"]);
this.uploadURLs.push(...response.data['urls']);
}
return this.uploadURLFetchInProgress;
} catch (e) {
console.log("fetch upload-url failed " + e);
console.log('fetch upload-url failed ' + e);
throw e;
}
}
private async putFile(fileUploadURL: uploadURL, file: Uint8Array | string): Promise<string> {
private async putFile(
fileUploadURL: uploadURL,
file: Uint8Array | string
): Promise<string> {
try {
const fileSize = file.length.toString();
await HTTPService.put(fileUploadURL.url, file, null, { contentLengthHeader: fileSize })
await HTTPService.put(fileUploadURL.url, file, null, {
contentLengthHeader: fileSize,
});
return fileUploadURL.objectKey;
} catch (e) {
console.log('putFile to dataStore failed ' + e);
@ -434,41 +563,52 @@ class UploadService {
private async getExifData(recievedFile) {
try {
const exifData: any = await new Promise((resolve, reject) => {
const reader = new FileReader()
const reader = new FileReader();
reader.onload = () => {
resolve(EXIF.readFromBinaryFile(reader.result));
}
reader.readAsArrayBuffer(recievedFile)
};
reader.readAsArrayBuffer(recievedFile);
});
if (!exifData)
if (!exifData) {
return { location: null, creationTime: null };
}
return {
location: this.getLocation(exifData),
creationTime: this.getUNIXTime(exifData)
creationTime: this.getUNIXTime(exifData),
};
} catch (e) {
console.log("error reading exif data");
console.log('error reading exif data');
}
}
private getUNIXTime(exifData: any) {
if (!exifData.DateTimeOriginal)
if (!exifData.DateTimeOriginal) {
return null;
}
let dateString: string = exifData.DateTimeOriginal;
var parts = dateString.split(' ')[0].split(":");
var date = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
var parts = dateString.split(' ')[0].split(':');
var date = new Date(
Number(parts[0]),
Number(parts[1]) - 1,
Number(parts[2])
);
return date.getTime() * 1000;
}
private getLocation(exifData) {
if (!exifData.GPSLatitude)
if (!exifData.GPSLatitude) {
return null;
}
var latDegree = exifData.GPSLatitude[0].numerator;
var latMinute = exifData.GPSLatitude[1].numerator;
var latSecond = exifData.GPSLatitude[2].numerator;
var latDirection = exifData.GPSLatitudeRef;
var latFinal = this.convertDMSToDD(latDegree, latMinute, latSecond, latDirection);
var latFinal = this.convertDMSToDD(
latDegree,
latMinute,
latSecond,
latDirection
);
// Calculate longitude decimal
var lonDegree = exifData.GPSLongitude[0].numerator;
@ -476,16 +616,20 @@ class UploadService {
var lonSecond = exifData.GPSLongitude[2].numerator;
var lonDirection = exifData.GPSLongitudeRef;
var lonFinal = this.convertDMSToDD(lonDegree, lonMinute, lonSecond, lonDirection);
var lonFinal = this.convertDMSToDD(
lonDegree,
lonMinute,
lonSecond,
lonDirection
);
return { latitude: latFinal * 1.0, longitude: lonFinal * 1.0 };
}
private convertDMSToDD(degrees, minutes, seconds, direction) {
var dd = degrees + minutes / 60 + seconds / 3600;
var dd = degrees + (minutes / 60) + (seconds / 3600);
if (direction == "S" || direction == "W") {
if (direction == 'S' || direction == 'W') {
dd = dd * -1;
}
@ -494,4 +638,3 @@ class UploadService {
}
export default new UploadService();

View file

@ -5,16 +5,28 @@ import { getEndpoint } from 'utils/common/apiUtil';
const ENDPOINT = getEndpoint();
export const getOtt = (email: string) => {
return HTTPService.get(`${ENDPOINT}/users/ott`, { 'email': email, 'client': 'web' })
}
return HTTPService.get(`${ENDPOINT}/users/ott`, {
email: email,
client: 'web',
});
};
export const verifyOtt = (email: string, ott: string) => {
return HTTPService.get(`${ENDPOINT}/users/credentials`, { email, ott });
}
};
export const putAttributes = (token: string, name: string, keyAttributes: keyAttributes) => {
console.log("name " + name);
return HTTPService.put(`${ENDPOINT}/users/attributes`, { 'name': name, 'keyAttributes': keyAttributes }, null, {
'X-Auth-Token': token,
});
}
export const putAttributes = (
token: string,
name: string,
keyAttributes: keyAttributes
) => {
console.log('name ' + name);
return HTTPService.put(
`${ENDPOINT}/users/attributes`,
{ name: name, keyAttributes: keyAttributes },
null,
{
'X-Auth-Token': token,
}
);
};

View file

@ -3,4 +3,4 @@ export interface keyAttributes {
kekHash: string;
encryptedKey: string;
keyDecryptionNonce: string;
};
}

View file

@ -1,4 +1,5 @@
export const getEndpoint = () => {
const endPoint = process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? "https://api.ente.io";
const endPoint =
process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? 'https://api.ente.io';
return endPoint;
}
};

View file

@ -1,20 +1,26 @@
import { getData, LS_KEYS } from "utils/storage/localStorage";
import { getKey, SESSION_KEYS } from "utils/storage/sessionStorage";
import * as Comlink from "comlink";
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
import * as Comlink from 'comlink';
const CryptoWorker: any = typeof window !== 'undefined'
&& Comlink.wrap(new Worker("worker/crypto.worker.js", { type: 'module' }));
const CryptoWorker: any =
typeof window !== 'undefined' &&
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
export const getActualKey = async () => {
const session = getData(LS_KEYS.SESSION);
if (session == null)
if (session == null) {
return;
}
const cryptoWorker = await new CryptoWorker();
const encryptedKey = getKey(SESSION_KEYS.ENCRYPTION_KEY)?.encryptionKey;
const key: string = await cryptoWorker.decryptB64(encryptedKey, session.sessionNonce, session.sessionKey);
const key: string = await cryptoWorker.decryptB64(
encryptedKey,
session.sessionNonce,
session.sessionKey
);
return key;
}
};
export const getToken = () => {
return getData(LS_KEYS.USER)?.token;
}
};

View file

@ -2,18 +2,37 @@ import sodium from 'libsodium-wrappers';
const encryptionChunkSize = 4 * 1024 * 1024;
export async function decryptChaChaOneShot(data: Uint8Array, header: Uint8Array, key: string) {
export async function decryptChaChaOneShot(
data: Uint8Array,
header: Uint8Array,
key: string
) {
await sodium.ready;
const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, await fromB64(key));
const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(pullState, data, null);
const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
header,
await fromB64(key)
);
const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(
pullState,
data,
null
);
return pullResult.message;
}
export async function decryptChaCha(data: Uint8Array, header: Uint8Array, key: string) {
export async function decryptChaCha(
data: Uint8Array,
header: Uint8Array,
key: string
) {
await sodium.ready;
const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, await fromB64(key));
const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
header,
await fromB64(key)
);
const decryptionChunkSize =
encryptionChunkSize + sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
encryptionChunkSize +
sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
var bytesRead = 0;
var decryptedData = [];
var tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
@ -23,7 +42,10 @@ export async function decryptChaCha(data: Uint8Array, header: Uint8Array, key: s
chunkSize = data.length - bytesRead;
}
const buffer = data.slice(bytesRead, bytesRead + chunkSize);
const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(pullState, buffer);
const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(
pullState,
buffer
);
for (var index = 0; index < pullResult.message.length; index++) {
decryptedData.push(pullResult.message[index]);
}
@ -36,27 +58,41 @@ export async function decryptChaCha(data: Uint8Array, header: Uint8Array, key: s
export async function encryptChaChaOneShot(data: Uint8Array, key?: string) {
await sodium.ready;
const uintkey: Uint8Array = key ? await fromB64(key) : sodium.crypto_secretstream_xchacha20poly1305_keygen();
let initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
const uintkey: Uint8Array = key
? await fromB64(key)
: sodium.crypto_secretstream_xchacha20poly1305_keygen();
let initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(
uintkey
);
let [pushState, header] = [initPushResult.state, initPushResult.header];
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(pushState, data, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL);
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
pushState,
data,
null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);
return {
key: await toB64(uintkey), file: {
key: await toB64(uintkey),
file: {
encryptedData: pushResult,
decryptionHeader: await toB64(header),
creationTime: Date.now(),
fileType: 0
}
}
fileType: 0,
},
};
}
export async function encryptChaCha(data: Uint8Array, key?: string) {
await sodium.ready;
const uintkey: Uint8Array = key ? await fromB64(key) : sodium.crypto_secretstream_xchacha20poly1305_keygen();
const uintkey: Uint8Array = key
? await fromB64(key)
: sodium.crypto_secretstream_xchacha20poly1305_keygen();
let initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
let initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(
uintkey
);
let [pushState, header] = [initPushResult.state, initPushResult.header];
let bytesRead = 0;
let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
@ -72,46 +108,59 @@ export async function encryptChaCha(data: Uint8Array, key?: string) {
const buffer = data.slice(bytesRead, bytesRead + chunkSize);
bytesRead += chunkSize;
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(pushState, buffer, null, tag);
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
pushState,
buffer,
null,
tag
);
for (var index = 0; index < pushResult.length; index++) {
encryptedData.push(pushResult[index]);
}
}
return {
key: await toB64(uintkey), file: {
key: await toB64(uintkey),
file: {
encryptedData: new Uint8Array(encryptedData),
decryptionHeader: await toB64(header),
creationTime: Date.now(),
fileType: 0
}
}
fileType: 0,
},
};
}
export async function encryptToB64(data: string, key?: string) {
await sodium.ready;
const encrypted = await encrypt(await fromB64(data), (key ? await fromB64(key) : null));
const encrypted = await encrypt(
await fromB64(data),
key ? await fromB64(key) : null
);
return {
encryptedData: await toB64(encrypted.encryptedData),
key: await toB64(encrypted.key),
nonce: await toB64(encrypted.nonce),
}
};
}
export async function decryptB64(data: string, nonce: string, key: string) {
await sodium.ready;
const decrypted = await decrypt(await fromB64(data),
const decrypted = await decrypt(
await fromB64(data),
await fromB64(nonce),
await fromB64(key));
await fromB64(key)
);
return await toB64(decrypted);
}
export async function decryptString(data: string, nonce: string, key: string) {
await sodium.ready;
const decrypted = await decrypt(await fromB64(data),
const decrypted = await decrypt(
await fromB64(data),
await fromB64(nonce),
await fromB64(key));
await fromB64(key)
);
return sodium.to_string(decrypted);
}
@ -125,10 +174,14 @@ export async function encrypt(data: Uint8Array, key?: Uint8Array) {
encryptedData: encryptedData,
key: uintkey,
nonce: nonce,
}
};
}
export async function decrypt(data: Uint8Array, nonce: Uint8Array, key: Uint8Array) {
export async function decrypt(
data: Uint8Array,
nonce: Uint8Array,
key: Uint8Array
) {
await sodium.ready;
return sodium.crypto_secretbox_open_easy(data, nonce, key);
}
@ -143,20 +196,22 @@ export async function hash(input: string) {
return sodium.crypto_pwhash_str(
await fromB64(input),
sodium.crypto_pwhash_OPSLIMIT_SENSITIVE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE
);
}
export async function deriveKey(passphrase: string, salt: string) {
await sodium.ready;
return await toB64(sodium.crypto_pwhash(
sodium.crypto_secretbox_KEYBYTES,
await fromString(passphrase),
await fromB64(salt),
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
sodium.crypto_pwhash_ALG_DEFAULT,
));
return await toB64(
sodium.crypto_pwhash(
sodium.crypto_secretbox_KEYBYTES,
await fromString(passphrase),
await fromB64(salt),
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
sodium.crypto_pwhash_ALG_DEFAULT
)
);
}
export async function generateMasterKey() {
@ -172,12 +227,25 @@ export async function generateSaltToDeriveKey() {
export async function generateKeyPair() {
await sodium.ready;
const keyPair: sodium.KeyPair = sodium.crypto_box_keypair();
return { privateKey: await toB64(keyPair.privateKey), publicKey: await toB64(keyPair.publicKey) }
return {
privateKey: await toB64(keyPair.privateKey),
publicKey: await toB64(keyPair.publicKey),
};
}
export async function boxSealOpen(input: string, publicKey: string, secretKey: string) {
export async function boxSealOpen(
input: string,
publicKey: string,
secretKey: string
) {
await sodium.ready;
return await toB64(sodium.crypto_box_seal_open(await fromB64(input), await fromB64(publicKey), await fromB64(secretKey)));
return await toB64(
sodium.crypto_box_seal_open(
await fromB64(input),
await fromB64(publicKey),
await fromB64(secretKey)
)
);
}
export async function fromB64(input: string) {
@ -187,9 +255,7 @@ export async function fromB64(input: string) {
result = sodium.from_base64(input, sodium.base64_variants.ORIGINAL);
} catch (e) {
result = await fromB64(await toB64(await fromString(input)));
}
finally {
} finally {
return result;
}
}
@ -202,4 +268,4 @@ export async function toB64(input: Uint8Array) {
export async function fromString(input: string) {
await sodium.ready;
return sodium.from_string(input);
}
}

View file

@ -1,7 +1,7 @@
export enum LS_KEYS {
USER='user',
SESSION='session',
KEY_ATTRIBUTES='keyAttributes',
USER = 'user',
SESSION = 'session',
KEY_ATTRIBUTES = 'keyAttributes',
}
export const setData = (key: LS_KEYS, value: object) => {
@ -9,18 +9,18 @@ export const setData = (key: LS_KEYS, value: object) => {
return null;
}
localStorage.setItem(key, JSON.stringify(value));
}
};
export const getData = (key: LS_KEYS) => {
if (typeof localStorage === 'undefined') {
return null;
}
return JSON.parse(localStorage.getItem(key));
}
};
export const clearData = () => {
if (typeof localStorage === 'undefined') {
return null;
}
localStorage.clear();
}
};

View file

@ -1,5 +1,5 @@
export enum SESSION_KEYS {
ENCRYPTION_KEY='encryptionKey',
ENCRYPTION_KEY = 'encryptionKey',
}
export const setKey = (key: SESSION_KEYS, value: object) => {
@ -7,18 +7,18 @@ export const setKey = (key: SESSION_KEYS, value: object) => {
return null;
}
sessionStorage.setItem(key, JSON.stringify(value));
}
};
export const getKey = (key: SESSION_KEYS) => {
if (typeof sessionStorage === 'undefined') {
return null;
}
return JSON.parse(sessionStorage.getItem(key));
}
};
export const clearKeys = () => {
if (typeof sessionStorage === 'undefined') {
return null;
}
sessionStorage.clear();
}
};

View file

@ -1,4 +1,4 @@
import { getConstantValue } from "./vernacularStrings";
import { getConstantValue } from './vernacularStrings';
const constants = getConstantValue();
export default constants;

View file

@ -1,4 +1,4 @@
import { template } from "./vernacularStrings";
import { template } from './vernacularStrings';
/**
* Global English constants.
@ -16,7 +16,11 @@ const englishConstants = {
EMAIL_ERROR: 'Enter a valid email address',
REQUIRED: 'Required',
VERIFY_EMAIL: 'Verify Email',
EMAIL_SENT: ({ email }) => (<p>We have sent a mail to <b>{email}</b>.</p>),
EMAIL_SENT: ({ email }) => (
<p>
We have sent a mail to <b>{email}</b>.
</p>
),
CHECK_INBOX: 'Please check your inbox (and spam) to complete verification.',
ENTER_OTT: 'Enter verification code here',
RESEND_MAIL: 'Did not get email?',
@ -30,7 +34,8 @@ const englishConstants = {
SET_PASSPHRASE: 'Set Passphrase',
VERIFY_PASSPHRASE: 'Verify Passphrase',
INCORRECT_PASSPHRASE: 'Incorrect Passphrase',
ENTER_ENC_PASSPHRASE: 'Please enter a passphrase that we can use to encrypt your data.',
ENTER_ENC_PASSPHRASE:
'Please enter a passphrase that we can use to encrypt your data.',
PASSPHRASE_DISCLAIMER: () => (
<p>
We don't store your passphrase, so if you forget,
@ -46,14 +51,16 @@ const englishConstants = {
CLOSE: 'Close',
NOTHING_HERE: `nothing to see here! 👀`,
UPLOAD: {
0: "Preparing to upload",
1: "Encryting your files",
2: "Uploading your Files",
3: "Files Uploaded Successfully !!!"
0: 'Preparing to upload',
1: 'Encryting your files',
2: 'Uploading your Files',
3: 'Files Uploaded Successfully !!!',
},
OF: 'of',
SUBSCRIPTION_EXPIRED: 'You don\'t have a active subscription plan!! Please get one in the mobile app',
STORAGE_QUOTA_EXCEEDED:'You have exceeded your designated storage Quota, please upgrade your plan to add more files',
SUBSCRIPTION_EXPIRED:
"You don't have a active subscription plan!! Please get one in the mobile app",
STORAGE_QUOTA_EXCEEDED:
'You have exceeded your designated storage Quota, please upgrade your plan to add more files',
};
export default englishConstants;

View file

@ -2,8 +2,8 @@ import englishConstants from './englishConstants';
/** Enums of supported locale */
export enum locale {
en='en',
hi='hi',
en = 'en',
hi = 'hi',
}
/**
@ -16,7 +16,7 @@ export enum locale {
* @param keys
*/
export function template(strings: TemplateStringsArray, ...keys: string[]) {
return ((...values: any[]) => {
return (...values: any[]) => {
const dict = values[values.length - 1] || {};
const result = [strings[0]];
keys.forEach((key, i) => {
@ -24,16 +24,16 @@ export function template(strings: TemplateStringsArray, ...keys: string[]) {
result.push(value, strings[i + 1]);
});
return result.join('');
});
};
}
/** Type for vernacular string constants */
export type VernacularConstants<T> = {
[locale.en]: T,
[locale.en]: T;
[locale.hi]?: {
[x in keyof T]?: string | ReturnType<typeof template>;
},
}
};
};
/**
* Returns a valid locale from string and defaults
@ -43,10 +43,10 @@ export type VernacularConstants<T> = {
*/
export const getLocale = (lang: string) => {
switch (lang) {
case locale.hi:
return locale.hi;
default:
return locale.en;
case locale.hi:
return locale.hi;
default:
return locale.en;
}
};
@ -62,7 +62,8 @@ const globalConstants: VernacularConstants<typeof englishConstants> = {
* @param localConstants
*/
export function getConstantValue<T>(localConstants?: VernacularConstants<T>) {
const searchParam = typeof window !== 'undefined' ? window.location.search : '';
const searchParam =
typeof window !== 'undefined' ? window.location.search : '';
const query = new URLSearchParams(searchParam);
const currLocale = getLocale(query.get('lang'));

View file

@ -6,44 +6,39 @@ export class Crypto {
const encodedMetadata = await libsodium.decryptChaChaOneShot(
await libsodium.fromB64(file.metadata.encryptedData),
await libsodium.fromB64(file.metadata.decryptionHeader),
file.key);
file.key
);
return JSON.parse(new TextDecoder().decode(encodedMetadata));
}
async decryptThumbnail(fileData, header, key) {
return libsodium.decryptChaChaOneShot(
fileData,
header,
key);
return libsodium.decryptChaChaOneShot(fileData, header, key);
}
async decryptFile(fileData, header, key) {
return libsodium.decryptChaCha(
fileData,
header,
key);
return libsodium.decryptChaCha(fileData, header, key);
}
async encryptMetadata(metadata, key) {
const encodedMetadata = new TextEncoder().encode(JSON.stringify(metadata));
const encodedMetadata = new TextEncoder().encode(
JSON.stringify(metadata)
);
const { file: encryptedMetadata } = await libsodium.encryptChaChaOneShot(
encodedMetadata,
key);
const { encryptedData, ...other } = encryptedMetadata
const {
file: encryptedMetadata,
} = await libsodium.encryptChaChaOneShot(encodedMetadata, key);
const { encryptedData, ...other } = encryptedMetadata;
return {
file: {
encryptedData: await libsodium.toB64(encryptedData),
...other
...other,
},
key
key,
};
}
async encryptThumbnail(fileData, key) {
return libsodium.encryptChaChaOneShot(
fileData,
key);
return libsodium.encryptChaChaOneShot(fileData, key);
}
async encryptFile(fileData, key) {
@ -71,11 +66,11 @@ export class Crypto {
}
async decryptB64(data, nonce, key) {
return libsodium.decryptB64(data, nonce, key)
return libsodium.decryptB64(data, nonce, key);
}
async decryptString(data, nonce, key) {
return libsodium.decryptString(data, nonce, key)
return libsodium.decryptString(data, nonce, key);
}
async encryptToB64(data, key) {
@ -99,7 +94,7 @@ export class Crypto {
}
async boxSealOpen(input, publicKey, secretKey) {
return libsodium.boxSealOpen(input, publicKey, secretKey)
return libsodium.boxSealOpen(input, publicKey, secretKey);
}
async fromString(string) {