clearErrors('secret')}\n />\n \n {t('create.title')}\n \n \n >\n );\n};\n\nexport const OneTime = (props: { register: UseFormMethods['register'] }) => {\n const { t } = useTranslation();\n return (\n \n \n }\n label={t('create.inputOneTimeLabel')}\n />\n \n );\n};\n\nexport const SpecifyPasswordInput = (props: {\n register: UseFormMethods['register'];\n}) => {\n const { t } = useTranslation();\n return (\n \n {t('create.inputPasswordLabel')}\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('create.inputGenerateLabel')}\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 } from '@mui/material';\nimport makeStyles from '@mui/styles/makeStyles';\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: '1rem',\n borderRadius: '4px',\n wordWrap: 'break-word',\n wordBreak: 'break-all',\n fontFamily: 'monospace, monospace', // https://github.com/necolas/normalize.css/issues/519#issuecomment-197131966\n },\n}));\n\nconst RenderSecret = ({ secret }: { readonly secret: string }) => {\n const { t } = useTranslation();\n const [copy, copyToClipboard] = useCopyToClipboard();\n const classes = useStyles();\n\n return (\n \n {t('secret.titleMessage')}\n {t('secret.subtitleMessage')}\n \n \n {secret}\n \n
\n );\n};\n\nconst DownloadSecret = ({\n secret,\n fileName,\n}: {\n readonly secret: string;\n readonly fileName: string;\n}) => {\n const { t } = useTranslation();\n\n useEffect(() => {\n saveAs(\n new Blob([secret], {\n type: 'application/octet-stream',\n }),\n fileName,\n );\n }, [fileName, secret]);\n\n return (\n \n {t('secret.titleFile')}\n
\n );\n};\n\nconst Secret = ({\n secret,\n fileName,\n}: {\n readonly secret: string;\n readonly fileName?: string;\n}) => {\n if (fileName) {\n return ;\n }\n\n return ;\n};\n\nexport default Secret;\n","import { useTranslation } from 'react-i18next';\nimport { Typography } from '@mui/material';\nimport makeStyles from '@mui/styles/makeStyles';\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('error.title')}\n {t('error.subtitle')}\n
\n \n {t('error.titleOpened')}\n \n \n {t('error.subtitleOpenedBefore')}\n
\n {t('error.subtitleOpenedCompromised')}\n \n {t('error.titleBrokenLink')}\n \n {t('error.subtitleBrokenLink')}\n \n {t('error.titleExpired')}\n \n {t('error.subtitleExpired')}\n \n
\n );\n};\nexport default ErrorPage;\n","import { useTranslation } from 'react-i18next';\nimport { useState } from 'react';\nimport {\n Alert,\n Button,\n Dialog,\n DialogTitle,\n DialogContent,\n DialogContentText,\n DialogActions,\n useMediaQuery,\n useTheme,\n} from '@mui/material';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faTrash } from '@fortawesome/free-solid-svg-icons';\nimport Error from '../shared/Error';\n\nconst deleteSecret = async (url: string): Promise => {\n return await fetch(url, {\n method: 'DELETE',\n });\n};\n\nconst DeleteSecret = ({ url }: { readonly url: string }) => {\n const { t } = useTranslation();\n\n const theme = useTheme();\n const fullScreen = useMediaQuery(theme.breakpoints.down('md'));\n const [open, setOpen] = useState(false);\n\n const [deleted, setDeleted] = useState(false);\n const [error, setError] = useState('');\n const [loading, setLoading] = useState(false);\n\n const onDelete = async (): Promise => {\n setLoading(true);\n setError('');\n try {\n const response = await deleteSecret(url);\n\n if (response.status !== 204) {\n const data = await response.json();\n setError(data.message);\n } else {\n setDeleted(true);\n setOpen(false);\n }\n } catch (e) {\n setError(e.message);\n }\n setLoading(false);\n };\n\n const handleConfirmOpen = () => {\n setOpen(true);\n };\n\n const handleClose = () => {\n setOpen(false);\n };\n\n if (deleted) {\n return {t('delete.messageDeleted')};\n }\n\n return (\n <>\n \n\n \n >\n );\n};\n\nexport default DeleteSecret;\n","import { useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport useSWR from 'swr';\nimport { backendDomain, decryptMessage } from '../utils/utils';\nimport Secret from './Secret';\nimport ErrorPage from './Error';\nimport { Button, Container, Grid, TextField, Typography } from '@mui/material';\nimport { useTranslation } from 'react-i18next';\nimport { useAsync } from 'react-use';\nimport DeleteSecret from './DeleteSecret';\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 return await request.json();\n};\n\nconst EnterDecryptionKey = ({\n setPassword,\n password,\n loaded,\n}: {\n setPassword: (password: string) => any;\n readonly password?: string;\n readonly loaded?: boolean;\n}) => {\n const { t } = useTranslation();\n const [tempPassword, setTempPassword] = useState(password);\n const invalidPassword = !!password;\n\n const submitPassword = () => {\n if (tempPassword) {\n setPassword(tempPassword);\n }\n };\n return (\n \n \n \n \n {t('display.titleDecryptionKey')}\n \n {loaded ? (\n \n {t('display.captionDecryptionKey')}\n \n ) : null}\n \n \n setTempPassword(e.target.value)}\n inputProps={{ spellCheck: 'false', 'data-gramm': 'false' }}\n />\n \n \n \n \n \n \n );\n};\n\nconst DisplaySecret = () => {\n const { t } = useTranslation();\n const { format, key, password: paramsPassword } = useParams();\n\n const isFile = format === 'f';\n const url = isFile\n ? `${backendDomain}/file/${key}`\n : `${backendDomain}/secret/${key}`;\n\n const [password, setPassword] = useState(paramsPassword);\n const [loadSecret, setLoadSecret] = useState(!!password);\n\n // Ensure that we unload the password when this param changes\n useEffect(() => {\n setPassword(paramsPassword);\n setLoadSecret(!!paramsPassword);\n }, [paramsPassword, key]);\n\n // Ensure that we unload the secret when the key changes\n useEffect(() => {\n setLoadSecret(!!password);\n }, [password, key]);\n\n // Load the secret data when required\n const { data, error } = useSWR(loadSecret ? url : null, fetcher, {\n shouldRetryOnError: false,\n revalidateOnFocus: false,\n });\n\n // Decrypt the secret if password or data is changed\n const {\n loading,\n error: decryptError,\n value,\n } = useAsync(async () => {\n if (!data || !password) {\n return;\n }\n\n return await decryptMessage(\n data.message,\n password,\n isFile ? 'binary' : 'utf8',\n );\n }, [password, data]);\n\n // Handle the loaded of the secret\n if (loadSecret) {\n if (error) {\n return ;\n }\n if (!data) {\n return {t('display.titleFetching')};\n }\n }\n\n // Handle the decrypting\n if (loading) {\n return {t('display.titleDecrypting')};\n }\n if (decryptError) {\n return (\n \n );\n }\n if (value) {\n return (\n <>\n \n {data.one_time ? null : }\n >\n );\n }\n\n // If there is no password we need to fetch it.\n return (\n {\n setPassword(password);\n setLoadSecret(true);\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, createMessage } from 'openpgp';\nimport { useCallback, useState } from 'react';\nimport { useDropzone } from 'react-dropzone';\nimport {\n OneTime,\n SpecifyPasswordToggle,\n SpecifyPasswordInput,\n} from './CreateSecret';\nimport Error from '../shared/Error';\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 '@mui/material';\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 message = await encrypt({\n format: 'armored',\n message: await createMessage({\n binary: new Uint8Array(reader.result as ArrayBuffer),\n filename: acceptedFiles[0].name,\n }),\n passwords: pw,\n });\n const { data, status } = await uploadFile({\n expiration: parseInt(form.expiration),\n message,\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 { getRootProps, getInputProps, fileRejections, 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, Routes } from 'react-router-dom';\n\nimport CreateSecret from './createSecret/CreateSecret';\nimport DisplaySecret from './displaySecret/DisplaySecret';\nimport Upload from './createSecret/Upload';\n\nexport const Routing = () => {\n return (\n \n } />\n } />\n } />\n } />\n \n );\n};\n","import {\n faBomb,\n faCodeBranch,\n faDownload,\n faLock,\n faShareAlt,\n faUserAltSlash,\n IconDefinition,\n} from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { useTranslation } from 'react-i18next';\nimport { Grid, Paper, Typography, Divider, Box } from '@mui/material';\nimport createStyles from '@mui/styles/createStyles';\nimport makeStyles from '@mui/styles/makeStyles';\n\nconst useStyles = makeStyles((theme) =>\n createStyles({\n feature: {\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n alignItems: 'center',\n minHeight: 230,\n padding: 16,\n },\n featureHeader: {\n padding: 10,\n },\n }),\n);\n\nexport const Features = () => {\n const { t } = useTranslation();\n return (\n \n \n \n \n \n {t('features.title')}\n \n \n {t('features.subtitle')}\n \n \n \n \n {t('features.featureEndToEndText')}\n \n \n {t('features.featureSelfDestructionText')}\n \n \n {t('features.featureOneTimeText')}\n \n \n {t('features.featureSimpleSharingText')}\n \n \n {t('features.featureNoAccountsText')}\n \n \n {t('features.featureOpenSourceText')}\n \n \n );\n};\n\ntype FeatureProps = {\n readonly title: string;\n readonly icon: IconDefinition;\n readonly children: JSX.Element;\n};\n\nconst Feature = (props: FeatureProps) => {\n const classes = useStyles();\n return (\n \n \n \n \n {props.title}\n \n {props.children}\n \n \n );\n};\n","import { Container, Link, Typography } from '@mui/material';\nimport { useTranslation } from 'react-i18next';\n\nexport const Attribution = () => {\n const { t } = useTranslation();\n\n const translationAttribution = () => {\n return (\n \n {t('attribution.translatedBy')}{' '}\n \n {t('attribution.translatorName')}\n \n \n );\n };\n\n return (\n \n \n {t('attribution.createdBy')}{' '}\n Johan Haals\n \n {t('attribution.translatorName') && translationAttribution()}\n \n );\n};\n","import { createTheme } from '@mui/material/styles';\nimport { blueGrey } from '@mui/material/colors';\n\nexport const theme = createTheme({\n palette: {\n primary: blueGrey,\n },\n});\n","import { Container } from '@mui/material';\nimport {\n ThemeProvider,\n Theme,\n StyledEngineProvider,\n} from '@mui/material/styles';\n\nimport { Header } from './shared/Header';\nimport { Routing } from './Routing';\nimport { Features } from './shared/Features';\nimport { Attribution } from './shared/Attribution';\nimport { theme } from './theme';\nimport { HashRouter } from 'react-router-dom';\n\ndeclare module '@mui/styles/defaultTheme' {\n // eslint-disable-next-line @typescript-eslint/no-empty-interface\n interface DefaultTheme extends Theme {}\n}\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 );\n};\n\nexport default App;\n","import i18n from 'i18next';\nimport { initReactI18next } from 'react-i18next';\nimport Backend from 'i18next-http-backend';\nimport LanguageDetector from 'i18next-browser-languagedetector';\n\ni18n\n .use(initReactI18next)\n .use(Backend)\n .use(LanguageDetector)\n .init({\n backend: {\n loadPath: process.env.PUBLIC_URL + '/locales/{{lng}}.json',\n },\n\n fallbackLng: process.env.REACT_APP_FALLBACK_LANGUAGE || 'en',\n debug: false,\n\n // have a common namespace used around the full app\n ns: ['translations'],\n defaultNS: 'translations',\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';\nimport '@fontsource/roboto/300.css';\nimport '@fontsource/roboto/400.css';\nimport '@fontsource/roboto/500.css';\nimport '@fontsource/roboto/700.css';\n\nReactDOM.render(\n Loading...}>\n \n ,\n document.getElementById('root'),\n);\n"],"sourceRoot":""}