Merge pull request #338 from status-im/use-docs-website

Use docs website
This commit is contained in:
F 2021-12-13 13:08:03 +11:00 committed by GitHub
commit 4574e2e0b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 15 additions and 1581 deletions

View File

@ -69,6 +69,7 @@
"reactjs", "reactjs",
"recid", "recid",
"rlnrelay", "rlnrelay",
"roadmap",
"sandboxed", "sandboxed",
"secio", "secio",
"seckey", "seckey",

296
README.md
View File

@ -9,312 +9,38 @@ A JavaScript implementation of the [Waku v2 protocol](https://rfc.vac.dev/spec/1
## Documentation ## Documentation
In the [section below](#usage) you can find explanations for the main API. - [Quick start](https://docs.dappconnect.dev/docs/quick_start/)
- [Full documentation](https://docs.dappconnect.dev/)
- [API documentation (`main` branch)](https://status-im.github.io/js-waku/docs/)
We also have [guides](https://github.com/status-im/js-waku/blob/main/guides/menu.md) available API Documentation can also be generated locally:
and [code examples](https://github.com/status-im/js-waku/blob/main/examples/examples.md).
You can read the latest `main` branch documentation at [https://status-im.github.io/js-waku/docs/](https://status-im.github.io/js-waku/docs/).
Docs can also be generated locally using:
```shell ```shell
git clone https://github.com/status-im/js-waku.git
cd js-waku
npm install npm install
npm run doc npm run doc
``` ```
## Usage
Install `js-waku` package:
```shell
npm install js-waku
```
### Import js-waku
To use js-waku in your application, you can:
use `import`:
```js
import { Waku } from 'js-waku';
const waku = await Waku.create();
```
use `require`:
```js
const jsWaku = require('js-waku');
jsWaku.Waku.create().then(waku => {
// ...
});
```
Or directly import it in a `<script>` tag:
```html
<script src='https://unpkg.com/js-waku@latest/build/umd/js-waku.min.bundle.js'></script>
<script>
jswaku.Waku.create().then(waku => {
// ...
}
</script>
```
### Start a waku node
```ts
const waku = await Waku.create({ bootstrap: true });
```
### Listen for messages
The `contentTopic` is a metadata `string` that allows categorization of messages on the waku network.
Depending on your use case, you can either create one (or several) new `contentTopic`(s) or look at the [RFCs](https://rfc.vac.dev/) and use an existing `contentTopic`.
See [How to Choose a Content Topic](./guides/choose-content-topic.md) for more details.
For example, if you were to use a new `contentTopic` such as `/my-cool-app/1/my-use-case/proto`,
here is how to listen to new messages received via [Waku v2 Relay](https://rfc.vac.dev/spec/11/):
```ts
waku.relay.addObserver((msg) => {
console.log("Message received:", msg.payloadAsUtf8)
}, ["/my-cool-app/1/my-use-case/proto"]);
```
The examples chat apps currently use content topic `"/toy-chat/2/huilong/proto"`.
### Send messages
There are two ways to send messages:
#### Waku Relay
[Waku Relay](https://rfc.vac.dev/spec/11/) is the most decentralized option,
peer receiving your messages are unlikely to know whether you are the originator or simply forwarding them.
However, it does not give you any delivery information.
```ts
import { WakuMessage } from 'js-waku';
const msg = await WakuMessage.fromUtf8String("Here is a message!", "/my-cool-app/1/my-use-case/proto")
await waku.relay.send(msg);
```
#### Waku Light Push
[Waku Light Push](https://rfc.vac.dev/spec/19/) gives you confirmation that the light push server node has
received your message.
However, it means that said node knows you are the originator of the message.
It cannot guarantee that the node will forward the message.
```ts
const ack = await waku.lightPush.push(message);
if (!ack?.isSuccess) {
// Message was not sent
}
```
### Retrieve archived messages
The [Waku v2 Store protocol](https://rfc.vac.dev/spec/13/) enables more permanent nodes to store messages received via relay
and ephemeral clients to retrieve them (e.g. mobile phone resuming connectivity).
The protocol implements pagination meaning that it may take several queries to retrieve all messages.
Query a waku store peer to check historical messages:
```ts
// Process messages once they are all retrieved
const messages = await waku.store.queryHistory(['/my-cool-app/1/my-use-case/proto']);
messages.forEach((msg) => {
console.log('Message retrieved:', msg.payloadAsUtf8);
});
// Or, pass a callback function to be executed as pages are received:
waku.store.queryHistory(['/my-cool-app/1/my-use-case/proto'], {
callback: (messages) => {
messages.forEach((msg) => {
console.log('Message retrieved:', msg.payloadAsUtf8);
});
}
});
```
### Encryption & Signature
With js-waku, you can:
- Encrypt messages over the wire using public/private key pair (asymmetric encryption),
- Encrypt messages over the wire using a unique key to both encrypt and decrypt (symmetric encryption),
- Sign and verify your waku messages (must use encryption, compatible with both symmetric and asymmetric).
#### Cryptographic Libraries
A quick note on the cryptographic libraries used as it is a not a straightforward affair:
- Asymmetric encryption:
Uses [ecies-geth](https://github.com/cyrildever/ecies-geth/)
which in turns uses [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) Web API (browser),
[secp256k1](https://www.npmjs.com/package/secp256k1) (native binding for node)
or [elliptic](https://www.npmjs.com/package/elliptic) (pure JS if none of the other libraries are available).
- Symmetric encryption:
Uses [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) Web API (browser)
or [NodeJS' crypto](https://nodejs.org/api/crypto.html) module.
### Create new keys
Asymmetric private keys and symmetric keys are expected to be 32 bytes arrays.
```ts
import { generatePrivateKey, generateSymmetricKey, getPublicKey } from 'js-waku';
// Asymmetric
const privateKey = generatePrivateKey();
const publicKey = getPublicKey(privateKey);
// Symmetric
const symKey = generateSymmetricKey();
```
#### Encrypt Waku Messages
To encrypt your waku messages, simply pass the encryption key when creating it:
```ts
import { WakuMessage } from "js-waku";
// Asymmetric
const message1 = await WakuMessage.fromBytes(payload, myAppContentTopic, {
encPublicKey: publicKey,
});
// Symmetric
const message2 = await WakuMessage.fromBytes(payload, myAppContentTopic, {
symKey: symKey,
});
```
#### Decrypt Waku Messages
##### Waku Relay
If you expect to receive encrypted messages then simply add private decryption key(s) to `WakuRelay`.
Waku Relay will attempt to decrypt incoming messages with each keys, both for symmetric and asymmetric encryption.
Messages that are successfully decrypted (or received in clear) will be passed to the observers, other messages will be omitted.
```ts
// Asymmetric
waku.relay.addDecryptionKey(privateKey);
// Symmetric
waku.relay.addDecryptionKey(symKey);
// Then add the observer
waku.relay.addObserver(callback, [contentTopic]);
```
Keys can be removed using `WakuMessage.deleteDecryptionKey`.
##### Waku Store
```ts
const messages = await waku.store.queryHistory([], {
decryptionKeys: [privateKey, symKey]
});
```
Similarly to relay, only decrypted or clear messages will be returned.
#### Sign Waku Messages
As per version 1`s [specs](https://rfc.vac.dev/spec/26/), signatures are only included in encrypted messages.
In the case where your app does not need encryption then you could use symmetric encryption with a trivial key, I intend to dig [more on the subject](https://github.com/status-im/js-waku/issues/74#issuecomment-880440186) and come back with recommendation and examples.
Signature keys can be generated the same way asymmetric keys for encryption are:
```ts
import { generatePrivateKey, getPublicKey, WakuMessage } from 'js-waku';
const signPrivateKey = generatePrivateKey();
// Asymmetric Encryption
const message1 = await WakuMessage.fromBytes(payload, myAppContentTopic, {
encPublicKey: recipientPublicKey,
sigPrivKey: signPrivateKey
});
// Symmetric Encryption
const message2 = await WakuMessage.fromBytes(payload, myAppContentTopic, {
encPublicKey: symKey,
sigPrivKey: signPrivateKey
});
```
#### Verify Waku Message signatures
Two fields are available on `WakuMessage` regarding signatures:
- `signaturePublicKey`: If the message is signed, it holds the public key of the signature,
- `signature`: If the message is signed, it holds the actual signature.
Thus, if you expect messages to be signed by Alice,
you can simply compare `WakuMessage.signaturePublicKey` with Alice's public key.
As comparing hex string can lead to issues (is the `0x` prefix present?),
simply use helper function `equalByteArrays`.
```ts
import { equalByteArrays } from 'js-waku/lib/utils';
const sigPubKey = wakuMessage.signaturePublicKey;
const isSignedByAlice = sigPubKey && equalByteArrays(sigPubKey, alicePublicKey);
```
## Changelog ## Changelog
Release changelog can be found [here](https://github.com/status-im/js-waku/blob/main/CHANGELOG.md). Release changelog can be found in [CHANGELOG.md](https://github.com/status-im/js-waku/blob/main/CHANGELOG.md).
## Bugs, Questions & Features ## Bugs, Questions & Features
If you encounter any bug or would like to propose new features, feel free to [open an issue](https://github.com/status-im/js-waku/issues/new/). If you encounter any bug or would like to propose new features, feel free to [open an issue](https://github.com/status-im/js-waku/issues/new/).
To get help, join #dappconnect-support on [Vac Discord](https://discord.gg/j5pGbn7MHZ) or [Telegram](https://t.me/dappconnectsupport). To get help, join **#dappconnect-support** on [Vac Discord](https://discord.gg/j5pGbn7MHZ) or [Telegram](https://t.me/dappconnectsupport).
For more general discussion and latest news, join #dappconnect on [Vac Discord](https://discord.gg/9DgykdmpZ6) or [Telegram](https://t.me/dappconnect). For more general discussion and latest news, join **#dappconnect** on [Vac Discord](https://discord.gg/9DgykdmpZ6) or [Telegram](https://t.me/dappconnect).
## Waku Protocol Support ## Roadmap
You can track progress on the [project board](https://github.com/status-im/js-waku/projects/1). You can track progress on the [project board](https://github.com/status-im/js-waku/projects/1).
- ✔: Supported
- 🚧: Implementation in progress
- ⛔: Support is not planned
| Spec | Implementation Status |
| ---- | -------------- |
|[6/WAKU1](https://rfc.vac.dev/spec/6)|⛔|
|[7/WAKU-DATA](https://rfc.vac.dev/spec/7)|⛔|
|[8/WAKU-MAIL](https://rfc.vac.dev/spec/8)|⛔|
|[9/WAKU-RPC](https://rfc.vac.dev/spec/9)|⛔|
|[10/WAKU2](https://rfc.vac.dev/spec/10)|🚧|
|[11/WAKU2-RELAY](https://rfc.vac.dev/spec/11)|✔|
|[12/WAKU2-FILTER](https://rfc.vac.dev/spec/12)||
|[13/WAKU2-STORE](https://rfc.vac.dev/spec/13)|✔ (querying node only)|
|[14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14)|✔|
|[15/WAKU2-BRIDGE](https://rfc.vac.dev/spec/15)||
|[16/WAKU2-RPC](https://rfc.vac.dev/spec/16)|⛔|
|[17/WAKU2-RLNRELAY](https://rfc.vac.dev/spec/17)||
|[18/WAKU2-SWAP](https://rfc.vac.dev/spec/18)||
|[19/WAKU2-LIGHTPUSH](https://rfc.vac.dev/spec/19/)|✔|
## Contributing ## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md). See [CONTRIBUTING.md](https://github.com/status-im/js-waku/blob/main/CONTRIBUTING.md).
## License ## License
Licensed and distributed under either of Licensed and distributed under either of

View File

@ -7,3 +7,5 @@ Here is the list of the code examples and the features they demonstrate:
- [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`. - [Minimal ReactJS Waku Store App](store-reactjs-chat): Waku Store, React/JavaScript, Protobuf using `protons`.
- [Pure Javascript Using Minified Library](unpkg-js-store): Stop retrieving results from Waku Store on condition, Use minified bundle from Unpkg.com, JavaScript. - [Pure Javascript Using Minified Library](unpkg-js-store): Stop retrieving results from Waku Store on condition, Use minified bundle from Unpkg.com, JavaScript.
See https://docs.dappconnect.dev/docs/examples/ for more examples.

1
guides/README.md Normal file
View File

@ -0,0 +1 @@
Moved to https://docs.dappconnect.dev/guides/index.html

View File

@ -1,26 +0,0 @@
# How to Choose a Content Topic
A content topic is used for content based filtering.
It allows you to filter out the messages that your dApp processes,
both when receiving live messages (Relay) or retrieving historical messages (Store).
The format for content topics is as follows:
`/{dapp-name}/{version}/{content-topic-name}/{encoding}`
- `dapp-name`: The name of your dApp, it must be unique to avoid conflict with other dApps.
- `version`: We usually start at `1`, useful when introducing breaking changes in your messages.
- `content-topic-name`: The actual content topic name to use for filtering.
If your dApp uses DappConnect for several features,
you should use a content topic per feature.
- `encoding`: The encoding format of the message, Protobuf is most often used: `proto`.
For example: Your dApp's name is SuperCrypto,
it enables users to receive notifications and send private messages.
You may want to use the following content topics:
- `/supercrypto/1/notification/proto`
- `/supercrypto/1/private-message/proto`
You can learn more about Waku topics in the [23/WAKU2-TOPICS](https://rfc.vac.dev/spec/23/) specs.

View File

@ -1,238 +0,0 @@
# Encrypt Messages Using Waku Message Version 1
The Waku Message format provides an easy way to encrypt messages using symmetric or asymmetric encryption.
The encryption comes with several handy [design requirements](https://rfc.vac.dev/spec/26/#design-requirements):
confidentiality, authenticity and integrity.
You can find more details about Waku Message Payload Encryption in [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/).
## What data is encrypted
With Waku Message Version 1, the entire payload is encrypted.
Which means that the only discriminating data available in clear text is the content topic and timestamp (if present).
Hence, if Alice expects to receive messages under a given content topic, she needs to try to decrypt all messages received on said content topic.
This needs to be kept in mind for scalability and forward secrecy concerns:
- If there is high traffic on a given content topic then all clients need to process and attempt decryption of all messages with said content topic;
- If a content topic is only used by a given (group of) user(s) then it is possible to deduce some information about said user(s) communications such as sent time and frequency of messages.
## Key management
By using Waku Message Version 1, you will need to provide a way to your users to generate and store keys in a secure manner.
Storing, backing up and recovering key is out of the scope of this guide.
If key recovery is important for your dApp, then check out
[SubtleCrypto.wrapKey()](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/wrapKey) which can be used to securely store or export private keys.
An example to save and load a key pair in local storage, protected with a password, can be found in [Eth-PM](https://github.com/status-im/js-waku/blob/main/examples/eth-pm/src/key_pair_handling/key_pair_storage.ts).
## Which encryption method should I use?
Whether you should use symmetric or asymmetric encryption depends on your use case.
**Symmetric** encryption is done using a single key to encrypt and decrypt.
Which means that if Alice knows the symmetric key `K` and uses it to encrypt a message,
she can also use `K` to decrypt any message encrypted with `K`,
even if she is not the sender.
Group chats is a possible use case for symmetric encryption:
All participants can use an out-of-band method to agree on a `K`.
Participants can then use `K` to encrypt and decrypt messages within the group chat.
Participants MUST keep `K` secret to ensure that no external party can decrypt the group chat messages.
**Asymmetric** encryption is done using a key pair:
the public key is used to encrypt messages,
the matching private key is used to decrypt messages.
For Alice to encrypt a message for Bob, she needs to know Bob's Public Key `K`.
Bob can then use his private key `k` to decrypt the message.
As long as Bob keep his private key `k` secret, then he, and only he, can decrypt messages encrypted with `K`.
Private 1:1 messaging is a possible use case for asymmetric encryption:
When Alice sends an encrypted message for Bob, only Bob can decrypt it.
## Symmetric Encryption
### Generate Key
To use symmetric encryption, you first need to generate a key.
Use `generateSymmetricKey` for secure key generation:
```js
import { generateSymmetricKey } from 'js-waku';
const symmetricKey = generateSymmetricKey();
```
### Encrypt Message
To encrypt a message with the previously generated key,
pass the key in the `symKey` property to `WakuMessage.fromBytes`.
Same as Waku Messages version 0 (unencrypted),
`payload` is your message payload and `contentTopic` is the content topic for your dApp.
See [Receive and Send Messages Using Waku Relay](relay-receive-send-messages.md) for details.
```js
import { WakuMessage } from 'js-waku';
const message = await WakuMessage.fromBytes(payload, contentTopic, {
symKey: symmetricKey
});
```
The Waku Message can then be sent to the Waku network using [Waku Relay](relay-receive-send-messages.md) or Waku Light Push:
```js
await waku.lightPush.push(message);
```
### Decrypt Messages
To decrypt messages,
whether they are received over Waku Relay or using Waku Store,
add the symmetric key as a decryption key to your Waku instance.
```js
waku.addDecryptionKey(symmetricKey);
```
Alternatively, you can pass the key when creating the instance:
```js
import { Waku } from 'js-waku';
const waku = Waku.create({ decryptionKeys: [symmetricKey] });
```
It will attempt to decrypt any message it receives using the key, for both symmetric and asymmetric encryption.
You can call `addDecryptionKey` several times if you are using multiple keys,
symmetric key and asymmetric private keys can be used together.
Messages that are not successfully decrypted are dropped.
## Asymmetric Encryption
### Generate Key Pair
To use asymmetric encryption, you first need to generate a private key and calculate the corresponding public key.
Use `generatePrivateKey` for secure key generation:
```js
import { generatePrivateKey, getPublicKey } from 'js-waku';
const privateKey = generatePrivateKey();
const publicKey = getPublicKey(privateKey);
```
The private key must be securely stored and remain private.
If leaked then other parties may be able to decrypt the user's messages.
The public key is unique for a given private key and can always be recovered given the private key,
hence it is not needed to save it as long as as the private key can be recovered.
### Encrypt Message
The public key is used to encrypt messages;
to do so, pass it in the `encPublicKey` property to `WakuMessage.fromBytes`.
Same as clear Waku Messages,
`payload` is your message payload and `contentTopic` is the content topic for your dApp.
See [Receive and Send Messages Using Waku Relay](relay-receive-send-messages.md) for details.
```js
import { WakuMessage } from 'js-waku';
const message = await WakuMessage.fromBytes(payload, contentTopic, {
encPublicKey: publicKey
});
```
The Waku Message can then be sent to the Waku network using [Waku Relay](relay-receive-send-messages.md) or Waku Light Push:
```js
await waku.lightPush.push(message);
```
### Decrypt Messages
The private key is needed to decrypt messages.
To decrypt messages,
whether they are received over Waku Relay or using Waku Store,
add the private key as a decryption key to your Waku instance.
```js
waku.addDecryptionKey(privateKey);
```
Alternatively, you can pass the key when creating the instance:
```js
import { Waku } from 'js-waku';
const waku = Waku.create({ decryptionKeys: [privateKey] });
```
It will attempt to decrypt any message it receives using the key, for both symmetric and asymmetric encryption.
You can call `addDecryptionKey` several times if you are using multiple keys,
symmetric key and asymmetric private keys can be used together.
Messages that are not successfully decrypted are dropped.
## Handling `WakuMessage` instances
When creating a Waku Message using `WakuMessage.fromBytes` with an encryption key (symmetric or asymmetric),
the payload gets encrypted.
Which means that `wakuMessage.payload` returns an encrypted payload:
```js
import { WakuMessage } from 'js-waku';
const message = await WakuMessage.fromBytes(payload, contentTopic, {
encPublicKey: publicKey
});
console.log(message.payload); // This is encrypted
```
However, `WakuMessage` instances returned by `WakuRelay` or `WakuStore` are always decrypted.
`WakuRelay` and `WakuStore` never return messages that are encrypted.
If a message was not successfully decrypted, then it will be dropped from the results.
Which means that `WakuMessage` instances returned by `WakuRelay` and `WakuStore` always have a clear payload (in regard to Waku Message version 1):
```js
import { Waku } from 'js-waku';
const waku = Waku.create({ decryptionKeys: [privateKey] });
const messages = await waku.store.queryHistory([contentTopic]);
if (messages && messages[0]) {
console.log(messages[0].payload); // This payload is decrypted
}
waku.relay.addObserver((message) => {
console.log(message.payload); // This payload is decrypted
}, [contentTopic]);
```
## Code Example
The [Eth-PM](https://github.com/status-im/js-waku/tree/main/examples/eth-pm) Web App example demonstrates both the use of symmetric and asymmetric encryption.
Asymmetric encryption is used for private messages so that only the intended recipient can read said messages.
Symmetric encryption is used for the public key messages.
In this instance, the same key is used for all users: the Keccak-256 hash of the content topic (which results in 32 bytes array).
While this does not add functional value, it does demonstrate the usage of symmetric encryption in a web app.
A live version of Eth-PM can be found at https://status-im.github.io/js-waku/eth-pm/.
The specifications of the protocol it implements can be found at [20/TOY-ETH-PM](https://rfc.vac.dev/spec/20/).

View File

@ -1,7 +0,0 @@
# Guides
- [Receive and Send Messages Using Waku Relay](relay-receive-send-messages.md)
- [How to Choose a Content Topic](choose-content-topic.md)
- [Receive and Send Messages Using Waku Relay With ReactJS](reactjs-relay.md)
- [Retrieve Messages Using Waku Store](store-retrieve-messages.md)
- [Encrypt Messages Using Waku Message Version 1](encrypt-messages-version-1.md)

View File

@ -1,300 +0,0 @@
# 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 single content topic: `/min-react-js-chat/1/chat/proto`.
# Setup
Create a new React app:
```shell
npx create-react-app min-react-js-chat
cd min-react-js-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 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('Started');
});
}, [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 relay peers available to send and receive messages,
use the `Waku.waitForConnectedPeer()` async function:
```js
React.useEffect(() => {
if (!!waku) return;
if (wakuStatus !== 'None') return;
setWakuStatus('Starting');
Waku.create({ bootstrap: true }).then((waku) => {
setWaku(waku);
setWakuStatus('Connecting');
waku.waitForConnectedPeer().then(() => {
setWakuStatus('Ready');
});
});
}, [waku, wakuStatus]);
```
# 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-react-js-chat/1/chat/proto`;
function sendMessage(message, timestamp, waku) {
const time = timestamp.getTime();
// Encode to protobuf
const payload = proto.SimpleChatMessage.encode({
timestamp: time,
text: message
});
// Wrap in a Waku Message
return WakuMessage.fromBytes(payload, ContentTopic).then((wakuMessage) =>
// Send over Waku Relay
waku.relay.send(wakuMessage)
);
}
```
Then, add a button 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 = () => {
// Check Waku is started and connected first.
if (wakuStatus !== 'Ready') return;
sendMessage(`Here is message #${sendCounter}`, waku, new Date()).then(() =>
console.log('Message sent')
);
// For demonstration purposes.
setSendCounter(sendCounter + 1);
};
return (
<div className="App">
<header className="App-header">
<p>{wakuStatus}</p>
<button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}> // Grey the button is Waku is not yet ready.
Send Message
</button>
</header>
</div>
);
}
```
# Receive Messages
To process incoming messages, you need to register an observer on Waku Relay.
First, you need to define the observer function.
You will need to remove the observer when the component unmount.
Hence, you need the reference to the function to remain the same.
For that, use `React.useCallback`:
```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 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, add incoming messages to the state of the `App` component:
```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((messages) => {
return [message].concat(messages);
});
}, []);
// ...
}
```
Then, render the messages:
```js
function App() {
// ...
return (
<div className="App">
<header className="App-header">
<p>{wakuStatus}</p>
<button onClick={sendMessageOnClick} disabled={wakuStatus !== 'Ready'}>
Send Message
</button>
<ul>
{messages.map((msg) => {
return (
<li>
<p>
{msg.timestamp.toString()}: {msg.text}
</p>
</li>
);
})}
</ul>
</header>
</div>
);
}
```
And Voilà! You should now be able to send and receive messages.
Try out by opening the app from different browsers.
You can see the complete code in the [Minimal ReactJS Chat App](/examples/min-react-js-chat).

View File

@ -1,296 +0,0 @@
# 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.
Note that Waku Store queries are paginated.
The API provided by `js-waku` automatically traverses all pages of the Waku Store response.
By default, the most recent page is retrieved first but this can be changed with the `pageDirection` option.
First, define a React state to save the messages:
```js
function App() {
const [messages, setMessages] = React.useState([]);
/// [..]
}
```
Then, define `processMessages` to decode and then store messages in the React state.
You will pass `processMessages` as a `callback` option to `WakuStore.queryHistory`.
`processMessages` will be called each time a page is received from the Waku Store.
```js
const processMessages = (retrievedMessages) => {
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
setMessages((currentMessages) => {
return currentMessages.concat(messages.reverse());
});
};
```
Finally, pass `processMessage` in `WakuStore.queryHistory` as the `callback` value:
```js
waku.store
.queryHistory([ContentTopic], { callback: processMessages });
```
All together, you should now have:
```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;
const processMessages = (retrievedMessages) => {
const messages = retrievedMessages.map(decodeMessage).filter(Boolean);
setMessages((currentMessages) => {
return currentMessages.concat(messages.reverse());
});
};
waku.store
.queryHistory([ContentTopic], { callback: processMessages })
.catch((e) => {
console.log('Failed to retrieve messages', e);
});
}, [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.
## Filter messages by send time
By default, Waku Store nodes store messages for 30 days.
Depending on your use case, you may not need to retrieve 30 days worth of messages.
[Waku Message](https://rfc.vac.dev/spec/14/) defines an optional unencrypted `timestamp` field.
The timestamp is set by the sender.
By default, js-waku [sets the timestamp of outgoing message to the current time](https://github.com/status-im/js-waku/blob/a056227538f9409aa9134c7ef0df25f602dbea58/src/lib/waku_message/index.ts#L76).
You can filter messages that include a timestamp within given bounds with the `timeFilter` option.
Retrieve messages up to a week old:
```js
const startTime = new Date();
// 7 days/week, 24 hours/day, 60min/hour, 60secs/min, 100ms/sec
startTime.setTime(startTime.getTime() - 7 * 24 * 60 * 60 * 1000);
waku.store
.queryHistory([ContentTopic], {
callback: processMessages,
timeFilter: { startTime, endTime: new Date() }
});
```
## End result
You can see the complete code in the [Minimal ReactJS Waku Store App](/examples/store-reactjs-chat).

View File

@ -1,227 +0,0 @@
# Receive and Send Messages Using Waku Relay
Waku Relay is a gossip protocol that enables you to send and receive messages.
You can find Waku Relay's specifications on [Vac RFC](https://rfc.vac.dev/spec/11/).
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 single content topic: `/relay-guide/1/chat/proto`.
# Installation
You can install [js-waku](https://npmjs.com/package/js-waku) using your favorite package manager:
```shell
npm install js-waku
```
# Create Waku Instance
In order to interact with the Waku network, you first need a Waku instance:
```js
import { Waku } from 'js-waku';
const wakuNode = await Waku.create({ bootstrap: true });
```
Passing the `bootstrap` option will connect your node to predefined Waku nodes.
If you want to bootstrap to your own nodes, you can pass an array of multiaddresses instead:
```js
import { Waku } from 'js-waku';
const wakuNode = await Waku.create({
bootstrap: [
'/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm',
'/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'
]
});
```
# Wait to be connected
When using the `bootstrap` option, it may take some time to connect to other peers.
To ensure that you have relay peers available to send and receive messages,
use the following function:
```js
await waku.waitForConnectedPeer();
```
The returned Promise will resolve once you are connected to a Waku Relay peer.
# Receive messages
To watch messages for your app, you need to register an observer on relay for your app's content topic:
```js
const processIncomingMessage = (wakuMessage) => {
console.log(`Message Received: ${wakuMessage.payloadAsUtf8}`);
};
waku.relay.addObserver(processIncomingMessage, ['/relay-guide/1/chat/proto']);
```
# Send Messages
You are now ready to send messages.
Let's start by sending simple strings as messages.
To send a message, you need to wrap the message in a `WakuMessage`.
When using a basic string payload, you can use the `WakuMessage.fromUtf8String` helper:
```js
import { WakuMessage } from 'js-waku';
const wakuMessage = await WakuMessage.fromUtf8String('Here is a message', `/relay-guide/1/chat/proto`);
```
Then, use the `relay` module to send the message to our peers,
the message will then be relayed to the rest of the network thanks to Waku Relay:
```js
await waku.relay.send(wakuMessage);
```
# Use Protobuf
Sending strings as messages in unlikely to cover your dApps needs.
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 include structured objects in Waku Messages.
First, define a data structure.
For this guide, we will use a simple chat message that contains a timestamp and text:
```js
{
timestamp: Date;
text: string;
}
```
To encode and decode protobuf payloads, you can use the [protons](https://www.npmjs.com/package/protons) package.
## Install Protobuf Library
First, install protons:
```shell
npm install protons
```
## Protobuf Definition
Then define the simple chat message:
```js
import protons from 'protons';
const proto = protons(`
message SimpleChatMessage {
uint64 timestamp = 1;
string text = 2;
}
`);
```
You can learn about protobuf message definitions here:
[Protocol Buffers Language Guide](https://developers.google.com/protocol-buffers/docs/proto).
## Encode Messages
Instead of wrapping an utf-8 string in a Waku Message,
you are going to wrap a protobuf payload.
First, encode the object:
```js
const payload = proto.SimpleChatMessage.encode({
timestamp: Date.now(),
text: 'Here is a message'
});
```
Then, wrap it in a Waku Message:
```js
const wakuMessage = await WakuMessage.fromBytes(payload, ContentTopic);
```
Now, you can send the message over Waku Relay the same way than before:
```js
await waku.relay.send(wakuMessage);
```
## Decode Messages
To decode the messages received over Waku Relay,
you need to extract the protobuf payload and decode it using `protons`.
```js
const processIncomingMessage = (wakuMessage) => {
// No need to attempt to decode a message if the payload is absent
if (!wakuMessage.payload) return;
const { timestamp, text } = proto.SimpleChatMessage.decode(
wakuMessage.payload
);
console.log(`Message Received: ${text}, sent at ${timestamp.toString()}`);
};
```
Like before, add this callback as an observer to Waku Relay:
```js
waku.relay.addObserver(processIncomingMessage, ['/relay-guide/1/chat/proto']);
```
# Conclusion
That is it! Now, you know how to send and receive messages over Waku using the Waku Relay protocol.
Feel free to check out other [guides](menu.md) or [examples](/examples/examples.md).
Here is the final code:
```js
import { getBootstrapNodes, Waku, WakuMessage } from 'js-waku';
import protons from 'protons';
const proto = protons(`
message SimpleChatMessage {
uint64 timestamp = 1;
string text = 2;
}
`);
const wakuNode = await Waku.create();
const nodes = await getBootstrapNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr)));
const processIncomingMessage = (wakuMessage) => {
// No need to attempt to decode a message if the payload is absent
if (!wakuMessage.payload) return;
const { timestamp, text } = proto.SimpleChatMessage.decode(
wakuMessage.payload
);
console.log(`Message Received: ${text}, sent at ${timestamp.toString()}`);
};
waku.relay.addObserver(processIncomingMessage, ['/relay-guide/1/chat/proto']);
const payload = proto.SimpleChatMessage.encode({
timestamp: Date.now(),
text: 'Here is a message'
});
const wakuMessage = await WakuMessage.fromBytes(payload, ContentTopic);
await waku.relay.send(wakuMessage);
```

View File

@ -1,202 +0,0 @@
# Retrieve Messages Using Waku Store
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.
For this guide, we are using a single content topic: `/store-guide/1/news/proto`.
# Installation
You can install [js-waku](https://npmjs.com/package/js-waku) using your favorite package manager:
```shell
npm install js-waku
```
# Create Waku Instance
In order to interact with the Waku network, you first need a Waku instance:
```js
import { Waku } from 'js-waku';
const wakuNode = await Waku.create({ bootstrap: true });
```
Passing the `bootstrap` option will connect your node to predefined Waku nodes.
If you want to bootstrap to your own nodes, you can pass an array of multiaddresses instead:
```js
import { Waku } from 'js-waku';
const wakuNode = await Waku.create({
bootstrap: [
'/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm',
'/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'
]
});
```
# Wait to be connected
When using the `bootstrap` option, it may take some times to connect to other peers.
To ensure that you have store peers available to retrieve historical messages from,
use the following function:
```js
await waku.waitForConnectedPeer();
```
The returned Promise will resolve once you are connected to a Waku Store peer.
# 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 send structured data.
First, define a data structure.
For this guide, we will use a simple news article that contains a date of publication, title and body:
```js
{
date: Date;
title: string;
body: string;
}
```
To encode and decode protobuf payloads, you can use the [protons](https://www.npmjs.com/package/protons) package.
## Install Protobuf Library
First, install protons:
```shell
npm install protons
```
## Protobuf Definition
Then specify the data structure:
```js
import protons from 'protons';
const proto = protons(`
message ArticleMessage {
uint64 date = 1;
string title = 2;
string body = 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
const decodeWakuMessage = (wakuMessage) => {
// No need to attempt to decode a message if the payload is absent
if (!wakuMessage.payload) return;
const { date, title, body } = proto.SimpleChatMessage.decode(
wakuMessage.payload
);
// In protobuf, fields are optional so best to check
if (!date || !title || !body) return;
const publishDate = new Date();
publishDate.setTime(date);
return { publishDate, title, body };
};
```
## Retrieve messages
You now have all the building blocks to retrieve and decode messages for a store node.
Store node responses are paginated.
The `WakuStore.queryHistory` API automatically query all the pages in a sequential manner.
To process messages as soon as they received (page by page), use the `callback` option:
```js
const ContentTopic = '/store-guide/1/news/proto';
const callback = (retrievedMessages) => {
const articles = retrievedMessages
.map(decodeWakuMessage) // Decode messages
.filter(Boolean); // Filter out undefined values
console.log(`${articles.length} articles have been retrieved`);
};
waku.store
.queryHistory([ContentTopic], { callback })
.catch((e) => {
// Catch any potential error
console.log('Failed to retrieve messages from store', e);
});
```
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.
## Filter messages by send time
By default, Waku Store nodes store messages for 30 days.
Depending on your use case, you may not need to retrieve 30 days worth of messages.
[Waku Message](https://rfc.vac.dev/spec/14/) defiles an optional unencrypted `timestamp` field.
The timestamp is set by the sender.
By default, js-waku [sets the timestamp of outgoing message to the current time](https://github.com/status-im/js-waku/blob/a056227538f9409aa9134c7ef0df25f602dbea58/src/lib/waku_message/index.ts#L76).
You can filter messages that include a timestamp within given bounds with the `timeFilter` option.
Retrieve messages up to a week old:
```js
// [..] `ContentTopic` and `callback` definitions
const startTime = new Date();
// 7 days/week, 24 hours/day, 60min/hour, 60secs/min, 100ms/sec
startTime.setTime(startTime.getTime() - 7 * 24 * 60 * 60 * 1000);
waku.store
.queryHistory([ContentTopic], {
callback,
timeFilter: { startTime, endTime: new Date() }
})
.catch((e) => {
console.log('Failed to retrieve messages from store', e);
});
```
## End result
You can see a similar example implemented in ReactJS in the [Minimal ReactJS Waku Store App](/examples/store-reactjs-chat).