mirror of https://github.com/waku-org/js-waku.git
Merge pull request #292 from status-im/store-guide-react-js
This commit is contained in:
commit
898f93371c
|
@ -5,3 +5,4 @@ Here is the list of the code examples and the features they demonstrate:
|
||||||
- [Web Chat App](web-chat): Group chat, React/TypeScript, Relay, Store.
|
- [Web Chat App](web-chat): Group chat, React/TypeScript, Relay, Store.
|
||||||
- [Ethereum Private Message Web App](eth-pm): Private Messaging, React/TypeScript, Light Push, Signature with Web3, Asymmetric Encryption.
|
- [Ethereum Private Message Web App](eth-pm): Private Messaging, React/TypeScript, Light Push, Signature with Web3, Asymmetric Encryption.
|
||||||
- [Minimal ReactJS Chat App](min-react-js-chat): Group chat, React/JavaScript, Relay, Protobuf using `protons`.
|
- [Minimal ReactJS Chat App](min-react-js-chat): Group chat, React/JavaScript, Relay, Protobuf using `protons`.
|
||||||
|
- [Minimal ReactJS Waku Store App](store-reactjs-chat): Waku Store, React/JavaScript, Protobuf using `protons`.
|
||||||
|
|
|
@ -1,70 +1,19 @@
|
||||||
# Getting Started with Create React App
|
# Minimal ReactJS Waku Store App
|
||||||
|
|
||||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
- React/JavaScript,
|
||||||
|
- Waku Store,
|
||||||
|
- Protobuf using `protons`.
|
||||||
|
|
||||||
## Available Scripts
|
A simple app that retrieves chat messages using [Waku Store](https://rfc.vac.dev/spec/13/)
|
||||||
|
to illustrate the [Retrieve Messages Using Waku Store With ReactJS guide](/guides/reactjs-store.md).
|
||||||
|
|
||||||
In the project directory, you can run:
|
To run a development version locally, do:
|
||||||
|
|
||||||
### `npm start`
|
```shell
|
||||||
|
git clone https://github.com/status-im/js-waku/ ; cd js-waku
|
||||||
Runs the app in the development mode.\
|
npm install # Install dependencies for js-waku
|
||||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
npm run build # Build js-waku
|
||||||
|
cd examples/store-reactjs-chat
|
||||||
The page will reload if you make edits.\
|
npm install # Install dependencies for the web app
|
||||||
You will also see any lint errors in the console.
|
npm run start # Start development server to serve the web app on http://localhost:3000/
|
||||||
|
```
|
||||||
### `npm test`
|
|
||||||
|
|
||||||
Launches the test runner in the interactive watch mode.\
|
|
||||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
|
||||||
|
|
||||||
### `npm run build`
|
|
||||||
|
|
||||||
Builds the app for production to the `build` folder.\
|
|
||||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
|
||||||
|
|
||||||
The build is minified and the filenames include the hashes.\
|
|
||||||
Your app is ready to be deployed!
|
|
||||||
|
|
||||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
|
||||||
|
|
||||||
### `npm run eject`
|
|
||||||
|
|
||||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
|
||||||
|
|
||||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
|
||||||
|
|
||||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
|
||||||
|
|
||||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
|
||||||
|
|
||||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
|
||||||
|
|
||||||
### Code Splitting
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
|
||||||
|
|
||||||
### Analyzing the Bundle Size
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
|
||||||
|
|
||||||
### Making a Progressive Web App
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
|
||||||
|
|
||||||
### Advanced Configuration
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
|
||||||
|
|
||||||
### Deployment
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
|
||||||
|
|
||||||
### `npm run build` fails to minify
|
|
||||||
|
|
||||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ function App() {
|
||||||
const [messages, setMessages] = React.useState([]);
|
const [messages, setMessages] = React.useState([]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!!waku) return;
|
|
||||||
if (wakuStatus !== 'None') return;
|
if (wakuStatus !== 'None') return;
|
||||||
|
|
||||||
setWakuStatus('Starting');
|
setWakuStatus('Starting');
|
||||||
|
@ -30,39 +29,33 @@ function App() {
|
||||||
});
|
});
|
||||||
}, [waku, wakuStatus]);
|
}, [waku, wakuStatus]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!waku) return;
|
|
||||||
if (wakuStatus !== 'Connected to Store') return;
|
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
waku.store
|
|
||||||
.queryHistory([ContentTopic])
|
|
||||||
.catch((e) => {
|
|
||||||
// We may not be connected to a store node just yet
|
|
||||||
console.log('Failed to retrieve messages', e);
|
|
||||||
})
|
|
||||||
.then((retrievedMessages) => {
|
|
||||||
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
|
|
||||||
|
|
||||||
setMessages(messages);
|
|
||||||
});
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [waku, wakuStatus]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!waku) return;
|
if (!waku) return;
|
||||||
|
|
||||||
// We do not handle disconnection/re-connection in this example
|
// We do not handle disconnection/re-connection in this example
|
||||||
if (wakuStatus === 'Connected to Store') return;
|
if (wakuStatus === 'Connected') return;
|
||||||
|
|
||||||
waku.waitForConnectedPeer().then(() => {
|
waku.waitForConnectedPeer().then(() => {
|
||||||
// We are now connected to a store node
|
// We are now connected to a store node
|
||||||
setWakuStatus('Connected to Store');
|
setWakuStatus('Connected');
|
||||||
});
|
});
|
||||||
}, [waku, wakuStatus]);
|
}, [waku, wakuStatus]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (wakuStatus !== 'Connected') return;
|
||||||
|
|
||||||
|
waku.store
|
||||||
|
.queryHistory([ContentTopic])
|
||||||
|
.catch((e) => {
|
||||||
|
console.log('Failed to retrieve messages', e);
|
||||||
|
})
|
||||||
|
.then((retrievedMessages) => {
|
||||||
|
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
|
||||||
|
|
||||||
|
setMessages(messages);
|
||||||
|
});
|
||||||
|
}, [waku, wakuStatus]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<header className="App-header">
|
||||||
|
|
|
@ -9,7 +9,7 @@ For this guide, we are using a single content topic: `/min-react-js-chat/1/chat/
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
Create a new react app:
|
Create a new React app:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npx create-react-app min-react-js-chat
|
npx create-react-app min-react-js-chat
|
||||||
|
@ -100,7 +100,6 @@ React.useEffect(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [waku, wakuStatus]);
|
}, [waku, wakuStatus]);
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Define Message Format
|
# Define Message Format
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
# Retrieve Messages Using Waku Store With ReactJS
|
||||||
|
|
||||||
|
It is easy to use DappConnect with ReactJS.
|
||||||
|
In this guide, we will demonstrate how your ReactJS dApp can use Waku Store to retrieve messages.
|
||||||
|
|
||||||
|
DApps running on a phone or in a browser are often offline:
|
||||||
|
The browser could be closed or mobile app in the background.
|
||||||
|
|
||||||
|
[Waku Relay](https://rfc.vac.dev/spec/18/) is a gossip protocol.
|
||||||
|
As a user, it means that your peers forward you messages they just received.
|
||||||
|
If you cannot be reached by your peers, then messages are not relayed;
|
||||||
|
relay peers do **not** save messages for later.
|
||||||
|
|
||||||
|
However, [Waku Store](https://rfc.vac.dev/spec/13/) peers do save messages they relay,
|
||||||
|
allowing you to retrieve them at a later time.
|
||||||
|
The Waku Store protocol is best-effort and does not guarantee data availability.
|
||||||
|
Waku Relay should still be preferred when online;
|
||||||
|
Waku Store can be used after resuming connectivity:
|
||||||
|
For example, when the dApp starts.
|
||||||
|
|
||||||
|
In this guide, we'll review how you can use Waku Store to retrieve 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.
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
Create a new React app:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx create-react-app my-app
|
||||||
|
cd my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
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 status not None, it means we are already starting Waku
|
||||||
|
if (wakuStatus !== 'None') return;
|
||||||
|
|
||||||
|
setWakuStatus('Starting');
|
||||||
|
|
||||||
|
// Create Waku
|
||||||
|
Waku.create({ bootstrap: true }).then((waku) => {
|
||||||
|
// Once done, put it in the state
|
||||||
|
setWaku(waku);
|
||||||
|
// And update the status
|
||||||
|
setWakuStatus('Connecting');
|
||||||
|
});
|
||||||
|
}, [waku, wakuStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='App'>
|
||||||
|
<header className='App-header'>
|
||||||
|
// Display the status on the web page
|
||||||
|
<p>{wakuStatus}</p>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Wait to be connected
|
||||||
|
|
||||||
|
When using the `bootstrap` option, it may take some time to connect to other peers.
|
||||||
|
To ensure that you have store peers available to retrieve messages from,
|
||||||
|
use the `Waku.waitForConnectedPeer()` async function:
|
||||||
|
|
||||||
|
```js
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!waku) return;
|
||||||
|
|
||||||
|
if (wakuStatus === 'Connected') return;
|
||||||
|
|
||||||
|
waku.waitForConnectedPeer().then(() => {
|
||||||
|
setWakuStatus('Connected');
|
||||||
|
});
|
||||||
|
}, [waku, wakuStatus]);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Use Protobuf
|
||||||
|
|
||||||
|
Waku v2 protocols use [protobuf](https://developers.google.com/protocol-buffers/) [by default](https://rfc.vac.dev/spec/10/).
|
||||||
|
|
||||||
|
Let's review how you can use protobuf to decode structured data.
|
||||||
|
|
||||||
|
First, define a data structure.
|
||||||
|
For this guide, we will use a simple chat message that contains a timestamp, nick and text:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
timestamp: Date;
|
||||||
|
nick: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To encode and decode protobuf payloads, you can use the [protons](https://www.npmjs.com/package/protons) package.
|
||||||
|
|
||||||
|
## Install Protobuf Library
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install protons
|
||||||
|
```
|
||||||
|
|
||||||
|
## Protobuf Definition
|
||||||
|
|
||||||
|
Define the data structure with protons:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import protons from 'protons';
|
||||||
|
|
||||||
|
const proto = protons(`
|
||||||
|
message ChatMessage {
|
||||||
|
uint64 timestamp = 1;
|
||||||
|
string nick = 2;
|
||||||
|
bytes text = 3;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can learn about protobuf message definitions here:
|
||||||
|
[Protocol Buffers Language Guide](https://developers.google.com/protocol-buffers/docs/proto).
|
||||||
|
|
||||||
|
## Decode Messages
|
||||||
|
|
||||||
|
To decode the messages retrieved from a Waku Store node,
|
||||||
|
you need to extract the protobuf payload and decode it using `protons`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
function decodeMessage(wakuMessage) {
|
||||||
|
if (!wakuMessage.payload) return;
|
||||||
|
|
||||||
|
const { timestamp, nick, text } = proto.ChatMessage.decode(
|
||||||
|
wakuMessage.payload
|
||||||
|
);
|
||||||
|
|
||||||
|
// All fields in protobuf are optional so be sure to check
|
||||||
|
if (!timestamp || !text || !nick) return;
|
||||||
|
|
||||||
|
const time = new Date();
|
||||||
|
time.setTime(timestamp);
|
||||||
|
|
||||||
|
const utf8Text = Buffer.from(text).toString('utf-8');
|
||||||
|
|
||||||
|
return { text: utf8Text, timestamp: time, nick };
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Retrieve messages
|
||||||
|
|
||||||
|
You now have all the building blocks to retrieve and decode messages for a store node.
|
||||||
|
|
||||||
|
Finally, retrieve messages from a store node:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const ContentTopic = '/toy-chat/2/huilong/proto';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
// [..]
|
||||||
|
// Store messages in the state
|
||||||
|
const [messages, setMessages] = React.useState([]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (wakuStatus !== 'Connected') return;
|
||||||
|
|
||||||
|
waku.store
|
||||||
|
.queryHistory([ContentTopic])
|
||||||
|
.catch((e) => {
|
||||||
|
console.log('Failed to retrieve messages', e);
|
||||||
|
})
|
||||||
|
.then((retrievedMessages) => {
|
||||||
|
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
|
||||||
|
|
||||||
|
setMessages(messages);
|
||||||
|
});
|
||||||
|
}, [waku, wakuStatus]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='App'>
|
||||||
|
<header className='App-header'>
|
||||||
|
<h2>{wakuStatus}</h2>
|
||||||
|
<h3>Messages</h3>
|
||||||
|
<ul>
|
||||||
|
<Messages messages={messages} />
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `WakuStore.queryHistory` select an available store node for you.
|
||||||
|
However, it can only select a connected node, which is why the bootstrapping is necessary.
|
||||||
|
It will throw an error if no store node is available.
|
||||||
|
|
||||||
|
You can see the complete code in the [Minimal ReactJS Waku Store App](/examples/store-reactjs-chat).
|
|
@ -17,7 +17,6 @@ For example, when the dApp starts.
|
||||||
|
|
||||||
In this guide, we'll review how you can use Waku Store to retrieve messages.
|
In this guide, we'll review how you can use Waku Store to retrieve messages.
|
||||||
|
|
||||||
|
|
||||||
Before starting, you need to choose a _Content Topic_ for your dApp.
|
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.
|
Check out the [how to choose a content topic guide](choose-content-topic.md) to learn more about content topics.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue