mirror of https://github.com/status-im/js-waku.git
Merge pull request #222 from status-im/eth-dm-observer-infinite-loop
This commit is contained in:
commit
7c47a6b215
|
@ -10,7 +10,6 @@ import { Message } from './messaging/Messages';
|
||||||
import 'fontsource-roboto';
|
import 'fontsource-roboto';
|
||||||
import { AppBar, IconButton, Toolbar, Typography } from '@material-ui/core';
|
import { AppBar, IconButton, Toolbar, Typography } from '@material-ui/core';
|
||||||
import KeyPairHandling from './key_pair_handling/KeyPairHandling';
|
import KeyPairHandling from './key_pair_handling/KeyPairHandling';
|
||||||
import InitWaku from './InitWaku';
|
|
||||||
import {
|
import {
|
||||||
createMuiTheme,
|
createMuiTheme,
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
|
@ -20,6 +19,13 @@ import { teal, purple, green } from '@material-ui/core/colors';
|
||||||
import WifiIcon from '@material-ui/icons/Wifi';
|
import WifiIcon from '@material-ui/icons/Wifi';
|
||||||
import BroadcastPublicKey from './BroadcastPublicKey';
|
import BroadcastPublicKey from './BroadcastPublicKey';
|
||||||
import Messaging from './messaging/Messaging';
|
import Messaging from './messaging/Messaging';
|
||||||
|
import {
|
||||||
|
DirectMessageContentTopic,
|
||||||
|
handleDirectMessage,
|
||||||
|
handlePublicKeyMessage,
|
||||||
|
initWaku,
|
||||||
|
PublicKeyContentTopic,
|
||||||
|
} from './waku';
|
||||||
|
|
||||||
declare let window: any;
|
declare let window: any;
|
||||||
|
|
||||||
|
@ -85,6 +91,62 @@ function App() {
|
||||||
}
|
}
|
||||||
}, [address, signer]);
|
}, [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;
|
let peers = 0;
|
||||||
if (waku) {
|
if (waku) {
|
||||||
peers = waku.libp2p.connectionManager.connections.size;
|
peers = waku.libp2p.connectionManager.connections.size;
|
||||||
|
@ -123,14 +185,6 @@ function App() {
|
||||||
|
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<main className={classes.main}>
|
<main className={classes.main}>
|
||||||
<InitWaku
|
|
||||||
ethDmKeyPair={ethDmKeyPair}
|
|
||||||
setMessages={setMessages}
|
|
||||||
setPublicKeys={setPublicKeys}
|
|
||||||
setWaku={setWaku}
|
|
||||||
waku={waku}
|
|
||||||
address={address}
|
|
||||||
/>
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Eth-DM Key Pair</legend>
|
<legend>Eth-DM Key Pair</legend>
|
||||||
<KeyPairHandling
|
<KeyPairHandling
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { createPublicKeyMessage, KeyPair } from './crypto';
|
||||||
import { PublicKeyMessage } from './messaging/wire';
|
import { PublicKeyMessage } from './messaging/wire';
|
||||||
import { WakuMessage, Waku } from 'js-waku';
|
import { WakuMessage, Waku } from 'js-waku';
|
||||||
import { Signer } from '@ethersproject/abstract-signer';
|
import { Signer } from '@ethersproject/abstract-signer';
|
||||||
import { PublicKeyContentTopic } from './InitWaku';
|
import { PublicKeyContentTopic } from './waku';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
ethDmKeyPair: KeyPair | undefined;
|
ethDmKeyPair: KeyPair | undefined;
|
||||||
|
|
|
@ -1,156 +0,0 @@
|
||||||
import { Dispatch, SetStateAction, useEffect } from 'react';
|
|
||||||
import { Environment, getStatusFleetNodes, Waku, WakuMessage } from 'js-waku';
|
|
||||||
import { decode, DirectMessage, PublicKeyMessage } from './messaging/wire';
|
|
||||||
import { decryptMessage, KeyPair, validatePublicKeyMessage } from './crypto';
|
|
||||||
import { Message } from './messaging/Messages';
|
|
||||||
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<SetStateAction<Map<string, string>>>;
|
|
||||||
setMessages: Dispatch<SetStateAction<Message[]>>;
|
|
||||||
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<Waku> {
|
|
||||||
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<SetStateAction<Map<string, string>>>,
|
|
||||||
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<string, string>) => {
|
|
||||||
prevPks.set(byteArrayToHex(publicKeyMsg.ethAddress), ethDmPublicKey);
|
|
||||||
return new Map(prevPks);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDirectMessage(
|
|
||||||
setter: Dispatch<SetStateAction<Message[]>>,
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ import React, { ChangeEvent, useState, KeyboardEvent } from 'react';
|
||||||
import { Waku, WakuMessage } from 'js-waku';
|
import { Waku, WakuMessage } from 'js-waku';
|
||||||
import { DirectMessage, encode } from './wire';
|
import { DirectMessage, encode } from './wire';
|
||||||
import { encryptMessage } from '../crypto';
|
import { encryptMessage } from '../crypto';
|
||||||
import { DirectMessageContentTopic } from '../InitWaku';
|
import { DirectMessageContentTopic } from '../waku';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
formControl: {
|
formControl: {
|
||||||
|
|
|
@ -6,3 +6,24 @@ export function byteArrayToHex(bytes: Uint8Array): string {
|
||||||
export function hexToBuf(str: string): Buffer {
|
export function hexToBuf(str: string): Buffer {
|
||||||
return Buffer.from(str.replace(/0x/, ''), 'hex');
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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<Waku> {
|
||||||
|
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<SetStateAction<Map<string, string>>>,
|
||||||
|
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<string, string>) => {
|
||||||
|
prevPks.set(byteArrayToHex(publicKeyMsg.ethAddress), ethDmPublicKey);
|
||||||
|
return new Map(prevPks);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleDirectMessage(
|
||||||
|
setter: Dispatch<SetStateAction<Message[]>>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue