diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..290ad02 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "10:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..13da9c1 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,50 @@ +# Automatically merge pull requests opened by web3-bot, as soon as (and only if) all tests pass. +# This reduces the friction associated with updating with our workflows. + +on: [ pull_request ] +name: Automerge + +jobs: + automerge-check: + if: github.event.pull_request.user.login == 'web3-bot' + runs-on: ubuntu-latest + outputs: + status: ${{ steps.should-automerge.outputs.status }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Check if we should automerge + id: should-automerge + run: | + for commit in $(git rev-list --first-parent origin/${{ github.event.pull_request.base.ref }}..${{ github.event.pull_request.head.sha }}); do + committer=$(git show --format=$'%ce' -s $commit) + echo "Committer: $committer" + if [[ "$committer" != "web3-bot@users.noreply.github.com" ]]; then + echo "Commit $commit wasn't committed by web3-bot, but by $committer." + echo "::set-output name=status::false" + exit + fi + done + echo "::set-output name=status::true" + automerge: + needs: automerge-check + runs-on: ubuntu-latest + # The check for the user is redundant here, as this job depends on the automerge-check job, + # but it prevents this job from spinning up, just to be skipped shortly after. + if: github.event.pull_request.user.login == 'web3-bot' && needs.automerge-check.outputs.status == 'true' + steps: + - name: Wait on tests + uses: lewagon/wait-on-check-action@bafe56a6863672c681c3cf671f5e10b20abf2eaa # v0.2 + with: + ref: ${{ github.event.pull_request.head.sha }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + running-workflow-name: 'automerge' # the name of this job + - name: Merge PR + uses: pascalgn/automerge-action@741c311a47881be9625932b0a0de1b0937aab1ae # v0.13.1 + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + MERGE_LABELS: "" + MERGE_METHOD: "squash" + MERGE_DELETE_BRANCH: true diff --git a/.github/workflows/js-test-and-release.yml b/.github/workflows/js-test-and-release.yml new file mode 100644 index 0000000..8630dc5 --- /dev/null +++ b/.github/workflows/js-test-and-release.yml @@ -0,0 +1,152 @@ +name: test & maybe release +on: + push: + branches: + - master # with #262 - ${{{ github.default_branch }}} + pull_request: + branches: + - master # with #262 - ${{{ github.default_branch }}} + +jobs: + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present lint + - run: npm run --if-present dep-check + + test-node: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + node: [16] + fail-fast: true + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:node + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: node + + test-chrome: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: chrome + + test-chrome-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:chrome-webworker + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: chrome-webworker + + test-firefox: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: firefox + + test-firefox-webworker: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npm run --if-present test:firefox-webworker + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: firefox-webworker + + test-electron-main: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-main + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: electron-main + + test-electron-renderer: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - run: npx xvfb-maybe npm run --if-present test:electron-renderer + - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 + with: + directory: ./.nyc_output + flags: electron-renderer + + release: + needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-electron-main, test-electron-renderer] + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/master' # with #262 - 'refs/heads/${{{ github.default_branch }}}' + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-node@v2 + with: + node-version: lts/* + - uses: ipfs/aegir/actions/cache-node-modules@master + - uses: ipfs/aegir/actions/docker-login@master + with: + docker-token: ${{ secrets.DOCKER_TOKEN }} + docker-username: ${{ secrets.DOCKER_USERNAME }} + - run: npm run --if-present release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 400e18f..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: ci -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: npm install - - run: npx aegir lint - - run: npx aegir dep-check - - run: npx aegir build - test-node: - needs: check - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - node: [16] - fail-fast: true - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node }} - - run: npm install - - run: npx aegir test -t node --bail --cov - - uses: codecov/codecov-action@v1 - test-chrome: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: lts/* - - run: npm install - - run: npx aegir test -t browser -t webworker --bail - test-firefox: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: lts/* - - run: npm install - - run: npx aegir test -t browser -t webworker --bail -- --browser firefox \ No newline at end of file diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 00a160d..0000000 --- a/.npmignore +++ /dev/null @@ -1,29 +0,0 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git -node_modules - -test diff --git a/LICENSE b/LICENSE index 59a33ba..20ce483 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,4 @@ -The MIT License (MIT) - -Copyright (c) 2015 David Dias - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +This project is dual licensed under MIT and Apache-2.0. +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index b736803..1a96286 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,16 @@ -js-libp2p-bootstrap -================= +# js-libp2p-bootstrap [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai) [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) [![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p) [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) [![](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-bootstrap.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-bootstrap) -[![](https://img.shields.io/travis/libp2p/js-libp2p-bootstrap.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-bootstrap) +[![Build Status](https://github.com/libp2p/js-libp2p-bootstrap/actions/workflows/js-test-and-release.yml/badge.svg?branch=main)](https://github.com/libp2p/js-libp2p-bootstrap/actions/workflows/js-test-and-release.yml) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) ![](https://img.shields.io/badge/Node.js-%3E%3D14.0.0-orange.svg?style=flat-square) > JavaScript libp2p Implementation of the railing process of a Node through a bootstrap peer list -## Lead Maintainer - -[Vasco Santos](https://github.com/vasco-santos). - ## Usage ```JavaScript @@ -38,7 +33,7 @@ let options = { list: [ // a list of bootstrap peer multiaddrs to connect to on node startup "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa" + "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa" ], interval: 5000 // default is 10 ms, enabled: true @@ -60,3 +55,21 @@ async function start () { start() ``` + +## Contribute + +The libp2p implementation in JavaScript is a work in progress. As such, there are a few things you can do right now to help out: + + - Go through the modules and **check out existing issues**. This is especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it - for instance, you may need to read up on p2p and more complex operations like muxing to be able to help technically. + - **Perform code reviews**. More eyes will help a) speed the project along b) ensure quality and c) reduce possible future bugs. + +## License + +Licensed under either of + + * Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0) + * MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT) + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/examples/try.js b/examples/try.js index 419b69e..62995bd 100644 --- a/examples/try.js +++ b/examples/try.js @@ -1,6 +1,5 @@ -'use strict' +import { Bootstrap } from './../src/index.js' -var Bootstrap = require('./../src') -var b = new Bootstrap(Bootstrap.default) +const b = new Bootstrap() console.log(b) diff --git a/package.json b/package.json index 7724079..1a02219 100644 --- a/package.json +++ b/package.json @@ -1,64 +1,147 @@ { - "name": "libp2p-bootstrap", + "name": "@libp2p/bootstrap", "version": "0.14.0", "description": "Node.js IPFS Implementation of the railing process of a Node through a bootstrap peer list", - "leadMaintainer": "Vasco Santos ", - "main": "src/index.js", - "scripts": { - "lint": "aegir lint", - "build": "aegir build", - "test": "aegir test", - "test:node": "aegir test -t node", - "test:browser": "aegir test -t browser", - "test:webworker": "aegir test -t webworker", - "release": "aegir release -t node", - "release-minor": "aegir release --type minor -t node", - "release-major": "aegir release --type major -t node", - "coverage": "aegir coverage", - "coverage-publish": "aegir coverage publish" + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p-bootstrap#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/libp2p/js-libp2p-bootstrap.git" + }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p-bootstrap/issues" }, "keywords": [ "IPFS" ], - "license": "MIT", - "bugs": "https://github.com/libp2p/js-libp2p-bootstrap/issues", - "homepage": "https://github.com/libp2p/js-libp2p-bootstrap", - "repository": "github:libp2p/js-libp2p-bootstrap", "engines": { - "node": ">=15.0.0" + "node": ">=16.0.0", + "npm": ">=7.0.0" }, - "types": "dist/src/index.d.ts", - "devDependencies": { - "@types/debug": "^4.1.5", - "aegir": "^36.0.2", - "libp2p-interfaces": "^2.0.1", - "libp2p-interfaces-compliance-tests": "^2.0.1" + "type": "module", + "types": "./dist/src/index.d.ts", + "files": [ + "src", + "dist/src", + "!dist/test", + "!**/*.tsbuildinfo" + ], + "exports": { + ".": { + "import": "./dist/src/index.js" + } + }, + "eslintConfig": { + "extends": "ipfs", + "parserOptions": { + "sourceType": "module" + } + }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "chore", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Trivial Changes" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, + "scripts": { + "lint": "aegir lint", + "dep-check": "aegir dep-check dist/**/*.js", + "build": "tsc", + "pretest": "npm run build", + "test": "aegir test -f ./dist/test", + "test:chrome": "npm run test -- -t browser --cov", + "test:chrome-webworker": "npm run test -- -t webworker", + "test:firefox": "npm run test -- -t browser -- --browser firefox", + "test:firefox-webworker": "npm run test -- -t webworker -- --browser firefox", + "test:node": "npm run test -- -t node --cov", + "test:electron-main": "npm run test -- -t electron-main", + "release": "semantic-release" }, "dependencies": { - "debug": "^4.3.1", - "mafmt": "^10.0.0", - "multiaddr": "^10.0.0", - "peer-id": "^0.16.0" + "@libp2p/peer-id": "^1.0.4", + "@multiformats/mafmt": "^11.0.2", + "@multiformats/multiaddr": "^10.1.2", + "debug": "^4.3.1" }, - "contributors": [ - "David Dias ", - "Vasco Santos ", - "Alex Potsides ", - "Friedel Ziegelmayer ", - "acolytec3 <17355484+acolytec3@users.noreply.github.com>", - "Alan Shaw ", - "Jacob Heun ", - "Victor Bjelkholm ", - "Abraham Elmahrek ", - "ころ ", - "Dmitriy Ryajov ", - "Hugo Dias ", - "Kevin Kwok ", - "Michael Burns <5170+mburns@users.noreply.github.com>", - "Nuno Nogueira ", - "Richard Littauer ", - "Zane Starr ", - "dirkmc ", - "victorbjelkholm " - ] + "devDependencies": { + "@libp2p/interface-compliance-tests": "^1.0.8", + "@libp2p/interfaces": "^1.1.1", + "@types/debug": "^4.1.5", + "aegir": "^36.1.3" + } } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index a49ab08..0000000 --- a/src/index.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict' - -const PeerId = require('peer-id') -const { Multiaddr } = require('multiaddr') -const mafmt = require('mafmt') -const { EventEmitter } = require('events') -const debug = require('debug') - -const log = Object.assign(debug('libp2p:bootstrap'), { - error: debug('libp2p:bootstrap:error') -}) - -/** - * Emits 'peer' events on a regular interval for each peer in the provided list. - */ -class Bootstrap extends EventEmitter { - /** - * Constructs a new Bootstrap. - * - * @param {Object} options - * @param {Array} options.list - the list of peer addresses in multi-address format - * @param {number} [options.interval = 10000] - the interval between emitting addresses in milliseconds - * - */ - constructor (options = { list: [] }) { - if (!options.list || !options.list.length) { - throw new Error('Bootstrap requires a list of peer addresses') - } - super() - - this._list = options.list - this._interval = options.interval || 10000 - this._timer = null - } - - /** - * Start emitting events. - */ - start () { - if (this._timer) { - return - } - - this._timer = setInterval(() => this._discoverBootstrapPeers(), this._interval) - log('Starting bootstrap node discovery') - this._discoverBootstrapPeers() - } - - /** - * Emit each address in the list as a PeerInfo. - */ - _discoverBootstrapPeers () { - if (!this._timer) { - return - } - - this._list.forEach((candidate) => { - if (!mafmt.P2P.matches(candidate)) { - return log.error('Invalid multiaddr') - } - - const ma = new Multiaddr(candidate) - const peerIdStr = ma.getPeerId() - - if (!peerIdStr) { - log.error('Invalid bootstrap multiaddr without peer id') - return - } - - const peerId = PeerId.createFromB58String(peerIdStr) - - try { - this.emit('peer', { - id: peerId, - multiaddrs: [ma] - }) - } catch (err) { - log.error('Invalid bootstrap peer id', err) - } - }) - } - - /** - * Stop emitting events. - */ - stop () { - if (this._timer) clearInterval(this._timer) - this._timer = null - } -} - -exports = module.exports = Bootstrap -exports.tag = 'bootstrap' diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..479af3e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,108 @@ +import { PeerId } from '@libp2p/peer-id' +import { Multiaddr } from '@multiformats/multiaddr' +import { P2P } from '@multiformats/mafmt' +import { EventEmitter } from 'events' +import debug from 'debug' +import type PeerDiscovery from '@libp2p/interfaces/peer-discovery' +import type { PeerData } from '@libp2p/interfaces/peer-data' + +const log = Object.assign(debug('libp2p:bootstrap'), { + error: debug('libp2p:bootstrap:error') +}) + +export interface BootstrapOptions { + /** + * The list of peer addresses in multi-address format + */ + list: string[] + + /** + * The interval between emitting addresses in milliseconds + */ + interval?: number +} + +/** + * Emits 'peer' events on a regular interval for each peer in the provided list. + */ +export class Bootstrap extends EventEmitter implements PeerDiscovery { + static tag = 'bootstrap' + + private timer?: NodeJS.Timer + private readonly list: PeerData[] + private readonly interval: number + + constructor (options: BootstrapOptions = { list: [] }) { + if (options.list == null || options.list.length === 0) { + throw new Error('Bootstrap requires a list of peer addresses') + } + super() + + this.interval = options.interval ?? 10000 + this.list = [] + + for (const candidate of options.list) { + if (!P2P.matches(candidate)) { + log.error('Invalid multiaddr') + continue + } + + const ma = new Multiaddr(candidate) + const peerIdStr = ma.getPeerId() + + if (peerIdStr == null) { + log.error('Invalid bootstrap multiaddr without peer id') + continue + } + + const peerData: PeerData = { + id: PeerId.fromString(peerIdStr), + multiaddrs: [ma], + protocols: [] + } + + this.list.push(peerData) + } + } + + isStarted () { + return Boolean(this.timer) + } + + /** + * Start emitting events + */ + start () { + if (this.timer != null) { + return + } + + this.timer = setInterval(() => this._discoverBootstrapPeers(), this.interval) + log('Starting bootstrap node discovery') + this._discoverBootstrapPeers() + } + + /** + * Emit each address in the list as a PeerInfo + */ + _discoverBootstrapPeers () { + if (this.timer == null) { + return + } + + this.list.forEach((peerData) => { + this.emit('peer', peerData) + }) + } + + /** + * Stop emitting events + */ + stop () { + if (this.timer != null) { + clearInterval(this.timer) + } + + this.timer = undefined + } +} diff --git a/test/bootstrap.spec.js b/test/bootstrap.spec.ts similarity index 68% rename from test/bootstrap.spec.js rename to test/bootstrap.spec.ts index 6528ebf..84ce18f 100644 --- a/test/bootstrap.spec.js +++ b/test/bootstrap.spec.ts @@ -1,24 +1,17 @@ -'use strict' /* eslint-env mocha */ -const { expect } = require('aegir/utils/chai') - -const mafmt = require('mafmt') -const PeerId = require('peer-id') - -const Bootstrap = require('../src') -const peerList = require('./default-peers') -const partialValidPeerList = require('./some-invalid-peers') +import { expect } from 'aegir/utils/chai.js' +import { IPFS } from '@multiformats/mafmt' +import { PeerId } from '@libp2p/peer-id' +import { Bootstrap } from '../src/index.js' +import peerList from './fixtures/default-peers.js' +import partialValidPeerList from './fixtures/some-invalid-peers.js' +import type { PeerData } from '@libp2p/interfaces/peer-data' describe('bootstrap', () => { it('should throw if no peer list is provided', () => { - try { - const b = new Bootstrap() // eslint-disable-line no-unused-vars - } catch (err) { - expect(err).to.exist() - return - } - throw new Error('should throw if no peer list is provided') + expect(() => new Bootstrap()) + .to.throw('Bootstrap requires a list of peer addresses') }) it('find the other peer', async function () { @@ -43,12 +36,12 @@ describe('bootstrap', () => { interval: 2000 }) - const p = new Promise((resolve) => { + const p = new Promise((resolve) => { r.once('peer', ({ id, multiaddrs }) => { expect(id).to.exist() - expect(PeerId.isPeerId(id)).to.eql(true) + expect(id).to.be.an.instanceOf(PeerId) expect(multiaddrs.length).to.eq(1) - expect(mafmt.IPFS.matches(multiaddrs[0].toString())).equals(true) + expect(IPFS.matches(multiaddrs[0].toString())).equals(true) resolve() }) }) @@ -66,7 +59,7 @@ describe('bootstrap', () => { interval }) - let emitted = [] + let emitted: PeerData[] = [] r.on('peer', p => emitted.push(p)) // Should fire emit event for each peer in list on start, diff --git a/test/compliance.spec.js b/test/compliance.spec.js deleted file mode 100644 index 429e17e..0000000 --- a/test/compliance.spec.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -/* eslint-env mocha */ - -const tests = require('libp2p-interfaces-compliance-tests/src/peer-discovery') - -const Bootstrap = require('../src') -const peerList = require('./default-peers') - -describe('compliance tests', () => { - tests({ - setup () { - const bootstrap = new Bootstrap({ - list: peerList, - interval: 2000 - }) - - return bootstrap - } - }) -}) diff --git a/test/compliance.spec.ts b/test/compliance.spec.ts new file mode 100644 index 0000000..b8564cc --- /dev/null +++ b/test/compliance.spec.ts @@ -0,0 +1,19 @@ +/* eslint-env mocha */ + +import tests from '@libp2p/interface-compliance-tests/peer-discovery' +import { Bootstrap } from '../src/index.js' +import peerList from './fixtures/default-peers.js' + +describe('compliance tests', () => { + tests({ + async setup () { + const bootstrap = new Bootstrap({ + list: peerList, + interval: 2000 + }) + + return bootstrap + }, + async teardown () {} + }) +}) diff --git a/test/default-peers.json b/test/default-peers.json deleted file mode 100644 index d1a07ef..0000000 --- a/test/default-peers.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - "/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", - "/dnsaddr/bootstrap.libp2p.io/ipfs/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp" -] diff --git a/test/fixtures/default-peers.ts b/test/fixtures/default-peers.ts new file mode 100644 index 0000000..3b611d6 --- /dev/null +++ b/test/fixtures/default-peers.ts @@ -0,0 +1,10 @@ +const peers: string[] = [ + '/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', + '/dnsaddr/bootstrap.libp2p.io/ipfs/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', + '/dnsaddr/bootstrap.libp2p.io/ipfs/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa', + '/dnsaddr/bootstrap.libp2p.io/ipfs/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', + '/dnsaddr/bootstrap.libp2p.io/ipfs/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', + '/dnsaddr/bootstrap.libp2p.io/ipfs/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp' +] + +export default peers diff --git a/test/fixtures/some-invalid-peers.ts b/test/fixtures/some-invalid-peers.ts new file mode 100644 index 0000000..2449b3a --- /dev/null +++ b/test/fixtures/some-invalid-peers.ts @@ -0,0 +1,9 @@ +const peers: string[] = [ + // @ts-expect-error this is an invalid peer + null, + '/ip4/104.236.151.122/tcp/4001/ipfs/malformed-peer-id', + '/ip4/bad.ip.addr/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ', + '/ip4/104.236.151.122/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx' +] + +export default peers diff --git a/test/some-invalid-peers.json b/test/some-invalid-peers.json deleted file mode 100644 index dde03e2..0000000 --- a/test/some-invalid-peers.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - null, - "/ip4/104.236.151.122/tcp/4001/ipfs/malformed-peer-id", - "/ip4/bad.ip.addr/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/ip4/104.236.151.122/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx" -] \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index ee88f58..f296f99 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,12 @@ { - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist", - "baseUrl": "./", - "paths": { - "*": ["./types/*"] - } - }, - "include": [ - "types", - "src" - ] + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020" + }, + "include": [ + "src", + "test" + ] }