diff --git a/.cspell.json b/.cspell.json index 70f59b46ac..ab5c739a25 100644 --- a/.cspell.json +++ b/.cspell.json @@ -69,6 +69,7 @@ "reactjs", "recid", "rlnrelay", + "roadmap", "sandboxed", "secio", "seckey", diff --git a/README.md b/README.md index 855b0ff3a4..41555f90a2 100644 --- a/README.md +++ b/README.md @@ -9,312 +9,38 @@ A JavaScript implementation of the [Waku v2 protocol](https://rfc.vac.dev/spec/1 ## 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 -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: +API Documentation can also be generated locally: ```shell +git clone https://github.com/status-im/js-waku.git +cd js-waku npm install 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 ` - -``` - -### 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 -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 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). -- ✔: 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 -See [CONTRIBUTING.md](./CONTRIBUTING.md). +See [CONTRIBUTING.md](https://github.com/status-im/js-waku/blob/main/CONTRIBUTING.md). ## License Licensed and distributed under either of diff --git a/examples/examples.md b/examples/README.md similarity index 90% rename from examples/examples.md rename to examples/README.md index e1dac59613..bb7d0996fe 100644 --- a/examples/examples.md +++ b/examples/README.md @@ -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 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. + +See https://docs.dappconnect.dev/docs/examples/ for more examples. diff --git a/guides/README.md b/guides/README.md new file mode 100644 index 0000000000..ecd60af477 --- /dev/null +++ b/guides/README.md @@ -0,0 +1 @@ +Moved to https://docs.dappconnect.dev/guides/index.html diff --git a/guides/choose-content-topic.md b/guides/choose-content-topic.md deleted file mode 100644 index d64b21ec49..0000000000 --- a/guides/choose-content-topic.md +++ /dev/null @@ -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. diff --git a/guides/encrypt-messages-version-1.md b/guides/encrypt-messages-version-1.md deleted file mode 100644 index d9fcc6176c..0000000000 --- a/guides/encrypt-messages-version-1.md +++ /dev/null @@ -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/). diff --git a/guides/menu.md b/guides/menu.md deleted file mode 100644 index 75f7deba30..0000000000 --- a/guides/menu.md +++ /dev/null @@ -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) diff --git a/guides/reactjs-relay.md b/guides/reactjs-relay.md deleted file mode 100644 index 0b6a2a088c..0000000000 --- a/guides/reactjs-relay.md +++ /dev/null @@ -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 ( -
-
- // Display the status on the web page -

{wakuStatus}

-
-
- ); -} -``` - -# 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 ( -
-
-

{wakuStatus}

- -
-
- ); -} -``` - -# 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 ( -
-
-

{wakuStatus}

- - -
-
- ); -} -``` - -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). diff --git a/guides/reactjs-store.md b/guides/reactjs-store.md deleted file mode 100644 index 7bd2d462e1..0000000000 --- a/guides/reactjs-store.md +++ /dev/null @@ -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 ( -
-
- // Display the status on the web page -

{wakuStatus}

-
-
- ); -} -``` - -# 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 ( -
-
-

{wakuStatus}

-

Messages

- -
-
- ); -} - -``` - -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). diff --git a/guides/relay-receive-send-messages.md b/guides/relay-receive-send-messages.md deleted file mode 100644 index 07e3014f4c..0000000000 --- a/guides/relay-receive-send-messages.md +++ /dev/null @@ -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); -``` diff --git a/guides/store-retrieve-messages.md b/guides/store-retrieve-messages.md deleted file mode 100644 index e37c4b856e..0000000000 --- a/guides/store-retrieve-messages.md +++ /dev/null @@ -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).