From 943b1c2456ff19c17673f523f524ba8e3af187df Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 30 Jul 2021 15:29:38 +1000 Subject: [PATCH] Add ReactJS Relay guide --- examples/min-js-web-chat/src/App.js | 14 +- guides/reactjs-relay.md | 309 ++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+), 12 deletions(-) create mode 100644 guides/reactjs-relay.md diff --git a/examples/min-js-web-chat/src/App.js b/examples/min-js-web-chat/src/App.js index 24fa5c04b4..dcb0382d19 100644 --- a/examples/min-js-web-chat/src/App.js +++ b/examples/min-js-web-chat/src/App.js @@ -15,9 +15,8 @@ message SimpleChatMessage { function App() { const [waku, setWaku] = React.useState(undefined); const [wakuStatus, setWakuStatus] = React.useState('None'); - const [messages, setMessages] = React.useState([]); - const [currentTime, setCurrentTime] = React.useState(new Date()); const [sendCounter, setSendCounter] = React.useState(0); + const [messages, setMessages] = React.useState([]); React.useEffect(() => { if (!!waku) return; @@ -61,19 +60,10 @@ function App() { }; }, [waku, wakuStatus, processIncomingMessage]); - React.useEffect(() => { - const timer = setInterval(() => { - setCurrentTime(new Date()); - }, 1000); - return () => { - clearInterval(timer); - }; - }, [currentTime]); - const sendMessageOnClick = () => { if (wakuStatus !== 'Ready') return; - sendMessage(`Here is message #${sendCounter}`, waku, currentTime).then(() => + sendMessage(`Here is message #${sendCounter}`, waku, new Date()).then(() => console.log('Message sent') ); diff --git a/guides/reactjs-relay.md b/guides/reactjs-relay.md new file mode 100644 index 0000000000..4063260185 --- /dev/null +++ b/guides/reactjs-relay.md @@ -0,0 +1,309 @@ +# Receive and Send Messages Using Waku Relay With ReactJS + +It is easy to use DappConnect with ReactJS. +In this guide, we will demonstrate how your ReactJS dApp can use Waku Relay to send and receive messages. + +Before starting, you need to choose a _Content Topic_ for your dApp. +Check out the [how to choose a content topic guide](choose-content-topic.md) to learn more about content topics. +For this guide, we are using a unique content topic: `/min-js-web-chat/1/chat/proto`. + +# Setup + +Create a new react app: + +```shell +npx create-react-app min-js-web-chat +cd min-js-web-chat +``` + +Then, install [js-waku](https://npmjs.com/package/js-waku): + +```shell +npm install js-waku +``` + +Start the dev server and open the dApp in your browser: + +```shell +npm run start +``` + +Note: We have noticed some issues with React bundling due to `npm` pulling an old version of babel. +If you are getting an error about the [optional chaining (?.)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) +character not being valid, try cleaning up and re-installing your dependencies: + +```shell +rm -rf node_modules package-lock.json +npm install +``` + +# Create Waku Instance + +In order to interact with the Waku network, you first need a Waku instance. +Go to `App.js` and modify the `App` function: + +```js +import { Waku } from 'js-waku'; +import * as React from 'react'; + +function App() { + const [waku, setWaku] = React.useState(undefined); + const [wakuStatus, setWakuStatus] = React.useState('None'); + + // Start Waku + React.useEffect(() => { + // If Waku is already assigned, the job is done + if (!!waku) return; + // If Waku status not None, it means we already started Waku + if (wakuStatus !== 'None') return; + + setWakuStatus('Starting'); + + // Create Waku + Waku.create().then((waku) => { + // Once done, put it in the state + setWaku(waku); + // And update the status + setWakuStatus('Started'); + }); + }, [waku, wakuStatus]); + + return ( +
+
+ // Display the status on the web page +

{wakuStatus}

+
+
+ ); +} +``` + +# Connect to Other Peers + +The Waku instance needs to connect to other peers to communicate with the network. +First, create `bootstrapWaku` to connect to the Status fleet: + +```js +import { getStatusFleetNodes } from 'js-waku'; + +async function bootstrapWaku(waku) { + // Retrieve node addresses from https://fleets.status.im/ + const nodes = await getStatusFleetNodes(); + // Connect to the nodes + await Promise.all(nodes.map((addr) => waku.dial(addr))); +} +``` + +Then, bootstrap after Waku is created in the previous `useEffect` block: + +```js +React.useEffect(() => { + if (!!waku) return; + if (wakuStatus !== 'None') return; + + setWakuStatus('Starting'); + + Waku.create().then((waku) => { + setWaku(waku); + setWakuStatus('Connecting'); + bootstrapWaku(waku).then(() => { + setWakuStatus('Ready'); + }); + }); +}, [waku, wakuStatus]); +``` + +DappConnect will provide more discovery and bootstrap methods over time, or you can make your own. + +# Define Message Format + +To define the Protobuf message format, +use [protons](https://www.npmjs.com/package/protons) + +```shell +npm install protons +``` + +Define `SimpleChatMessage` with two fields: `timestamp` and `text`. + +```js +import protons from 'protons'; + +const proto = protons(` +message SimpleChatMessage { + uint64 timestamp = 1; + string text = 2; +} +`); +``` + +# Send Messages + +Create a function that takes the Waku instance and a message to send: + +```js +import { WakuMessage } from 'js-waku'; + +const ContentTopic = `/min-js-web-chat/1/chat/proto`; + +async function sendMessage(message, waku, timestamp) { + const time = timestamp.getTime(); + + // Encode to protobuf + const payload = proto.SimpleChatMessage.encode({ + timestamp: time, + text: message, + }); + + // Wrap in a Waku Message + const wakuMessage = await WakuMessage.fromBytes(payload, ContentTopic); + + // Send over Waku Relay + await waku.relay.send(wakuMessage); +} +``` + +Then, add a button to send messages to the `App` function: + +```js +function App() { + const [waku, setWaku] = React.useState(undefined); + const [wakuStatus, setWakuStatus] = React.useState('None'); + // Using a counter just for the messages to be different + const [sendCounter, setSendCounter] = React.useState(0); + + React.useEffect(() => { + // ... creates Waku + }, [waku, wakuStatus]); + + const sendMessageOnClick = () => { + if (wakuStatus !== 'Ready') return; + + sendMessage(`Here is message #${sendCounter}`, waku, new Date()).then(() => + console.log('Message sent') + ); + + setSendCounter(sendCounter + 1); + }; + + return ( +
+
+

{wakuStatus}

+ +
+
+ ); +} +``` + +# Receive Messages + +To process incoming messages, you need to register an observer on Waku Relay. +First, you need to define the function that acts as observer. + +As you are passing the function to Waku Relay, it is best to use `React.useCallback` so that the reference to the function remains the same: + +```js +const processIncomingMessage = React.useCallback((wakuMessage) => { + // Empty message? + if (!wakuMessage.payload) return; + + // Decode the protobuf payload + const { timestamp, text } = proto.SimpleChatMessage.decode( + wakuMessage.payload + ); + const time = new Date(); + time.setTime(timestamp); + + // For now, just log new messages on the console + console.log(`message received at ${time.toString()}: ${text}`); +}, []); +``` + +Then, add this function as an observer to Waku Relay. +Do not forget to delete the observer is the component is being unmounted: + +```js +React.useEffect(() => { + if (!waku) return; + + // Pass the content topic to only process messages related to your dApp + waku.relay.addObserver(processIncomingMessage, [ContentTopic]); + + // `cleanUp` is called when the component is unmounted, see ReactJS doc. + return function cleanUp() { + waku.relay.deleteObserver(processIncomingMessage, [ContentTopic]); + }; +}, [waku, wakuStatus, processIncomingMessage]); +``` + +# Display Messages + +The Waku work is now done. +Your dApp is able to send and receive messages using Waku. +For the sake of completeness, let's display received messages on the page. + +First, modify the `App()` function to store incoming messages: + +```js +function App() { + //... + + const [messages, setMessages] = React.useState([]); + + const processIncomingMessage = React.useCallback((wakuMessage) => { + if (!wakuMessage.payload) return; + + const { text, timestamp } = proto.SimpleChatMessage.decode( + wakuMessage.payload + ); + + const time = new Date(); + time.setTime(timestamp); + const message = { text, timestamp: time }; + + setMessages((currMessages) => { + return [message].concat(currMessages); + }); + }, []); + + // ... +} +``` +Then, add the messages to the rendering : + +```js +function App() { + // ... + + return ( +
+
+

{wakuStatus}

+ +
    + {messages.map((msg) => { + return ( +
  • +

    + {msg.timestamp.toString()}: {msg.text} +

    +
  • + ); + })} +
+
+
+ ); +} +``` + +And VoilĂ ! You should now be able to send and receive messages from two different browsers. + +You can see the complete code in the [Minimal JS Web Chat App](/examples/min-js-web-chat).