Merge pull request #287 from status-im/wait-for-connected-peer

Added `Waku.waitForConnectedPeer` helper
This commit is contained in:
Franck Royer 2021-09-02 16:28:14 +10:00 committed by GitHub
commit 0d38e80497
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 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 `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`. - 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 ### 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. - **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 './App.css';
import { getBootstrapNodes, Waku, WakuMessage } from 'js-waku'; import { Waku, WakuMessage } from 'js-waku';
import * as React from 'react'; import * as React from 'react';
import protons from 'protons'; import protons from 'protons';
@ -24,10 +24,10 @@ function App() {
setWakuStatus('Starting'); setWakuStatus('Starting');
Waku.create().then((waku) => { Waku.create({ bootstrap: true }).then((waku) => {
setWaku(waku); setWaku(waku);
setWakuStatus('Connecting'); setWakuStatus('Connecting');
bootstrapWaku(waku).then(() => { waku.waitForConnectedPeer().then(() => {
setWakuStatus('Ready'); setWakuStatus('Ready');
}); });
}); });
@ -95,15 +95,6 @@ function App() {
export default 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) { async function sendMessage(message, timestamp, waku) {
const time = timestamp.getTime(); const time = timestamp.getTime();

View File

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

View File

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

View File

@ -60,7 +60,7 @@ function App() {
setWakuStatus('Starting'); setWakuStatus('Starting');
// Create Waku // Create Waku
Waku.create().then((waku) => { Waku.create({ bootstrap: true }).then((waku) => {
// Once done, put it in the state // Once done, put it in the state
setWaku(waku); setWaku(waku);
// And update the status // And update the status
@ -69,8 +69,8 @@ function App() {
}, [waku, wakuStatus]); }, [waku, wakuStatus]);
return ( return (
<div className="App"> <div className='App'>
<header className="App-header"> <header className='App-header'>
// Display the status on the web page // Display the status on the web page
<p>{wakuStatus}</p> <p>{wakuStatus}</p>
</header> </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. When using the `bootstrap` option, it may take some times to connect to other peers.
First, create `bootstrapWaku` to connect to Waku bootstrap nodes: To ensure that you have relay peers available to send and receive messages,
use the `Waku.waitForConnectedPeer()` async function:
```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:
```js ```js
React.useEffect(() => { React.useEffect(() => {
@ -106,17 +92,16 @@ React.useEffect(() => {
setWakuStatus('Starting'); setWakuStatus('Starting');
Waku.create().then((waku) => { Waku.create({ bootstrap: true }).then((waku) => {
setWaku(waku); setWaku(waku);
setWakuStatus('Connecting'); setWakuStatus('Connecting');
bootstrapWaku(waku).then(() => { waku.waitForConnectedPeer().then(() => {
setWakuStatus('Ready'); setWakuStatus('Ready');
}); });
}); });
}, [waku, wakuStatus]); }, [waku, wakuStatus]);
```
DappConnect will provide more discovery and bootstrap methods over time, or you can make your own. ```
# Define Message Format # Define Message Format

View File

@ -23,23 +23,35 @@ In order to interact with the Waku network, you first need a Waku instance:
```js ```js
import { Waku } from 'js-waku'; import { Waku } from 'js-waku';
const wakuNode = await Waku.create(); const wakuNode = await Waku.create({ bootstrap: true });
``` ```
# Connect to Other Peers 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:
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:
```js ```js
import { getBootstrapNodes } from 'js-waku'; import { Waku } from 'js-waku';
const nodes = await getBootstrapNodes(); const wakuNode = await Waku.create({
await Promise.all(nodes.map((addr) => waku.dial(addr))); 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 # Receive messages
To watch messages for your app, you need to register an observer on relay for your app's content topic: 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 # Use Protobuf
Waku v2 protocols use [protobuf](https://developers.google.com/protocol-buffers/) [by default](https://rfc.vac.dev/spec/10/). 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. 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. 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 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available // @ts-ignore: No types available
import filters from 'libp2p-websockets/src/filters'; import filters from 'libp2p-websockets/src/filters';
import { Peer } from 'libp2p/dist/src/peer-store';
import Ping from 'libp2p/src/ping'; import Ping from 'libp2p/src/ping';
import { Multiaddr, multiaddr } from 'multiaddr'; import { Multiaddr, multiaddr } from 'multiaddr';
import PeerId from 'peer-id'; import PeerId from 'peer-id';
import { getBootstrapNodes } from './discovery'; 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 { WakuMessage } from './waku_message';
import { RelayCodecs, WakuRelay } from './waku_relay'; import { RelayCodecs, WakuRelay } from './waku_relay';
import { RelayPingContentTopic } from './waku_relay/constants'; import { RelayPingContentTopic } from './waku_relay/constants';
@ -310,6 +312,46 @@ export class Waku {
return localMultiaddr + '/p2p/' + this.libp2p.peerId.toB58String(); 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( private startKeepAlive(
peerId: PeerId, peerId: PeerId,
pingPeriodSecs: number, pingPeriodSecs: number,