From eec6de9f0c096db1ba912067df59c5a7af22db33 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 9 Jul 2021 13:52:17 +1000 Subject: [PATCH 1/4] Remove InitWaku component There was an issue where the observers are added/removed continously. This is due to using `useEffect` on props. By removing this component then `useEffect` ends only being called when waku changes, ie, at initialisation. --- examples/eth-dm/src/App.tsx | 71 +++++++++++++-- examples/eth-dm/src/BroadcastPublicKey.tsx | 2 +- examples/eth-dm/src/messaging/SendMessage.tsx | 2 +- examples/eth-dm/src/{InitWaku.tsx => waku.ts} | 86 +------------------ 4 files changed, 67 insertions(+), 94 deletions(-) rename examples/eth-dm/src/{InitWaku.tsx => waku.ts} (52%) diff --git a/examples/eth-dm/src/App.tsx b/examples/eth-dm/src/App.tsx index 1d0de0594a..29f3654e20 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; @@ -71,6 +77,61 @@ function App() { const classes = useStyles(); + // 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]); + + 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, + ]); + }; + }, [waku]); + + 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, + ]); + }; + }, [waku]); + useEffect(() => { try { window.ethereum @@ -123,14 +184,6 @@ function App() {
-
Eth-DM Key Pair ({ formControl: { diff --git a/examples/eth-dm/src/InitWaku.tsx b/examples/eth-dm/src/waku.ts similarity index 52% rename from examples/eth-dm/src/InitWaku.tsx rename to examples/eth-dm/src/waku.ts index 1a90ec3a63..8d2c60e592 100644 --- a/examples/eth-dm/src/InitWaku.tsx +++ b/examples/eth-dm/src/waku.ts @@ -8,87 +8,7 @@ import { byteArrayToHex } from './utils'; export const PublicKeyContentTopic = '/eth-dm/1/public-key/proto'; export const DirectMessageContentTopic = '/eth-dm/1/direct-message/json'; -interface Props { - waku: Waku | undefined; - setWaku: (waku: Waku) => 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 { +export async function initWaku(): Promise { const waku = await Waku.create({}); const nodes = await getNodes(); @@ -105,7 +25,7 @@ function getNodes() { return getStatusFleetNodes(Environment.Prod); } -function handlePublicKeyMessage( +export function handlePublicKeyMessage( myPublicKey: string | undefined, setter: Dispatch>>, msg: WakuMessage @@ -128,7 +48,7 @@ function handlePublicKeyMessage( } } -async function handleDirectMessage( +export async function handleDirectMessage( setter: Dispatch>, privateKey: string, address: string, From 44db58d2fd03f51e8b88d298c065eb23f096febd Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 9 Jul 2021 14:07:55 +1000 Subject: [PATCH 2/4] Fix the dependencies --- examples/eth-dm/src/App.tsx | 111 ++++++++++++++++++----------------- examples/eth-dm/src/utils.ts | 21 +++++++ examples/eth-dm/src/waku.ts | 11 ++-- 3 files changed, 83 insertions(+), 60 deletions(-) diff --git a/examples/eth-dm/src/App.tsx b/examples/eth-dm/src/App.tsx index 29f3654e20..a452dbbb56 100644 --- a/examples/eth-dm/src/App.tsx +++ b/examples/eth-dm/src/App.tsx @@ -77,61 +77,6 @@ function App() { const classes = useStyles(); - // 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]); - - 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, - ]); - }; - }, [waku]); - - 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, - ]); - }; - }, [waku]); - useEffect(() => { try { window.ethereum @@ -146,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; 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 index 8d2c60e592..4ed5f957bd 100644 --- a/examples/eth-dm/src/waku.ts +++ b/examples/eth-dm/src/waku.ts @@ -1,9 +1,9 @@ -import { Dispatch, SetStateAction, useEffect } from 'react'; +import { Dispatch, SetStateAction } from 'react'; import { Environment, getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { decode, DirectMessage, PublicKeyMessage } from './messaging/wire'; -import { decryptMessage, KeyPair, validatePublicKeyMessage } from './crypto'; +import { decryptMessage, validatePublicKeyMessage } from './crypto'; import { Message } from './messaging/Messages'; -import { byteArrayToHex } from './utils'; +import { byteArrayToHex, equalByteArrays } from './utils'; export const PublicKeyContentTopic = '/eth-dm/1/public-key/proto'; export const DirectMessageContentTopic = '/eth-dm/1/direct-message/json'; @@ -26,7 +26,7 @@ function getNodes() { } export function handlePublicKeyMessage( - myPublicKey: string | undefined, + myAddress: string, setter: Dispatch>>, msg: WakuMessage ) { @@ -35,7 +35,8 @@ export function handlePublicKeyMessage( const publicKeyMsg = PublicKeyMessage.decode(msg.payload); if (!publicKeyMsg) return; const ethDmPublicKey = byteArrayToHex(publicKeyMsg.ethDmPublicKey); - if (ethDmPublicKey === myPublicKey) return; + console.log(ethDmPublicKey, myAddress); + if (myAddress && equalByteArrays(publicKeyMsg.ethAddress, myAddress)) return; const res = validatePublicKeyMessage(publicKeyMsg); console.log('Is Public Key Message valid?', res); From 66d450ae3639520ba5c926b5ebeeaf46a0520ed2 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 9 Jul 2021 14:12:17 +1000 Subject: [PATCH 3/4] Fix bad comparison logic --- examples/eth-dm/src/waku.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/eth-dm/src/waku.ts b/examples/eth-dm/src/waku.ts index 4ed5f957bd..900d8002bf 100644 --- a/examples/eth-dm/src/waku.ts +++ b/examples/eth-dm/src/waku.ts @@ -58,20 +58,24 @@ export async function handleDirectMessage( 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; + // Only decrypt messages for us + if (!equalByteArrays(directMessage.toAddress, address)) return; - const text = await decryptMessage(privateKey, directMessage); + try { + const text = await decryptMessage(privateKey, directMessage); - const timestamp = wakuMsg.timestamp ? wakuMsg.timestamp : new Date(); + 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, + console.log('Message decrypted:', text); + setter((prevMsgs: Message[]) => { + const copy = prevMsgs.slice(); + copy.push({ + text: text, + timestamp: timestamp, + }); + return copy; }); - return copy; - }); + } catch (e) { + console.log(' Failed to decrypt message', e); + } } From d00c1c874aa54e075b10befd9a4dcec592416dd9 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 9 Jul 2021 14:23:42 +1000 Subject: [PATCH 4/4] Do not fail if connection to one peer fails As long as we connect to at least one peer then we can move forward. --- examples/eth-dm/src/waku.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/examples/eth-dm/src/waku.ts b/examples/eth-dm/src/waku.ts index 900d8002bf..59454da0ed 100644 --- a/examples/eth-dm/src/waku.ts +++ b/examples/eth-dm/src/waku.ts @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction } from 'react'; -import { Environment, getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; +import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { decode, DirectMessage, PublicKeyMessage } from './messaging/wire'; import { decryptMessage, validatePublicKeyMessage } from './crypto'; import { Message } from './messaging/Messages'; @@ -11,20 +11,27 @@ export const DirectMessageContentTopic = '/eth-dm/1/direct-message/json'; export async function initWaku(): Promise { const waku = await Waku.create({}); - const nodes = await getNodes(); - await Promise.all( - nodes.map((addr) => { - return waku.dial(addr); - }) - ); + // 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; } -function getNodes() { - return getStatusFleetNodes(Environment.Prod); -} - export function handlePublicKeyMessage( myAddress: string, setter: Dispatch>>,