diff --git a/examples/eth-dm/src/App.tsx b/examples/eth-dm/src/App.tsx index 1d0de0594a..a452dbbb56 100644 --- a/examples/eth-dm/src/App.tsx +++ b/examples/eth-dm/src/App.tsx @@ -10,7 +10,6 @@ import { Message } from './messaging/Messages'; import 'fontsource-roboto'; import { AppBar, IconButton, Toolbar, Typography } from '@material-ui/core'; import KeyPairHandling from './key_pair_handling/KeyPairHandling'; -import InitWaku from './InitWaku'; import { createMuiTheme, ThemeProvider, @@ -20,6 +19,13 @@ import { teal, purple, green } from '@material-ui/core/colors'; import WifiIcon from '@material-ui/icons/Wifi'; import BroadcastPublicKey from './BroadcastPublicKey'; import Messaging from './messaging/Messaging'; +import { + DirectMessageContentTopic, + handleDirectMessage, + handlePublicKeyMessage, + initWaku, + PublicKeyContentTopic, +} from './waku'; declare let window: any; @@ -85,6 +91,62 @@ function App() { } }, [address, signer]); + // Waku initialization + useEffect(() => { + if (waku) return; + initWaku() + .then((_waku) => { + console.log('waku: ready'); + setWaku(_waku); + }) + .catch((e) => { + console.error('Failed to initiate Waku', e); + }); + }, [waku]); + + useEffect(() => { + if (!waku) return; + if (!address) return; + + const observerPublicKeyMessage = handlePublicKeyMessage.bind( + {}, + address, + setPublicKeys + ); + + waku.relay.addObserver(observerPublicKeyMessage, [PublicKeyContentTopic]); + + return function cleanUp() { + if (!waku) return; + waku.relay.deleteObserver(observerPublicKeyMessage, [ + PublicKeyContentTopic, + ]); + }; + }, [waku, address]); + + useEffect(() => { + if (!waku) return; + if (!ethDmKeyPair) return; + if (!address) return; + + const observerDirectMessage = handleDirectMessage.bind( + {}, + setMessages, + ethDmKeyPair.privateKey, + address + ); + + waku.relay.addObserver(observerDirectMessage, [DirectMessageContentTopic]); + + return function cleanUp() { + if (!waku) return; + if (!observerDirectMessage) return; + waku.relay.deleteObserver(observerDirectMessage, [ + DirectMessageContentTopic, + ]); + }; + }, [waku, address, ethDmKeyPair]); + let peers = 0; if (waku) { peers = waku.libp2p.connectionManager.connections.size; @@ -123,14 +185,6 @@ function App() {
-
Eth-DM Key Pair void; - ethDmKeyPair: KeyPair | undefined; - setPublicKeys: Dispatch>>; - setMessages: Dispatch>; - address: string | undefined; -} - -/** - * Does all the waku initialization - */ -export default function InitWaku({ - waku, - setWaku, - ethDmKeyPair, - setPublicKeys, - setMessages, - address, -}: Props) { - useEffect(() => { - if (waku) return; - initWaku() - .then((wakuNode) => { - console.log('waku: ready'); - setWaku(wakuNode); - }) - .catch((e) => { - console.error('Failed to initiate Waku', e); - }); - }, [waku, setWaku]); - - const observerPublicKeyMessage = handlePublicKeyMessage.bind( - {}, - ethDmKeyPair?.publicKey, - setPublicKeys - ); - - const observerDirectMessage = - ethDmKeyPair && address - ? handleDirectMessage.bind( - {}, - setMessages, - ethDmKeyPair.privateKey, - address - ) - : undefined; - - useEffect(() => { - if (!waku) return; - waku.relay.addObserver(observerPublicKeyMessage, [PublicKeyContentTopic]); - - return function cleanUp() { - if (!waku) return; - waku.relay.deleteObserver(observerPublicKeyMessage, [ - PublicKeyContentTopic, - ]); - }; - }); - - useEffect(() => { - if (!waku) return; - if (!observerDirectMessage) return; - waku.relay.addObserver(observerDirectMessage, [DirectMessageContentTopic]); - - return function cleanUp() { - if (!waku) return; - if (!observerDirectMessage) return; - waku.relay.deleteObserver(observerDirectMessage, [ - DirectMessageContentTopic, - ]); - }; - }); - - // Returns an empty fragment. - // Taking advantages of React's state management and useEffect() - // Not sure it is best practice but it works. - return <>; -} - -async function initWaku(): Promise { - const waku = await Waku.create({}); - - const nodes = await getNodes(); - await Promise.all( - nodes.map((addr) => { - return waku.dial(addr); - }) - ); - - return waku; -} - -function getNodes() { - return getStatusFleetNodes(Environment.Prod); -} - -function handlePublicKeyMessage( - myPublicKey: string | undefined, - setter: Dispatch>>, - msg: WakuMessage -) { - console.log('Public Key Message received:', msg); - if (!msg.payload) return; - const publicKeyMsg = PublicKeyMessage.decode(msg.payload); - if (!publicKeyMsg) return; - const ethDmPublicKey = byteArrayToHex(publicKeyMsg.ethDmPublicKey); - if (ethDmPublicKey === myPublicKey) return; - - const res = validatePublicKeyMessage(publicKeyMsg); - console.log('Is Public Key Message valid?', res); - - if (res) { - setter((prevPks: Map) => { - prevPks.set(byteArrayToHex(publicKeyMsg.ethAddress), ethDmPublicKey); - return new Map(prevPks); - }); - } -} - -async function handleDirectMessage( - setter: Dispatch>, - privateKey: string, - address: string, - wakuMsg: WakuMessage -) { - console.log('Direct Message received:', wakuMsg); - if (!wakuMsg.payload) return; - const directMessage: DirectMessage = decode(wakuMsg.payload); - // Do not return our own messages - if (directMessage.toAddress === address) return; - - const text = await decryptMessage(privateKey, directMessage); - - const timestamp = wakuMsg.timestamp ? wakuMsg.timestamp : new Date(); - - console.log('Message decrypted:', text); - setter((prevMsgs: Message[]) => { - const copy = prevMsgs.slice(); - copy.push({ - text: text, - timestamp: timestamp, - }); - return copy; - }); -} diff --git a/examples/eth-dm/src/messaging/SendMessage.tsx b/examples/eth-dm/src/messaging/SendMessage.tsx index 9057c21df9..fb61bfd315 100644 --- a/examples/eth-dm/src/messaging/SendMessage.tsx +++ b/examples/eth-dm/src/messaging/SendMessage.tsx @@ -10,7 +10,7 @@ import React, { ChangeEvent, useState, KeyboardEvent } from 'react'; import { Waku, WakuMessage } from 'js-waku'; import { DirectMessage, encode } from './wire'; import { encryptMessage } from '../crypto'; -import { DirectMessageContentTopic } from '../InitWaku'; +import { DirectMessageContentTopic } from '../waku'; const useStyles = makeStyles((theme) => ({ formControl: { diff --git a/examples/eth-dm/src/utils.ts b/examples/eth-dm/src/utils.ts index b1f36ac825..3007efb418 100644 --- a/examples/eth-dm/src/utils.ts +++ b/examples/eth-dm/src/utils.ts @@ -6,3 +6,24 @@ export function byteArrayToHex(bytes: Uint8Array): string { export function hexToBuf(str: string): Buffer { return Buffer.from(str.replace(/0x/, ''), 'hex'); } + +export function equalByteArrays( + a: Uint8Array | Buffer | string, + b: Uint8Array | Buffer | string +): boolean { + let aBuf: Buffer; + let bBuf: Buffer; + if (typeof a === 'string') { + aBuf = hexToBuf(a); + } else { + aBuf = Buffer.from(a); + } + + if (typeof b === 'string') { + bBuf = hexToBuf(b); + } else { + bBuf = Buffer.from(b); + } + + return aBuf.compare(bBuf) === 0; +} diff --git a/examples/eth-dm/src/waku.ts b/examples/eth-dm/src/waku.ts new file mode 100644 index 0000000000..59454da0ed --- /dev/null +++ b/examples/eth-dm/src/waku.ts @@ -0,0 +1,88 @@ +import { Dispatch, SetStateAction } from 'react'; +import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; +import { decode, DirectMessage, PublicKeyMessage } from './messaging/wire'; +import { decryptMessage, validatePublicKeyMessage } from './crypto'; +import { Message } from './messaging/Messages'; +import { byteArrayToHex, equalByteArrays } from './utils'; + +export const PublicKeyContentTopic = '/eth-dm/1/public-key/proto'; +export const DirectMessageContentTopic = '/eth-dm/1/direct-message/json'; + +export async function initWaku(): Promise { + const waku = await Waku.create({}); + + // Dial all nodes it can find + getStatusFleetNodes().then((nodes) => { + nodes.forEach((addr) => { + waku.dial(addr); + }); + }); + + // Wait to be connected to at least one peer + await new Promise((resolve, reject) => { + // If we are not connected to any peer within 10sec let's just reject + // As we are not implementing connection management in this example + + setTimeout(reject, 10000); + waku.libp2p.connectionManager.on('peer:connect', () => { + resolve(null); + }); + }); + + return waku; +} + +export function handlePublicKeyMessage( + myAddress: string, + setter: Dispatch>>, + msg: WakuMessage +) { + console.log('Public Key Message received:', msg); + if (!msg.payload) return; + const publicKeyMsg = PublicKeyMessage.decode(msg.payload); + if (!publicKeyMsg) return; + const ethDmPublicKey = byteArrayToHex(publicKeyMsg.ethDmPublicKey); + console.log(ethDmPublicKey, myAddress); + if (myAddress && equalByteArrays(publicKeyMsg.ethAddress, myAddress)) return; + + const res = validatePublicKeyMessage(publicKeyMsg); + console.log('Is Public Key Message valid?', res); + + if (res) { + setter((prevPks: Map) => { + prevPks.set(byteArrayToHex(publicKeyMsg.ethAddress), ethDmPublicKey); + return new Map(prevPks); + }); + } +} + +export async function handleDirectMessage( + setter: Dispatch>, + privateKey: string, + address: string, + wakuMsg: WakuMessage +) { + console.log('Direct Message received:', wakuMsg); + if (!wakuMsg.payload) return; + const directMessage: DirectMessage = decode(wakuMsg.payload); + // Only decrypt messages for us + if (!equalByteArrays(directMessage.toAddress, address)) return; + + try { + const text = await decryptMessage(privateKey, directMessage); + + const timestamp = wakuMsg.timestamp ? wakuMsg.timestamp : new Date(); + + console.log('Message decrypted:', text); + setter((prevMsgs: Message[]) => { + const copy = prevMsgs.slice(); + copy.push({ + text: text, + timestamp: timestamp, + }); + return copy; + }); + } catch (e) { + console.log(' Failed to decrypt message', e); + } +}