mirror of https://github.com/waku-org/js-waku.git
Merge pull request #338 from status-im/use-docs-website
Use docs website
This commit is contained in:
commit
4574e2e0b6
|
@ -69,6 +69,7 @@
|
||||||
"reactjs",
|
"reactjs",
|
||||||
"recid",
|
"recid",
|
||||||
"rlnrelay",
|
"rlnrelay",
|
||||||
|
"roadmap",
|
||||||
"sandboxed",
|
"sandboxed",
|
||||||
"secio",
|
"secio",
|
||||||
"seckey",
|
"seckey",
|
||||||
|
|
296
README.md
296
README.md
|
@ -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
|
||||||
|
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
||||||
|
Moved to https://docs.dappconnect.dev/guides/index.html
|
|
@ -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.
|
|
|
@ -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/).
|
|
|
@ -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)
|
|
|
@ -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).
|
|
|
@ -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).
|
|
|
@ -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);
|
|
||||||
```
|
|
|
@ -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).
|
|
Loading…
Reference in New Issue