Use protobuf

This commit is contained in:
Franck Royer 2021-07-28 15:12:37 +10:00
parent 3b71fd0b26
commit 1f370ae53e
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
4 changed files with 247 additions and 13 deletions

View File

@ -11,6 +11,7 @@
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",
"js-waku": "../../build/main", "js-waku": "../../build/main",
"protons": "^2.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
@ -13000,6 +13001,11 @@
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
}, },
"node_modules/multiformats": {
"version": "9.4.3",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.4.3.tgz",
"integrity": "sha512-sCNjBP/NPCeQu83Mst8IQZq9+HuR7Catvk/m7CeH0r/nupsU6gM7GINf5E1HCDRxDeU+Cgda/WPmcwQhYs3dyA=="
},
"node_modules/nan": { "node_modules/nan": {
"version": "2.14.2", "version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
@ -15507,6 +15513,22 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/protocol-buffers-schema": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.5.1.tgz",
"integrity": "sha512-YVCvdhxWNDP8/nJDyXLuM+UFsuPk4+1PB7WGPVDzm3HTHbzFLxQYeW2iZpS4mmnXrQJGBzt230t/BbEb7PrQaw=="
},
"node_modules/protons": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/protons/-/protons-2.0.1.tgz",
"integrity": "sha512-FlmPorLEeCEDPu+uIn0Qardgiy5XqVA4IyNTz9wb9c0e2U7BEXdRcIbx64r09o4Abtf+4B7mkTtMbsIXMxZzKw==",
"dependencies": {
"protocol-buffers-schema": "^3.3.1",
"signed-varint": "^2.0.1",
"uint8arrays": "^2.1.3",
"varint": "^5.0.0"
}
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -17447,6 +17469,14 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
}, },
"node_modules/signed-varint": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz",
"integrity": "sha1-UKmYnafJjCxh2tEZvJdHDvhSgSk=",
"dependencies": {
"varint": "~5.0.0"
}
},
"node_modules/simple-swizzle": { "node_modules/simple-swizzle": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@ -19096,6 +19126,14 @@
"node": ">=4.2.0" "node": ">=4.2.0"
} }
}, },
"node_modules/uint8arrays": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.8.tgz",
"integrity": "sha512-qpZ/B88mSea11W3LvoimtnGWIC2i3gGuXby5wBkn8jY+OFulbaQwyjpOYSyrASqgcNEvKdAkLiOwiUt5cPSdcQ==",
"dependencies": {
"multiformats": "^9.4.2"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -19465,6 +19503,11 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"node_modules/varint": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz",
"integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow=="
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -31476,6 +31519,11 @@
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
}, },
"multiformats": {
"version": "9.4.3",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.4.3.tgz",
"integrity": "sha512-sCNjBP/NPCeQu83Mst8IQZq9+HuR7Catvk/m7CeH0r/nupsU6gM7GINf5E1HCDRxDeU+Cgda/WPmcwQhYs3dyA=="
},
"nan": { "nan": {
"version": "2.14.2", "version": "2.14.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
@ -33490,6 +33538,22 @@
} }
} }
}, },
"protocol-buffers-schema": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.5.1.tgz",
"integrity": "sha512-YVCvdhxWNDP8/nJDyXLuM+UFsuPk4+1PB7WGPVDzm3HTHbzFLxQYeW2iZpS4mmnXrQJGBzt230t/BbEb7PrQaw=="
},
"protons": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/protons/-/protons-2.0.1.tgz",
"integrity": "sha512-FlmPorLEeCEDPu+uIn0Qardgiy5XqVA4IyNTz9wb9c0e2U7BEXdRcIbx64r09o4Abtf+4B7mkTtMbsIXMxZzKw==",
"requires": {
"protocol-buffers-schema": "^3.3.1",
"signed-varint": "^2.0.1",
"uint8arrays": "^2.1.3",
"varint": "^5.0.0"
}
},
"proxy-addr": { "proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -35017,6 +35081,14 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
}, },
"signed-varint": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz",
"integrity": "sha1-UKmYnafJjCxh2tEZvJdHDvhSgSk=",
"requires": {
"varint": "~5.0.0"
}
},
"simple-swizzle": { "simple-swizzle": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@ -36352,6 +36424,14 @@
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"peer": true "peer": true
}, },
"uint8arrays": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.8.tgz",
"integrity": "sha512-qpZ/B88mSea11W3LvoimtnGWIC2i3gGuXby5wBkn8jY+OFulbaQwyjpOYSyrASqgcNEvKdAkLiOwiUt5cPSdcQ==",
"requires": {
"multiformats": "^9.4.2"
}
},
"unbox-primitive": { "unbox-primitive": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -36646,6 +36726,11 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"varint": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz",
"integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@ -7,6 +7,7 @@
"@testing-library/react": "^11.1.0", "@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",
"js-waku": "../../build/main", "js-waku": "../../build/main",
"protons": "^2.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",

View File

@ -1,6 +1,7 @@
import './App.css'; import './App.css';
import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku';
import * as React from 'react'; import * as React from 'react';
import protons from 'protons';
const ContentTopic = `/relay-guide/1/chat/proto`; const ContentTopic = `/relay-guide/1/chat/proto`;
@ -8,6 +9,13 @@ const initialMessageState = {
messages: [], messages: [],
}; };
const proto = protons(`
message SimpleChatMessage {
float timestamp = 1;
string text = 2;
}
`);
function App() { function App() {
const [waku, setWaku] = React.useState(undefined); const [waku, setWaku] = React.useState(undefined);
const [wakuStatus, setWakuStatus] = React.useState('NotStarted'); const [wakuStatus, setWakuStatus] = React.useState('NotStarted');
@ -33,7 +41,19 @@ function App() {
// Need to keep the same reference around to add and delete from relay observer // Need to keep the same reference around to add and delete from relay observer
const processIncomingMessage = React.useCallback((wakuMessage) => { const processIncomingMessage = React.useCallback((wakuMessage) => {
dispatchMessages({ type: 'Add', message: wakuMessage.payloadAsUtf8 }); if (!wakuMessage.payload) return;
const { timestamp, text } = proto.SimpleChatMessage.decode(
wakuMessage.payload
);
dispatchMessages({
type: 'Add',
message: {
timestamp: new Date(timestamp),
text,
},
});
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
@ -63,7 +83,13 @@ function App() {
</button> </button>
<ul> <ul>
{messagesState.messages.map((msg) => { {messagesState.messages.map((msg) => {
return <li>{msg}</li>; return (
<li>
<p>
{msg.timestamp.toString()}: {msg.text}
</p>
</li>
);
})} })}
</ul> </ul>
</header> </header>
@ -79,7 +105,12 @@ async function bootstrapWaku(waku) {
} }
async function sendMessage(message, waku) { async function sendMessage(message, waku) {
const wakuMessage = await WakuMessage.fromUtf8String(message, ContentTopic); const payload = proto.SimpleChatMessage.encode({
timestamp: Date.now(),
text: message,
});
const wakuMessage = await WakuMessage.fromBytes(payload, ContentTopic);
await waku.relay.send(wakuMessage); await waku.relay.send(wakuMessage);
} }

View File

@ -4,7 +4,12 @@ Waku
Relay Relay
is is
a a
gossip protocol that enables you to send and receive messages. 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/). 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. Before starting, you need to choose a _Content Topic_ for your dApp.
@ -50,12 +55,15 @@ To monitor messages for your app, you need to register an observer on relay for
```js ```js
const processIncomingMessage = (wakuMessage) => { const processIncomingMessage = (wakuMessage) => {
console.log("Message Received", wakuMessage); console.log(`Message Received: ${wakuMessage.payloadAsUtf8}`);
}; };
waku.relay.addObserver(processIncomingMessage, ["/relay-guide/1/chat/proto"]); waku.relay.addObserver(processIncomingMessage, ['/relay-guide/1/chat/proto']);
``` ```
`WakuMessage.payloadAsUtf8` is a nice helper to show UTF-8 encoding messages.
However, you will probably need more structure messages, this is covered in [use protobuf section](#use-protobuf).
# Send messages # Send messages
You are now ready to send messages. You are now ready to send messages.
@ -67,20 +75,110 @@ When using a basic string payload, you can use the `WakuMessage.fromUtf8String`
```js ```js
import { WakuMessage } from 'js-waku'; import { WakuMessage } from 'js-waku';
const wakuMessage = await WakuMessage.fromUtf8String(message, `/relay-guide/1/chat/proto`); 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, 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: the message will then be relayed to the rest of the network thanks to Waku Relay:
```js ```js
import { WakuMessage } from 'js-waku';
const wakuMessage = await WakuMessage.fromUtf8String(message, `/relay-guide/1/chat/proto`);
await waku.relay.send(wakuMessage); await waku.relay.send(wakuMessage);
``` ```
# Use protobuf
Sending strings as messages in unlikely to cover your dApps needs.
To include structured objects in Waku Messages,
it is recommended to use [protobuf](https://developers.google.com/protocol-buffers/).
First, let's define an object.
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, you can use the [protons](https://www.npmjs.com/package/protons) package.
## Install protobuf library
First, install it:
```shell
npm install protons
```
## Protobuf Definition
Then define the simple chat message:
```js
import protons from 'protons';
const proto = protons(`
message SimpleChatMessage {
float timestamp = 1;
string text = 2;
}
`);
```
You can learn about protobuf definitions here:
[Protocol Buffers Language Guide](https://developers.google.com/protocol-buffers/docs/proto).
## Encode messages
Instead of wrapping a string in a Waku Message, you need to encode the message in protobuf.
The result is a byte array that can then be wrapped in a Waku Message.
First, encode the message:
```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()}`);
};
```
Same than before, you can pass add this function as an observer to Waku Relay to process incoming messages:
```js
waku.relay.addObserver(processIncomingMessage, ['/relay-guide/1/chat/proto']);
```
# Conclusion # Conclusion
That is it! Now, you know how to send and receive messages over Waku using the Waku Relay protocol. That is it! Now, you know how to send and receive messages over Waku using the Waku Relay protocol.
@ -91,6 +189,14 @@ Here is the final code:
```js ```js
import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku';
import protons from 'protons';
const proto = protons(`
message SimpleChatMessage {
float timestamp = 1;
string text = 2;
}
`);
const wakuNode = await Waku.create(); const wakuNode = await Waku.create();
@ -98,11 +204,22 @@ const nodes = await getStatusFleetNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr))); await Promise.all(nodes.map((addr) => waku.dial(addr)));
const processIncomingMessage = (wakuMessage) => { const processIncomingMessage = (wakuMessage) => {
console.log(`Message Received: ${wakuMessage.payloadAsUtf8}`); // 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']); waku.relay.addObserver(processIncomingMessage, ['/relay-guide/1/chat/proto']);
const wakuMessage = await WakuMessage.fromUtf8String(message, `/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); await waku.relay.send(wakuMessage);
``` ```