From cbb21108d8f853ff0dc2dfca42037a287fa8550c Mon Sep 17 00:00:00 2001 From: F Date: Thu, 2 Dec 2021 15:06:11 +1100 Subject: [PATCH] Remove guides They have been moved to the docs website. --- guides/README.md | 1 + guides/choose-content-topic.md | 26 --- guides/encrypt-messages-version-1.md | 238 -------------------- guides/menu.md | 7 - guides/reactjs-relay.md | 300 -------------------------- guides/reactjs-store.md | 296 ------------------------- guides/relay-receive-send-messages.md | 227 ------------------- guides/store-retrieve-messages.md | 202 ----------------- 8 files changed, 1 insertion(+), 1296 deletions(-) create mode 100644 guides/README.md delete mode 100644 guides/choose-content-topic.md delete mode 100644 guides/encrypt-messages-version-1.md delete mode 100644 guides/menu.md delete mode 100644 guides/reactjs-relay.md delete mode 100644 guides/reactjs-store.md delete mode 100644 guides/relay-receive-send-messages.md delete mode 100644 guides/store-retrieve-messages.md 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}

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

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

    -
  • - ); - })} -
-
-
- ); -} -``` - -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).