This commit is contained in:
Morten Olsen
2021-06-09 16:06:49 +02:00
parent b8640fbefa
commit 7c6c1d3489
6 changed files with 195 additions and 36 deletions

View File

@@ -2,9 +2,7 @@ import React, { useCallback } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
interface Props { interface Props {
name: string; message: any;
type: string;
src: string;
} }
const Wrapper = styled.div` const Wrapper = styled.div`
@@ -16,7 +14,8 @@ const Wrapper = styled.div`
`; `;
const Preview = styled.div` const Preview = styled.div`
flex: 1 flex: 1;
background: #eee;
`; `;
const Meta = styled.div` const Meta = styled.div`
@@ -45,23 +44,34 @@ const getPreview = (type: string, src: string) => {
); );
}; };
const File: React.FC<Props> = ({ name, type, src }) => { const File: React.FC<Props> = ({ message }) => {
const { content } = message;
const download = useCallback(() => { const download = useCallback(() => {
const link = document.createElement("a"); const link = document.createElement("a");
link.download = name; link.download = content.name;
link.href = src; link.href = content.body;
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
}, [src]); }, [content?.body, content?.name]);
if (message.type === 'incomplete') {
return (
<Wrapper>
<Preview />
<Meta>
{Math.round(message.current / message.length * 100)}%
</Meta>
</Wrapper>
);
}
return ( return (
<Wrapper onClick={download}> <Wrapper onClick={download}>
<Preview> <Preview>
{getPreview(type, src)} {getPreview(content.type, content.body)}
</Preview> </Preview>
<Meta> <Meta>
{name} {content.name}
</Meta> </Meta>
</Wrapper> </Wrapper>
); );

View File

@@ -8,6 +8,7 @@ const readFile = (file: File) => new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = () => {
resolve({ resolve({
mediaType: 'file',
name: file.name, name: file.name,
type: file.type, type: file.type,
body: reader.result, body: reader.result,
@@ -45,7 +46,7 @@ const Connected: React.FC<{}> = () => {
</div> </div>
<FileGrid> <FileGrid>
{messages.map((message) => ( {messages.map((message) => (
<FileView name={message.name} type={message.type} src={message.body} /> <FileView message={message} />
))} ))}
</FileGrid> </FileGrid>
</> </>

View File

@@ -46,7 +46,6 @@ const Welcome: React.FC<{}> = () => {
text.select(); text.select();
let successful = document.execCommand('copy'); let successful = document.execCommand('copy');
let msg = successful ? 'successful' : 'unsuccessful'; let msg = successful ? 'successful' : 'unsuccessful';
alert('Copy text command was ' + msg);
}, [linkRef]); }, [linkRef]);
useEffect(() => { useEffect(() => {

View File

@@ -2,6 +2,7 @@ import React, { createContext, useMemo, useState, useCallback, useEffect } from
import Peer, { DataConnection } from 'peerjs'; import Peer, { DataConnection } from 'peerjs';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import useCrypto from '../hooks/useCrypto'; import useCrypto from '../hooks/useCrypto';
import useMessages from '../hooks/useMessages';
enum States { enum States {
WAITING, WAITING,
@@ -36,13 +37,24 @@ function dataURItoBlob(dataURI: string) {
const ConnectionContext = createContext<ConnectionContextValue>(undefined as any); const ConnectionContext = createContext<ConnectionContextValue>(undefined as any);
const postProcess = (input: any) => {
if (input.mediaType === 'file') {
return {
...input,
body: dataURItoBlob(input.body),
};
}
return input;
};
const ConnectionProvider: React.FC = ({ children }) => { const ConnectionProvider: React.FC = ({ children }) => {
const { messages, addMessage, formatMessage } = useMessages(postProcess);
const [secret, setSecret] = useState(nanoid()); const [secret, setSecret] = useState(nanoid());
const { encrypt, decrypt } = useCrypto(secret); const { encrypt, decrypt } = useCrypto(secret);
const id = useMemo(() => nanoid(), []); const id = useMemo(() => nanoid(), []);
const peer = useMemo(() => new Peer(id), [id]); const peer = useMemo(() => new Peer(id), [id]);
const [connection, setConnection] = useState<DataConnection | undefined>(undefined); const [connection, setConnection] = useState<DataConnection | undefined>(undefined);
const [messages, setMessages] = useState<Message[]>([]);
const [state, setState] = useState<States>(States.WAITING); const [state, setState] = useState<States>(States.WAITING);
const clientInfo = useMemo(() => ({ const clientInfo = useMemo(() => ({
id, id,
@@ -51,14 +63,14 @@ const ConnectionProvider: React.FC = ({ children }) => {
const send = useCallback(async (message: any) => { const send = useCallback(async (message: any) => {
if (!connection) return; if (!connection) return;
setMessages(current => [ const { startMsg, updateMsgs } = formatMessage(message);
...current,
{ addMessage(startMsg);
...message, connection.send(await encrypt(startMsg));
body: dataURItoBlob(message.body), for (let updateMsg of updateMsgs) {
}, connection.send(await encrypt(updateMsg));
]); addMessage(updateMsg);
connection.send(await encrypt(message)); }
}, [connection, encrypt]); }, [connection, encrypt]);
const connect = useCallback(async (clientInfo: any) => { const connect = useCallback(async (clientInfo: any) => {
@@ -68,22 +80,23 @@ const ConnectionProvider: React.FC = ({ children }) => {
setSecret(clientInfo.secret); setSecret(clientInfo.secret);
setState(States.CONNECTED); setState(States.CONNECTED);
setConnection(newConnection); setConnection(newConnection);
console.log('connected', newConnection);
}); });
}, [peer]); }, [peer]);
useEffect(() => { useEffect(() => {
if (connection) {
return;
}
const onConnect = (newConnection: DataConnection) => { const onConnect = (newConnection: DataConnection) => {
setState(States.CONNECTED); setState(States.CONNECTED);
setConnection(newConnection); setConnection(newConnection);
console.log('connected', newConnection);
}; };
peer.on('connection', onConnect); peer.on('connection', onConnect);
return () => { return () => {
peer.off('connection', onConnect); peer.off('connection', onConnect);
}; };
}, [peer]); }, [peer, connection]);
useEffect(() => { useEffect(() => {
if (!connection) { if (!connection) {
@@ -91,13 +104,7 @@ const ConnectionProvider: React.FC = ({ children }) => {
} }
const handleData = async (encrypted: any) => { const handleData = async (encrypted: any) => {
const message = await decrypt(encrypted); const message = await decrypt(encrypted);
setMessages(current => [ addMessage(message);
...current,
{
...message,
body: dataURItoBlob(message.body),
},
]);
}; };
connection.on('data', handleData); connection.on('data', handleData);
return () => { return () => {

147
src/hooks/useMessages.ts Normal file
View File

@@ -0,0 +1,147 @@
import { nanoid } from 'nanoid';
import { useState, useCallback } from 'react';
interface BaseRequest {
type: string;
}
interface StartRequest extends BaseRequest {
type: 'start-message';
payload: {
id: string;
length: number;
};
}
interface UpdateRequest extends BaseRequest {
type: 'update-message';
payload: {
id: string;
index: number;
part: any;
};
}
interface BaseMessage {
id: string;
type: string;
}
interface IncompleteMessage extends BaseMessage {
type: 'incomplete';
length: number;
current: number;
parts: any[];
}
interface CompleteMessage extends BaseMessage {
type: 'complete';
content: any;
}
type Message = CompleteMessage | IncompleteMessage;
type Request = StartRequest | UpdateRequest;
function chunkSubstr(str: string, size: number) {
const numChunks = Math.ceil(str.length / size)
const chunks = new Array(numChunks)
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = str.substr(o, size)
}
return chunks
}
const updateMessage = (
message: Message,
request: UpdateRequest,
postProcess: (a: any) => any,
): Message => {
if (message.type === 'complete') {
return message;
}
const parts = [...message.parts];
parts[request.payload.index] = request.payload.part;
message.current += 1;
if (message.current === message.length) {
console.log('data', JSON.parse(parts.join('')));
return {
id: message.id,
type: 'complete',
content: postProcess(JSON.parse(parts.join(''))),
};
}
return {
...message,
parts,
};
};
const useMessages = (postProcess: (input: any) => any) => {
const [messages, setMessage] = useState<Message[]>([]);
const addMessage = useCallback((request: Request) => {
setMessage((current) => {
if (request.type === 'start-message') {
const message: IncompleteMessage = {
id: request.payload.id,
type: 'incomplete',
length: request.payload.length,
current: 0,
parts: [],
};
return [
...current,
message,
];
}
if (request.type === 'update-message') {
return current.map(message => {
if (message.id !== request.payload.id) {
return message;
}
return updateMessage(message, request, postProcess);
});
}
return current;
});
}, []);
const formatMessage = (msg: any) => {
const dataString = JSON.stringify(msg);
const parts = chunkSubstr(dataString, 100000);
const id = nanoid();
const startMsg: StartRequest = {
type: 'start-message',
payload: {
length: parts.length,
id,
},
};
const updateMsgs = parts.map<UpdateRequest>((part, index) => ({
type: 'update-message',
payload: {
id,
index,
part,
},
}));
return {
startMsg,
updateMsgs,
};
};
return {
messages,
addMessage,
formatMessage,
}
};
export default useMessages;

View File

@@ -9,11 +9,6 @@ function chunkSubstr(str: string, size: number) {
return chunks return chunks
} }
const buff_to_base64 = (buff) => btoa(String.fromCharCode.apply(null, buff));
const base64_to_buf = (b64) =>
Uint8Array.from(atob(b64), (c) => c.charCodeAt(null));
const enc = new TextEncoder(); const enc = new TextEncoder();
const dec = new TextDecoder(); const dec = new TextDecoder();