chore: quality of life improvements (#449)

This commit is contained in:
2022-04-13 22:16:34 +02:00
committed by GitHub
parent e916177569
commit c10817716e
34 changed files with 14038 additions and 11804 deletions

7
.eslintrc Normal file
View File

@@ -0,0 +1,7 @@
{
"extends": "@react-native-community",
"rules": {
"quotes": [2, "single"],
"prettier/prettier": ["error", { "singleQuote": true }]
}
}

View File

@@ -9,12 +9,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
uses: actions/checkout@v2.3.1
with:
persist-credentials: false
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
- name: Install and Build 🔧
run: |
corepack enable
yarn install
NODE_ENV=production yarn build
@@ -22,5 +23,5 @@ jobs:
uses: JamesIves/github-pages-deploy-action@4.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages # The branch the action should deploy to.
folder: dist # The folder the action should deploy.
branch: gh-pages
folder: dist

View File

@@ -7,14 +7,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
uses: actions/checkout@v2.3.1
with:
persist-credentials: false
- name: Install 🔧
run: |
corepack enable
yarn install
- name: Test
run: |
yarn test
NODE_ENV=production yarn test

1
.gitignore vendored
View File

@@ -3,4 +3,5 @@
/.cache
/.env
/.tmp
/.yarn
*.log

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"singleQuote": true
}

1
.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -1,3 +1,5 @@
const isDevelopment = process.env.NODE_ENV !== 'production';
const config = (api) => {
api.cache(false);
return {
@@ -12,8 +14,8 @@ const config = (api) => {
'GITHUB_REPOSITORY',
],
}],
[require.resolve('react-hot-loader/babel')],
],
isDevelopment && require.resolve('react-refresh/babel'),
].filter(Boolean),
};
};

View File

@@ -2,6 +2,5 @@ const path = require('path');
module.exports = {
preset: 'ts-jest',
//testEnvironment: 'node',
testEnvironment: path.join(__dirname, 'tests', 'env-ts.js'),
testEnvironment: path.join(__dirname, 'tests', 'env.js'),
};

View File

@@ -1,47 +1,49 @@
{
"name": "dropbox",
"name": "@morten-olsen/parcel",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"packageManager": "yarn@3.1.0",
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack",
"test": "jest"
},
"devDependencies": {
"@ant-design/icons": "^4.7.0",
"@babel/core": "^7.17.9",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@hot-loader/react-dom": "^17.0.2",
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"@react-native-community/eslint-config": "^3.0.1",
"@types/express": "^4.17.13",
"@types/fs-extra": "^9.0.13",
"@types/get-port": "^4.2.0",
"@types/html-webpack-plugin": "^3.2.6",
"@types/jest": "^27.4.1",
"@types/jszip": "^3.4.1",
"@types/react": "^18.0.3",
"@types/react": "^18.0.4",
"@types/react-dom": "^18.0.0",
"@types/react-router": "^5.1.18",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.25",
"@types/webpack-subresource-integrity": "^5.0.0",
"@types/workbox-webpack-plugin": "^5.1.8",
"antd": "^4.19.5",
"@typescript-eslint/eslint-plugin": "^5.19.0",
"axios": "^0.26.1",
"babel-loader": "^8.2.4",
"babel-plugin-transform-inline-environment-variables": "^0.4.3",
"css-loader": "^6.7.1",
"dotenv": "^16.0.0",
"eslint": "^8.13.0",
"express": "^4.17.3",
"fs-extra": "^10.0.1",
"get-port": "^5",
"get-port": "^6.1.2",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"jest": "^27.5.1",
"offline-plugin": "^5.0.7",
"parcel-bundler": "^1.12.5",
"prettier": "^2.6.2",
"puppeteer": "^13.5.2",
"react-hot-loader": "^4.13.0",
"react-refresh": "^0.12.0",
"style-loader": "^3.3.1",
"ts-jest": "^27.1.4",
"ts-node": "^10.7.0",
@@ -54,6 +56,8 @@
"workbox-webpack-plugin": "^6.5.3"
},
"dependencies": {
"@ant-design/icons": "^4.7.0",
"antd": "^4.19.5",
"jszip": "^3.9.1",
"nanoid": "^3.3.2",
"openpgp": "^5.2.1",

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { hot } from 'react-hot-loader/root';
import { Layout } from 'antd';
import { HashRouter as Router } from 'react-router-dom';
import { GithubProvider } from './contexts/Github';
@@ -12,7 +11,7 @@ const App: React.FC = () => {
<GithubProvider>
<EncryptionProvider>
<DecryptionProvider>
<Layout style={{minHeight:"100vh"}}>
<Layout style={{ minHeight: '100vh' }}>
<Router>
<AppRouter />
</Router>
@@ -23,4 +22,4 @@ const App: React.FC = () => {
);
};
export default hot(App);
export default App;

View File

@@ -1,9 +1,5 @@
import React from 'react';
import {
Routes,
Route,
useNavigate,
} from 'react-router-dom';
import { Routes, Route, useNavigate } from 'react-router-dom';
import { HomeFilled } from '@ant-design/icons';
import { Layout, Button, Space } from 'antd';
@@ -19,14 +15,18 @@ const AppRouter: React.FC = () => {
return (
<>
<Space>
<Button
onClick={() => navigate('/')}
icon={<HomeFilled />}
>
<Button onClick={() => navigate('/')} icon={<HomeFilled />}>
Home
</Button>
</Space>
<Layout.Content style={{ padding: '25px', maxWidth: '800px', width: '100%', margin: 'auto' }}>
<Layout.Content
style={{
padding: '25px',
maxWidth: '800px',
width: '100%',
margin: 'auto',
}}
>
<Routes>
<Route path="/debug" element={<Debug />} />
<Route path="/welcome" element={<Welcome />} />
@@ -38,6 +38,6 @@ const AppRouter: React.FC = () => {
</Layout.Content>
</>
);
}
};
export default AppRouter;

View File

@@ -1,9 +1,5 @@
import React from 'react';
import {
List,
Button,
Popconfirm,
} from 'antd';
import { List, Button, Popconfirm } from 'antd';
import {
DeleteOutlined,
SyncOutlined,
@@ -32,17 +28,10 @@ const icons: {[name: string]: any} = {
};
const IconText = ({ icon, text, ...props }) => (
<Button
{...props}
shape="round"
icon={React.createElement(icon)}
/>
<Button {...props} shape="round" icon={React.createElement(icon)} />
);
const FileView: React.FC<Props> = ({
file,
remove,
}) => {
const FileView: React.FC<Props> = ({ file, remove }) => {
const icon = icons[file.status];
const actions = [];
@@ -54,11 +43,7 @@ const FileView: React.FC<Props> = ({
okText="Yes"
cancelText="No"
>
<IconText
icon={DeleteOutlined}
danger
text="Delete"
/>
<IconText icon={DeleteOutlined} danger text="Delete" />
</Popconfirm>
);
}
@@ -76,14 +61,8 @@ const FileView: React.FC<Props> = ({
}
return (
<List.Item
actions={actions}
className="msg-item"
>
<List.Item.Meta
avatar={icon}
title={file.name}
/>
<List.Item actions={actions} className="msg-item">
<List.Item.Meta avatar={icon} title={file.name} />
</List.Item>
);
};

View File

@@ -10,25 +10,18 @@ interface Props {
deleteFile: (id: string) => void;
}
const Encrypt: React.FC<Props> = ({
files,
deleteFile,
}) => {
const Encrypt: React.FC<Props> = ({ files, deleteFile }) => {
const { status, downloadAll } = useDownloadAll();
if (Object.keys(files).length === 0) {
return <Empty />
return <Empty />;
}
return (
<Space direction="vertical" style={{ width: '100%' }}>
<List>
{Object.entries(files).map(([id, file]) => (
<File
key={id}
file={file}
remove={() => deleteFile(id)}
/>
<File key={id} file={file} remove={() => deleteFile(id)} />
))}
</List>
{downloadAll && (

View File

@@ -20,9 +20,12 @@ const DropWrapper = styled(Layout)`
const AddFile: React.FC = () => {
const { addFile } = useContext(DecryptionContext);
const onDrop = useCallback(acceptedFiles => {
const onDrop = useCallback(
(acceptedFiles) => {
acceptedFiles.forEach(addFile);
}, [addFile])
},
[addFile]
);
const { getRootProps, getInputProps } = useDropzone({ onDrop });
return (
<DropWrapper {...getRootProps()}>

View File

@@ -12,9 +12,16 @@ const Add: React.FC = () => {
return (
<>
<Divider>
<Radio.Group onChange={evt => setType(evt.target.value)} defaultValue={DEFAULT_VALUE}>
<Radio.Button className="add-text-tab" value="text"><FileTextOutlined /> Text</Radio.Button>
<Radio.Button className="add-file-tab" value="file"><FileOutlined /> File</Radio.Button>
<Radio.Group
onChange={(evt) => setType(evt.target.value)}
defaultValue={DEFAULT_VALUE}
>
<Radio.Button className="add-text-tab" value="text">
<FileTextOutlined /> Text
</Radio.Button>
<Radio.Button className="add-file-tab" value="file">
<FileOutlined /> File
</Radio.Button>
</Radio.Group>
</Divider>
{type === 'text' && <AddText />}

View File

@@ -20,9 +20,12 @@ const DropWrapper = styled(Layout)`
const AddFile: React.FC = () => {
const { addFile } = useContext(EncryptionContext);
const onDrop = useCallback(acceptedFiles => {
const onDrop = useCallback(
(acceptedFiles) => {
acceptedFiles.forEach(addFile);
}, [addFile])
},
[addFile]
);
const { getRootProps, getInputProps } = useDropzone({ onDrop });
return (
<DropWrapper {...getRootProps()}>

View File

@@ -21,7 +21,7 @@ const AddText : React.FC = () => {
placeholder="Title (Not encrypted)"
className="msg-title"
value={name}
onChange={evt => setName(evt.target.value)}
onChange={(evt) => setName(evt.target.value)}
/>
</Form.Item>
<Form.Item>
@@ -30,7 +30,7 @@ const AddText : React.FC = () => {
placeholder="Your message here..."
value={text}
rows={6}
onChange={evt => setText(evt.target.value)}
onChange={(evt) => setText(evt.target.value)}
/>
</Form.Item>
<Form.Item>
@@ -49,4 +49,3 @@ const AddText : React.FC = () => {
};
export default AddText;

View File

@@ -1,5 +1,19 @@
import React, { useState, useCallback, useContext, createContext, useEffect } from 'react';
import { readMessage, readKey, decrypt as pgpDecrypt, readPrivateKeys, readPrivateKey, generateKey } from 'openpgp';
import React, {
useState,
useCallback,
useContext,
createContext,
useEffect,
ReactNode,
} from 'react';
import {
readMessage,
readKey,
decrypt as pgpDecrypt,
readPrivateKeys,
readPrivateKey,
generateKey,
} from 'openpgp';
import GithubContext from './Github';
import FileType from '../types/File';
import { createFile } from '../helpers/files';
@@ -14,6 +28,10 @@ interface DecryptionContextType {
deleteFile: (id: string) => void;
}
type DecryptionProviderProps = {
children: ReactNode;
};
const removeExtension = (name: string) => {
const parts = name.split('.');
parts.pop();
@@ -24,21 +42,32 @@ const DecryptionContext = createContext<DecryptionContextType>({
publicKey: undefined,
privateKey: undefined,
files: {},
createKey: async () => { throw new Error('Not using provider'); },
deleteKey: async () => { throw new Error('Not using provider'); },
addFile: async () => { throw new Error('Not using provider'); },
deleteFile: async () => { throw new Error('Not using provider'); },
createKey: async () => {
throw new Error('Not using provider');
},
deleteKey: async () => {
throw new Error('Not using provider');
},
addFile: async () => {
throw new Error('Not using provider');
},
deleteFile: async () => {
throw new Error('Not using provider');
},
});
const decrypt = async (privateKey: string, keys: string[], content: string) => {
const armoredKeys = await Promise.all(
keys.map(key => readKey({ armoredKey: key })),
keys.map((key) => readKey({ armoredKey: key }))
);
const message = await readMessage({ armoredMessage: content });
const encrypted = await pgpDecrypt({
message,
decryptionKeys: await readPrivateKeys({ armoredKeys: privateKey }),
verificationKeys: armoredKeys.reduce<any>((output, key: any) => [...output, ...key], []),
verificationKeys: armoredKeys.reduce<any>(
(output, key: any) => [...output, ...key],
[]
),
});
const { data } = encrypted;
const blob = new Blob([data as any], {
@@ -47,7 +76,7 @@ const decrypt = async (privateKey: string, keys: string[], content: string) => {
return blob;
};
const DecryptionProvider: React.FC = ({
const DecryptionProvider: React.FC<DecryptionProviderProps> = ({
children,
}) => {
const { keys } = useContext(GithubContext);
@@ -55,12 +84,15 @@ const DecryptionProvider: React.FC = ({
const [publicKey, setPublicKey] = useState<string | undefined>(undefined);
const [files, setFiles] = useState<DecryptionContextType['files']>({});
const deleteFile = useCallback((id: string) => {
const deleteFile = useCallback(
(id: string) => {
delete files[id];
setFiles({
...files,
});
}, [files]);
},
[files]
);
useEffect(() => {
const run = async () => {
@@ -90,22 +122,27 @@ const DecryptionProvider: React.FC = ({
setPrivateKey(key.privateKey);
setPublicKey(key.publicKey);
localStorage.setItem('key', key.privateKey);
}
};
const addFile = useCallback(async (file: File) => {
if (!keys || !privateKey) return;
const addFile = useCallback(
async (file: File) => {
if (!keys || !privateKey) {
return;
}
const addedFile = createFile(setFiles, removeExtension(file.name));
const reader = new FileReader()
const reader = new FileReader();
reader.onabort = addedFile.setFailed,
reader.onerror = addedFile.setFailed,
reader.onload = () => {
(reader.onabort = addedFile.setFailed),
(reader.onerror = addedFile.setFailed),
(reader.onload = () => {
addedFile.setContent(
decrypt(privateKey, keys, reader.result as string),
decrypt(privateKey, keys, reader.result as string)
);
}
});
reader.readAsText(file);
}, [keys, privateKey]);
},
[keys, privateKey]
);
return (
<DecryptionContext.Provider
@@ -124,8 +161,6 @@ const DecryptionProvider: React.FC = ({
);
};
export {
DecryptionProvider,
};
export { DecryptionProvider };
export default DecryptionContext;;
export default DecryptionContext;

View File

@@ -1,4 +1,10 @@
import React, { useState, useCallback, useContext, createContext } from 'react';
import React, {
useState,
useCallback,
useContext,
createContext,
ReactNode,
} from 'react';
import * as openpgp from 'openpgp';
import GithubContext from './Github';
import { createFile } from '../helpers/files';
@@ -11,21 +17,34 @@ interface EncryptionContextType {
deleteFile: (id: string) => void;
}
type EncryptionProviderProps = {
children: ReactNode;
};
const EncryptionContext = createContext<EncryptionContextType>({
files: {},
addFile: async () => { throw new Error('Not using provider'); },
addText: async () => { throw new Error('Not using provider'); },
deleteFile: async () => { throw new Error('Not using provider'); },
addFile: async () => {
throw new Error('Not using provider');
},
addText: async () => {
throw new Error('Not using provider');
},
deleteFile: async () => {
throw new Error('Not using provider');
},
});
const encrypt = async (keys: string[], content: string) => {
const armoredKeys = await Promise.all(
keys.map(key => openpgp.readKeys({ armoredKeys: key })),
keys.map((key) => openpgp.readKeys({ armoredKeys: key }))
);
const message = await openpgp.createMessage({ text: content });
const encrypted = await openpgp.encrypt({
message,
encryptionKeys: armoredKeys.reduce<any>((output, key: any) => [...output, ...key], []),
encryptionKeys: armoredKeys.reduce<any>(
(output, key: any) => [...output, ...key],
[]
),
});
const data = encrypted;
const blob = new Blob([data as any], {
@@ -34,41 +53,50 @@ const encrypt = async (keys: string[], content: string) => {
return blob;
};
const EncryptionProvider: React.FC = ({
const EncryptionProvider: React.FC<EncryptionProviderProps> = ({
children,
}) => {
const { username, keys } = useContext(GithubContext);
const [files, setFiles] = useState<EncryptionContextType['files']>({});
const deleteFile = useCallback((id: string) => {
const deleteFile = useCallback(
(id: string) => {
delete files[id];
setFiles({
...files,
});
}, [files]);
const addFile = useCallback(async (file: File) => {
if (!keys) return;
const addedFile = createFile(setFiles, `${file.name}.acs`);
const reader = new FileReader()
reader.onabort = addedFile.setFailed,
reader.onerror = addedFile.setFailed,
reader.onload = () => {
addedFile.setContent(
encrypt(keys, reader.result as string),
},
[files]
);
const addFile = useCallback(
async (file: File) => {
if (!keys) {
return;
}
reader.readAsText(file)
}, [keys, username]);
const addedFile = createFile(setFiles, `${file.name}.acs`);
const reader = new FileReader();
const addText = useCallback(async (text: string, name: string) => {
if (!keys) return;
const file = createFile(setFiles, `${name}.txt.asc`);
file.setContent(
encrypt(keys, text),
(reader.onabort = addedFile.setFailed),
(reader.onerror = addedFile.setFailed),
(reader.onload = () => {
addedFile.setContent(encrypt(keys, reader.result as string));
});
reader.readAsText(file);
},
[keys, username]
);
const addText = useCallback(
async (text: string, name: string) => {
if (!keys) {
return;
}
const file = createFile(setFiles, `${name}.txt.asc`);
file.setContent(encrypt(keys, text));
},
[keys, username]
);
}, [keys, username]);
return (
<EncryptionContext.Provider
@@ -84,9 +112,6 @@ const EncryptionProvider: React.FC = ({
);
};
export {
EncryptionProvider,
};
export { EncryptionProvider };
export default EncryptionContext;

View File

@@ -1,4 +1,4 @@
import React, { createContext } from 'react';
import React, { createContext, ReactNode } from 'react';
declare var data: any;
@@ -7,19 +7,17 @@ interface GithubContextType {
keys?: string[];
}
type GithubProviderProps = {
children: ReactNode;
};
const GithubContext = createContext<GithubContextType>(data);
const GithubProvider: React.FC = ({
children,
}) => (
<GithubContext.Provider
value={{ ...data }}
>
const GithubProvider: React.FC<GithubProviderProps> = ({ children }) => (
<GithubContext.Provider value={{ ...data }}>
{children}
</GithubContext.Provider>
);
export {
GithubProvider,
};
export { GithubProvider };
export default GithubContext;

View File

@@ -38,7 +38,7 @@ export const createFile = (setFiles: SetFilesType, name: string) => {
}));
})
.catch(setFailed);
}
};
const setFailed = (err: any) => {
console.error(err);

View File

@@ -8,7 +8,8 @@ type Statuses = 'packing' | 'ready';
const useDownloadAll = () => {
const [status, setStatus] = useState<Statuses>('ready');
const { files } = useContext(EncryptionContext);
const allFilesReady = Object.values(files).filter(f => f.status === 'success').length > 1;
const allFilesReady =
Object.values(files).filter((f) => f.status === 'success').length > 1;
const downloadAll = useCallback(() => {
setStatus('packing');

View File

@@ -1,24 +1,25 @@
import React, { useMemo } from 'react';
import {
Table,
} from 'antd';
import { Table } from 'antd';
import config from '../config';
const columns = [{
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
}, {
},
{
title: 'Value',
dataIndex: 'value',
key: 'value',
}];
},
];
const Debug: React.FC = () => {
const data = useMemo(() => {
const vals = {
'Repository': config.repo,
'User': config.user,
Repository: config.repo,
User: config.user,
'Is Production': config.isProd,
};
@@ -29,9 +30,7 @@ const Debug: React.FC = () => {
}));
}, []);
return (
<Table dataSource={data} columns={columns} />
);
return <Table dataSource={data} columns={columns} />;
};
export default Debug;

View File

@@ -13,10 +13,7 @@ const Decrypt: React.FC = () => {
{Object.keys(files).length > 0 && (
<>
<Divider>Files</Divider>
<FileList
files={files}
deleteFile={deleteFile}
/>
<FileList files={files} deleteFile={deleteFile} />
</>
)}
</>

View File

@@ -13,13 +13,18 @@ const Encrypt: React.FC = () => {
{Object.keys(files).length > 0 && (
<>
<Divider>Files</Divider>
<FileList
files={files}
deleteFile={deleteFile}
/>
<FileList files={files} deleteFile={deleteFile} />
<Divider />
<i style={{ textAlign: 'center', paddingTop: '10px', display: 'block', fontSize: 12 }}>
Note: files are not send to me, you still have to download the encrypted files and send it to me.
<i
style={{
textAlign: 'center',
paddingTop: '10px',
display: 'block',
fontSize: 12,
}}
>
Note: files are not send to me, you still have to download the
encrypted files and send it to me.
</i>
</>
)}

View File

@@ -1,23 +1,14 @@
import React from 'react';
import { useNavigate } from 'react-router';
import Welcome from './Welcome';
import {
Button,
Space,
} from 'antd';
import { Button, Space } from 'antd';
import {
UploadOutlined,
DownloadOutlined,
KeyOutlined,
} from '@ant-design/icons';
const Thumb: React.FC = ({
title,
Icon,
link,
className,
}) => {
const Thumb: React.FC = ({ title, Icon, link, className }) => {
const navigate = useNavigate();
return (
<Button

View File

@@ -10,11 +10,7 @@ import {
} from '@ant-design/icons';
const SetupKey: React.FC = () => {
const {
createKey,
deleteKey,
publicKey,
} = useContext(DecryptionContext);
const { createKey, deleteKey, publicKey } = useContext(DecryptionContext);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
@@ -37,11 +33,11 @@ const SetupKey: React.FC = () => {
<LockTwoTone style={{ fontSize: 150 }} />
<Typography.Title>Create your sharing key</Typography.Title>
<p>
Before I can send protected information to you I need a "sharing" key, which is a key that gets stored this device, allowing this device (and this device only) to read the informations I am sending.
</p>
<p>
After creating it you need to send it to me
Before I can send protected information to you I need a "sharing"
key, which is a key that gets stored this device, allowing this
device (and this device only) to read the informations I am sending.
</p>
<p>After creating it you need to send it to me</p>
</Space>
<Form>
<Form.Item>
@@ -50,7 +46,7 @@ const SetupKey: React.FC = () => {
size="large"
prefix={<UserOutlined />}
value={name}
onChange={evt => setName(evt.target.value)}
onChange={(evt) => setName(evt.target.value)}
/>
</Form.Item>
<Form.Item>
@@ -59,7 +55,7 @@ const SetupKey: React.FC = () => {
size="large"
prefix={<MailOutlined />}
value={email}
onChange={evt => setEmail(evt.target.value)}
onChange={(evt) => setEmail(evt.target.value)}
/>
</Form.Item>
<Form.Item style={{ textAlign: 'center' }}>
@@ -81,11 +77,10 @@ const SetupKey: React.FC = () => {
<div style={{ textAlign: 'center' }}>
<RocketTwoTone style={{ fontSize: 150 }} />
<Typography.Title>Okay, you are all set.</Typography.Title>
<p>Just send me your sharing key, and I will send files using it.</p>
<p>
Just send me your sharing key, and I will send files using it.
</p>
<p>
Remember that you need to go to this website on this device to decrypt the files after receiving them
Remember that you need to go to this website on this device to decrypt
the files after receiving them
</p>
<Space direction="vertical" size="large">
<Button
@@ -96,15 +91,8 @@ const SetupKey: React.FC = () => {
>
Download sharing key
</Button>
<Popconfirm
title="Are you sure?"
onConfirm={deleteKey}
>
<Button
danger
size="small"
type="link"
>
<Popconfirm title="Are you sure?" onConfirm={deleteKey}>
<Button danger size="small" type="link">
Delete sharing key
</Button>
</Popconfirm>

View File

@@ -8,15 +8,16 @@ const Welcome: React.FC = () => {
<Space direction="vertical">
<EyeInvisibleTwoTone style={{ fontSize: 200 }} />
<Typography.Title level={1}>Protect before sending</Typography.Title>
<p>The internet can seem like a scary place...</p>
<p>
The internet can seem like a scary place...
Especially because a lot of the tools we use everyday (such as e-mail)
wasn't build for the internet that we have today. This is why it is
important to have an additional layer of security when sending
sensitive information.
</p>
<p>
Especially because a lot of the tools we use everyday (such as e-mail) wasn't build for the internet that we have today.
This is why it is important to have an additional layer of security when sending sensitive information.
</p>
<p>
This is a tool that will help you have that extra layer of security when sharing files with me.
This is a tool that will help you have that extra layer of security
when sharing files with me.
</p>
</Space>
</Layout>

View File

@@ -10,7 +10,7 @@ interface FileProcessing extends FileBase {
}
interface FileFailed extends FileBase {
status: 'failed',
status: 'failed';
error: any;
}

View File

@@ -1,51 +0,0 @@
const NodeEnvironment = require('jest-environment-node');
const { Server, createServer } = require('http');
const getPort = require('get-port');
const webpack = require('webpack');
const path = require('path');
const express = require('express');
const { default: createConfig } = require('../webpack.config');
const build = () => new Promise(async (resolve, reject) => {
const config = await createConfig({
test: true,
});
const port = await getPort();
const bundler = webpack(config);
bundler.run((err, stats) => {
if (err || !stats) {
return reject(err);
} else if (stats.hasErrors()) {
return reject(new Error('Webpack errors'));
}
const app = express();
app.use(express.static(path.join(__dirname, '..', 'dist')));
const server = createServer(app);
const listener = server.listen(port, '127.0.0.1', () => {
resolve(listener);
});
});
});
class CustomEnvironment extends NodeEnvironment {
constructor(config) {
super(config);
}
async setup() {
await super.setup();
this._server = await build();
const address = this._server.address();
this.global.testUrl = `http://${address.address}:${address.port}`
}
async teardown() {
await super.teardown();
if (!this._server) {
return;
}
this._server.close();
}
}
module.exports = CustomEnvironment;

View File

@@ -1,4 +1,51 @@
require('ts-node/register');
const Env = require('./env-ts');
const NodeEnvironment = require('jest-environment-node');
const { Server, createServer } = require('http');
const webpack = require('webpack');
const path = require('path');
const express = require('express');
const { default: createConfig } = require('../webpack.config');
module.exports = Env;
const build = () => new Promise(async (resolve, reject) => {
const { default: getPort } = await import('get-port');
const config = await createConfig({
test: true,
});
const port = await getPort();
const bundler = webpack(config);
bundler.run((err, stats) => {
if (err || !stats) {
return reject(err);
} else if (stats.hasErrors()) {
return reject(new Error('Webpack errors'));
}
const app = express();
app.use(express.static(path.join(__dirname, '..', 'dist')));
const server = createServer(app);
const listener = server.listen(port, '127.0.0.1', () => {
resolve(listener);
});
});
});
class CustomEnvironment extends NodeEnvironment {
constructor(config) {
super(config);
}
async setup() {
await super.setup();
this._server = await build();
const address = this._server.address();
this.global.testUrl = `http://${address.address}:${address.port}`
}
async teardown() {
await super.teardown();
if (!this._server) {
return;
}
this._server.close();
}
}
module.exports = CustomEnvironment;

View File

@@ -4,6 +4,7 @@ import axios from 'axios';
import fs from 'fs';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import WorkboxWebpackPlugin from 'workbox-webpack-plugin';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import path from 'path';
interface Options {
@@ -19,7 +20,9 @@ const [username] = repo.split('/');
const __DEV__ = process.env.NODE_ENV !== 'production';
const getData = async () => {
const { data: keyList } = await axios.get(`https://api.github.com/users/${username}/gpg_keys`);
const { data: keyList } = await axios.get(
`https://api.github.com/users/${username}/gpg_keys`
);
if (keyList.length === 0) {
throw new Error(`The user ${username} does not have any GPG keys`);
}
@@ -32,26 +35,26 @@ const getData = async () => {
};
const getTestData: typeof getData = async () => {
const pubKey = fs.readFileSync(path.join(__dirname, 'test-assets', 'key.pub'), 'utf-8');
const pubKey = fs.readFileSync(
path.join(__dirname, 'test-assets', 'key.pub'),
'utf-8'
);
return {
username: 'foobar',
keys: [
pubKey,
],
keys: [pubKey],
};
};
const createConfig = async (options: Options = {
const createConfig = async (
options: Options = {
test: false,
}):Promise<Configuration> => {
}
): Promise<Configuration> => {
const data = await (options.test ? getTestData() : getData());
const config: Configuration = {
mode: __DEV__ ? 'development' : 'production',
entry: {
app: [
...(__DEV__ ? ['react-hot-loader/patch'] : []),
path.join(__dirname, 'src', 'index.tsx'),
],
app: [path.join(__dirname, 'src', 'index.tsx')],
},
output: {
path: path.join(__dirname, 'dist'),
@@ -59,9 +62,6 @@ const createConfig = async (options: Options = {
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'react-dom': '@hot-loader/react-dom',
},
},
plugins: [
new webpack.DefinePlugin({
@@ -73,20 +73,27 @@ const createConfig = async (options: Options = {
minify: true,
template: path.join(__dirname, 'html.html'),
}),
...(__DEV__
? [new ReactRefreshWebpackPlugin()]
: [
new WorkboxWebpackPlugin.GenerateSW({
swDest: 'sw.js',
clientsClaim: true,
skipWaiting: true,
}),
]),
],
module: {
rules: [{
rules: [
{
test: /\.tsx?$/,
use: ['babel-loader'],
}, {
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
}],
},
],
},
};

25125
yarn.lock

File diff suppressed because it is too large Load Diff