mirror of https://github.com/waku-org/js-waku.git
Merge pull request #287 from status-im/wait-for-connected-peer
Added `Waku.waitForConnectedPeer` helper
This commit is contained in:
commit
0d38e80497
|
@ -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.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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!
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue