Added `Waku.waitForConnectedPeer` helper

To ensure that we are connected to Waku peers when using the bootstrap
option.
This commit is contained in:
Franck Royer 2021-09-02 15:01:52 +10:00
parent 524fbc9361
commit a21d641280
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
8 changed files with 110 additions and 105 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Allow passing decryption keys in hex string format.
- Allow passing decryption keys to `WakuStore` instance to avoid having to pass them at every `queryHistory` call.
- Allow passing decryption keys to `Waku` instance to avoid having to pass them to both `WakuRelay` and `WakuStore`.
- `Waku.waitForConnectedPeer` helper to ensure that we are connected to Waku peers when using the bootstrap option.
### Changed
- **Breaking**: Moved `startTime` and `endTime` for history queries to a `timeFilter` property as both or neither must be passed; passing only one parameter is not supported.

View File

@ -1,5 +1,5 @@
import './App.css';
import { getBootstrapNodes, Waku, WakuMessage } from 'js-waku';
import { Waku, WakuMessage } from 'js-waku';
import * as React from 'react';
import protons from 'protons';
@ -24,10 +24,10 @@ function App() {
setWakuStatus('Starting');
Waku.create().then((waku) => {
Waku.create({ bootstrap: true }).then((waku) => {
setWaku(waku);
setWakuStatus('Connecting');
bootstrapWaku(waku).then(() => {
waku.waitForConnectedPeer().then(() => {
setWakuStatus('Ready');
});
});
@ -95,15 +95,6 @@ function App() {
export default App;
async function bootstrapWaku(waku) {
try {
const nodes = await getBootstrapNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr)));
} catch (e) {
console.error('Failed to bootstrap to Waku network');
}
}
async function sendMessage(message, timestamp, waku) {
const time = timestamp.getTime();

View File

@ -1,5 +1,5 @@
import './App.css';
import { StoreCodec, Waku } from 'js-waku';
import { Waku } from 'js-waku';
import * as React from 'react';
import protons from 'protons';
@ -57,18 +57,10 @@ function App() {
// We do not handle disconnection/re-connection in this example
if (wakuStatus === 'Connected to Store') return;
const isStoreNode = ({ protocols }) => {
if (protocols.includes(StoreCodec)) {
// We are now connected to a store node
setWakuStatus('Connected to Store');
}
};
waku.libp2p.peerStore.on('change:protocols', isStoreNode);
return () => {
waku.libp2p.peerStore.removeListener('change:protocols', isStoreNode);
};
waku.waitForConnectedPeer().then(() => {
// We are now connected to a store node
setWakuStatus('Connected to Store');
});
}, [waku, wakuStatus]);
return (

View File

@ -1,12 +1,6 @@
import { useEffect, useReducer, useState } from 'react';
import './App.css';
import {
Direction,
getBootstrapNodes,
StoreCodec,
Waku,
WakuMessage,
} from 'js-waku';
import { Direction, getBootstrapNodes, Waku, WakuMessage } from 'js-waku';
import handleCommand from './command';
import Room from './Room';
import { WakuContext } from './WakuContext';
@ -79,8 +73,8 @@ async function retrieveStoreMessages(
});
return res.length;
} catch {
console.log('Failed to retrieve messages');
} catch (e) {
console.log('Failed to retrieve messages', e);
return 0;
}
}
@ -131,29 +125,21 @@ export default function App() {
if (!waku) return;
if (historicalMessagesRetrieved) return;
const checkAndRetrieve = ({ protocols }: { protocols: string[] }) => {
if (protocols.includes(StoreCodec)) {
console.log(`Retrieving archived messages}`);
setHistoricalMessagesRetrieved(true);
const retrieveMessages = async () => {
await waku.waitForConnectedPeer();
console.log(`Retrieving archived messages}`);
try {
retrieveStoreMessages(waku, dispatchMessages).then((length) =>
console.log(`Messages retrieved:`, length)
);
} catch (e) {
console.log(`Error encountered when retrieving archived messages`, e);
}
try {
retrieveStoreMessages(waku, dispatchMessages).then((length) => {
console.log(`Messages retrieved:`, length);
setHistoricalMessagesRetrieved(true);
});
} catch (e) {
console.log(`Error encountered when retrieving archived messages`, e);
}
};
waku.libp2p.peerStore.on('change:protocols', checkAndRetrieve);
return () => {
waku.libp2p.peerStore.removeListener(
'change:protocols',
checkAndRetrieve
);
};
retrieveMessages();
}, [waku, historicalMessagesRetrieved]);
return (

View File

@ -60,7 +60,7 @@ function App() {
setWakuStatus('Starting');
// Create Waku
Waku.create().then((waku) => {
Waku.create({ bootstrap: true }).then((waku) => {
// Once done, put it in the state
setWaku(waku);
// And update the status
@ -69,8 +69,8 @@ function App() {
}, [waku, wakuStatus]);
return (
<div className="App">
<header className="App-header">
<div className='App'>
<header className='App-header'>
// Display the status on the web page
<p>{wakuStatus}</p>
</header>
@ -79,25 +79,11 @@ function App() {
}
```
# Connect to Other Peers
# Wait to be connected
The Waku instance needs to connect to other peers to communicate with the network.
First, create `bootstrapWaku` to connect to Waku bootstrap nodes:
```js
import { getBootstrapNodes } from 'js-waku';
async function bootstrapWaku(waku) {
try {
const nodes = await getBootstrapNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr)));
} catch (e) {
console.error('Failed to bootstrap to Waku network');
}
}
```
Then, bootstrap after Waku is created in the previous `useEffect` block:
When using the `bootstrap` option, it may take some times 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(() => {
@ -106,17 +92,16 @@ React.useEffect(() => {
setWakuStatus('Starting');
Waku.create().then((waku) => {
Waku.create({ bootstrap: true }).then((waku) => {
setWaku(waku);
setWakuStatus('Connecting');
bootstrapWaku(waku).then(() => {
waku.waitForConnectedPeer().then(() => {
setWakuStatus('Ready');
});
});
}, [waku, wakuStatus]);
```
DappConnect will provide more discovery and bootstrap methods over time, or you can make your own.
```
# Define Message Format

View File

@ -23,23 +23,35 @@ 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();
const wakuNode = await Waku.create({ bootstrap: true });
```
# Connect to Other Peers
The Waku instance needs to connect to other peers to communicate with the network.
You are free to choose any method to bootstrap and DappConnect will ship with new methods in the future.
For now, the easiest way is to connect to Status' Waku fleet:
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 { getBootstrapNodes } from 'js-waku';
import { Waku } from 'js-waku';
const nodes = await getBootstrapNodes();
await Promise.all(nodes.map((addr) => waku.dial(addr)));
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 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:

View File

@ -55,6 +55,18 @@ const wakuNode = await Waku.create({
});
```
# 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/).
@ -153,19 +165,3 @@ 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.
## Wait to be connected
Depending on your dApp design, you may want to wait for a store node to be available first.
In this case, you can listen for the [PeerStore's change protocol event](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md#known-protocols-for-a-peer-change)
to know whether any of your connected peers is a store peer:
```js
import { StoreCodec } from 'js-waku';
// Or using a callback
waku.libp2p.peerStore.on('change:protocols', ({ peerId, protocols }) => {
if (protocols.includes(StoreCodec)) {
// A Store node is available!
}
});
```

View File

@ -13,12 +13,14 @@ import Websockets from 'libp2p-websockets';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
import filters from 'libp2p-websockets/src/filters';
import { Peer } from 'libp2p/dist/src/peer-store';
import Ping from 'libp2p/src/ping';
import { Multiaddr, multiaddr } from 'multiaddr';
import PeerId from 'peer-id';
import { getBootstrapNodes } from './discovery';
import { WakuLightPush } from './waku_light_push';
import { getPeersForProtocol } from './select_peer';
import { LightPushCodec, WakuLightPush } from './waku_light_push';
import { WakuMessage } from './waku_message';
import { RelayCodecs, WakuRelay } from './waku_relay';
import { RelayPingContentTopic } from './waku_relay/constants';
@ -310,6 +312,46 @@ export class Waku {
return localMultiaddr + '/p2p/' + this.libp2p.peerId.toB58String();
}
/**
* Wait to be connected to a peer. Useful when using the [[CreateOptions.bootstrap]]
* with [[Waku.create]]. The Promise resolves only once we are connected to a
* Store peer, Relay peer and Light Push peer.
*/
async waitForConnectedPeer(): Promise<void> {
const desiredProtocols = [[StoreCodec], [LightPushCodec], RelayCodecs];
await Promise.all(
desiredProtocols.map((desiredProtocolVersions) => {
const peers = new Array<Peer>();
desiredProtocolVersions.forEach((proto) => {
getPeersForProtocol(this.libp2p, proto).forEach((peer) =>
peers.push(peer)
);
});
dbg('peers for ', desiredProtocolVersions, peers);
if (peers.length > 0) {
return Promise.resolve();
} else {
// No peer available for this protocol, waiting to connect to one.
return new Promise<void>((resolve) => {
this.libp2p.peerStore.on(
'change:protocols',
({ protocols: connectedPeerProtocols }) => {
desiredProtocolVersions.forEach((desiredProto) => {
if (connectedPeerProtocols.includes(desiredProto)) {
dbg('Resolving for', desiredProto, connectedPeerProtocols);
resolve();
}
});
}
);
});
}
})
);
}
private startKeepAlive(
peerId: PeerId,
pingPeriodSecs: number,