diff --git a/.cspell.json b/.cspell.json index 90db492bb2..499e87d912 100644 --- a/.cspell.json +++ b/.cspell.json @@ -47,6 +47,7 @@ "livechat", "mkdir", "multiaddr", + "multiaddresses", "multiaddrs", "multicodecs", "mplex", diff --git a/CHANGELOG.md b/CHANGELOG.md index 8969317afa..cfbc8032bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- New `bootstrap` option for `Waku.create` to easily connect to Waku nodes upon start up. + +### Changed +- Renamed `discover.getStatusFleetNodes` to `discovery.getBootstrapNodes`; + Changed the API to allow retrieval of bootstrap nodes from other sources. + ### Removed - Examples (cli-chat): The focus of this library is Web environment; Several examples now cover usage of Waku Relay and Waku Store making cli-chat example obsolete; diff --git a/README.md b/README.md index 568881ca25..91780aef5b 100644 --- a/README.md +++ b/README.md @@ -32,32 +32,7 @@ npm install js-waku ```ts import { Waku } from 'js-waku'; -const waku = await Waku.create(); -``` - -### Connect to a new peer - -```ts -// Directly dial a new peer -await waku.dial('/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ'); - -// Or, add peer to address book so it auto dials in the background -waku.addPeerToAddressBook( - '16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ', - ['/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss'] -); -``` - -You can also use `getStatusFleetNodes` to connect to nodes run by Status: - -```ts -import { getStatusFleetNodes } from 'js-waku'; - -getStatusFleetNodes().then((nodes) => { - nodes.forEach((addr) => { - waku.dial(addr); - }); -}); +const waku = await Waku.create({ bootstrap: true }); ``` ### Listen for messages diff --git a/examples/eth-dm/src/waku.ts b/examples/eth-dm/src/waku.ts index 1eded8b646..83214e1457 100644 --- a/examples/eth-dm/src/waku.ts +++ b/examples/eth-dm/src/waku.ts @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction } from 'react'; -import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; +import { Waku, WakuMessage } from 'js-waku'; import { DirectMessage, PublicKeyMessage } from './messaging/wire'; import { validatePublicKeyMessage } from './crypto'; import { Message } from './messaging/Messages'; @@ -9,14 +9,7 @@ export const PublicKeyContentTopic = '/eth-dm/1/public-key/proto'; export const DirectMessageContentTopic = '/eth-dm/1/direct-message/proto'; export async function initWaku(): Promise { - const waku = await Waku.create({}); - - // Dial all nodes it can find - getStatusFleetNodes().then((nodes) => { - nodes.forEach((addr) => { - waku.dial(addr); - }); - }); + const waku = await Waku.create({ bootstrap: true }); // Wait to be connected to at least one peer await new Promise((resolve, reject) => { diff --git a/examples/eth-pm-wallet-encryption/src/waku.ts b/examples/eth-pm-wallet-encryption/src/waku.ts index b1c4b5b274..c85649276c 100644 --- a/examples/eth-pm-wallet-encryption/src/waku.ts +++ b/examples/eth-pm-wallet-encryption/src/waku.ts @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction } from 'react'; -import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; +import { Waku, WakuMessage } from 'js-waku'; import { DirectMessage, PublicKeyMessage } from './messaging/wire'; import { validatePublicKeyMessage } from './crypto'; import { Message } from './messaging/Messages'; @@ -11,14 +11,7 @@ export const DirectMessageContentTopic = '/eth-pm-wallet/1/direct-message/proto'; export async function initWaku(): Promise { - const waku = await Waku.create({}); - - // Dial all nodes it can find - getStatusFleetNodes().then((nodes) => { - nodes.forEach((addr) => { - waku.dial(addr); - }); - }); + const waku = await Waku.create({ bootstrap: true }); // Wait to be connected to at least one peer await new Promise((resolve, reject) => { diff --git a/examples/min-react-js-chat/src/App.js b/examples/min-react-js-chat/src/App.js index ce6e902af1..2eeddf446a 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 { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; +import { getBootstrapNodes, Waku, WakuMessage } from 'js-waku'; import * as React from 'react'; import protons from 'protons'; @@ -96,8 +96,12 @@ function App() { export default App; async function bootstrapWaku(waku) { - const nodes = await getStatusFleetNodes(); - await Promise.all(nodes.map((addr) => waku.dial(addr))); + 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) { diff --git a/examples/store-reactjs-chat/src/App.js b/examples/store-reactjs-chat/src/App.js index ffd23da3a8..fbb206b2dc 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 { getStatusFleetNodes, StoreCodec, Waku } from 'js-waku'; +import { StoreCodec, Waku } from 'js-waku'; import * as React from 'react'; import protons from 'protons'; @@ -17,9 +17,6 @@ function App() { const [waku, setWaku] = React.useState(undefined); const [wakuStatus, setWakuStatus] = React.useState('None'); const [messages, setMessages] = React.useState([]); - // Set to true when Waku connects to a store node - // it does not reflect whether we then disconnected from said node. - const [connectedToStore, setConnectedToStore] = React.useState(false); React.useEffect(() => { if (!!waku) return; @@ -27,20 +24,15 @@ function App() { setWakuStatus('Starting'); - Waku.create().then((waku) => { + Waku.create({ bootstrap: true }).then((waku) => { setWaku(waku); setWakuStatus('Connecting'); - bootstrapWaku(waku).then(() => { - setWakuStatus('Ready'); - }); }); }, [waku, wakuStatus]); React.useEffect(() => { if (!waku) return; - // This is superfluous as the try/catch block would catch the failure if - // we are indeed not connected to any store node. - if (!connectedToStore) return; + if (wakuStatus !== 'Connected to Store') return; const interval = setInterval(() => { waku.store @@ -57,33 +49,27 @@ function App() { }, 10000); return () => clearInterval(interval); - }, [waku, connectedToStore]); + }, [waku, wakuStatus]); React.useEffect(() => { if (!waku) return; // We do not handle disconnection/re-connection in this example - if (connectedToStore) return; + if (wakuStatus === 'Connected to Store') return; const isStoreNode = ({ protocols }) => { if (protocols.includes(StoreCodec)) { // We are now connected to a store node - setConnectedToStore(true); + setWakuStatus('Connected to Store'); } }; - // This demonstrates how to wait for a connection to a store node. - // - // This is only for demonstration purposes. It is not really needed in this - // example app as we query the store node every 10s and catch if it fails. - // Meaning if we are not connected to a store node, then it just fails and - // we try again 10s later. waku.libp2p.peerStore.on('change:protocols', isStoreNode); return () => { waku.libp2p.peerStore.removeListener('change:protocols', isStoreNode); }; - }, [waku, connectedToStore]); + }, [waku, wakuStatus]); return (
@@ -100,11 +86,6 @@ function App() { export default App; -async function bootstrapWaku(waku) { - const nodes = await getStatusFleetNodes(); - await Promise.all(nodes.map((addr) => waku.dial(addr))); -} - function decodeMessage(wakuMessage) { if (!wakuMessage.payload) return; diff --git a/examples/web-chat/src/App.tsx b/examples/web-chat/src/App.tsx index 072eebda9b..fdc292e938 100644 --- a/examples/web-chat/src/App.tsx +++ b/examples/web-chat/src/App.tsx @@ -2,8 +2,7 @@ import { useEffect, useReducer, useState } from 'react'; import './App.css'; import { Direction, - Environment, - getStatusFleetNodes, + getBootstrapNodes, StoreCodec, Waku, WakuMessage, @@ -182,16 +181,10 @@ async function initWaku(setter: (waku: Waku) => void) { }, }, }, + bootstrap: getBootstrapNodes.bind({}, selectFleetEnv()), }); setter(waku); - - const nodes = await getStatusFleetNodes(selectFleetEnv()); - await Promise.all( - nodes.map((addr) => { - return waku.dial(addr); - }) - ); } catch (e) { console.log('Issue starting waku ', e); } @@ -200,9 +193,9 @@ async function initWaku(setter: (waku: Waku) => void) { function selectFleetEnv() { // Works with react-scripts if (process?.env?.NODE_ENV === 'development') { - return Environment.Test; + return ['fleets', 'wakuv2.test', 'waku-websocket']; } else { - return Environment.Prod; + return ['fleets', 'wakuv2.prod', 'waku-websocket']; } } diff --git a/guides/reactjs-relay.md b/guides/reactjs-relay.md index a9cb34b4ef..be576608a9 100644 --- a/guides/reactjs-relay.md +++ b/guides/reactjs-relay.md @@ -82,16 +82,18 @@ function App() { # Connect to Other Peers The Waku instance needs to connect to other peers to communicate with the network. -First, create `bootstrapWaku` to connect to the Status fleet: +First, create `bootstrapWaku` to connect to Waku bootstrap nodes: ```js -import { getStatusFleetNodes } from 'js-waku'; +import { getBootstrapNodes } from 'js-waku'; async function bootstrapWaku(waku) { - // Retrieve node addresses from https://fleets.status.im/ - const nodes = await getStatusFleetNodes(); - // Connect to the nodes - await Promise.all(nodes.map((addr) => waku.dial(addr))); + try { + const nodes = await getBootstrapNodes(); + await Promise.all(nodes.map((addr) => waku.dial(addr))); + } catch (e) { + console.error('Failed to bootstrap to Waku network'); + } } ``` diff --git a/guides/relay-receive-send-messages.md b/guides/relay-receive-send-messages.md index e6890f93bb..47b6637d5e 100644 --- a/guides/relay-receive-send-messages.md +++ b/guides/relay-receive-send-messages.md @@ -34,9 +34,10 @@ You are free to choose any method to bootstrap and DappConnect will ship with ne For now, the easiest way is to connect to Status' Waku fleet: ```js -import { getStatusFleetNodes } from 'js-waku'; -const nodes = await getStatusFleetNodes(); -await Promise.all(nodes.map((addr) => waku.dial(addr))); +import { getBootstrapNodes } from 'js-waku'; + +const nodes = await getBootstrapNodes(); +await Promise.all(nodes.map((addr) => waku.dial(addr))); ``` # Receive messages @@ -177,7 +178,7 @@ Feel free to check out other [guides](menu.md) or [examples](/examples/examples. Here is the final code: ```js -import { getStatusFleetNodes, Waku, WakuMessage } from 'js-waku'; +import { getBootstrapNodes, Waku, WakuMessage } from 'js-waku'; import protons from 'protons'; const proto = protons(` @@ -189,7 +190,7 @@ message SimpleChatMessage { const wakuNode = await Waku.create(); -const nodes = await getStatusFleetNodes(); +const nodes = await getBootstrapNodes(); await Promise.all(nodes.map((addr) => waku.dial(addr))); const processIncomingMessage = (wakuMessage) => { diff --git a/guides/store-retrieve-messages.md b/guides/store-retrieve-messages.md index b49d7e85d9..822009f0e9 100644 --- a/guides/store-retrieve-messages.md +++ b/guides/store-retrieve-messages.md @@ -38,20 +38,21 @@ 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 other methods to bootstrap and DappConnect will ship with new bootstrap mechanisms 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 { getStatusFleetNodes } from 'js-waku'; -const nodes = await getStatusFleetNodes(); -await Promise.all(nodes.map((addr) => waku.dial(addr))); +import { Waku } from 'js-waku'; + +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' + ] +}); ``` # Use Protobuf diff --git a/package-lock.json b/package-lock.json index a2ea0fe611..d50285af27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "it-length-prefixed": "^5.0.2", "js-sha3": "^0.8.0", "libp2p": "^0.32.0", + "libp2p-bootstrap": "^0.13.0", "libp2p-gossipsub": "^0.10.0", "libp2p-mplex": "^0.10.4", "libp2p-noise": "^4.0.0", @@ -15055,6 +15056,39 @@ "node": ">=14.0.0" } }, + "node_modules/libp2p-bootstrap": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/libp2p-bootstrap/-/libp2p-bootstrap-0.13.0.tgz", + "integrity": "sha512-8sXEZrikY+chKvMorkvOi9E/v9GvwsYr9DAEfzQZrOKQZByqhan1aXQKWrSpc4AxEv5/UopRzu1P47bkOi8wdw==", + "dependencies": { + "debug": "^4.3.1", + "mafmt": "^10.0.0", + "multiaddr": "^10.0.0", + "peer-id": "^0.15.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/libp2p-bootstrap/node_modules/peer-id": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/peer-id/-/peer-id-0.15.2.tgz", + "integrity": "sha512-3OMbup76F28gKsQK4rGheEJHwosnJGe2+Obsf1xFaS9DpUaG9/JK0rtguWVLbrkxPclsCceci8g3/ulg8jsORA==", + "dependencies": { + "class-is": "^1.1.0", + "libp2p-crypto": "^0.19.0", + "minimist": "^1.2.5", + "multiformats": "^9.3.0", + "protobufjs": "^6.10.2", + "uint8arrays": "^2.0.5" + }, + "bin": { + "peer-id": "src/bin.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/libp2p-crypto": { "version": "0.19.6", "resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.19.6.tgz", @@ -36622,6 +36656,32 @@ } } }, + "libp2p-bootstrap": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/libp2p-bootstrap/-/libp2p-bootstrap-0.13.0.tgz", + "integrity": "sha512-8sXEZrikY+chKvMorkvOi9E/v9GvwsYr9DAEfzQZrOKQZByqhan1aXQKWrSpc4AxEv5/UopRzu1P47bkOi8wdw==", + "requires": { + "debug": "^4.3.1", + "mafmt": "^10.0.0", + "multiaddr": "^10.0.0", + "peer-id": "^0.15.0" + }, + "dependencies": { + "peer-id": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/peer-id/-/peer-id-0.15.2.tgz", + "integrity": "sha512-3OMbup76F28gKsQK4rGheEJHwosnJGe2+Obsf1xFaS9DpUaG9/JK0rtguWVLbrkxPclsCceci8g3/ulg8jsORA==", + "requires": { + "class-is": "^1.1.0", + "libp2p-crypto": "^0.19.0", + "minimist": "^1.2.5", + "multiformats": "^9.3.0", + "protobufjs": "^6.10.2", + "uint8arrays": "^2.0.5" + } + } + } + }, "libp2p-crypto": { "version": "0.19.6", "resolved": "https://registry.npmjs.org/libp2p-crypto/-/libp2p-crypto-0.19.6.tgz", diff --git a/package.json b/package.json index b6f3799250..3e45650197 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "it-length-prefixed": "^5.0.2", "js-sha3": "^0.8.0", "libp2p": "^0.32.0", + "libp2p-bootstrap": "^0.13.0", "libp2p-gossipsub": "^0.10.0", "libp2p-mplex": "^0.10.4", "libp2p-noise": "^4.0.0", diff --git a/src/index.ts b/src/index.ts index e952044319..9fa7f20c8b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { getStatusFleetNodes, Environment, Protocol } from './lib/discover'; +export { getBootstrapNodes } from './lib/discovery'; export * as utils from './lib/utils'; diff --git a/src/lib/discover.ts b/src/lib/discover.ts deleted file mode 100644 index b532630a36..0000000000 --- a/src/lib/discover.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Returns multiaddrs (inc. ip) of nim-waku nodes ran by Status. - * Used as a temporary discovery helper until more parties run their own nodes. - */ -import axios from 'axios'; - -export enum Protocol { - websocket = 'websocket', - tcp = 'tcp', -} - -export enum Environment { - Test = 'test', - Prod = 'prod', -} - -export async function getStatusFleetNodes( - env: Environment = Environment.Prod, - protocol: Protocol = Protocol.websocket -): Promise { - const res = await axios.get('https://fleets.status.im/', { - headers: { 'Content-Type': 'application/json' }, - }); - - const wakuFleet = res.data.fleets[`wakuv2.${env}`]; - - switch (protocol) { - case Protocol.tcp: - return Object.values(wakuFleet['waku']); - default: - return Object.values(wakuFleet['waku-websocket']); - } -} diff --git a/src/lib/discovery.ts b/src/lib/discovery.ts new file mode 100644 index 0000000000..4999bdcb33 --- /dev/null +++ b/src/lib/discovery.ts @@ -0,0 +1,60 @@ +import axios from 'axios'; +import debug from 'debug'; + +const dbg = debug('waku:discovery'); + +/** + * GET list of nodes from remote HTTP host. + * + * Default behaviour is to return nodes hosted by Status. + * + * @param path The property path to access the node list. The result should be + * a string, a string array or an object. If the result is an object then the + * values of the objects are used as multiaddresses. For example, if the GET + * request returns `{ foo: { bar: [address1, address2] } }` then `path` should be + * `[ "foo", "bar" ]`. + * @param url Remote host containing bootstrap peers in JSON format. + * + * @returns An array of multiaddresses. + * @throws If the remote host is unreachable or the response cannot be parsed + * according to the passed _path_. + */ +export async function getBootstrapNodes( + path: string[] = ['fleets', 'wakuv2.prod', 'waku-websocket'], + url = 'https://fleets.status.im/' +): Promise { + const res = await axios.get(url, { + headers: { 'Content-Type': 'application/json' }, + }); + + let nodes = res.data; + + for (const prop of path) { + if (nodes[prop] === undefined) { + dbg( + `Failed to retrieve bootstrap nodes: ${prop} does not exist on `, + nodes + ); + throw `Failed to retrieve bootstrap nodes: ${prop} does not exist on ${JSON.stringify( + nodes + )}`; + } + nodes = nodes[prop]; + } + + if (Array.isArray(nodes)) { + return nodes; + } + + if (typeof nodes === 'string') { + return [nodes]; + } + + if (typeof nodes === 'object') { + return Object.values(nodes); + } + + throw `Failed to retrieve bootstrap nodes: response format is not supported: ${JSON.stringify( + nodes + )}`; +} diff --git a/src/lib/waku.spec.ts b/src/lib/waku.spec.ts index bad7599e33..797700bf3d 100644 --- a/src/lib/waku.spec.ts +++ b/src/lib/waku.spec.ts @@ -2,20 +2,98 @@ import { expect } from 'chai'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: No types available import TCP from 'libp2p-tcp'; +import PeerId from 'peer-id'; -import { makeLogFileName, NimWaku, NOISE_KEY_1 } from '../test_utils/'; +import { + makeLogFileName, + NimWaku, + NOISE_KEY_1, + NOISE_KEY_2, +} from '../test_utils/'; import { Waku } from './waku'; describe('Waku Dial', function () { let waku: Waku; + let waku2: Waku; let nimWaku: NimWaku; afterEach(async function () { this.timeout(10_000); nimWaku ? nimWaku.stop() : null; - waku ? await waku.stop() : null; + + await Promise.all([waku ? waku.stop() : null, waku2 ? waku2.stop() : null]); + }); + + describe('Bootstrap', function () { + it('Passing an array', async function () { + this.timeout(10_000); + + waku = await Waku.create({ + staticNoiseKey: NOISE_KEY_1, + libp2p: { + addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, + modules: { transport: [TCP] }, + }, + }); + + const multiAddrWithId = waku.getLocalMultiaddrWithID(); + + waku2 = await Waku.create({ + staticNoiseKey: NOISE_KEY_2, + libp2p: { + modules: { transport: [TCP] }, + }, + bootstrap: [multiAddrWithId], + }); + + const connectedPeerID: PeerId = await new Promise((resolve) => { + waku.libp2p.connectionManager.on('peer:connect', (connection) => { + resolve(connection.remotePeer); + }); + }); + + expect(connectedPeerID.toB58String()).to.eq( + waku2.libp2p.peerId.toB58String() + ); + }); + }); + + describe('Bootstrap', function () { + it('Passing a function', async function () { + this.timeout(10_000); + + waku = await Waku.create({ + staticNoiseKey: NOISE_KEY_1, + libp2p: { + addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, + modules: { transport: [TCP] }, + }, + }); + + const multiAddrWithId = waku.getLocalMultiaddrWithID(); + + waku2 = await Waku.create({ + staticNoiseKey: NOISE_KEY_2, + libp2p: { + modules: { transport: [TCP] }, + }, + bootstrap: () => { + return [multiAddrWithId]; + }, + }); + + const connectedPeerID: PeerId = await new Promise((resolve) => { + waku.libp2p.connectionManager.on('peer:connect', (connection) => { + resolve(connection.remotePeer); + }); + }); + + expect(connectedPeerID.toB58String()).to.eq( + waku2.libp2p.peerId.toB58String() + ); + }); }); describe('Interop: Nim', function () { diff --git a/src/lib/waku.ts b/src/lib/waku.ts index 49c813cd5b..c3c1fe9c2c 100644 --- a/src/lib/waku.ts +++ b/src/lib/waku.ts @@ -1,4 +1,6 @@ +import debug from 'debug'; import Libp2p, { Connection, Libp2pModules, Libp2pOptions } from 'libp2p'; +import Bootstrap from 'libp2p-bootstrap'; import { MuxedStream } from 'libp2p-interfaces/dist/src/stream-muxer/types'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: No types available @@ -15,6 +17,7 @@ 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 { WakuMessage } from './waku_message'; import { RelayCodecs, WakuRelay } from './waku_relay'; @@ -23,8 +26,10 @@ import { StoreCodec, WakuStore } from './waku_store'; const websocketsTransportKey = Websockets.prototype[Symbol.toStringTag]; -const DefaultPingKeepAliveValueSecs = 0; -const DefaultRelayKeepAliveValueSecs = 5 * 60; +export const DefaultPingKeepAliveValueSecs = 0; +export const DefaultRelayKeepAliveValueSecs = 5 * 60; + +const dbg = debug('waku:waku'); export interface CreateOptions { /** @@ -71,6 +76,18 @@ export interface CreateOptions { * This is only used for test purposes to not run out of entropy during CI runs. */ staticNoiseKey?: bytes; + /** + * Use libp2p-bootstrap to discover and connect to new nodes. + * + * You can pass: + * - `true` to use {@link getBootstrapNodes}, + * - an array of multiaddresses, + * - a function that returns an array of multiaddresses (or Promise of). + * + * Note: It overrides any other peerDiscovery modules that may have been set via + * {@link CreateOptions.libp2p}. + */ + bootstrap?: boolean | string[] | (() => string[] | Promise); } export class Waku { @@ -161,6 +178,40 @@ export class Waku { pubsub: WakuRelay, }); + if (options?.bootstrap) { + let bootstrap: undefined | (() => string[] | Promise); + + if (options.bootstrap === true) { + bootstrap = getBootstrapNodes; + } else if (Array.isArray(options.bootstrap)) { + bootstrap = (): string[] => { + return options.bootstrap as string[]; + }; + } else if (typeof options.bootstrap === 'function') { + bootstrap = options.bootstrap; + } + + if (bootstrap !== undefined) { + // Note: this overrides any other peer discover + libp2pOpts.modules = Object.assign(libp2pOpts.modules, { + peerDiscovery: [Bootstrap], + }); + + try { + const list = await bootstrap(); + + libp2pOpts.config.peerDiscovery = { + [Bootstrap.tag]: { + list, + enabled: true, + }, + }; + } catch (e) { + dbg('Failed to retrieve bootstrap nodes', e); + } + } + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: modules property is correctly set thanks to voodoo const libp2p = await Libp2p.create(libp2pOpts);