diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d69a3d8a4..ed6a710df2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/examples/min-react-js-chat/src/App.js b/examples/min-react-js-chat/src/App.js
index 2eeddf446a..0110c0920e 100644
--- a/examples/min-react-js-chat/src/App.js
+++ b/examples/min-react-js-chat/src/App.js
@@ -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();
diff --git a/examples/store-reactjs-chat/src/App.js b/examples/store-reactjs-chat/src/App.js
index fbb206b2dc..0143015d44 100644
--- a/examples/store-reactjs-chat/src/App.js
+++ b/examples/store-reactjs-chat/src/App.js
@@ -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 (
diff --git a/examples/web-chat/src/App.tsx b/examples/web-chat/src/App.tsx
index 27fd4a3b08..bebbe23cc5 100644
--- a/examples/web-chat/src/App.tsx
+++ b/examples/web-chat/src/App.tsx
@@ -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 (
diff --git a/guides/reactjs-relay.md b/guides/reactjs-relay.md
index be576608a9..fc5678a6d0 100644
--- a/guides/reactjs-relay.md
+++ b/guides/reactjs-relay.md
@@ -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 (
-
-
+
+
// Display the status on the web page
{wakuStatus}
@@ -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
diff --git a/guides/relay-receive-send-messages.md b/guides/relay-receive-send-messages.md
index 47b6637d5e..60e868f9fc 100644
--- a/guides/relay-receive-send-messages.md
+++ b/guides/relay-receive-send-messages.md
@@ -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:
diff --git a/guides/store-retrieve-messages.md b/guides/store-retrieve-messages.md
index 822009f0e9..2859c3e4b8 100644
--- a/guides/store-retrieve-messages.md
+++ b/guides/store-retrieve-messages.md
@@ -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!
- }
-});
-```
diff --git a/src/lib/waku.ts b/src/lib/waku.ts
index 729ef77066..55833a6350 100644
--- a/src/lib/waku.ts
+++ b/src/lib/waku.ts
@@ -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
{
+ const desiredProtocols = [[StoreCodec], [LightPushCodec], RelayCodecs];
+
+ await Promise.all(
+ desiredProtocols.map((desiredProtocolVersions) => {
+ const peers = new Array();
+ 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((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,