clearErrors('secret')}\n />\n \n {t('Encrypt message')}\n \n \n >\n );\n};\n\nexport const Error = (props: { message?: string; onClick?: () => void }) =>\n props.message ? (\n \n {props.message}\n \n ) : null;\n\nexport const OneTime = (props: { register: UseFormMethods['register'] }) => {\n const { t } = useTranslation();\n return (\n \n \n }\n label={t('One-time download')}\n />\n \n );\n};\n\nexport const SpecifyPasswordInput = (props: {\n register: UseFormMethods['register'];\n}) => {\n const { t } = useTranslation();\n return (\n \n {t('Custom decryption key')} \n \n \n );\n};\n\nexport const SpecifyPasswordToggle = (props: {\n register: UseFormMethods['register'];\n}) => {\n const { t } = useTranslation();\n return (\n \n \n }\n label={t('Generate decryption key')}\n />\n \n );\n};\n\nexport default CreateSecret;\n","import { useTranslation } from 'react-i18next';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faCopy } from '@fortawesome/free-solid-svg-icons';\nimport { Button, Typography, makeStyles } from '@material-ui/core';\nimport { useCopyToClipboard } from 'react-use';\nimport { saveAs } from 'file-saver';\nimport { useEffect } from 'react';\n\nconst useStyles = makeStyles(() => ({\n pre: {\n backgroundColor: '#ecf0f1',\n padding: '15px',\n border: '1px solid #cccccc',\n display: 'block',\n fontSize: '14px',\n borderRadius: '4px',\n wordWrap: 'break-word',\n wordBreak: 'break-all',\n },\n}));\n\nconst Secret = ({\n secret,\n fileName,\n}: {\n readonly secret: string;\n readonly fileName?: string;\n}) => {\n const { t } = useTranslation();\n const [copy, copyToClipboard] = useCopyToClipboard();\n const classes = useStyles();\n\n useEffect(() => {\n fileName &&\n saveAs(\n new Blob([secret], {\n type: 'application/octet-stream',\n }),\n fileName,\n );\n }, [fileName, secret]);\n\n if (fileName) {\n return (\n \n {t('File downloaded')} \n
\n );\n }\n return (\n \n
{t('Decrypted Message')} \n
\n {t(\n 'This secret might not be viewable again, make sure to save it now!',\n )}\n \n
copyToClipboard(secret)}\n >\n {t('Copy')}\n \n
\n {secret}\n \n
\n );\n};\n\nexport default Secret;\n","import { useTranslation } from 'react-i18next';\nimport { makeStyles, Typography } from '@material-ui/core';\n\nconst useStyles = makeStyles((theme) => ({\n header: {\n paddingTop: theme.spacing(1),\n paddingBottom: theme.spacing(1),\n },\n}));\n\nconst ErrorPage = (props: { error?: Error }) => {\n const { t } = useTranslation();\n const classes = useStyles();\n if (!props.error) {\n return null;\n }\n\n return (\n \n {t('Secret does not exist')} \n \n {t('It might be caused by any of these reasons.')}\n \n \n \n {t('Opened before')}\n \n \n {t(\n 'A secret can be restricted to a single download. It might be lost because the sender clicked this link before you viewed it.',\n )}\n \n {t(\n 'The secret might have been compromised and read by someone else. You should contact the sender and request a new secret.',\n )}\n\n \n {t('Broken link')}\n \n {t(\n 'The link must match perfectly in order for the decryption to work, it might be missing some magic digits.',\n )}\n \n {t('Expired')}\n \n {t(\n 'No secret last forever. All stored secrets will expires and self destruct automatically. Lifetime varies from one hour up to one week.',\n )}\n \n
\n );\n};\nexport default ErrorPage;\n","import { useState } from 'react';\nimport { useLocation, useParams } from 'react-router-dom';\nimport useSWR from 'swr';\nimport { backendDomain, decryptMessage } from '../utils/utils';\nimport Secret from './Secret';\nimport ErrorPage from './Error';\nimport {\n Container,\n Grid,\n TextField,\n Button,\n Typography,\n} from '@material-ui/core';\nimport { useTranslation } from 'react-i18next';\nimport { useAsync } from 'react-use';\n\nconst fetcher = async (url: string) => {\n const request = await fetch(url);\n if (!request.ok) {\n throw new Error('Failed to fetch secret');\n }\n const data = await request.json();\n return data.message;\n};\n\nconst DisplaySecret = () => {\n const { key, password: paramsPassword } = useParams<{\n key: string;\n password: string;\n }>();\n const location = useLocation();\n const isFile = null !== location.pathname.match(/\\/d|f\\//);\n const [password, setPassword] = useState(\n paramsPassword ? paramsPassword : '',\n );\n const [secret, setSecret] = useState('');\n const [fileName, setFileName] = useState('');\n const [invalidPassword, setInvalidPassword] = useState(false);\n const { t } = useTranslation();\n\n const url = isFile\n ? `${backendDomain}/file/${key}`\n : `${backendDomain}/secret/${key}`;\n const { data, error } = useSWR(url, fetcher, {\n shouldRetryOnError: false,\n revalidateOnFocus: false,\n });\n\n useAsync(async () => {\n return decrypt();\n }, [paramsPassword, data]);\n\n const decrypt = async () => {\n if (!data || !password) {\n return;\n }\n try {\n const r = await decryptMessage(\n data,\n password,\n isFile ? 'binary' : 'utf8',\n );\n if (isFile) {\n setFileName(r.filename);\n }\n setSecret(r.data);\n } catch (e) {\n setInvalidPassword(true);\n return false;\n }\n return true;\n };\n\n if (error) return ;\n if (!data)\n return (\n \n {t('Fetching from database, please hold...')}\n \n );\n if (secret) {\n return ;\n }\n if (paramsPassword && !secret && !invalidPassword) {\n return (\n {t('Decrypting, please hold...')} \n );\n }\n\n return (\n \n \n \n Enter decryption key \n \n Do not refresh this window as secret might be restricted to one time\n download.\n \n \n \n setPassword(e.target.value)}\n inputProps={{ spellCheck: 'false', ['data-gramm']: 'false' }}\n />\n \n \n \n {t('Decrypt Secret')}\n \n \n \n \n );\n};\n\nexport default DisplaySecret;\n","import { faFileUpload } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { encrypt, message } from 'openpgp';\nimport { useCallback, useState } from 'react';\nimport { useDropzone } from 'react-dropzone';\nimport {\n Error,\n OneTime,\n SpecifyPasswordToggle,\n SpecifyPasswordInput,\n} from './CreateSecret';\nimport Expiration from './../shared/Expiration';\nimport Result from '../displaySecret/Result';\nimport { randomString, uploadFile } from '../utils/utils';\nimport { useTranslation } from 'react-i18next';\nimport { useForm } from 'react-hook-form';\nimport { Grid, Typography } from '@material-ui/core';\n\nconst Upload = () => {\n const maxSize = 1024 * 500;\n const [error, setError] = useState('');\n const { t } = useTranslation();\n const [result, setResult] = useState({\n password: '',\n customPassword: false,\n uuid: '',\n });\n\n const { control, register, handleSubmit, watch } = useForm({\n defaultValues: {\n generateDecryptionKey: true,\n secret: '',\n password: '',\n expiration: '3600',\n onetime: true,\n },\n });\n\n const form = watch();\n const onDrop = useCallback(\n (acceptedFiles: File[]) => {\n const reader = new FileReader();\n reader.onabort = () => console.log('file reading was aborted');\n reader.onerror = () => console.log('file reading has failed');\n reader.onload = async () => {\n handleSubmit(onSubmit)();\n const pw = form.password ? form.password : randomString();\n const file = await encrypt({\n armor: true,\n message: message.fromBinary(\n new Uint8Array(reader.result as ArrayBuffer),\n acceptedFiles[0].name,\n ),\n passwords: pw,\n });\n const { data, status } = await uploadFile({\n expiration: parseInt(form.expiration),\n message: file.data,\n one_time: form.onetime,\n });\n\n if (status !== 200) {\n setError(data.message);\n } else {\n setResult({\n uuid: data.message,\n password: pw,\n customPassword: form.password ? true : false,\n });\n }\n };\n acceptedFiles.forEach((file) => reader.readAsArrayBuffer(file));\n },\n [form, handleSubmit],\n );\n\n const {\n getRootProps,\n getInputProps,\n fileRejections,\n isDragActive,\n } = useDropzone({\n maxSize,\n minSize: 0,\n onDrop,\n });\n\n const onSubmit = () => {};\n\n const isFileTooLarge =\n fileRejections.length > 0 &&\n fileRejections[0].errors[0].code === 'file-too-large';\n\n const generateDecryptionKey = watch('generateDecryptionKey');\n\n if (result.uuid) {\n return (\n \n );\n }\n return (\n \n {isFileTooLarge && }\n setError('')} />\n \n \n );\n};\n\nexport default Upload;\n","import { Route } from 'react-router-dom';\n\nimport CreateSecret from './createSecret/CreateSecret';\nimport DisplaySecret from './displaySecret/DisplaySecret';\nimport Upload from './createSecret/Upload';\n\nexport const Routes = () => {\n return (\n \n \n \n \n \n \n \n
\n );\n};\n","import { Container, Link, makeStyles, Typography } from '@material-ui/core';\nimport { useTranslation } from 'react-i18next';\n\nconst useStyles = makeStyles((theme) => ({\n attribution: {\n margin: theme.spacing(1),\n position: \"absolute\",\n bottom: 0\n },\n}));\n\nexport const Attribution = () => {\n const { t } = useTranslation();\n const classes = useStyles();\n return (\n \n \n {t('Created by')}{' '}\n Johan Haals\n \n \n );\n};\n","import { createMuiTheme } from '@material-ui/core/styles';\n\nexport const theme = createMuiTheme({\n typography: {\n fontFamily: [\n 'Open Sans',\n 'sans-serif',\n ].join(','),\n },\n palette: {\n primary: {\n main: '#02B9E5',\n contrastText: '#fff',\n },\n },\n});\n","import { HashRouter as Router } from 'react-router-dom';\nimport { Container } from '@material-ui/core';\nimport { ThemeProvider } from '@material-ui/core/styles';\n\nimport { Header } from './shared/Header';\nimport { Routes } from './Routes';\nimport { Attribution } from './shared/Attribution';\nimport { theme } from './theme';\n\nconst App = () => {\n // TODO: Removed in future version.\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.getRegistrations().then((registrations) => {\n for (const registration of registrations) {\n registration.unregister();\n }\n });\n }\n\n return (\n \n \n \n \n \n {/* */}\n \n \n \n \n );\n};\n\nexport default App;\n","import i18n from 'i18next';\nimport { initReactI18next } from 'react-i18next';\n\nimport Backend from 'i18next-http-backend';\n\ni18n\n .use(initReactI18next)\n .use(Backend)\n .init({\n backend: {\n loadPath: process.env.PUBLIC_URL + '/locales/{{lng}}.json',\n },\n\n fallbackLng: 'en',\n lng: 'en',\n debug: false,\n\n // have a common namespace used around the full app\n ns: ['translations'],\n defaultNS: 'translations',\n\n keySeparator: false, // we use content as keys\n\n interpolation: {\n escapeValue: false, // not needed for react!!\n formatSeparator: ',',\n },\n\n appendNamespaceToMissingKey: true,\n });\n\nexport default i18n;\n","import ReactDOM from 'react-dom';\nimport { Suspense } from 'react';\nimport App from './App';\nimport './i18n';\n\nReactDOM.render(\n Loading...}>\n \n ,\n document.getElementById('root'),\n);\n"],"sourceRoot":""}