278 lines
7.3 KiB
TypeScript
Raw Normal View History

2021-06-11 15:33:29 +10:00
import '@ethersproject/shims';
2021-06-15 15:29:41 +10:00
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
2021-05-28 15:35:50 +10:00
import './App.css';
import { Environment, getStatusFleetNodes, Waku, WakuMessage } from 'js-waku';
2021-06-11 15:33:29 +10:00
import { ethers } from 'ethers';
import { Web3Provider } from '@ethersproject/providers';
import {
createPublicKeyMessage,
generateEthDmKeyPair,
KeyPair,
validatePublicKeyMessage,
2021-06-11 15:33:29 +10:00
} from './crypto';
2021-06-15 15:29:41 +10:00
import * as EthCrypto from 'eth-crypto';
import { DirectMessage, PublicKeyMessage } from './messages';
2021-06-18 10:04:38 +10:00
import { Message, Messages } from './Messages';
2021-06-18 13:53:12 +10:00
import 'fontsource-roboto';
import { Button } from '@material-ui/core';
2021-06-11 15:33:29 +10:00
const PublicKeyContentTopic = '/eth-dm/1/public-key/json';
2021-06-15 15:29:41 +10:00
const DirectMessageContentTopic = '/eth-dm/1/direct-message/json';
2021-05-28 15:35:50 +10:00
declare let window: any;
function App() {
const [waku, setWaku] = useState<Waku>();
2021-06-11 15:33:29 +10:00
const [provider, setProvider] = useState<Web3Provider>();
const [ethDmKeyPair, setEthDmKeyPair] = useState<KeyPair>();
const [publicKeyMsg, setPublicKeyMsg] = useState<PublicKeyMessage>();
2021-06-15 15:29:41 +10:00
const [publicKeys, setPublicKeys] = useState<Map<string, string>>(new Map());
2021-06-18 10:04:38 +10:00
const [messages, setMessages] = useState<Message[]>([]);
2021-05-28 15:35:50 +10:00
useEffect(() => {
2021-06-11 15:33:29 +10:00
if (provider) return;
try {
2021-06-17 13:53:00 +10:00
window.ethereum.enable();
const _provider = new ethers.providers.Web3Provider(window.ethereum);
setProvider(_provider);
} catch (e) {
console.error('No web3 provider available');
}
2021-06-11 15:33:29 +10:00
}, [provider]);
useEffect(() => {
if (waku) return;
initWaku()
.then((wakuNode) => {
console.log('waku: ready');
2021-05-28 15:35:50 +10:00
setWaku(wakuNode);
2021-06-11 15:33:29 +10:00
})
.catch((e) => {
2021-05-28 15:35:50 +10:00
console.error('Failed to initiate Waku', e);
});
}, [waku]);
const generateKeyPair = () => {
2021-06-11 15:33:29 +10:00
if (ethDmKeyPair) return;
if (!provider) return;
generateEthDmKeyPair(provider.getSigner())
.then((keyPair) => {
setEthDmKeyPair(keyPair);
})
.catch((e) => {
console.error('Failed to generate Key Pair', e);
});
};
2021-06-15 15:29:41 +10:00
const observerPublicKeyMessage = handlePublicKeyMessage.bind(
{},
setPublicKeys
);
2021-06-17 16:22:49 +10:00
const observerDirectMessage = ethDmKeyPair
? handleDirectMessage.bind({}, setMessages, ethDmKeyPair.privateKey)
: undefined;
2021-06-16 11:20:21 +10:00
useEffect(() => {
if (!waku) return;
2021-06-15 15:29:41 +10:00
waku.relay.addObserver(observerPublicKeyMessage, [PublicKeyContentTopic]);
return function cleanUp() {
if (!waku) return;
2021-06-17 16:22:49 +10:00
waku.relay.deleteObserver(observerPublicKeyMessage, [
PublicKeyContentTopic,
]);
};
});
2021-06-11 15:33:29 +10:00
2021-06-16 11:20:21 +10:00
useEffect(() => {
if (!waku) return;
2021-06-17 16:22:49 +10:00
if (!observerDirectMessage) return;
waku.relay.addObserver(observerDirectMessage, [DirectMessageContentTopic]);
return function cleanUp() {
if (!waku) return;
2021-06-17 16:22:49 +10:00
if (!observerDirectMessage) return;
waku.relay.deleteObserver(observerDirectMessage, [
DirectMessageContentTopic,
]);
};
2021-06-16 11:20:21 +10:00
});
const broadcastPublicKey = () => {
2021-06-11 15:33:29 +10:00
if (!ethDmKeyPair) return;
if (!provider) return;
if (!waku) return;
if (publicKeyMsg) {
2021-06-15 15:29:41 +10:00
const wakuMsg = encodePublicKeyWakuMessage(publicKeyMsg);
waku.relay.send(wakuMsg).catch((e) => {
console.error('Failed to send Public Key Message');
2021-06-11 15:33:29 +10:00
});
} else {
createPublicKeyMessage(provider.getSigner(), ethDmKeyPair.publicKey)
.then((msg) => {
setPublicKeyMsg(msg);
2021-06-15 15:29:41 +10:00
const wakuMsg = encodePublicKeyWakuMessage(msg);
waku.relay.send(wakuMsg).catch((e) => {
console.error('Failed to send Public Key Message');
});
})
.catch((e) => {
console.error('Failed to creat Eth-Dm Publication message', e);
});
}
2021-06-11 15:33:29 +10:00
};
2021-06-15 15:29:41 +10:00
const sendDummyMessage = () => {
if (!waku) return;
2021-06-16 11:20:21 +10:00
console.log(`Sending messages to ${publicKeys.size} peers`);
2021-06-15 15:29:41 +10:00
publicKeys.forEach(async (publicKey, address) => {
const msg = await encodeEncryptedWakuMessage(
'Here is a secret message',
publicKey,
address
);
await waku?.lightPush.push(msg);
2021-06-15 15:29:41 +10:00
});
};
2021-06-16 16:36:27 +10:00
const wakuReady = !!waku ? 'Waku is ready' : 'Waku is loading';
2021-05-28 15:35:50 +10:00
return (
2021-06-11 15:33:29 +10:00
<div className="App">
<header className="App-header">
2021-06-16 16:36:27 +10:00
{wakuReady}
2021-06-18 13:53:12 +10:00
<Button
variant="contained"
color="primary"
onClick={generateKeyPair}
disabled={!provider}
>
Generate Eth-DM Key Pair
2021-06-18 13:53:12 +10:00
</Button>
<Button
variant="contained"
color="primary"
onClick={broadcastPublicKey}
disabled={!ethDmKeyPair || !waku}
2021-06-15 15:29:41 +10:00
>
2021-06-18 13:53:12 +10:00
Broadcast Eth-DM Public Key
</Button>
<div>
<Button
variant="contained"
color="primary"
onClick={sendDummyMessage}
disabled={!waku || publicKeys.size === 0}
>
Send Direct Message
</Button>
<Messages messages={messages} />
</div>
2021-05-28 15:35:50 +10:00
</header>
</div>
);
}
export default App;
async function initWaku(): Promise<Waku> {
const waku = await Waku.create({});
const nodes = await getNodes();
await Promise.all(
nodes.map((addr) => {
return waku.dial(addr);
})
);
return waku;
}
function getNodes() {
// Works with react-scripts
2021-06-17 14:02:07 +10:00
// if (process?.env?.NODE_ENV === 'development') {
return getStatusFleetNodes(Environment.Test);
// } else {
// return getStatusFleetNodes(Environment.Prod);
// }
2021-05-28 15:35:50 +10:00
}
2021-06-11 15:33:29 +10:00
2021-06-15 15:29:41 +10:00
function encodePublicKeyWakuMessage(ethDmMsg: PublicKeyMessage): WakuMessage {
const payload = encode(ethDmMsg);
return WakuMessage.fromBytes(payload, PublicKeyContentTopic);
}
2021-06-15 15:29:41 +10:00
async function encodeEncryptedWakuMessage(
message: string,
publicKey: string,
address: string
): Promise<WakuMessage> {
const encryptedMsg = await EthCrypto.encryptWithPublicKey(publicKey, message);
const directMsg: DirectMessage = {
toAddress: address,
encMessage: encryptedMsg,
};
const payload = encode(directMsg);
return WakuMessage.fromBytes(payload, DirectMessageContentTopic);
}
function handlePublicKeyMessage(
setter: Dispatch<SetStateAction<Map<string, string>>>,
msg: WakuMessage
) {
if (!msg.payload) return;
const publicKeyMsg: PublicKeyMessage = decode(msg.payload);
const res = validatePublicKeyMessage(publicKeyMsg);
console.log(`Public Key Message Received, valid: ${res}`, publicKeyMsg);
setter((prevPks: Map<string, string>) => {
prevPks.set(publicKeyMsg.ethAddress, publicKeyMsg.ethDmPublicKey);
return new Map(prevPks);
});
}
2021-06-16 11:20:21 +10:00
async function handleDirectMessage(
2021-06-18 10:04:38 +10:00
setter: Dispatch<SetStateAction<Message[]>>,
2021-06-16 11:20:21 +10:00
privateKey: string,
wakuMsg: WakuMessage
) {
console.log('Waku Message received:', wakuMsg);
if (!wakuMsg.payload) return;
const directMessage: DirectMessage = decode(wakuMsg.payload);
2021-06-18 10:04:38 +10:00
const text = await EthCrypto.decryptWithPrivateKey(
2021-06-16 11:20:21 +10:00
privateKey,
directMessage.encMessage
);
2021-06-18 10:04:38 +10:00
const timestamp = wakuMsg.timestamp ? wakuMsg.timestamp : new Date();
console.log('Message decrypted:', text);
setter((prevMsgs: Message[]) => {
2021-06-16 11:20:21 +10:00
const copy = prevMsgs.slice();
2021-06-18 10:04:38 +10:00
copy.push({
text: text,
timestamp: timestamp,
});
2021-06-16 11:20:21 +10:00
return copy;
});
}
2021-06-15 15:29:41 +10:00
function encode<T>(msg: T): Buffer {
const jsonStr = JSON.stringify(msg);
return Buffer.from(jsonStr, 'utf-8');
}
2021-06-15 15:29:41 +10:00
function decode<T>(bytes: Uint8Array): T {
const buf = Buffer.from(bytes);
const str = buf.toString('utf-8');
return JSON.parse(str);
2021-06-11 15:33:29 +10:00
}