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';
interface Props {
name: string;
type: string;
src: string;
message: any;
}
const Wrapper = styled.div`
@@ -16,7 +14,8 @@ const Wrapper = styled.div`
`;
const Preview = styled.div`
flex: 1
flex: 1;
background: #eee;
`;
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 link = document.createElement("a");
link.download = name;
link.href = src;
link.download = content.name;
link.href = content.body;
document.body.appendChild(link);
link.click();
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 (
<Wrapper onClick={download}>
<Preview>
{getPreview(type, src)}
{getPreview(content.type, content.body)}
</Preview>
<Meta>
{name}
{content.name}
</Meta>
</Wrapper>
);

View File

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

View File

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

View File

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