mirror of
https://github.com/morten-olsen/catpic.delivery.git
synced 2026-02-08 01:46:26 +01:00
Multi session support
This commit is contained in:
30
src/App.tsx
30
src/App.tsx
@@ -1,20 +1,32 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import Welcome from './containers/Welcome';
|
import Welcome from './containers/Welcome';
|
||||||
import Connected from './containers/Connected';
|
import Connected from './containers/Connected';
|
||||||
import useConnection, { ConnectionStates } from './hooks/useConnection';
|
import { SessionProvider } from './contexts/SessionContext';
|
||||||
|
import useSessions from './hooks/useSessions';
|
||||||
|
import Session from './containers/Session';
|
||||||
|
|
||||||
const App: React.FC<{}> = () => {
|
const App: React.FC<{}> = () => {
|
||||||
const { state } = useConnection();
|
const { sessions, addSession } = useSessions();
|
||||||
|
|
||||||
if (state === ConnectionStates.WAITING) {
|
useEffect(() => {
|
||||||
return <Welcome />
|
if (sessions.length === 0) {
|
||||||
|
addSession()
|
||||||
}
|
}
|
||||||
if (state === ConnectionStates.CONNECTED) {
|
}, [sessions.length]);
|
||||||
return <Connected />
|
|
||||||
|
if (sessions.length === 0) {
|
||||||
|
return <div>Setting up</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>Connected</div>
|
<>
|
||||||
);
|
{sessions.map((session) => (
|
||||||
|
<SessionProvider session={session}>
|
||||||
|
<Session />
|
||||||
|
</SessionProvider>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
30
src/Crypto/index.tsx
Normal file
30
src/Crypto/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { encrypt, decrypt } from '../utils/crypto';
|
||||||
|
|
||||||
|
class Crypto {
|
||||||
|
#secret: string;
|
||||||
|
#ticket?: Symbol;
|
||||||
|
|
||||||
|
constructor(secret: string, ticket?: Symbol) {
|
||||||
|
this.#secret = secret;
|
||||||
|
this.#ticket = ticket
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt = async (data: any) => {
|
||||||
|
const raw = JSON.stringify(data);
|
||||||
|
const result = await encrypt(raw, this.#secret);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
decrypt = async (data: any) => {
|
||||||
|
return decrypt(data, this.#secret);
|
||||||
|
};
|
||||||
|
|
||||||
|
getSecret(ticket: Symbol) {
|
||||||
|
if (!this.#ticket || this.#ticket !== ticket) {
|
||||||
|
throw new Error('Ticket not valid');
|
||||||
|
}
|
||||||
|
return this.#secret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Crypto;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useState, useCallback } from 'react';
|
|
||||||
|
|
||||||
interface BaseRequest {
|
interface BaseRequest {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -82,11 +82,20 @@ const updateMessage = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const useMessages = (postProcess: (input: any) => any) => {
|
class MessageList extends EventEmitter {
|
||||||
const [messages, setMessage] = useState<Message[]>([]);
|
#messages: Message[] = [];
|
||||||
|
#postProcess: (a: any) => any;
|
||||||
|
|
||||||
const addMessage = useCallback((request: Request, self: boolean) => {
|
constructor(postProces: (a: any) => any = (a) => a) {
|
||||||
setMessage((current) => {
|
super();
|
||||||
|
this.#postProcess = postProces;
|
||||||
|
}
|
||||||
|
|
||||||
|
get list() {
|
||||||
|
return this.#messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessage = (request: Request, self: boolean) => {
|
||||||
if (request.type === 'start-message') {
|
if (request.type === 'start-message') {
|
||||||
const message: IncompleteMessage = {
|
const message: IncompleteMessage = {
|
||||||
id: request.payload.id,
|
id: request.payload.id,
|
||||||
@@ -96,26 +105,25 @@ const useMessages = (postProcess: (input: any) => any) => {
|
|||||||
current: 0,
|
current: 0,
|
||||||
parts: [],
|
parts: [],
|
||||||
};
|
};
|
||||||
return [
|
this.#messages = [
|
||||||
...current,
|
...this.#messages,
|
||||||
message,
|
message,
|
||||||
];
|
];
|
||||||
|
this.emit('updated');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.type === 'update-message') {
|
if (request.type === 'update-message') {
|
||||||
return current.map(message => {
|
this.#messages = this.#messages.map(message => {
|
||||||
if (message.id !== request.payload.id) {
|
if (message.id !== request.payload.id) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
return updateMessage(message, request, postProcess);
|
return updateMessage(message, request, this.#postProcess);
|
||||||
});
|
});
|
||||||
|
this.emit('updated');
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return current;
|
formatMessage = (msg: any) => {
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const formatMessage = (msg: any) => {
|
|
||||||
const dataString = JSON.stringify(msg);
|
const dataString = JSON.stringify(msg);
|
||||||
const parts = chunkSubstr(dataString, 100000);
|
const parts = chunkSubstr(dataString, 100000);
|
||||||
const id = nanoid();
|
const id = nanoid();
|
||||||
@@ -139,12 +147,6 @@ const useMessages = (postProcess: (input: any) => any) => {
|
|||||||
updateMsgs,
|
updateMsgs,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
addMessage,
|
|
||||||
formatMessage,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useMessages;
|
export default MessageList;
|
||||||
121
src/Session/index.tsx
Normal file
121
src/Session/index.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import Peer, { DataConnection } from 'peerjs';
|
||||||
|
import Crypto from '../Crypto';
|
||||||
|
import MessageList from '../MessageList';
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
READY,
|
||||||
|
CONNECTING,
|
||||||
|
CONNECTED,
|
||||||
|
DISCONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Session extends EventEmitter {
|
||||||
|
#cryptoTicket = Symbol('crypto-ticket');
|
||||||
|
#name: string;
|
||||||
|
#peer: Peer;
|
||||||
|
#connection?: DataConnection;
|
||||||
|
#crypto: Crypto;
|
||||||
|
#messages: MessageList = new MessageList();
|
||||||
|
#state: State = State.READY;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
name: string = 'unnamed',
|
||||||
|
id: string = nanoid(),
|
||||||
|
secret: string = nanoid(),
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.#name = name;
|
||||||
|
this.#peer = new Peer(id);
|
||||||
|
this.#crypto = new Crypto(secret, this.#cryptoTicket);
|
||||||
|
this.#peer.on('connection', this.#handleConnection);
|
||||||
|
this.#messages.on('updated', () => {
|
||||||
|
this.emit('updated');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleConnection = (connection: DataConnection) => {
|
||||||
|
if (this.#connection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#connection = connection;
|
||||||
|
this.#connection.on('data', this.#handleData);
|
||||||
|
this.#connection.on('close', this.#handleDisconnect);
|
||||||
|
this.#connection.on('error', this.#handleDisconnect);
|
||||||
|
this.#state = State.CONNECTED;
|
||||||
|
this.emit('updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleData = async (encrypted: any) => {
|
||||||
|
const message = await this.#crypto.decrypt(encrypted);
|
||||||
|
this.#messages.addMessage(message, false);
|
||||||
|
console.log('foo', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
#reconnect = () => {
|
||||||
|
if (!this.#connection) return;
|
||||||
|
const id = this.#connection.peer;
|
||||||
|
const secret = this.#crypto.getSecret(this.#cryptoTicket);
|
||||||
|
// TODO: Add reconnect functionality
|
||||||
|
}
|
||||||
|
|
||||||
|
#handleDisconnect = () => {
|
||||||
|
this.#state = State.DISCONNECTED;
|
||||||
|
this.emit('updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.#peer.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.#name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get messages() {
|
||||||
|
return this.#messages.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
get state() {
|
||||||
|
return this.#state;
|
||||||
|
}
|
||||||
|
|
||||||
|
get connectInfo() {
|
||||||
|
return {
|
||||||
|
id: this.#peer.id,
|
||||||
|
secret: this.#crypto.getSecret(this.#cryptoTicket),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect = (id: string, secret: string) => {
|
||||||
|
this.#state = State.CONNECTING;
|
||||||
|
this.#crypto = new Crypto(secret, this.#cryptoTicket);
|
||||||
|
this.#connection = this.#peer.connect(id);
|
||||||
|
this.#connection.on('close', this.#handleDisconnect);
|
||||||
|
this.#connection.on('error', this.#handleDisconnect);
|
||||||
|
this.emit('updated');
|
||||||
|
this.#connection.on('open', () => {
|
||||||
|
this.#state = State.CONNECTED;
|
||||||
|
this.emit('updated');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
send = async (data: any) => {
|
||||||
|
if (!this.#connection) {
|
||||||
|
throw new Error('Not connected');
|
||||||
|
}
|
||||||
|
const { startMsg, updateMsgs } = this.#messages.formatMessage(data);
|
||||||
|
|
||||||
|
this.#messages.addMessage(startMsg, true);
|
||||||
|
this.#connection.send(await this.#crypto.encrypt(startMsg));
|
||||||
|
for (let updateMsg of updateMsgs) {
|
||||||
|
this.#connection.send(await this.#crypto.encrypt(updateMsg));
|
||||||
|
this.#messages.addMessage(updateMsg, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { State };
|
||||||
|
|
||||||
|
export default Session;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import useConnection from '../hooks/useConnection';
|
import useSession, { State } from '../hooks/useSession';
|
||||||
import Message from '../components/Message';
|
import Message from '../components/Message';
|
||||||
import ComposeMessage from '../types/ComposeMessage';
|
import ComposeMessage from '../types/ComposeMessage';
|
||||||
import ComposeBar from '../components/ComposeBar';
|
import ComposeBar from '../components/ComposeBar';
|
||||||
@@ -22,7 +22,7 @@ const Loading = styled.div`
|
|||||||
|
|
||||||
|
|
||||||
const Connected: React.FC<{}> = () => {
|
const Connected: React.FC<{}> = () => {
|
||||||
const { send, messages } = useConnection();
|
const { send, messages, state } = useSession();
|
||||||
const [currentMessage, setCurrentMessage] = useState<ComposeMessage>({
|
const [currentMessage, setCurrentMessage] = useState<ComposeMessage>({
|
||||||
files: [],
|
files: [],
|
||||||
text: '',
|
text: '',
|
||||||
@@ -48,7 +48,9 @@ const Connected: React.FC<{}> = () => {
|
|||||||
<Loading>Loading {Math.round(message.current / message.length * 100)}%</Loading>
|
<Loading>Loading {Math.round(message.current / message.length * 100)}%</Loading>
|
||||||
)))}
|
)))}
|
||||||
</MessageList>
|
</MessageList>
|
||||||
|
{ state === State.CONNECTED && (
|
||||||
<ComposeBar onSend={onSend} message={currentMessage} setMessage={setCurrentMessage} />
|
<ComposeBar onSend={onSend} message={currentMessage} setMessage={setCurrentMessage} />
|
||||||
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/containers/Session.tsx
Normal file
24
src/containers/Session.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import useSession, { State } from '../hooks/useSession';
|
||||||
|
import Welcome from './Welcome';
|
||||||
|
import Connected from './Connected';
|
||||||
|
|
||||||
|
const Session: React.FC<{}> = () => {
|
||||||
|
const { state } = useSession();
|
||||||
|
|
||||||
|
if (state === State.READY) {
|
||||||
|
return (
|
||||||
|
<Welcome />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === State.CONNECTED) {
|
||||||
|
return <Connected />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>{state.toString()}</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Session;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import QRCode from 'react-qr-code';
|
import QRCode from 'react-qr-code';
|
||||||
import QRReader from 'react-qr-reader'
|
import QRReader from 'react-qr-reader'
|
||||||
import useConnection from '../hooks/useConnection';
|
import useSession from '../hooks/useSession';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -32,14 +32,15 @@ const Content = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Welcome: React.FC<{}> = () => {
|
const Welcome: React.FC<{}> = () => {
|
||||||
const { connect, clientInfo } = useConnection();
|
const { connect, connectInfo } = useSession();
|
||||||
const [mode, setMode] = useState<'view' | 'scan'>('view');
|
const [mode, setMode] = useState<'view' | 'scan'>('view');
|
||||||
|
|
||||||
const onScan = useCallback(
|
const onScan = useCallback(
|
||||||
(result) => {
|
(result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
setMode('view');
|
setMode('view');
|
||||||
connect(JSON.parse(result));
|
const { id, secret } = JSON.parse(result);
|
||||||
|
connect(id, secret);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
@@ -52,9 +53,10 @@ const Welcome: React.FC<{}> = () => {
|
|||||||
<Button active={mode==='scan'} onClick={() => setMode('scan')}>Scan</Button>
|
<Button active={mode==='scan'} onClick={() => setMode('scan')}>Scan</Button>
|
||||||
</Header>
|
</Header>
|
||||||
<Content>
|
<Content>
|
||||||
|
{JSON.stringify(connectInfo)}
|
||||||
{mode === 'view' && (
|
{mode === 'view' && (
|
||||||
<QRCode
|
<QRCode
|
||||||
value={JSON.stringify(clientInfo)}
|
value={JSON.stringify(connectInfo)}
|
||||||
size={300}
|
size={300}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
import React, { createContext, useMemo, useState, useCallback, useEffect } from 'react';
|
|
||||||
import Peer, { DataConnection } from 'peerjs';
|
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import useCrypto from '../hooks/useCrypto';
|
|
||||||
import useMessages from '../hooks/useMessages';
|
|
||||||
|
|
||||||
enum States {
|
|
||||||
WAITING,
|
|
||||||
CONNECTING,
|
|
||||||
CONNECTED,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ConnectionContextValue {
|
|
||||||
clientInfo: any;
|
|
||||||
state: States;
|
|
||||||
messages: any[];
|
|
||||||
send: (message: any) => Promise<void>;
|
|
||||||
connect: (connectionInfo: any) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function dataURItoBlob(dataURI: string) {
|
|
||||||
var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
|
|
||||||
var binary = atob(dataURI.split(',')[1]);
|
|
||||||
var array = [];
|
|
||||||
for (var i = 0; i < binary.length; i++) {
|
|
||||||
array.push(binary.charCodeAt(i));
|
|
||||||
}
|
|
||||||
const blob = new Blob([new Uint8Array(array)], {type: mime});
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 [state, setState] = useState<States>(States.WAITING);
|
|
||||||
const clientInfo = useMemo(() => ({
|
|
||||||
id,
|
|
||||||
secret,
|
|
||||||
}), [id]);
|
|
||||||
|
|
||||||
const send = useCallback(async (message: any) => {
|
|
||||||
if (!connection) return;
|
|
||||||
const { startMsg, updateMsgs } = formatMessage(message);
|
|
||||||
|
|
||||||
addMessage(startMsg, true);
|
|
||||||
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) => {
|
|
||||||
setState(States.CONNECTING);
|
|
||||||
const newConnection = peer.connect(clientInfo.id);
|
|
||||||
newConnection.on('open', () => {
|
|
||||||
setSecret(clientInfo.secret);
|
|
||||||
setState(States.CONNECTED);
|
|
||||||
setConnection(newConnection);
|
|
||||||
});
|
|
||||||
}, [peer]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (connection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const onConnect = (newConnection: DataConnection) => {
|
|
||||||
setState(States.CONNECTED);
|
|
||||||
setConnection(newConnection);
|
|
||||||
};
|
|
||||||
peer.on('connection', onConnect);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
peer.off('connection', onConnect);
|
|
||||||
};
|
|
||||||
}, [peer, connection]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!connection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const handleData = async (encrypted: any) => {
|
|
||||||
const message = await decrypt(encrypted);
|
|
||||||
addMessage(message, false);
|
|
||||||
};
|
|
||||||
connection.on('data', handleData);
|
|
||||||
return () => {
|
|
||||||
connection.off('data', handleData);
|
|
||||||
}
|
|
||||||
}, [connection, decrypt]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConnectionContext.Provider
|
|
||||||
value={{
|
|
||||||
clientInfo,
|
|
||||||
state,
|
|
||||||
messages,
|
|
||||||
send,
|
|
||||||
connect,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ConnectionContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { States, ConnectionProvider };
|
|
||||||
|
|
||||||
export default ConnectionContext;
|
|
||||||
54
src/contexts/SessionContext.tsx
Normal file
54
src/contexts/SessionContext.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import React, { createContext, ReactNode, useState, useEffect, useCallback } from 'react';
|
||||||
|
import Session, { State } from '../Session';
|
||||||
|
|
||||||
|
interface SessionContextValue {
|
||||||
|
messages: any[];
|
||||||
|
send: (data: any) => Promise<void>;
|
||||||
|
connectInfo: any;
|
||||||
|
state: State;
|
||||||
|
connect: (id: string, secret: string) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SessionProviderProps {
|
||||||
|
session: Session;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message = any;
|
||||||
|
|
||||||
|
const SessionContext = createContext<SessionContextValue>(undefined as any);
|
||||||
|
|
||||||
|
const SessionProvider: React.FC<SessionProviderProps> = ({ session, children }) => {
|
||||||
|
const [messages, setMessages] = useState<Message[]>(session.messages);
|
||||||
|
const [state, setState] = useState<State>(session.state);
|
||||||
|
const update = useCallback(() => {
|
||||||
|
setMessages(session.messages);
|
||||||
|
setState(session.state);
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
session.on('updated', update);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
session.off('updated', update);
|
||||||
|
}
|
||||||
|
}, [session, update]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SessionContext.Provider
|
||||||
|
value={{
|
||||||
|
send: session.send,
|
||||||
|
messages,
|
||||||
|
connectInfo: session.connectInfo,
|
||||||
|
state,
|
||||||
|
connect: session.connect,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SessionContext.Provider>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SessionProvider };
|
||||||
|
|
||||||
|
export default SessionContext;
|
||||||
42
src/contexts/SessionsContext.tsx
Normal file
42
src/contexts/SessionsContext.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import React, { createContext, useState, useCallback } from 'react';
|
||||||
|
import Session from '../Session';
|
||||||
|
|
||||||
|
interface ConnectionInfo {
|
||||||
|
id: string;
|
||||||
|
secret: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SessionsContextValue {
|
||||||
|
sessions: Session[];
|
||||||
|
addSession: (name?: string, id?: string, secret?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SessionsContext = createContext<SessionsContextValue>(undefined as any);
|
||||||
|
|
||||||
|
const SessionsProvider: React.FC = ({ children }) => {
|
||||||
|
const [sessions, setSessions] = useState<Session[]>([]);
|
||||||
|
|
||||||
|
const addSession = useCallback(() => {
|
||||||
|
const session = new Session('Unnamed session', nanoid(), nanoid());
|
||||||
|
setSessions(current => [
|
||||||
|
...current,
|
||||||
|
session,
|
||||||
|
]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SessionsContext.Provider
|
||||||
|
value={{
|
||||||
|
sessions,
|
||||||
|
addSession,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SessionsContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ConnectionInfo, SessionsProvider };
|
||||||
|
|
||||||
|
export default SessionsContext;
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { useContext } from 'react';
|
|
||||||
import ConnectionContext, { States } from '../contexts/ConnectionContext';
|
|
||||||
|
|
||||||
const useConnection = () => {
|
|
||||||
const context = useContext(ConnectionContext);
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConnectionStates = States;
|
|
||||||
|
|
||||||
export default useConnection;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
import { encrypt, decrypt } from '../utils/crypto';
|
|
||||||
|
|
||||||
|
|
||||||
const useCrypto = (secret: string) => {
|
|
||||||
const doEncrypt = useCallback(async (data: any) => {
|
|
||||||
const raw = JSON.stringify(data);
|
|
||||||
const result = await encrypt(raw, secret);
|
|
||||||
return result;
|
|
||||||
}, [secret]);
|
|
||||||
|
|
||||||
const doDecrypt = useCallback(async (data: string[]) => {
|
|
||||||
return decrypt(data, secret);
|
|
||||||
}, [secret]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
encrypt: doEncrypt,
|
|
||||||
decrypt: doDecrypt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useCrypto;
|
|
||||||
12
src/hooks/useSession.ts
Normal file
12
src/hooks/useSession.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { State } from '../Session';
|
||||||
|
import SessionContext from '../contexts/SessionContext';
|
||||||
|
|
||||||
|
const useSession = () => {
|
||||||
|
const context = useContext(SessionContext);
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { State };
|
||||||
|
|
||||||
|
export default useSession;
|
||||||
9
src/hooks/useSessions.ts
Normal file
9
src/hooks/useSessions.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import SessionsContext from '../contexts/SessionsContext';
|
||||||
|
|
||||||
|
const useSessions = () => {
|
||||||
|
const context = useContext(SessionsContext);
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSessions;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { ConnectionProvider } from './contexts/ConnectionContext';
|
import { SessionsProvider } from './contexts/SessionsContext';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
const root = document.getElementById('root');
|
const root = document.getElementById('root');
|
||||||
const app = (
|
const app = (
|
||||||
<ConnectionProvider>
|
<SessionsProvider>
|
||||||
<App />
|
<App />
|
||||||
</ConnectionProvider>
|
</SessionsProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
render(app, root);
|
render(app, root);
|
||||||
|
|||||||
Reference in New Issue
Block a user