diff --git a/.aegir.cjs b/.aegir.cjs new file mode 100644 index 0000000..53f9c6f --- /dev/null +++ b/.aegir.cjs @@ -0,0 +1,28 @@ +'use strict' + +/** @type {import('aegir').PartialOptions} */ +module.exports = { + test: { + async before () { + const { Multiaddr } = await import('@multiformats/multiaddr') + const { mockUpgrader } = await import('@libp2p/interface-compliance-tests/transport/utils') + const { WebSockets } = await import('./dist/src/index.js') + const { pipe } = await import('it-pipe') + + const ws = new WebSockets({ upgrader: mockUpgrader() }) + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9095/ws') + const listener = ws.createListener(conn => pipe(conn, conn)) + await listener.listen(ma) + listener.addEventListener('error', (evt) => { + console.error(evt.detail) + }) + + return { + listener + } + }, + async after (_, before) { + await before.listener.close() + } + } +} diff --git a/.aegir.js b/.aegir.js deleted file mode 100644 index 08fd135..0000000 --- a/.aegir.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict' - -const { Multiaddr } = require('multiaddr') -const pipe = require('it-pipe') -const WS = require('./src') - -const mockUpgrader = { - upgradeInbound: maConn => maConn, - upgradeOutbound: maConn => maConn -} -let listener - -async function before () { - const ws = new WS({ upgrader: mockUpgrader }) - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9095/ws') - listener = ws.createListener(conn => pipe(conn, conn)) - await listener.listen(ma) - listener.on('error', console.error) -} - -function after () { - return listener.close() -} - -module.exports = { - test: { - before, - after - } -} 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 4811148..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,48 +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 -- -i wrtc -i electron-webrtc - - run: npx aegir build --no-types - test-node: - needs: check - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - node: [14, 16] - fail-fast: true - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node }} - - run: npm install - - run: npx nyc --reporter=lcov aegir test -t node -- --bail - - uses: codecov/codecov-action@v1 - test-chrome: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - 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 - - run: npm install - - run: npx aegir test -t browser -t webworker --bail -- --browsers FirefoxHeadless diff --git a/LICENSE b/LICENSE index bb9cf40..20ce483 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,4 @@ -The MIT License (MIT) +This project is dual licensed under MIT and Apache-2.0. -Copyright (c) 2016 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. +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 4f3d367..98c4baa 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ -# js-libp2p-websockets +# js-libp2p-websockets [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) [![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io) [![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-websockets/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-websockets?branch=master) -[![Travis CI](https://travis-ci.org/libp2p/js-libp2p-websockets.svg?branch=master)](https://travis-ci.org/libp2p/js-libp2p-websockets) -[![Circle CI](https://circleci.com/gh/libp2p/js-libp2p-websockets.svg?style=svg)](https://circleci.com/gh/libp2p/js-libp2p-websockets) +[![Build Status](https://github.com/libp2p/js-libp2p-websockets/actions/workflows/js-test-and-release.yml/badge.svg?branch=main)](https://github.com/libp2p/js-libp2p-websockets/actions/workflows/js-test-and-release.yml) [![Dependency Status](https://david-dm.org/libp2p/js-libp2p-websockets.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-websockets) [![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) @@ -16,9 +15,19 @@ > JavaScript implementation of the WebSockets module that libp2p uses and that implements the interface-transport interface -## Lead Maintainer +## Table of Contents -[Jacob Heun](https://github.com/jacobheun) +- [Description](#description) +- [Usage](#usage) +- [Install](#install) + - [npm](#npm) + - [Constructor properties](#constructor-properties) +- [Libp2p Usage Example](#libp2p-usage-example) +- [API](#api) + - [Transport](#transport) + - [Connection](#connection) +- [License](#license) + - [Contribution](#contribution) ## Description @@ -31,13 +40,13 @@ ### npm ```sh -> npm i libp2p-websockets +> npm i @libp2p/websockets ``` ### Constructor properties ```js -const WS = require('libp2p-websockets') +import WS from '@libp2p/websockets' const properties = { upgrader, @@ -49,7 +58,7 @@ const ws = new WS(properties) | Name | Type | Description | Default | |------|------|-------------|---------| -| upgrader | [`Upgrader`](https://github.com/libp2p/interface-transport#upgrader) | connection upgrader object with `upgradeOutbound` and `upgradeInbound` | **REQUIRED** | +| upgrader | [`Upgrader`](https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#upgrader) | connection upgrader object with `upgradeOutbound` and `upgradeInbound` | **REQUIRED** | | filter | `(multiaddrs: Array) => Array` | override transport addresses filter | **Browser:** DNS+WSS multiaddrs / **Node.js:** DNS+{WS, WSS} multiaddrs | You can create your own address filters for this transports, or rely in the filters [provided](./src/filters.js). @@ -66,11 +75,11 @@ The available filters are: ## Libp2p Usage Example ```js -const Libp2p = require('libp2p') -const Websockets = require('libp2p-websockets') -const filters = require('libp2p-websockets/src/filters') -const MPLEX = require('libp2p-mplex') -const { NOISE } = require('libp2p-noise') +import Libp2p from 'libp2p' +import { Websockets } from '@libp2p/websockets' +import filters from 'libp2p-websockets/filters' +import { MPLEX } from 'libp2p-mplex' +import { NOISE } from 'libp2p-noise' const transportKey = Websockets.prototype[Symbol.toStringTag] const node = await Libp2p.create({ @@ -100,3 +109,14 @@ For more information see [libp2p/js-libp2p/doc/CONFIGURATION.md#customizing-tran ### Connection [![](https://github.com/raw/libp2p/interface-connection/master/img/badge.png)](https://github.com/libp2p/interface-connection) + +## 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/package.json b/package.json index 4cef846..4e853ef 100644 --- a/package.json +++ b/package.json @@ -1,88 +1,166 @@ { - "name": "libp2p-websockets", + "name": "@libp2p/websockets", "version": "0.16.2", "description": "JavaScript implementation of the WebSockets module that libp2p uses and that implements the interface-transport spec", - "leadMaintainer": "Jacob Heun ", - "main": "src/index.js", - "scripts": { - "lint": "aegir lint", - "build": "aegir build", - "test": "aegir test -t node -t browser ", - "test:node": "aegir test -t node", - "test:browser": "aegir test -t browser ", - "release": "aegir release -t node -t browser ", - "release-minor": "aegir release --type minor -t node -t browser", - "release-major": "aegir release --type major -t node -t browser", - "coverage": "nyc --reporter=lcov --reporter=text npm run test:node" - }, - "browser": { - "./src/listener.js": "./src/listener.browser.js" - }, - "files": [ - "src", - "dist" - ], - "pre-push": [ - "lint" - ], + "license": "Apache-2.0 OR MIT", + "homepage": "https://github.com/libp2p/js-libp2p-websockets#readme", "repository": { "type": "git", "url": "git+https://github.com/libp2p/js-libp2p-websockets.git" }, + "bugs": { + "url": "https://github.com/libp2p/js-libp2p-websockets/issues" + }, "keywords": [ "IPFS" ], - "license": "MIT", - "bugs": { - "url": "https://github.com/libp2p/js-libp2p-websockets/issues" + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "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/src/**/*.js dist/test/**/*.js", + "build": "tsc", + "pretest": "npm run build", + "test": "aegir test", + "test:chrome": "npm run test -- -t browser -f ./dist/test/browser.js --cov", + "test:chrome-webworker": "npm run test -- -t webworker -f ./dist/test/browser.js", + "test:firefox": "npm run test -- -t browser -f ./dist/test/browser.js -- --browser firefox", + "test:firefox-webworker": "npm run test -- -t webworker -f ./dist/test/browser.js -- --browser firefox", + "test:node": "npm run test -- -t node -f ./dist/test/node.js --cov", + "test:electron-main": "npm run test -- -t electron-main -f ./dist/test/node.js --cov", + "release": "semantic-release" }, - "homepage": "https://github.com/libp2p/js-libp2p-websockets#readme", "dependencies": { - "abortable-iterator": "^3.0.0", - "class-is": "^1.1.0", - "debug": "^4.3.1", + "@libp2p/logger": "^1.0.2", + "@libp2p/utils": "^1.0.0", + "@multiformats/mafmt": "^11.0.1", + "@multiformats/multiaddr": "^10.0.0", + "@multiformats/multiaddr-to-uri": "^9.0.0", + "abortable-iterator": "^4.0.2", "err-code": "^3.0.1", - "ipfs-utils": "^9.0.1", - "it-ws": "^4.0.0", - "libp2p-utils": "^0.4.0", - "mafmt": "^10.0.0", - "multiaddr": "^10.0.0", - "multiaddr-to-uri": "^8.0.0", - "p-defer": "^3.0.0", - "p-timeout": "^4.1.0" + "it-ws": "^5.0.0", + "p-defer": "^4.0.0", + "p-timeout": "^5.0.2", + "wherearewe": "^1.0.0" }, "devDependencies": { - "abort-controller": "^3.0.0", - "aegir": "^33.0.0", - "bl": "^5.0.0", - "is-loopback-addr": "^1.0.1", - "it-goodbye": "^3.0.0", - "it-pipe": "^1.1.0", - "libp2p-interfaces": "^1.0.0", - "libp2p-interfaces-compliance-tests": "^1.0.0", - "streaming-iterables": "^6.0.0", - "uint8arrays": "^2.1.2", + "@libp2p/interface-compliance-tests": "^1.1.2", + "@libp2p/interfaces": "^1.3.2", + "@types/ws": "^8.2.2", + "aegir": "^36.1.3", + "is-loopback-addr": "^2.0.1", + "it-all": "^1.0.6", + "it-drain": "^1.0.5", + "it-goodbye": "^4.0.1", + "it-pipe": "^2.0.2", + "it-take": "^1.0.2", + "p-wait-for": "^4.1.0", + "uint8arrays": "^3.0.0", "util": "^0.12.3" }, - "contributors": [ - "David Dias ", - "Vasco Santos ", - "Jacob Heun ", - "Friedel Ziegelmayer ", - "Alex Potsides ", - "Francisco Baio Dias ", - "Hugo Dias ", - "Dmitriy Ryajov ", - "Maciej Krüger ", - "ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ ", - "Chris Campbell ", - "Diogo Silva ", - "Irakli Gozalishvili ", - "Jack Kleeman ", - "Marcin Rataj ", - "Michael FIG ", - "Richard Littauer ", - "nikor ", - "Alan Shaw " - ] + "browser": { + "./dist/src/listener.js": "./dist/src/listener.browser.js" + } } diff --git a/src/constants.js b/src/constants.js deleted file mode 100644 index 28f2634..0000000 --- a/src/constants.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict' - -// p2p multi-address code -exports.CODE_P2P = 421 -exports.CODE_CIRCUIT = 290 - -exports.CODE_TCP = 6 -exports.CODE_WS = 477 -exports.CODE_WSS = 478 - -// Time to wait for a connection to close gracefully before destroying it manually -exports.CLOSE_TIMEOUT = 2000 diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..e8c3939 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,10 @@ +// p2p multi-address code +export const CODE_P2P = 421 +export const CODE_CIRCUIT = 290 + +export const CODE_TCP = 6 +export const CODE_WS = 477 +export const CODE_WSS = 478 + +// Time to wait for a connection to close gracefully before destroying it manually +export const CLOSE_TIMEOUT = 2000 diff --git a/src/filters.js b/src/filters.ts similarity index 70% rename from src/filters.js rename to src/filters.ts index 2aa0bc8..e01ffcf 100644 --- a/src/filters.js +++ b/src/filters.ts @@ -1,16 +1,15 @@ -'use strict' - -const mafmt = require('mafmt') -const { +import * as mafmt from '@multiformats/mafmt' +import type { Multiaddr } from '@multiformats/multiaddr' +import { CODE_CIRCUIT, CODE_P2P, CODE_TCP, CODE_WS, CODE_WSS -} = require('./constants') +} from './constants.js' -module.exports = { - all: (multiaddrs) => multiaddrs.filter((ma) => { +export function all (multiaddrs: Multiaddr[]) { + return multiaddrs.filter((ma) => { if (ma.protoCodes().includes(CODE_CIRCUIT)) { return false } @@ -19,8 +18,11 @@ module.exports = { return mafmt.WebSockets.matches(testMa) || mafmt.WebSocketsSecure.matches(testMa) - }), - dnsWss: (multiaddrs) => multiaddrs.filter((ma) => { + }) +} + +export function dnsWss (multiaddrs: Multiaddr[]) { + return multiaddrs.filter((ma) => { if (ma.protoCodes().includes(CODE_CIRCUIT)) { return false } @@ -29,8 +31,11 @@ module.exports = { return mafmt.WebSocketsSecure.matches(testMa) && mafmt.DNS.matches(testMa.decapsulateCode(CODE_TCP).decapsulateCode(CODE_WSS)) - }), - dnsWsOrWss: (multiaddrs) => multiaddrs.filter((ma) => { + }) +} + +export function dnsWsOrWss (multiaddrs: Multiaddr[]) { + return multiaddrs.filter((ma) => { if (ma.protoCodes().includes(CODE_CIRCUIT)) { return false } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index e6f74e7..0000000 --- a/src/index.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict' - -const connect = require('it-ws/client') -const withIs = require('class-is') -const toUri = require('multiaddr-to-uri') -const { AbortError } = require('abortable-iterator') -const pDefer = require('p-defer') - -const debug = require('debug') -const log = debug('libp2p:websockets') -log.error = debug('libp2p:websockets:error') -const env = require('ipfs-utils/src/env') - -const createListener = require('./listener') -const toConnection = require('./socket-to-conn') -const filters = require('./filters') - -/** - * @typedef {import('multiaddr').Multiaddr} Multiaddr - */ - -/** - * @class WebSockets - */ -class WebSockets { - /** - * @class - * @param {object} options - * @param {Upgrader} options.upgrader - * @param {(multiaddrs: Array) => Array} options.filter - override transport addresses filter - */ - constructor ({ upgrader, filter }) { - if (!upgrader) { - throw new Error('An upgrader must be provided. See https://github.com/libp2p/interface-transport#upgrader.') - } - this._upgrader = upgrader - this._filter = filter - } - - /** - * @async - * @param {Multiaddr} ma - * @param {object} [options] - * @param {AbortSignal} [options.signal] - Used to abort dial requests - * @returns {Connection} An upgraded Connection - */ - async dial (ma, options = {}) { - log('dialing %s', ma) - - const socket = await this._connect(ma, options) - const maConn = toConnection(socket, { remoteAddr: ma, signal: options.signal }) - log('new outbound connection %s', maConn.remoteAddr) - - const conn = await this._upgrader.upgradeOutbound(maConn) - log('outbound connection %s upgraded', maConn.remoteAddr) - return conn - } - - /** - * @private - * @param {Multiaddr} ma - * @param {object} [options] - * @param {AbortSignal} [options.signal] - Used to abort dial requests - * @returns {Promise} Resolves a extended duplex iterable on top of a WebSocket - */ - async _connect (ma, options = {}) { - if (options.signal && options.signal.aborted) { - throw new AbortError() - } - const cOpts = ma.toOptions() - log('dialing %s:%s', cOpts.host, cOpts.port) - - const errorPromise = pDefer() - const errfn = (err) => { - const msg = `connection error: ${err.message}` - log.error(msg) - - errorPromise.reject(err) - } - - const rawSocket = connect(toUri(ma), Object.assign({ binary: true }, options)) - - if (rawSocket.socket.on) { - rawSocket.socket.on('error', errfn) - } else { - rawSocket.socket.onerror = errfn - } - - if (!options.signal) { - await Promise.race([rawSocket.connected(), errorPromise.promise]) - - log('connected %s', ma) - return rawSocket - } - - // Allow abort via signal during connect - let onAbort - const abort = new Promise((resolve, reject) => { - onAbort = () => { - reject(new AbortError()) - // FIXME: https://github.com/libp2p/js-libp2p-websockets/issues/121 - setTimeout(() => { - rawSocket.close() - }) - } - - // Already aborted? - if (options.signal.aborted) return onAbort() - options.signal.addEventListener('abort', onAbort) - }) - - try { - await Promise.race([abort, errorPromise.promise, rawSocket.connected()]) - } finally { - options.signal.removeEventListener('abort', onAbort) - } - - log('connected %s', ma) - return rawSocket - } - - /** - * Creates a Websockets listener. The provided `handler` function will be called - * anytime a new incoming Connection has been successfully upgraded via - * `upgrader.upgradeInbound`. - * - * @param {object} [options] - * @param {http.Server} [options.server] - A pre-created Node.js HTTP/S server. - * @param {function (Connection)} handler - * @returns {Listener} A Websockets listener - */ - createListener (options = {}, handler) { - if (typeof options === 'function') { - handler = options - options = {} - } - - return createListener({ handler, upgrader: this._upgrader }, options) - } - - /** - * Takes a list of `Multiaddr`s and returns only valid Websockets addresses. - * By default, in a browser environment only DNS+WSS multiaddr is accepted, - * while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted. - * - * @param {Multiaddr[]} multiaddrs - * @returns {Multiaddr[]} Valid Websockets multiaddrs - */ - filter (multiaddrs) { - multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs] - - if (this._filter) { - return this._filter(multiaddrs) - } - - // Browser - if (env.isBrowser || env.isWebWorker) { - return filters.dnsWss(multiaddrs) - } - - return filters.all(multiaddrs) - } -} - -module.exports = withIs(WebSockets, { - className: 'WebSockets', - symbolName: '@libp2p/js-libp2p-websockets/websockets' -}) diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7ec5349 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,139 @@ +import { connect, WebSocketOptions } from 'it-ws/client' +import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri' +import { AbortError } from '@libp2p/interfaces/errors' +import pDefer from 'p-defer' +import { logger } from '@libp2p/logger' +import env from 'wherearewe' +import { createListener } from './listener.js' +import { socketToMaConn } from './socket-to-conn.js' +import * as filters from './filters.js' +import type { Transport, Upgrader, MultiaddrFilter } from '@libp2p/interfaces/transport' +import type { AbortOptions } from '@libp2p/interfaces' +import type { WebSocketListenerOptions } from './listener.js' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { DuplexWebSocket } from 'it-ws/dist/src/duplex' + +const log = logger('libp2p:websockets') + +/** + * @class WebSockets + */ +export class WebSockets implements Transport { + private readonly upgrader: Upgrader + private readonly _filter?: MultiaddrFilter + + constructor (opts: { upgrader: Upgrader, filter?: MultiaddrFilter }) { + const { upgrader, filter } = opts + + if (upgrader == null) { + throw new Error('An upgrader must be provided. See https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/libp2p-interfaces/src/transport#upgrader') + } + + this.upgrader = upgrader + this._filter = filter + } + + async dial (ma: Multiaddr, options?: AbortOptions & WebSocketOptions) { + log('dialing %s', ma) + options = options ?? {} + + const socket = await this._connect(ma, options) + const maConn = socketToMaConn(socket, ma) + log('new outbound connection %s', maConn.remoteAddr) + + const conn = await this.upgrader.upgradeOutbound(maConn) + log('outbound connection %s upgraded', maConn.remoteAddr) + return conn + } + + async _connect (ma: Multiaddr, options: AbortOptions & WebSocketOptions): Promise { + if (options?.signal?.aborted === true) { + throw new AbortError() + } + const cOpts = ma.toOptions() + log('dialing %s:%s', cOpts.host, cOpts.port) + + const errorPromise = pDefer() + const errfn = (err: any) => { + log.error('connection error:', err) + + errorPromise.reject(err) + } + + const rawSocket = connect(toUri(ma), options) + + if (rawSocket.socket.on != null) { + rawSocket.socket.on('error', errfn) + } else { + rawSocket.socket.onerror = errfn + } + + if (options.signal == null) { + await Promise.race([rawSocket.connected(), errorPromise.promise]) + + log('connected %s', ma) + return rawSocket + } + + // Allow abort via signal during connect + let onAbort + const abort = new Promise((resolve, reject) => { + onAbort = () => { + reject(new AbortError()) + // FIXME: https://github.com/libp2p/js-libp2p-websockets/issues/121 + setTimeout(() => { + rawSocket.close().catch(err => { + log.error('error closing raw socket', err) + }) + }) + } + + // Already aborted? + if (options?.signal?.aborted === true) { + return onAbort() + } + + options?.signal?.addEventListener('abort', onAbort) + }) + + try { + await Promise.race([abort, errorPromise.promise, rawSocket.connected()]) + } finally { + if (onAbort != null) { + options?.signal?.removeEventListener('abort', onAbort) + } + } + + log('connected %s', ma) + return rawSocket + } + + /** + * Creates a Websockets listener. The provided `handler` function will be called + * anytime a new incoming Connection has been successfully upgraded via + * `upgrader.upgradeInbound` + */ + createListener (options?: WebSocketListenerOptions) { + return createListener(this.upgrader, options) + } + + /** + * Takes a list of `Multiaddr`s and returns only valid Websockets addresses. + * By default, in a browser environment only DNS+WSS multiaddr is accepted, + * while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted. + */ + filter (multiaddrs: Multiaddr[]) { + multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs] + + if (this._filter != null) { + return this._filter(multiaddrs) + } + + // Browser + if (env.isBrowser || env.isWebWorker) { + return filters.dnsWss(multiaddrs) + } + + return filters.all(multiaddrs) + } +} diff --git a/src/listener.browser.js b/src/listener.browser.ts similarity index 63% rename from src/listener.browser.js rename to src/listener.browser.ts index 916c9a2..2d2457b 100644 --- a/src/listener.browser.js +++ b/src/listener.browser.ts @@ -1,5 +1,4 @@ -'use strict' -module.exports = function () { +export function createListener () { throw new Error('WebSocket Servers can not be created in the browser!') } diff --git a/src/listener.js b/src/listener.js deleted file mode 100644 index 4553635..0000000 --- a/src/listener.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict' - -const EventEmitter = require('events') -const os = require('os') -const { Multiaddr, protocols } = require('multiaddr') -const { createServer } = require('it-ws') -const debug = require('debug') -const log = debug('libp2p:websockets:listener') -log.error = debug('libp2p:websockets:listener:error') - -const toConnection = require('./socket-to-conn') - -module.exports = ({ handler, upgrader }, options = {}) => { - const listener = new EventEmitter() - - const server = createServer(options, async (stream) => { - let maConn, conn - - try { - maConn = toConnection(stream) - log('new inbound connection %s', maConn.remoteAddr) - conn = await upgrader.upgradeInbound(maConn) - } catch (err) { - log.error('inbound connection failed to upgrade', err) - return maConn && maConn.close() - } - - log('inbound connection %s upgraded', maConn.remoteAddr) - - trackConn(server, maConn) - - if (handler) handler(conn) - listener.emit('connection', conn) - }) - - server - .on('listening', () => listener.emit('listening')) - .on('error', err => listener.emit('error', err)) - .on('close', () => listener.emit('close')) - - // Keep track of open connections to destroy in case of timeout - server.__connections = [] - - let listeningMultiaddr - - listener.close = () => { - server.__connections.forEach(maConn => maConn.close()) - return server.close() - } - - listener.listen = (ma) => { - listeningMultiaddr = ma - - return server.listen(ma.toOptions()) - } - - listener.getAddrs = () => { - const multiaddrs = [] - const address = server.address() - - if (!address) { - throw new Error('Listener is not ready yet') - } - - const ipfsId = listeningMultiaddr.getPeerId() - const protos = listeningMultiaddr.protos() - - // Because TCP will only return the IPv6 version - // we need to capture from the passed multiaddr - if (protos.some(proto => proto.code === protocols('ip4').code)) { - const wsProto = protos.some(proto => proto.code === protocols('ws').code) ? '/ws' : '/wss' - let m = listeningMultiaddr.decapsulate('tcp') - m = m.encapsulate('/tcp/' + address.port + wsProto) - if (listeningMultiaddr.getPeerId()) { - m = m.encapsulate('/p2p/' + ipfsId) - } - - if (m.toString().indexOf('0.0.0.0') !== -1) { - const netInterfaces = os.networkInterfaces() - Object.keys(netInterfaces).forEach((niKey) => { - netInterfaces[niKey].forEach((ni) => { - if (ni.family === 'IPv4') { - multiaddrs.push(new Multiaddr(m.toString().replace('0.0.0.0', ni.address))) - } - }) - }) - } else { - multiaddrs.push(m) - } - } - - return multiaddrs - } - - return listener -} - -function trackConn (server, maConn) { - server.__connections.push(maConn) -} diff --git a/src/listener.ts b/src/listener.ts new file mode 100644 index 0000000..34958cc --- /dev/null +++ b/src/listener.ts @@ -0,0 +1,160 @@ +import os from 'os' +import { Multiaddr, protocols } from '@multiformats/multiaddr' +import { createServer } from 'it-ws/server' +import { logger } from '@libp2p/logger' +import { socketToMaConn } from './socket-to-conn.js' +import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr' +import type { ListenerOptions, Upgrader, Listener, ListenerEvents } from '@libp2p/interfaces/transport' +import type { Server } from 'http' +import type { WebSocketServer } from 'it-ws/server' +import type { DuplexWebSocket } from 'it-ws/duplex' +import { EventEmitter, CustomEvent } from '@libp2p/interfaces' + +const log = logger('libp2p:websockets:listener') + +class WebSocketListener extends EventEmitter implements Listener { + private readonly connections: Set + private listeningMultiaddr?: Multiaddr + private readonly server: WebSocketServer + + constructor (upgrader: Upgrader, options: WebSocketListenerOptions) { + super() + + // Keep track of open connections to destroy when the listener is closed + this.connections = new Set() + + const self = this // eslint-disable-line @typescript-eslint/no-this-alias + + this.server = createServer({ + ...options, + onConnection: (stream: DuplexWebSocket) => { + const maConn = socketToMaConn(stream, toMultiaddr(stream.remoteAddress ?? '', stream.remotePort ?? 0)) + log('new inbound connection %s', maConn.remoteAddr) + + this.connections.add(stream) + + stream.socket.on('close', function () { + self.connections.delete(stream) + }) + + try { + void upgrader.upgradeInbound(maConn) + .then((conn) => { + log('inbound connection %s upgraded', maConn.remoteAddr) + + if (options?.handler != null) { + options?.handler(conn) + } + + self.dispatchEvent(new CustomEvent('connection', { + detail: conn + })) + }) + .catch(async err => { + log.error('inbound connection failed to upgrade', err) + + await maConn.close().catch(err => { + log.error('inbound connection failed to close after upgrade failed', err) + }) + }) + } catch (err) { + log.error('inbound connection failed to upgrade', err) + maConn.close().catch(err => { + log.error('inbound connection failed to close after upgrade failed', err) + }) + } + } + }) + + this.server.on('listening', () => { + this.dispatchEvent(new CustomEvent('listening')) + }) + this.server.on('error', (err: Error) => { + this.dispatchEvent(new CustomEvent('error', { + detail: err + })) + }) + this.server.on('close', () => { + this.dispatchEvent(new CustomEvent('close')) + }) + } + + async close () { + await Promise.all( + Array.from(this.connections).map(async maConn => await maConn.close()) + ) + + if (this.server.address() == null) { + // not listening, close will throw an error + return + } + + return await this.server.close() + } + + async listen (ma: Multiaddr) { + this.listeningMultiaddr = ma + + await this.server.listen(ma.toOptions()) + } + + getAddrs () { + const multiaddrs = [] + const address = this.server.address() + + if (address == null) { + throw new Error('Listener is not ready yet') + } + + if (typeof address === 'string') { + throw new Error('Wrong address type received - expected AddressInfo, got string - are you trying to listen on a unix socket?') + } + + if (this.listeningMultiaddr == null) { + throw new Error('Listener is not ready yet') + } + + const ipfsId = this.listeningMultiaddr.getPeerId() + const protos = this.listeningMultiaddr.protos() + + // Because TCP will only return the IPv6 version + // we need to capture from the passed multiaddr + if (protos.some(proto => proto.code === protocols('ip4').code)) { + const wsProto = protos.some(proto => proto.code === protocols('ws').code) ? '/ws' : '/wss' + let m = this.listeningMultiaddr.decapsulate('tcp') + m = m.encapsulate(`/tcp/${address.port}${wsProto}`) + if (ipfsId != null) { + m = m.encapsulate(`/p2p/${ipfsId}`) + } + + if (m.toString().includes('0.0.0.0')) { + const netInterfaces = os.networkInterfaces() + Object.values(netInterfaces).forEach(niInfos => { + if (niInfos == null) { + return + } + + niInfos.forEach(ni => { + if (ni.family === 'IPv4') { + multiaddrs.push(new Multiaddr(m.toString().replace('0.0.0.0', ni.address))) + } + }) + }) + } else { + multiaddrs.push(m) + } + } + + return multiaddrs + } +} + +export interface WebSocketListenerOptions extends ListenerOptions { + server?: Server +} + +export function createListener (upgrader: Upgrader, options?: WebSocketListenerOptions): Listener { + options = options ?? {} + + return new WebSocketListener(upgrader, options) +} diff --git a/src/socket-to-conn.js b/src/socket-to-conn.js deleted file mode 100644 index 0efda21..0000000 --- a/src/socket-to-conn.js +++ /dev/null @@ -1,76 +0,0 @@ -'use strict' - -const abortable = require('abortable-iterator') -const { CLOSE_TIMEOUT } = require('./constants') -const toMultiaddr = require('libp2p-utils/src/ip-port-to-multiaddr') - -const pTimeout = require('p-timeout') - -const debug = require('debug') -const log = debug('libp2p:websockets:socket') -log.error = debug('libp2p:websockets:socket:error') - -// Convert a stream into a MultiaddrConnection -// https://github.com/libp2p/interface-transport#multiaddrconnection -module.exports = (stream, options = {}) => { - const maConn = { - async sink (source) { - if (options.signal) { - source = abortable(source, options.signal) - } - - try { - await stream.sink((async function * () { - for await (const chunk of source) { - // Convert BufferList to Buffer - yield chunk instanceof Uint8Array ? chunk : chunk.slice() - } - })()) - } catch (err) { - if (err.type !== 'aborted') { - log.error(err) - } - } - }, - - source: options.signal ? abortable(stream.source, options.signal) : stream.source, - - conn: stream, - - localAddr: options.localAddr || (stream.localAddress && stream.localPort - ? toMultiaddr(stream.localAddress, stream.localPort) - : undefined), - - // If the remote address was passed, use it - it may have the peer ID encapsulated - remoteAddr: options.remoteAddr || toMultiaddr(stream.remoteAddress, stream.remotePort), - - timeline: { open: Date.now() }, - - async close () { - const start = Date.now() - - try { - await pTimeout(stream.close(), CLOSE_TIMEOUT) - } catch (err) { - const { host, port } = maConn.remoteAddr.toOptions() - log('timeout closing stream to %s:%s after %dms, destroying it manually', - host, port, Date.now() - start) - - stream.destroy() - } finally { - maConn.timeline.close = Date.now() - } - } - } - - stream.socket.once && stream.socket.once('close', () => { - // In instances where `close` was not explicitly called, - // such as an iterable stream ending, ensure we have set the close - // timeline - if (!maConn.timeline.close) { - maConn.timeline.close = Date.now() - } - }) - - return maConn -} diff --git a/src/socket-to-conn.ts b/src/socket-to-conn.ts new file mode 100644 index 0000000..18a854a --- /dev/null +++ b/src/socket-to-conn.ts @@ -0,0 +1,69 @@ +import { abortableSource } from 'abortable-iterator' +import { CLOSE_TIMEOUT } from './constants.js' +import pTimeout from 'p-timeout' +import { logger } from '@libp2p/logger' +import type { AbortOptions } from '@libp2p/interfaces' +import type { MultiaddrConnection } from '@libp2p/interfaces/transport' +import type { Multiaddr } from '@multiformats/multiaddr' +import type { DuplexWebSocket } from 'it-ws/duplex' + +const log = logger('libp2p:websockets:socket') + +export interface SocketToConnOptions extends AbortOptions { + localAddr?: Multiaddr +} + +// Convert a stream into a MultiaddrConnection +// https://github.com/libp2p/interface-transport#multiaddrconnection +export function socketToMaConn (stream: DuplexWebSocket, remoteAddr: Multiaddr, options?: SocketToConnOptions): MultiaddrConnection { + options = options ?? {} + + const maConn: MultiaddrConnection = { + async sink (source) { + if ((options?.signal) != null) { + source = abortableSource(source, options.signal) + } + + try { + await stream.sink(source) + } catch (err: any) { + if (err.type !== 'aborted') { + log.error(err) + } + } + }, + + source: (options.signal != null) ? abortableSource(stream.source, options.signal) : stream.source, + + remoteAddr, + + timeline: { open: Date.now() }, + + async close () { + const start = Date.now() + + try { + await pTimeout(stream.close(), CLOSE_TIMEOUT) + } catch (err) { + const { host, port } = maConn.remoteAddr.toOptions() + log('timeout closing stream to %s:%s after %dms, destroying it manually', + host, port, Date.now() - start) + + stream.destroy() + } finally { + maConn.timeline.close = Date.now() + } + } + } + + stream.socket.once != null && stream.socket.once('close', () => { // eslint-disable-line @typescript-eslint/prefer-optional-chain + // In instances where `close` was not explicitly called, + // such as an iterable stream ending, ensure we have set the close + // timeline + if (maConn.timeline.close == null) { + maConn.timeline.close = Date.now() + } + }) + + return maConn +} diff --git a/test/browser.js b/test/browser.js deleted file mode 100644 index 78c6cef..0000000 --- a/test/browser.js +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const { expect } = require('aegir/utils/chai') - -const { Multiaddr } = require('multiaddr') -const pipe = require('it-pipe') -const goodbye = require('it-goodbye') -const { collect, take } = require('streaming-iterables') -const uint8ArrayFromString = require('uint8arrays/from-string') - -const WS = require('../src') - -const mockUpgrader = { - upgradeInbound: maConn => maConn, - upgradeOutbound: maConn => maConn -} - -describe('libp2p-websockets', () => { - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9095/ws') - let ws - let conn - - beforeEach(async () => { - ws = new WS({ upgrader: mockUpgrader }) - conn = await ws.dial(ma) - }) - - it('echo', async () => { - const message = uint8ArrayFromString('Hello World!') - const s = goodbye({ source: [message], sink: collect }) - - const results = await pipe(s, conn, s) - expect(results).to.eql([message]) - }) - - it('should filter out no DNS websocket addresses', function () { - const ma1 = new Multiaddr('/ip4/127.0.0.1/tcp/80/ws') - const ma2 = new Multiaddr('/ip4/127.0.0.1/tcp/443/wss') - const ma3 = new Multiaddr('/ip6/::1/tcp/80/ws') - const ma4 = new Multiaddr('/ip6/::1/tcp/443/wss') - - const valid = ws.filter([ma1, ma2, ma3, ma4]) - expect(valid.length).to.equal(0) - }) - - describe('stress', () => { - it('one big write', async () => { - const rawMessage = new Uint8Array(1000000).fill('a') - - const s = goodbye({ source: [rawMessage], sink: collect }) - - const results = await pipe(s, conn, s) - expect(results).to.eql([rawMessage]) - }) - - it('many writes', async function () { - this.timeout(10000) - const s = goodbye({ - source: pipe( - { - [Symbol.iterator] () { return this }, - next: () => ({ done: false, value: uint8ArrayFromString(Math.random().toString()) }) - }, - take(20000) - ), - sink: collect - }) - - const result = await pipe(s, conn, s) - expect(result).to.have.length(20000) - }) - }) - - it('.createServer throws in browser', () => { - expect(new WS({ upgrader: mockUpgrader }).createListener).to.throw() - }) -}) diff --git a/test/browser.ts b/test/browser.ts new file mode 100644 index 0000000..eab58c9 --- /dev/null +++ b/test/browser.ts @@ -0,0 +1,90 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/utils/chai.js' +import { Multiaddr } from '@multiformats/multiaddr' +import { pipe } from 'it-pipe' +import { goodbye } from 'it-goodbye' +import take from 'it-take' +import all from 'it-all' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { WebSockets } from '../src/index.js' +import { mockUpgrader } from '@libp2p/interface-compliance-tests/transport/utils' +import env from 'wherearewe' +import type { Connection } from '@libp2p/interfaces/connection' + +const upgrader = mockUpgrader() + +describe('libp2p-websockets', () => { + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9095/ws') + let ws: WebSockets + let conn: Connection + + beforeEach(async () => { + ws = new WebSockets({ upgrader }) + conn = await ws.dial(ma) + }) + + afterEach(async () => { + await conn.close() + }) + + it('echo', async () => { + const message = uint8ArrayFromString('Hello World!') + const s = goodbye({ source: [message], sink: all }) + const { stream } = await conn.newStream(['echo']) + + const results = await pipe(s, stream, s) + expect(results).to.eql([message]) + }) + + it('should filter out no DNS websocket addresses', function () { + const ma1 = new Multiaddr('/ip4/127.0.0.1/tcp/80/ws') + const ma2 = new Multiaddr('/ip4/127.0.0.1/tcp/443/wss') + const ma3 = new Multiaddr('/ip6/::1/tcp/80/ws') + const ma4 = new Multiaddr('/ip6/::1/tcp/443/wss') + + const valid = ws.filter([ma1, ma2, ma3, ma4]) + + if (env.isBrowser || env.isWebWorker) { + expect(valid.length).to.equal(0) + } else { + expect(valid.length).to.equal(4) + } + }) + + describe('stress', () => { + it('one big write', async () => { + const rawMessage = new Uint8Array(1000000).fill(5) + + const s = goodbye({ source: [rawMessage], sink: all }) + const { stream } = await conn.newStream(['echo']) + + const results = await pipe(s, stream, s) + expect(results).to.eql([rawMessage]) + }) + + it('many writes', async function () { + this.timeout(10000) + const s = goodbye({ + source: pipe( + (function * () { + while (true) { + yield uint8ArrayFromString(Math.random().toString()) + } + }()), + (source) => take(source, 20000) + ), + sink: all + }) + + const { stream } = await conn.newStream(['echo']) + + const results = await pipe(s, stream, s) + expect(results).to.have.length(20000) + }) + }) + + it('.createServer throws in browser', () => { + expect(new WebSockets({ upgrader }).createListener).to.throw() + }) +}) diff --git a/test/compliance.node.js b/test/compliance.node.ts similarity index 55% rename from test/compliance.node.js rename to test/compliance.node.ts index 970970f..e034cca 100644 --- a/test/compliance.node.js +++ b/test/compliance.node.ts @@ -1,16 +1,21 @@ /* eslint-env mocha */ -'use strict' -const tests = require('libp2p-interfaces-compliance-tests/src/transport') -const { Multiaddr } = require('multiaddr') -const http = require('http') -const WS = require('../src') -const filters = require('../src/filters') +import tests from '@libp2p/interface-compliance-tests/transport' +import { Multiaddr } from '@multiformats/multiaddr' +import http from 'http' +import { WebSockets } from '../src/index.js' +import * as filters from '../src/filters.js' +import type { WebSocketListenerOptions } from '../src/listener.js' describe('interface-transport compliance', () => { tests({ - async setup ({ upgrader }) { // eslint-disable-line require-await - const ws = new WS({ upgrader, filter: filters.all }) + async setup (args) { + if (args == null) { + throw new Error('No args') + } + + const { upgrader } = args + const ws = new WebSockets({ upgrader, filter: filters.all }) const addrs = [ new Multiaddr('/ip4/127.0.0.1/tcp/9091/ws'), new Multiaddr('/ip4/127.0.0.1/tcp/9092/ws'), @@ -19,41 +24,38 @@ describe('interface-transport compliance', () => { ] let delayMs = 0 - const delayedCreateListener = (options, handler) => { - if (typeof options === 'function') { - handler = options - options = {} - } - - options = options || {} + const delayedCreateListener = (options?: WebSocketListenerOptions) => { + options = options ?? {} // A server that will delay the upgrade event by delayMs options.server = new Proxy(http.createServer(), { get (server, prop) { if (prop === 'on') { - return (event, handler) => { + return (event: string, handler: (...args: any[]) => void) => { server.on(event, (...args) => { - if (event !== 'upgrade' || !delayMs) { + if (event !== 'upgrade' || delayMs === 0) { return handler(...args) } setTimeout(() => handler(...args), delayMs) }) } } + // @ts-expect-error cannot access props with a string return server[prop] } }) - return ws.createListener(options, handler) + return ws.createListener(options) } const wsProxy = new Proxy(ws, { + // @ts-expect-error cannot access props with a string get: (_, prop) => prop === 'createListener' ? delayedCreateListener : ws[prop] }) // Used by the dial tests to simulate a delayed connect const connector = { - delay (ms) { delayMs = ms }, + delay (ms: number) { delayMs = ms }, restore () { delayMs = 0 } } diff --git a/test/node.js b/test/node.ts similarity index 66% rename from test/node.js rename to test/node.ts index 6c551d5..162860e 100644 --- a/test/node.js +++ b/test/node.ts @@ -1,191 +1,203 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ["error", 6] */ -'use strict' -const https = require('https') -const fs = require('fs') - -const AbortController = require('abort-controller').default -const { expect } = require('aegir/utils/chai') -const { Multiaddr } = require('multiaddr') -const goodbye = require('it-goodbye') -const isLoopbackAddr = require('is-loopback-addr') -const { collect } = require('streaming-iterables') -const pipe = require('it-pipe') -const BufferList = require('bl/BufferList') -const uint8ArrayFromString = require('uint8arrays/from-string') - -const WS = require('../src') -const filters = require('../src/filters') - -require('./compliance.node') - -const mockUpgrader = { - upgradeInbound: maConn => maConn, - upgradeOutbound: maConn => maConn -} +import https from 'https' +import fs from 'fs' +import { expect } from 'aegir/utils/chai.js' +import { Multiaddr } from '@multiformats/multiaddr' +import { goodbye } from 'it-goodbye' +import { isLoopbackAddr } from 'is-loopback-addr' +import all from 'it-all' +import { pipe } from 'it-pipe' +import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' +import { mockUpgrader } from '@libp2p/interface-compliance-tests/transport/utils' +import defer from 'p-defer' +import waitFor from 'p-wait-for' +import { WebSockets } from '../src/index.js' +import * as filters from '../src/filters.js' +import type { Listener } from '@libp2p/interfaces/transport' + +import './compliance.node.js' + +const upgrader = mockUpgrader() describe('instantiate the transport', () => { it('create', () => { - const ws = new WS({ upgrader: mockUpgrader }) + const ws = new WebSockets({ upgrader }) expect(ws).to.exist() }) }) describe('listen', () => { + it('should close connections when stopping the listener', async () => { + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/47382/ws') + + const ws = new WebSockets({ upgrader }) + const listener = ws.createListener({ + handler: (conn) => { + void conn.newStream(['echo']).then(async ({ stream }) => { + return await pipe(stream, stream) + }) + } + }) + await listener.listen(ma) + + const conn = await ws.dial(ma) + const { stream } = await conn.newStream(['echo']) + void pipe(stream, stream) + + await listener.close() + + await waitFor(() => conn.stat.timeline.close != null) + }) + describe('ip4', () => { - let ws - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ws') + let ws: WebSockets + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/47382/ws') + let listener: Listener beforeEach(() => { - ws = new WS({ upgrader: mockUpgrader }) + ws = new WebSockets({ upgrader }) + }) + + afterEach(async () => { + return await listener.close() }) it('listen, check for promise', async () => { - const listener = ws.createListener((conn) => { }) + listener = ws.createListener() await listener.listen(ma) - await listener.close() }) it('listen, check for listening event', (done) => { - const listener = ws.createListener((conn) => { }) + listener = ws.createListener() - listener.on('listening', async () => { - await listener.close() + listener.addEventListener('listening', () => { done() }) - listener.listen(ma) + void listener.listen(ma) }) it('listen, check for the close event', (done) => { - const listener = ws.createListener((conn) => { }) + const listener = ws.createListener() - listener.on('listening', () => { - listener.on('close', done) - listener.close() + listener.addEventListener('listening', () => { + listener.addEventListener('close', () => done()) + void listener.close() }) - listener.listen(ma) + void listener.listen(ma) }) it('listen on addr with /ipfs/QmHASH', async () => { - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ws/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - const listener = ws.createListener((conn) => { }) + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/47382/ws/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') + listener = ws.createListener() await listener.listen(ma) - await listener.close() }) it('listen on port 0', async () => { const ma = new Multiaddr('/ip4/127.0.0.1/tcp/0/ws/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - const listener = ws.createListener((conn) => { }) + listener = ws.createListener() await listener.listen(ma) const addrs = await listener.getAddrs() expect(addrs.map((a) => a.toOptions().port)).to.not.include(0) - await listener.close() }) it('listen on any Interface', async () => { const ma = new Multiaddr('/ip4/0.0.0.0/tcp/0/ws/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - const listener = ws.createListener((conn) => { }) + listener = ws.createListener() await listener.listen(ma) const addrs = await listener.getAddrs() expect(addrs.map((a) => a.toOptions().host)).to.not.include('0.0.0.0') - await listener.close() }) it('getAddrs', async () => { - const listener = ws.createListener((conn) => { }) + listener = ws.createListener() await listener.listen(ma) const addrs = await listener.getAddrs() expect(addrs.length).to.equal(1) expect(addrs[0]).to.deep.equal(ma) - await listener.close() }) it('getAddrs on port 0 listen', async () => { const addr = new Multiaddr('/ip4/127.0.0.1/tcp/0/ws') - const listener = ws.createListener((conn) => { }) + listener = ws.createListener() await listener.listen(addr) const addrs = await listener.getAddrs() expect(addrs.length).to.equal(1) expect(addrs.map((a) => a.toOptions().port)).to.not.include('0') - await listener.close() }) it('getAddrs from listening on 0.0.0.0', async () => { - const addr = new Multiaddr('/ip4/0.0.0.0/tcp/9003/ws') - const listener = ws.createListener((conn) => { }) + const addr = new Multiaddr('/ip4/0.0.0.0/tcp/47382/ws') + listener = ws.createListener() await listener.listen(addr) const addrs = await listener.getAddrs() expect(addrs.map((a) => a.toOptions().host)).to.not.include('0.0.0.0') - await listener.close() }) it('getAddrs from listening on 0.0.0.0 and port 0', async () => { const addr = new Multiaddr('/ip4/0.0.0.0/tcp/0/ws') - const listener = ws.createListener((conn) => { }) + listener = ws.createListener() await listener.listen(addr) const addrs = await listener.getAddrs() expect(addrs.map((a) => a.toOptions().host)).to.not.include('0.0.0.0') expect(addrs.map((a) => a.toOptions().port)).to.not.include('0') - await listener.close() }) it('getAddrs preserves p2p Id', async () => { - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ws/p2p/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - const listener = ws.createListener((conn) => { }) + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/47382/ws/p2p/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') + listener = ws.createListener() await listener.listen(ma) const addrs = await listener.getAddrs() expect(addrs.length).to.equal(1) expect(addrs[0]).to.deep.equal(ma) - await listener.close() }) }) describe('ip6', () => { - let ws + let ws: WebSockets const ma = new Multiaddr('/ip6/::1/tcp/9091/ws') beforeEach(() => { - ws = new WS({ upgrader: mockUpgrader }) + ws = new WebSockets({ upgrader }) }) it('listen, check for promise', async () => { - const listener = ws.createListener((conn) => { }) + const listener = ws.createListener() await listener.listen(ma) await listener.close() }) it('listen, check for listening event', (done) => { - const listener = ws.createListener((conn) => { }) + const listener = ws.createListener() - listener.on('listening', async () => { - await listener.close() - done() + listener.addEventListener('listening', () => { + void listener.close().then(done, done) }) - listener.listen(ma) + void listener.listen(ma) }) it('listen, check for the close event', (done) => { - const listener = ws.createListener((conn) => { }) + const listener = ws.createListener() - listener.on('listening', () => { - listener.on('close', done) - listener.close() + listener.addEventListener('listening', () => { + listener.addEventListener('close', () => done()) + void listener.close() }) - listener.listen(ma) + void listener.listen(ma) }) it('listen on addr with /ipfs/QmHASH', async () => { const ma = new Multiaddr('/ip6/::1/tcp/9091/ws/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - const listener = ws.createListener((conn) => { }) + const listener = ws.createListener() await listener.listen(ma) await listener.close() }) @@ -194,35 +206,39 @@ describe('listen', () => { describe('dial', () => { describe('ip4', () => { - let ws - let listener + let ws: WebSockets + let listener: Listener const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9091/ws') - beforeEach(() => { - ws = new WS({ upgrader: mockUpgrader }) - listener = ws.createListener(conn => pipe(conn, conn)) - return listener.listen(ma) + beforeEach(async () => { + ws = new WebSockets({ upgrader }) + listener = ws.createListener({ + handler: (conn) => { + void conn.newStream(['echo']).then(async ({ stream }) => { + return await pipe(stream, stream) + }) + } + }) + return await listener.listen(ma) }) - afterEach(() => listener.close()) + afterEach(async () => await listener.close()) it('dial', async () => { const conn = await ws.dial(ma) - const s = goodbye({ source: ['hey'], sink: collect }) - - const result = await pipe(s, conn, s) + const s = goodbye({ source: [uint8ArrayFromString('hey')], sink: all }) + const { stream } = await conn.newStream(['echo']) - expect(result).to.be.eql([uint8ArrayFromString('hey')]) + await expect(pipe(s, stream, s)).to.eventually.deep.equal([uint8ArrayFromString('hey')]) }) it('dial with p2p Id', async () => { const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9091/ws/p2p/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') const conn = await ws.dial(ma) - const s = goodbye({ source: ['hey'], sink: collect }) + const s = goodbye({ source: [uint8ArrayFromString('hey')], sink: all }) + const { stream } = await conn.newStream(['echo']) - const result = await pipe(s, conn, s) - - expect(result).to.be.eql([uint8ArrayFromString('hey')]) + await expect(pipe(s, stream, s)).to.eventually.deep.equal([uint8ArrayFromString('hey')]) }) it('dial should throw on immediate abort', async () => { @@ -237,14 +253,12 @@ describe('dial', () => { it('should resolve port 0', async () => { const ma = new Multiaddr('/ip4/127.0.0.1/tcp/0/ws') - const ws = new WS({ upgrader: mockUpgrader }) + const ws = new WebSockets({ upgrader }) // Create a Promise that resolves when a connection is handled - let handled - const handlerPromise = new Promise(resolve => { handled = resolve }) - const handler = conn => handled(conn) + const deferred = defer() - const listener = ws.createListener(handler) + const listener = ws.createListener({ handler: deferred.resolve }) // Listen on the multiaddr await listener.listen(ma) @@ -256,7 +270,7 @@ describe('dial', () => { await ws.dial(localAddrs[0]) // Wait for the incoming dial to be handled - await handlerPromise + await deferred.promise // close the listener await listener.close() @@ -264,17 +278,23 @@ describe('dial', () => { }) describe('ip4 no loopback', () => { - let ws - let listener + let ws: WebSockets + let listener: Listener const ma = new Multiaddr('/ip4/0.0.0.0/tcp/0/ws') - beforeEach(() => { - ws = new WS({ upgrader: mockUpgrader }) - listener = ws.createListener(conn => pipe(conn, conn)) - return listener.listen(ma) + beforeEach(async () => { + ws = new WebSockets({ upgrader }) + listener = ws.createListener({ + handler: (conn) => { + void conn.newStream(['echo']).then(async ({ stream }) => { + return await pipe(stream, stream) + }) + } + }) + return await listener.listen(ma) }) - afterEach(() => listener.close()) + afterEach(async () => await listener.close()) it('dial', async () => { const addrs = listener.getAddrs().filter((ma) => { @@ -285,32 +305,41 @@ describe('dial', () => { // Dial first no loopback address const conn = await ws.dial(addrs[0]) - const s = goodbye({ source: ['hey'], sink: collect }) + const s = goodbye({ source: [uint8ArrayFromString('hey')], sink: all }) + const { stream } = await conn.newStream(['echo']) - const result = await pipe(s, conn, s) - - expect(result).to.be.eql([uint8ArrayFromString('hey')]) + await expect(pipe(s, stream, s)).to.eventually.deep.equal([uint8ArrayFromString('hey')]) }) }) describe('ip4 with wss', () => { - let ws - let listener - const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9091/wss') - - const server = https.createServer({ - cert: fs.readFileSync('./test/fixtures/certificate.pem'), - key: fs.readFileSync('./test/fixtures/key.pem') + let ws: WebSockets + let listener: Listener + const ma = new Multiaddr('/ip4/127.0.0.1/tcp/37284/wss') + let server: https.Server + + beforeEach(async () => { + server = https.createServer({ + cert: fs.readFileSync('./test/fixtures/certificate.pem'), + key: fs.readFileSync('./test/fixtures/key.pem') + }) + ws = new WebSockets({ upgrader }) + listener = ws.createListener({ + server, + handler: (conn) => { + void conn.newStream(['echo']).then(async ({ stream }) => { + return await pipe(stream, stream) + }) + } + }) + return await listener.listen(ma) }) - beforeEach(() => { - ws = new WS({ upgrader: mockUpgrader }) - listener = ws.createListener({ server }, conn => pipe(conn, conn)) - return listener.listen(ma) + afterEach(async () => { + await listener.close() + await server.close() }) - afterEach(() => listener.close()) - it('should listen on wss address', () => { const addrs = listener.getAddrs() @@ -318,45 +347,43 @@ describe('dial', () => { expect(ma.equals(addrs[0])).to.eql(true) }) - it('dial', async () => { + it('dial ip4', async () => { const conn = await ws.dial(ma, { websocket: { rejectUnauthorized: false } }) - const s = goodbye({ source: ['hey'], sink: collect }) + const s = goodbye({ source: [uint8ArrayFromString('hey')], sink: all }) + const { stream } = await conn.newStream(['echo']) - const result = await pipe(s, conn, s) + const res = await pipe(s, stream, s) - expect(result).to.be.eql([uint8ArrayFromString('hey')]) + expect(res[0]).to.equalBytes(uint8ArrayFromString('hey')) + await conn.close() }) }) describe('ip6', () => { - let ws - let listener - const ma = new Multiaddr('/ip6/::1/tcp/9091') + let ws: WebSockets + let listener: Listener + const ma = new Multiaddr('/ip6/::1/tcp/9091/ws') - beforeEach(() => { - ws = new WS({ upgrader: mockUpgrader }) - listener = ws.createListener(conn => pipe(conn, conn)) - return listener.listen(ma) + beforeEach(async () => { + ws = new WebSockets({ upgrader }) + listener = ws.createListener({ + handler: (conn) => { + void conn.newStream(['echo']).then(async ({ stream }) => { + return await pipe(stream, stream) + }) + } + }) + return await listener.listen(ma) }) - afterEach(() => listener.close()) - - it('dial', async () => { - const conn = await ws.dial(ma) - const s = goodbye({ source: ['hey'], sink: collect }) - - const result = await pipe(s, conn, s) + afterEach(async () => await listener.close()) - expect(result).to.be.eql([uint8ArrayFromString('hey')]) - }) - - it('dial and use BufferList', async () => { + it('dial ip6', async () => { const conn = await ws.dial(ma) - const s = goodbye({ source: [new BufferList('hey')], sink: collect }) - - const result = await pipe(s, conn, s) + const s = goodbye({ source: [uint8ArrayFromString('hey')], sink: all }) + const { stream } = await conn.newStream(['echo']) - expect(result).to.be.eql([uint8ArrayFromString('hey')]) + await expect(pipe(s, stream, s)).to.eventually.deep.equal([uint8ArrayFromString('hey')]) }) it('dial with p2p Id', async () => { @@ -364,22 +391,22 @@ describe('dial', () => { const conn = await ws.dial(ma) const s = goodbye({ - source: ['hey'], - sink: collect + source: [uint8ArrayFromString('hey')], + sink: all }) + const { stream } = await conn.newStream(['echo']) - const result = await pipe(s, conn, s) - expect(result).to.be.eql([uint8ArrayFromString('hey')]) + await expect(pipe(s, stream, s)).to.eventually.deep.equal([uint8ArrayFromString('hey')]) }) }) }) describe('filter addrs', () => { - let ws + let ws: WebSockets describe('default filter addrs with only dns', () => { before(() => { - ws = new WS({ upgrader: mockUpgrader }) + ws = new WebSockets({ upgrader }) }) it('should filter out invalid WS addresses', function () { @@ -447,7 +474,7 @@ describe('filter addrs', () => { describe('custom filter addrs', () => { before(() => { - ws = new WS({ upgrader: mockUpgrader, filter: filters.all }) + ws = new WebSockets({ upgrader, filter: filters.all }) }) it('should fail invalid WS addresses', function () { @@ -569,7 +596,7 @@ describe('filter addrs', () => { it('filter a single addr for this transport', () => { const ma = new Multiaddr('/ip4/127.0.0.1/tcp/9090/ws/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw') - const valid = ws.filter(ma) + const valid = ws.filter([ma]) expect(valid.length).to.equal(1) expect(valid[0]).to.deep.equal(ma) }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f296f99 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020" + }, + "include": [ + "src", + "test" + ] +}