diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..0bb7ae248 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: ci +'on': + - push + - pull_request +jobs: + test: + name: Node ${{ matrix.node }} / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + node: + - '18' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - run: npm install + - run: npm run build --if-present + - run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..61c6902cf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,36 @@ +name: Release + +on: + push: + branches: + - master + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + persist-credentials: false + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Cache + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-npm- + - name: Install dependencies + run: npm i + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npx semantic-release diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..2b2bab3bc --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '0 12 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v8 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?' + stale-pr-message: 'Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?' + exempt-issue-labels: accepted,blocked,bug,dependency,enhancement,'help wanted',question,security,meta + exempt-pr-labels: accepted,blocked,bug,dependency,enhancement,'help wanted',question,security,meta + stale-issue-label: 'stale' + stale-pr-label: 'stale' diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..504afef81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/.npmignore b/.npmignore index 5a33e813b..ae28c2087 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,6 @@ -.travis.yml CONTRIBUTING.md examples/ img/ test/ tools/ +.github/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e95e65451..000000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: node_js -sudo: false -node_js: - - lts/* -before_script: - # Add an IPv6 config - see the corresponding Travis issue - # https://github.com/travis-ci/travis-ci/issues/8361 - - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then - sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; - fi diff --git a/AUTHORS.md b/AUTHORS.md index 904c5d349..3b930ae6f 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -49,5 +49,26 @@ - Eric Guan (guanzo91@gmail.com) - Koushik Dutta (koush@koushikdutta.com) - KayleePop (34007889+KayleePop@users.noreply.github.com) +- Diego Rodriguez Baquero (diego.baquero@pager.com) +- Diego Rodríguez Baquero (github@diegorbaquero.com) +- Renovate Bot (bot@renovateapp.com) +- Diego Rodríguez Baquero (diego@mothership.com) +- Diego Rodriguez Baquero (diego@arc.io) +- semantic-release-bot (semantic-release-bot@martynus.net) +- renovate[bot] (29139614+renovate[bot]@users.noreply.github.com) +- Jocelyn Liu (yrliou@gmail.com) +- Bruce Hopkins (behopkinsjr@gmail.com) +- Ryan Finnie (ryan@finnie.org) +- Lookis (lookisliu@gmail.com) +- Paul Sharypov (pavloniym@gmail.com) +- Cas (6506529+ThaUnknown@users.noreply.github.com) +- Tom Snelling (tomsnelling8@gmail.com) +- Cas_ (6506529+ThaUnknown@users.noreply.github.com) +- Arsène Fougerouse (arsene582@gmail.com) +- Brad Marsden (silentbot1@gmail.com) +- krazak (krazak@vt.edu) +- Chocobozzz (chocobozzz@cpy.re) +- uriva (uriva@users.noreply.github.com) +- Subin Siby (mail@subinsb.com) #### Generated by tools/update-authors.sh. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..6b00b4dce --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,275 @@ +## [11.2.2](https://github.com/webtorrent/bittorrent-tracker/compare/v11.2.1...v11.2.2) (2025-09-06) + + +### Bug Fixes + +* export WebSocketTracker ([#558](https://github.com/webtorrent/bittorrent-tracker/issues/558)) ([1571551](https://github.com/webtorrent/bittorrent-tracker/commit/15715518decfed77d7888ba21d6ab592fa91cc85)) + +## [11.2.1](https://github.com/webtorrent/bittorrent-tracker/compare/v11.2.0...v11.2.1) (2025-01-19) + + +### Bug Fixes + +* http announce no left ([#548](https://github.com/webtorrent/bittorrent-tracker/issues/548)) ([3cd77f3](https://github.com/webtorrent/bittorrent-tracker/commit/3cd77f3e6f5b52f5d58adaf004b333cd2061a4da)) + +# [11.2.0](https://github.com/webtorrent/bittorrent-tracker/compare/v11.1.2...v11.2.0) (2024-12-28) + + +### Features + +* release [#539](https://github.com/webtorrent/bittorrent-tracker/issues/539) and [#535](https://github.com/webtorrent/bittorrent-tracker/issues/535) ([#544](https://github.com/webtorrent/bittorrent-tracker/issues/544)) ([e7de90c](https://github.com/webtorrent/bittorrent-tracker/commit/e7de90c0cbcfb41c9c53c5caf69cc37c6d3ef1e8)) + +## [11.1.2](https://github.com/webtorrent/bittorrent-tracker/compare/v11.1.1...v11.1.2) (2024-08-13) + + +### Bug Fixes + +* statuscode ([#526](https://github.com/webtorrent/bittorrent-tracker/issues/526)) ([e9d8f8c](https://github.com/webtorrent/bittorrent-tracker/commit/e9d8f8cd754ba26d86f32f9b8da0c0c4a3dcd646)) + +## [11.1.1](https://github.com/webtorrent/bittorrent-tracker/compare/v11.1.0...v11.1.1) (2024-07-01) + + +### Performance Improvements + +* drop clone ([#523](https://github.com/webtorrent/bittorrent-tracker/issues/523)) ([83a24ce](https://github.com/webtorrent/bittorrent-tracker/commit/83a24ce77fb1a96b7fe4c383ce92d7c28fc165a7)) + +# [11.1.0](https://github.com/webtorrent/bittorrent-tracker/compare/v11.0.2...v11.1.0) (2024-05-22) + + +### Bug Fixes + +* semantic release ([#520](https://github.com/webtorrent/bittorrent-tracker/issues/520)) ([428fb22](https://github.com/webtorrent/bittorrent-tracker/commit/428fb224f5666731332738032649f4448b2e1e4f)) + + +### Features + +* updated webrtc implementation ([#519](https://github.com/webtorrent/bittorrent-tracker/issues/519)) ([633d68a](https://github.com/webtorrent/bittorrent-tracker/commit/633d68a32c2c143fec0182317a9801dd1b64faef)) + +## [11.0.2](https://github.com/webtorrent/bittorrent-tracker/compare/v11.0.1...v11.0.2) (2024-03-12) + + +### Bug Fixes + +* **parse-http:** ignore announcements from peers with invalid announcement ports. ([#513](https://github.com/webtorrent/bittorrent-tracker/issues/513)) ([fe75272](https://github.com/webtorrent/bittorrent-tracker/commit/fe75272d51653e626583689081afb0b7aeadb84f)) + +## [11.0.1](https://github.com/webtorrent/bittorrent-tracker/compare/v11.0.0...v11.0.1) (2024-01-16) + + +### Bug Fixes + +* update build badge url ([#506](https://github.com/webtorrent/bittorrent-tracker/issues/506)) ([3f59b58](https://github.com/webtorrent/bittorrent-tracker/commit/3f59b58a020ea8c0926be135471a6666fe8e8b21)) + +# [11.0.0](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.12...v11.0.0) (2023-10-31) + + +### Features + +* **major:** drop simple-get ([#443](https://github.com/webtorrent/bittorrent-tracker/issues/443)) ([bce64e1](https://github.com/webtorrent/bittorrent-tracker/commit/bce64e155df6ff9fa605898cbf7498bf76188d8b)) + + +### BREAKING CHANGES + +* **major:** drop simple-get + +* perf: drop simple-get + +* feat: undici agent and socks + +* fix: undici as dev dependency + +* feat: require user passed proxy objects for http and ws + +* chore: include undici for tests + +## [10.0.12](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.11...v10.0.12) (2023-08-09) + + +### Bug Fixes + +* **deps:** update dependency bencode to v4 ([#487](https://github.com/webtorrent/bittorrent-tracker/issues/487)) ([aeccf9c](https://github.com/webtorrent/bittorrent-tracker/commit/aeccf9c1c4b9115fd23b4fe1a0ab990b5add0f17)) + +## [10.0.11](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.10...v10.0.11) (2023-08-01) + + +### Bug Fixes + +* mangled scrape infohashes ([#486](https://github.com/webtorrent/bittorrent-tracker/issues/486)) ([11cce83](https://github.com/webtorrent/bittorrent-tracker/commit/11cce83ddd858813f5684da8a116de4bee6e518b)) + +## [10.0.10](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.9...v10.0.10) (2023-06-16) + + +### Performance Improvements + +* use simple-peer/lite ([#475](https://github.com/webtorrent/bittorrent-tracker/issues/475)) ([5b8db06](https://github.com/webtorrent/bittorrent-tracker/commit/5b8db067e48cc81796728ff538d7ff6efafc59b8)) + +## [10.0.9](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.8...v10.0.9) (2023-06-16) + + +### Performance Improvements + +* use peer/lite ([#474](https://github.com/webtorrent/bittorrent-tracker/issues/474)) ([7c845f0](https://github.com/webtorrent/bittorrent-tracker/commit/7c845f030d07b1bf7060ab880b790ee85a8c7ac0)) + +## [10.0.8](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.7...v10.0.8) (2023-06-07) + + +### Bug Fixes + +* bigInt ([#472](https://github.com/webtorrent/bittorrent-tracker/issues/472)) ([d7061f7](https://github.com/webtorrent/bittorrent-tracker/commit/d7061f73b2ebff072e064971a5960749a7335bae)) + +## [10.0.7](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.6...v10.0.7) (2023-06-05) + + +### Bug Fixes + +* imports ([#471](https://github.com/webtorrent/bittorrent-tracker/issues/471)) ([a12022a](https://github.com/webtorrent/bittorrent-tracker/commit/a12022ac2c81d7fa3ecb81163852161e64199cf4)) + +## [10.0.6](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.5...v10.0.6) (2023-05-27) + + +### Bug Fixes + +* replace simple-peer with maintained one ([#466](https://github.com/webtorrent/bittorrent-tracker/issues/466)) ([3b2dedb](https://github.com/webtorrent/bittorrent-tracker/commit/3b2dedb4151615831ca12d3d0a830354b1c04e68)) + +## [10.0.5](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.4...v10.0.5) (2023-05-27) + + +### Bug Fixes + +* only stringify views ([#467](https://github.com/webtorrent/bittorrent-tracker/issues/467)) ([52f5502](https://github.com/webtorrent/bittorrent-tracker/commit/52f55020f38894e4d45e12c87184540d8b0acad3)) + +## [10.0.4](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.3...v10.0.4) (2023-05-26) + + +### Bug Fixes + +* drop buffer ([#465](https://github.com/webtorrent/bittorrent-tracker/issues/465)) ([c99eb89](https://github.com/webtorrent/bittorrent-tracker/commit/c99eb892088ef3c67ea5bf014dfdd86799251a7e)) + +## [10.0.3](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.2...v10.0.3) (2023-05-25) + + +### Performance Improvements + +* replace simple websocket with maintained one ([#464](https://github.com/webtorrent/bittorrent-tracker/issues/464)) ([3f01c29](https://github.com/webtorrent/bittorrent-tracker/commit/3f01c29122efd726d805673da82f43ce5592b793)) + +## [10.0.2](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.1...v10.0.2) (2023-02-01) + + +### Bug Fixes + +* **deps:** update dependency ws to v8 ([#448](https://github.com/webtorrent/bittorrent-tracker/issues/448)) ([2209d4f](https://github.com/webtorrent/bittorrent-tracker/commit/2209d4f21bdee10e575c1728c3accf7bd34380c9)), closes [#449](https://github.com/webtorrent/bittorrent-tracker/issues/449) + +## [10.0.1](https://github.com/webtorrent/bittorrent-tracker/compare/v10.0.0...v10.0.1) (2022-12-07) + + +### Bug Fixes + +* **deps:** update dependency bencode to v3 ([#434](https://github.com/webtorrent/bittorrent-tracker/issues/434)) [skip ci] ([926ceee](https://github.com/webtorrent/bittorrent-tracker/commit/926ceee0bac6dfe49877566aaa3cf645689492d1)) +* **deps:** update dependency string2compact to v2 ([#437](https://github.com/webtorrent/bittorrent-tracker/issues/437)) ([9be843c](https://github.com/webtorrent/bittorrent-tracker/commit/9be843c5e46ac2ab518187bf0d348e1e69e8633d)) + +# [10.0.0](https://github.com/webtorrent/bittorrent-tracker/compare/v9.19.0...v10.0.0) (2022-12-05) + + +### Features + +* esm ([#431](https://github.com/webtorrent/bittorrent-tracker/issues/431)) ([e6d3189](https://github.com/webtorrent/bittorrent-tracker/commit/e6d3189edf1a170197a799b97d84c632692b394f)) + + +### BREAKING CHANGES + +* ESM only + +* feat: esm + +* fix: linter oops + +# [9.19.0](https://github.com/webtorrent/bittorrent-tracker/compare/v9.18.6...v9.19.0) (2022-06-01) + + +### Features + +* **events:** Support of `paused` client event ([#411](https://github.com/webtorrent/bittorrent-tracker/issues/411)) ([ef76b3f](https://github.com/webtorrent/bittorrent-tracker/commit/ef76b3f3b6beee87f57d74addd0ca2ef2c517b6d)) + +## [9.18.6](https://github.com/webtorrent/bittorrent-tracker/compare/v9.18.5...v9.18.6) (2022-05-11) + + +### Bug Fixes + +* revert [#420](https://github.com/webtorrent/bittorrent-tracker/issues/420) ([8d54938](https://github.com/webtorrent/bittorrent-tracker/commit/8d54938f164347d57a7991268d191e44b752de7f)) + +## [9.18.5](https://github.com/webtorrent/bittorrent-tracker/compare/v9.18.4...v9.18.5) (2022-03-25) + + +### Bug Fixes + +* connection leaks ([#420](https://github.com/webtorrent/bittorrent-tracker/issues/420)) ([f7928cf](https://github.com/webtorrent/bittorrent-tracker/commit/f7928cfcc646cd95556549b64e61228892314682)) + +## [9.18.4](https://github.com/webtorrent/bittorrent-tracker/compare/v9.18.3...v9.18.4) (2022-03-06) + + +### Bug Fixes + +* typo in ws example ([#417](https://github.com/webtorrent/bittorrent-tracker/issues/417)) ([023afb9](https://github.com/webtorrent/bittorrent-tracker/commit/023afb9a3228d60392a18e70f85cdb6af5fa79fb)) + +## [9.18.3](https://github.com/webtorrent/bittorrent-tracker/compare/v9.18.2...v9.18.3) (2021-10-29) + + +### Bug Fixes + +* **deps:** update dependency clone to v2 ([#393](https://github.com/webtorrent/bittorrent-tracker/issues/393)) ([dc6f796](https://github.com/webtorrent/bittorrent-tracker/commit/dc6f7966844216c39491d6623dd412d5ca65d4c4)) + +## [9.18.2](https://github.com/webtorrent/bittorrent-tracker/compare/v9.18.1...v9.18.2) (2021-09-02) + + +### Bug Fixes + +* **deps:** update dependency socks to v2 ([#394](https://github.com/webtorrent/bittorrent-tracker/issues/394)) ([353e1f4](https://github.com/webtorrent/bittorrent-tracker/commit/353e1f40093a5e74cb54219abbae8ef0cc3d9e0b)) + +## [9.18.1](https://github.com/webtorrent/bittorrent-tracker/compare/v9.18.0...v9.18.1) (2021-09-01) + + +### Bug Fixes + +* disable socks in chromeapp ([#398](https://github.com/webtorrent/bittorrent-tracker/issues/398)) ([7fd5877](https://github.com/webtorrent/bittorrent-tracker/commit/7fd587789548453a852ea01e54900a5e9155db67)) + +# [9.18.0](https://github.com/webtorrent/bittorrent-tracker/compare/v9.17.4...v9.18.0) (2021-08-20) + + +### Features + +* add proxy support for tracker clients ([#356](https://github.com/webtorrent/bittorrent-tracker/issues/356)) ([ad64dc3](https://github.com/webtorrent/bittorrent-tracker/commit/ad64dc3a68cddccc2c1f05d0d8bb833f2c4860b2)) + +## [9.17.4](https://github.com/webtorrent/bittorrent-tracker/compare/v9.17.3...v9.17.4) (2021-07-22) + + +### Bug Fixes + +* if websocket closed, don't produce a response ([ca88435](https://github.com/webtorrent/bittorrent-tracker/commit/ca88435617e59714a456031c75b3a329897d97bd)) + +## [9.17.3](https://github.com/webtorrent/bittorrent-tracker/compare/v9.17.2...v9.17.3) (2021-07-02) + + +### Bug Fixes + +* auto update authors on version ([b5ffc70](https://github.com/webtorrent/bittorrent-tracker/commit/b5ffc708ada0bef66e7fa0cd1872527ea6dd8d53)) + +## [9.17.2](https://github.com/webtorrent/bittorrent-tracker/compare/v9.17.1...v9.17.2) (2021-06-15) + + +### Bug Fixes + +* modernize ([e5994d2](https://github.com/webtorrent/bittorrent-tracker/commit/e5994d2ebdec10fe2165e31f5b498382eeeaaf5f)) + +## [9.17.1](https://github.com/webtorrent/bittorrent-tracker/compare/v9.17.0...v9.17.1) (2021-06-15) + + +### Bug Fixes + +* add package-lock ([0e486b0](https://github.com/webtorrent/bittorrent-tracker/commit/0e486b09d80d30e1c13d4624e29c4251000d4092)) +* **deps:** update dependency bn.js to ^5.2.0 ([2d36e4a](https://github.com/webtorrent/bittorrent-tracker/commit/2d36e4ae60b1bac51773f2dca81c1a158b51cb28)) +* **deps:** update dependency chrome-dgram to ^3.0.6 ([a82aaaa](https://github.com/webtorrent/bittorrent-tracker/commit/a82aaaa31963a0d9adb640166f417142c5d7b970)) +* **deps:** update dependency run-parallel to ^1.2.0 ([fcf25ed](https://github.com/webtorrent/bittorrent-tracker/commit/fcf25ed40e1fd64e630b10a0281bc09604b901d3)) +* **deps:** update dependency run-series to ^1.1.9 ([fa2c33f](https://github.com/webtorrent/bittorrent-tracker/commit/fa2c33fc91f8ef0a47d0f40b7a046ae179ee328a)) +* **deps:** update dependency simple-websocket to ^9.1.0 ([96fedbd](https://github.com/webtorrent/bittorrent-tracker/commit/96fedbdf56ddcf6627eb373a33589db885cb4fb7)) +* **deps:** update dependency ws to ^7.4.5 ([6ad7ead](https://github.com/webtorrent/bittorrent-tracker/commit/6ad7ead994e5cb99980a406aea908e4b9ff6151c)) +* **deps:** update webtorrent ([1e8d47d](https://github.com/webtorrent/bittorrent-tracker/commit/1e8d47dcd8f5f53b42aa75265a129f950d16feef)) +* UDP url parsing ([8e24a8c](https://github.com/webtorrent/bittorrent-tracker/commit/8e24a8c97b55bbaaf2c92a496d1cd30b0c008934)) diff --git a/README.md b/README.md index 3e48a5357..db0eba161 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# bittorrent-tracker [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] +# bittorrent-tracker [![ci][ci-image]][ci-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] -[travis-image]: https://img.shields.io/travis/webtorrent/bittorrent-tracker/master.svg -[travis-url]: https://travis-ci.org/webtorrent/bittorrent-tracker +[ci-image]: https://img.shields.io/github/actions/workflow/status/webtorrent/bittorrent-tracker/ci.yml +[ci-url]: https://github.com/webtorrent/bittorrent-tracker/actions [npm-image]: https://img.shields.io/npm/v/bittorrent-tracker.svg [npm-url]: https://npmjs.org/package/bittorrent-tracker [downloads-image]: https://img.shields.io/npm/dm/bittorrent-tracker.svg @@ -55,16 +55,22 @@ npm install bittorrent-tracker To connect to a tracker, just do this: ```js -var Client = require('bittorrent-tracker') +import Client from 'bittorrent-tracker' -var requiredOpts = { +const requiredOpts = { infoHash: new Buffer('012345678901234567890'), // hex string or Buffer peerId: new Buffer('01234567890123456789'), // hex string or Buffer announce: [], // list of tracker server urls port: 6881 // torrent client port, (in browser, optional) } -var optionalOpts = { +const optionalOpts = { + // RTCPeerConnection config object (only used in browser) + rtcConfig: {}, + // User-Agent header for http requests + userAgent: '', + // Custom webrtc impl, useful in node to specify [wrtc](https://npmjs.com/package/wrtc) + wrtc: {}, getAnnounceOpts: function () { // Provide a callback that will be called whenever announce() is called // internally (on timer), or by the user @@ -75,15 +81,24 @@ var optionalOpts = { customParam: 'blah' // custom parameters supported } }, - // RTCPeerConnection config object (only used in browser) - rtcConfig: {}, - // User-Agent header for http requests - userAgent: '', - // Custom webrtc impl, useful in node to specify [wrtc](https://npmjs.com/package/wrtc) - wrtc: {}, + // Proxy options (used to proxy requests in node) + proxyOpts: { + // For WSS trackers this is always a http.Agent + // For UDP trackers this is an object of options for the Socks Connection + // For HTTP trackers this is either an undici Agent if using Node16 or later, or http.Agent if using versions prior to Node 16, ex: + // import Socks from 'socks' + // proxyOpts.socksProxy = new Socks.Agent(optionsObject, isHttps) + // or if using Node 16 or later + // import { socksDispatcher } from 'fetch-socks' + // proxyOpts.socksProxy = socksDispatcher(optionsObject) + socksProxy: new SocksProxy(socksOptionsObject), + // Populated with socksProxy if it's provided + httpAgent: new http.Agent(agentOptionsObject), + httpsAgent: new https.Agent(agentOptionsObject) + }, } -var client = new Client(requiredOpts) +const client = new Client(requiredOpts) client.on('error', function (err) { // fatal client error! @@ -144,13 +159,15 @@ client.on('scrape', function (data) { To start a BitTorrent tracker server to track swarms of peers: ```js -var Server = require('bittorrent-tracker').Server +import { Server } from 'bittorrent-tracker' +// Or import Server from 'bittorrent-tracker/server' -var server = new Server({ +const server = new Server({ udp: true, // enable udp server? [default=true] http: true, // enable http server? [default=true] ws: true, // enable websocket server? [default=true] stats: true, // enable web-based statistics? [default=true] + trustProxy: false, // enable trusting x-forwarded-for header for remote IP [default=false] filter: function (infoHash, params, cb) { // Blacklist/whitelist function for allowing/disallowing torrents. If this option is // omitted, all torrents are allowed. It is possible to interface with a database or @@ -162,7 +179,7 @@ var server = new Server({ // This example only allows one torrent. - var allowed = (infoHash === 'aaa67059ed6bd08362da625b3ae77f6f4a075aaa') + const allowed = (infoHash === 'aaa67059ed6bd08362da625b3ae77f6f4a075aaa') if (allowed) { // If the callback is passed `null`, the torrent will be allowed. cb(null) @@ -191,12 +208,34 @@ server.on('warning', function (err) { server.on('listening', function () { // fired when all requested servers are listening - console.log('listening on http port:' + server.http.address().port) - console.log('listening on udp port:' + server.udp.address().port) + + // HTTP + const httpAddr = server.http.address() + const httpHost = httpAddr.address !== '::' ? httpAddr.address : 'localhost' + const httpPort = httpAddr.port + console.log(`HTTP tracker: http://${httpHost}:${httpPort}/announce`) + + // UDP + const udpAddr = server.udp.address() + const udpHost = udpAddr.address + const udpPort = udpAddr.port + console.log(`UDP tracker: udp://${udpHost}:${udpPort}`) + + // WS + const wsAddr = server.ws.address() + const wsHost = wsAddr.address !== '::' ? wsAddr.address : 'localhost' + const wsPort = wsAddr.port + console.log(`WebSocket tracker: ws://${wsHost}:${wsPort}`) + }) + // start tracker server listening! Use 0 to listen on a random free port. -server.listen(port, hostname, onlistening) +const port = 0 +const hostname = "localhost" +server.listen(port, hostname, () => { + // Do something on listening... +}) // listen for individual tracker messages from peers: @@ -228,7 +267,9 @@ The http server will handle requests for the following paths: `/announce`, `/scr Scraping multiple torrent info is possible with a static `Client.scrape` method: ```js -var Client = require('bittorrent-tracker') +import Client from 'bittorrent-tracker' +// Or import Client from 'bittorrent-tracker/client' + Client.scrape({ announce: announceUrl, infoHash: [ infoHash1, infoHash2 ]}, function (err, results) { results[infoHash1].announce results[infoHash1].infoHash diff --git a/bin/cmd.js b/bin/cmd.js index cd3673ce2..c57eef55b 100755 --- a/bin/cmd.js +++ b/bin/cmd.js @@ -1,9 +1,9 @@ #!/usr/bin/env node -var minimist = require('minimist') -var Server = require('../').Server +import minimist from 'minimist' +import { Server } from '../index.js' -var argv = minimist(process.argv.slice(2), { +const argv = minimist(process.argv.slice(2), { alias: { h: 'help', p: 'port', @@ -39,7 +39,7 @@ if (argv.version) { } if (argv.help) { - console.log(function () { + console.log((() => { /* bittorrent-tracker - Start a bittorrent tracker server @@ -64,19 +64,19 @@ if (argv.help) { -v, --version print the current version */ - }.toString().split(/\n/).slice(2, -2).join('\n')) + }).toString().split(/\n/).slice(2, -2).join('\n')) process.exit(0) } if (argv.silent) argv.quiet = true -var allFalsy = !argv.http && !argv.udp && !argv.ws +const allFalsy = !argv.http && !argv.udp && !argv.ws argv.http = allFalsy || argv.http argv.udp = allFalsy || argv.udp argv.ws = allFalsy || argv.ws -var server = new Server({ +const server = new Server({ http: argv.http, interval: argv.interval, stats: argv.stats, @@ -85,60 +85,60 @@ var server = new Server({ ws: argv.ws }) -server.on('error', function (err) { - if (!argv.silent) console.error('ERROR: ' + err.message) +server.on('error', err => { + if (!argv.silent) console.error(`${new Date().toISOString()} ERROR: ${err.message}`) }) -server.on('warning', function (err) { - if (!argv.quiet) console.log('WARNING: ' + err.message) +server.on('warning', err => { + if (!argv.quiet) console.log(`${new Date().toISOString()} WARNING: ${err.message}`) }) -server.on('update', function (addr) { - if (!argv.quiet) console.log('update: ' + addr) +server.on('update', addr => { + if (!argv.quiet) console.log(`${new Date().toISOString()} update: ${addr}`) }) -server.on('complete', function (addr) { - if (!argv.quiet) console.log('complete: ' + addr) +server.on('complete', addr => { + if (!argv.quiet) console.log(`${new Date().toISOString()} complete: ${addr}`) }) -server.on('start', function (addr) { - if (!argv.quiet) console.log('start: ' + addr) +server.on('start', addr => { + if (!argv.quiet) console.log(`${new Date().toISOString()} start: ${addr}`) }) -server.on('stop', function (addr) { - if (!argv.quiet) console.log('stop: ' + addr) +server.on('stop', addr => { + if (!argv.quiet) console.log(`${new Date().toISOString()} stop: ${addr}`) }) -var hostname = { +const hostname = { http: argv['http-hostname'], udp4: argv['udp-hostname'], udp6: argv['udp6-hostname'] } -server.listen(argv.port, hostname, function () { +server.listen(argv.port, hostname, () => { if (server.http && argv.http && !argv.quiet) { - var httpAddr = server.http.address() - var httpHost = httpAddr.address !== '::' ? httpAddr.address : 'localhost' - var httpPort = httpAddr.port - console.log('HTTP tracker: http://' + httpHost + ':' + httpPort + '/announce') + const httpAddr = server.http.address() + const httpHost = httpAddr.address !== '::' ? httpAddr.address : 'localhost' + const httpPort = httpAddr.port + console.log(`${new Date().toISOString()} HTTP tracker: http://${httpHost}:${httpPort}/announce`) } if (server.udp && !argv.quiet) { - var udpAddr = server.udp.address() - var udpHost = udpAddr.address - var udpPort = udpAddr.port - console.log('UDP tracker: udp://' + udpHost + ':' + udpPort) + const udpAddr = server.udp.address() + const udpHost = udpAddr.address + const udpPort = udpAddr.port + console.log(`${new Date().toISOString()} UDP tracker: udp://${udpHost}:${udpPort}`) } if (server.udp6 && !argv.quiet) { - var udp6Addr = server.udp6.address() - var udp6Host = udp6Addr.address !== '::' ? udp6Addr.address : 'localhost' - var udp6Port = udp6Addr.port - console.log('UDP6 tracker: udp://' + udp6Host + ':' + udp6Port) + const udp6Addr = server.udp6.address() + const udp6Host = udp6Addr.address !== '::' ? udp6Addr.address : 'localhost' + const udp6Port = udp6Addr.port + console.log(`${new Date().toISOString()} UDP6 tracker: udp://${udp6Host}:${udp6Port}`) } if (server.ws && !argv.quiet) { - var wsAddr = server.http.address() - var wsHost = wsAddr.address !== '::' ? wsAddr.address : 'localhost' - var wsPort = wsAddr.port - console.log('WebSocket tracker: ws://' + wsHost + ':' + wsPort) + const wsAddr = server.http.address() + const wsHost = wsAddr.address !== '::' ? wsAddr.address : 'localhost' + const wsPort = wsAddr.port + console.log(`${new Date().toISOString()} WebSocket tracker: ws://${wsHost}:${wsPort}`) } if (server.http && argv.stats && !argv.quiet) { - var statsAddr = server.http.address() - var statsHost = statsAddr.address !== '::' ? statsAddr.address : 'localhost' - var statsPort = statsAddr.port - console.log('Tracker stats: http://' + statsHost + ':' + statsPort + '/stats') + const statsAddr = server.http.address() + const statsHost = statsAddr.address !== '::' ? statsAddr.address : 'localhost' + const statsPort = statsAddr.port + console.log(`${new Date().toISOString()} Tracker stats: http://${statsHost}:${statsPort}/stats`) } }) diff --git a/client.js b/client.js index 48f0dfb3b..c37345f17 100644 --- a/client.js +++ b/client.js @@ -1,13 +1,17 @@ -const debug = require('debug')('bittorrent-tracker:client') -const EventEmitter = require('events') -const once = require('once') -const parallel = require('run-parallel') -const Peer = require('simple-peer') +import Debug from 'debug' +import EventEmitter from 'events' +import once from 'once' +import parallel from 'run-parallel' +import Peer from '@thaunknown/simple-peer/lite.js' +import queueMicrotask from 'queue-microtask' +import { hex2arr, hex2bin, text2arr, arr2hex, arr2text } from 'uint8-util' -const common = require('./lib/common') -const HTTPTracker = require('./lib/client/http-tracker') // empty object in browser -const UDPTracker = require('./lib/client/udp-tracker') // empty object in browser -const WebSocketTracker = require('./lib/client/websocket-tracker') +import common from './lib/common.js' +import HTTPTracker from './lib/client/http-tracker.js' // empty object in browser +import UDPTracker from './lib/client/udp-tracker.js' // empty object in browser +import WebSocketTracker from './lib/client/websocket-tracker.js' + +const debug = Debug('bittorrent-tracker:client') /** * BitTorrent tracker client. @@ -15,14 +19,15 @@ const WebSocketTracker = require('./lib/client/websocket-tracker') * Find torrent peers, to help a torrent client participate in a torrent swarm. * * @param {Object} opts options object - * @param {string|Buffer} opts.infoHash torrent info hash - * @param {string|Buffer} opts.peerId peer id + * @param {string|Uint8Array} opts.infoHash torrent info hash + * @param {string|Uint8Array} opts.peerId peer id * @param {string|Array.} opts.announce announce * @param {number} opts.port torrent client listening port * @param {function} opts.getAnnounceOpts callback to provide data to tracker * @param {number} opts.rtcConfig RTCPeerConnection configuration object * @param {number} opts.userAgent User-Agent header for http requests * @param {number} opts.wrtc custom webrtc impl (useful in node.js) + * @param {object} opts.proxyOpts proxy options (useful in node.js) */ class Client extends EventEmitter { constructor (opts = {}) { @@ -35,15 +40,15 @@ class Client extends EventEmitter { this.peerId = typeof opts.peerId === 'string' ? opts.peerId - : opts.peerId.toString('hex') - this._peerIdBuffer = Buffer.from(this.peerId, 'hex') - this._peerIdBinary = this._peerIdBuffer.toString('binary') + : arr2hex(opts.peerId) + this._peerIdBuffer = hex2arr(this.peerId) + this._peerIdBinary = hex2bin(this.peerId) this.infoHash = typeof opts.infoHash === 'string' ? opts.infoHash.toLowerCase() - : opts.infoHash.toString('hex') - this._infoHashBuffer = Buffer.from(this.infoHash, 'hex') - this._infoHashBinary = this._infoHashBuffer.toString('binary') + : arr2hex(opts.infoHash) + this._infoHashBuffer = hex2arr(this.infoHash) + this._infoHashBinary = hex2bin(this.infoHash) debug('new client %s', this.infoHash) @@ -53,6 +58,7 @@ class Client extends EventEmitter { this._getAnnounceOpts = opts.getAnnounceOpts this._rtcConfig = opts.rtcConfig this._userAgent = opts.userAgent + this._proxyOpts = opts.proxyOpts // Support lazy 'wrtc' module initialization // See: https://github.com/webtorrent/webtorrent-hybrid/issues/46 @@ -64,7 +70,7 @@ class Client extends EventEmitter { // Remove trailing slash from trackers to catch duplicates announce = announce.map(announceUrl => { - announceUrl = announceUrl.toString() + if (ArrayBuffer.isView(announceUrl)) announceUrl = arr2text(announceUrl) if (announceUrl[announceUrl.length - 1] === '/') { announceUrl = announceUrl.substring(0, announceUrl.length - 1) } @@ -76,7 +82,7 @@ class Client extends EventEmitter { const webrtcSupport = this._wrtc !== false && (!!this._wrtc || Peer.WEBRTC_SUPPORT) const nextTickWarn = err => { - process.nextTick(() => { + queueMicrotask(() => { this.emit('warning', err) }) } @@ -85,7 +91,7 @@ class Client extends EventEmitter { .map(announceUrl => { let parsedUrl try { - parsedUrl = new URL(announceUrl) + parsedUrl = common.parseUrl(announceUrl) } catch (err) { nextTickWarn(new Error(`Invalid tracker URL: ${announceUrl}`)) return null @@ -255,7 +261,7 @@ Client.scrape = (opts, cb) => { const clientOpts = Object.assign({}, opts, { infoHash: Array.isArray(opts.infoHash) ? opts.infoHash[0] : opts.infoHash, - peerId: Buffer.from('01234567890123456789'), // dummy value + peerId: text2arr('01234567890123456789'), // dummy value port: 6881 // dummy value }) @@ -279,13 +285,8 @@ Client.scrape = (opts, cb) => { } }) - opts.infoHash = Array.isArray(opts.infoHash) - ? opts.infoHash.map(infoHash => { - return Buffer.from(infoHash, 'hex') - }) - : Buffer.from(opts.infoHash, 'hex') client.scrape({ infoHash: opts.infoHash }) return client } -module.exports = Client +export default Client diff --git a/examples/express-embed/server.js b/examples/express-embed/server.js index bbb85b126..947dcbfbe 100755 --- a/examples/express-embed/server.js +++ b/examples/express-embed/server.js @@ -1,27 +1,27 @@ #!/usr/bin/env node -var Server = require('../..').Server -var express = require('express') -var app = express() +import { Server } from '../../index.js' +import express from 'express' +const app = express() // https://wiki.theory.org/BitTorrentSpecification#peer_id -var whitelist = { +const whitelist = { UT: true // uTorrent } -var server = new Server({ +const server = new Server({ http: false, // we do our own udp: false, // not interested ws: false, // not interested - filter: function (params) { + filter (params) { // black/whitelist for disallowing/allowing specific clients [default=allow all] // this example only allows the uTorrent client - var client = params.peer_id[1] + params.peer_id[2] + const client = params.peer_id[1] + params.peer_id[2] return whitelist[client] } }) -var onHttpRequest = server.onHttpRequest.bind(server) +const onHttpRequest = server.onHttpRequest.bind(server) app.get('/announce', onHttpRequest) app.get('/scrape', onHttpRequest) diff --git a/index.js b/index.js index 1e7f2a456..e812c41c8 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -const Client = require('./client') -const Server = require('./server') +/*! bittorrent-tracker. MIT License. WebTorrent LLC */ +import Client from './client.js' +import Server from './server.js' -module.exports = Client -module.exports.Client = Client -module.exports.Server = Server +export default Client +export { Client, Server } diff --git a/lib/client/http-tracker.js b/lib/client/http-tracker.js index 5f14a37de..43464e8eb 100644 --- a/lib/client/http-tracker.js +++ b/lib/client/http-tracker.js @@ -1,14 +1,24 @@ -const arrayRemove = require('unordered-array-remove') -const bencode = require('bencode') -const compact2string = require('compact2string') -const debug = require('debug')('bittorrent-tracker:http-tracker') -const get = require('simple-get') +import arrayRemove from 'unordered-array-remove' +import bencode from 'bencode' +import Debug from 'debug' +import fetch from 'cross-fetch-ponyfill' +import { bin2hex, hex2bin, arr2text, text2arr, arr2hex } from 'uint8-util' -const common = require('../common') -const Tracker = require('./tracker') +import common from '../common.js' +import Tracker from './tracker.js' +import compact2string from 'compact2string' +const debug = Debug('bittorrent-tracker:http-tracker') const HTTP_SCRAPE_SUPPORT = /\/(announce)[^/]*$/ +function abortTimeout (ms) { + const controller = new AbortController() + setTimeout(() => { + controller.abort() + }, ms).unref?.() + return controller +} + /** * HTTP torrent tracker client (for an individual tracker) * @@ -17,7 +27,7 @@ const HTTP_SCRAPE_SUPPORT = /\/(announce)[^/]*$/ * @param {Object} opts options object */ class HTTPTracker extends Tracker { - constructor (client, announceUrl, opts) { + constructor (client, announceUrl) { super(client, announceUrl) debug('new http tracker %s', announceUrl) @@ -45,6 +55,8 @@ class HTTPTracker extends Tracker { peer_id: this.client._peerIdBinary, port: this.client._port }) + + if (params.left !== 0 && !params.left) params.left = 16384 if (this._trackerId) params.trackerid = this._trackerId this._request(this.announceUrl, params, (err, data) => { @@ -62,10 +74,8 @@ class HTTPTracker extends Tracker { } const infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0) - ? opts.infoHash.map(infoHash => { - return infoHash.toString('binary') - }) - : (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary + ? opts.infoHash.map(infoHash => hex2bin(infoHash)) + : (opts.infoHash && hex2bin(opts.infoHash)) || this.client._infoHashBinary const params = { info_hash: infoHashes } @@ -81,12 +91,14 @@ class HTTPTracker extends Tracker { this.destroyed = true clearInterval(this.interval) + let timeout + // If there are no pending requests, destroy immediately. if (this.cleanupFns.length === 0) return destroyCleanup() // Otherwise, wait a short time for pending requests to complete, then force // destroy them. - var timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) + timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) // But, if all pending requests complete before the timeout fires, do cleanup // right away. @@ -108,63 +120,73 @@ class HTTPTracker extends Tracker { } } - _request (requestUrl, params, cb) { - const self = this - const u = requestUrl + (!requestUrl.includes('?') ? '?' : '&') + - common.querystringStringify(params) - - this.cleanupFns.push(cleanup) - - let request = get.concat({ - url: u, - timeout: common.REQUEST_TIMEOUT, - headers: { - 'user-agent': this.client._userAgent || '' + async _request (requestUrl, params, cb) { + const parsedUrl = new URL(requestUrl + (requestUrl.indexOf('?') === -1 ? '?' : '&') + common.querystringStringify(params)) + let agent + if (this.client._proxyOpts) { + agent = parsedUrl.protocol === 'https:' ? this.client._proxyOpts.httpsAgent : this.client._proxyOpts.httpAgent + if (!agent && this.client._proxyOpts.socksProxy) { + agent = this.client._proxyOpts.socksProxy } - }, onResponse) + } - function cleanup () { - if (request) { - arrayRemove(self.cleanupFns, self.cleanupFns.indexOf(cleanup)) - request.abort() - request = null + const cleanup = () => { + if (!controller.signal.aborted) { + arrayRemove(this.cleanupFns, this.cleanupFns.indexOf(cleanup)) + controller.abort() + controller = null } - if (self.maybeDestroyCleanup) self.maybeDestroyCleanup() + if (this.maybeDestroyCleanup) this.maybeDestroyCleanup() } - function onResponse (err, res, data) { - cleanup() - if (self.destroyed) return + this.cleanupFns.push(cleanup) + let res + let controller = abortTimeout(common.REQUEST_TIMEOUT) + try { + res = await fetch(parsedUrl.toString(), { + agent, + signal: controller.signal, + dispatcher: agent, + headers: { + 'user-agent': this.client._userAgent || '' + } + }) + if (res.body.on) res.body.on('error', cb) + } catch (err) { if (err) return cb(err) - if (res.statusCode !== 200) { - return cb(new Error(`Non-200 response code ${res.statusCode} from ${self.announceUrl}`)) - } - if (!data || data.length === 0) { - return cb(new Error(`Invalid tracker response from${self.announceUrl}`)) - } - - try { - data = bencode.decode(data) - } catch (err) { - return cb(new Error(`Error decoding tracker response: ${err.message}`)) - } - const failure = data['failure reason'] - if (failure) { - debug(`failure from ${requestUrl} (${failure})`) - return cb(new Error(failure)) - } + } + let data = new Uint8Array(await res.arrayBuffer()) + cleanup() + if (this.destroyed) return - const warning = data['warning message'] - if (warning) { - debug(`warning from ${requestUrl} (${warning})`) - self.client.emit('warning', new Error(warning)) - } + if (res.status !== 200) { + return cb(new Error(`Non-200 response code ${res.status} from ${this.announceUrl}`)) + } + if (!data || data.length === 0) { + return cb(new Error(`Invalid tracker response from${this.announceUrl}`)) + } - debug(`response from ${requestUrl}`) + try { + data = bencode.decode(data) + } catch (err) { + return cb(new Error(`Error decoding tracker response: ${err.message}`)) + } + const failure = data['failure reason'] && arr2text(data['failure reason']) + if (failure) { + debug(`failure from ${requestUrl} (${failure})`) + return cb(new Error(failure)) + } - cb(null, data) + const warning = data['warning message'] && arr2text(data['warning message']) + if (warning) { + debug(`warning from ${requestUrl} (${warning})`) + this.client.emit('warning', new Error(warning)) } + + debug(`response from ${requestUrl}`) + + cb(null, data) } _onAnnounceResponse (data) { @@ -179,15 +201,15 @@ class HTTPTracker extends Tracker { const response = Object.assign({}, data, { announce: this.announceUrl, - infoHash: common.binaryToHex(data.info_hash) + infoHash: bin2hex(data.info_hash || String(data.info_hash)) }) this.client.emit('update', response) let addrs - if (Buffer.isBuffer(data.peers)) { + if (ArrayBuffer.isView(data.peers)) { // tracker returned compact response try { - addrs = compact2string.multi(data.peers) + addrs = compact2string.multi(Buffer.from(data.peers)) } catch (err) { return this.client.emit('warning', err) } @@ -201,10 +223,10 @@ class HTTPTracker extends Tracker { }) } - if (Buffer.isBuffer(data.peers6)) { + if (ArrayBuffer.isView(data.peers6)) { // tracker returned compact response try { - addrs = compact2string.multi6(data.peers6) + addrs = compact2string.multi6(Buffer.from(data.peers6)) } catch (err) { return this.client.emit('warning', err) } @@ -233,12 +255,14 @@ class HTTPTracker extends Tracker { return } - keys.forEach(infoHash => { + keys.forEach(_infoHash => { // TODO: optionally handle data.flags.min_request_interval // (separate from announce interval) - const response = Object.assign(data[infoHash], { + const infoHash = _infoHash.length !== 20 ? arr2hex(text2arr(_infoHash)) : bin2hex(_infoHash) + + const response = Object.assign(data[_infoHash], { announce: this.announceUrl, - infoHash: common.binaryToHex(infoHash) + infoHash }) this.client.emit('scrape', response) }) @@ -247,4 +271,4 @@ class HTTPTracker extends Tracker { HTTPTracker.prototype.DEFAULT_ANNOUNCE_INTERVAL = 30 * 60 * 1000 // 30 minutes -module.exports = HTTPTracker +export default HTTPTracker diff --git a/lib/client/tracker.js b/lib/client/tracker.js index cbbd23dee..1129ecd84 100644 --- a/lib/client/tracker.js +++ b/lib/client/tracker.js @@ -1,4 +1,4 @@ -const EventEmitter = require('events') +import EventEmitter from 'events' class Tracker extends EventEmitter { constructor (client, announceUrl) { @@ -25,4 +25,4 @@ class Tracker extends EventEmitter { } } -module.exports = Tracker +export default Tracker diff --git a/lib/client/udp-tracker.js b/lib/client/udp-tracker.js index 5f8764070..b4ac4bdba 100644 --- a/lib/client/udp-tracker.js +++ b/lib/client/udp-tracker.js @@ -1,12 +1,17 @@ -const arrayRemove = require('unordered-array-remove') -const BN = require('bn.js') -const compact2string = require('compact2string') -const debug = require('debug')('bittorrent-tracker:udp-tracker') -const dgram = require('dgram') -const randombytes = require('randombytes') +import arrayRemove from 'unordered-array-remove' +import Debug from 'debug' +import dgram from 'dgram' +import Socks from 'socks' +import { concat, hex2arr, randomBytes } from 'uint8-util' -const common = require('../common') -const Tracker = require('./tracker') +import common from '../common.js' +import Tracker from './tracker.js' +import compact2string from 'compact2string' + +const debug = Debug('bittorrent-tracker:udp-tracker') + +// this was done some many years ago to fix "prevent Socks instances concurrency", and used some bloated package, no clue if it's needed, but this is simpler, #356 +const clone = obj => JSON.parse(JSON.stringify(obj)) /** * UDP torrent tracker client (for an individual tracker) @@ -16,7 +21,7 @@ const Tracker = require('./tracker') * @param {Object} opts options object */ class UDPTracker extends Tracker { - constructor (client, announceUrl, opts) { + constructor (client, announceUrl) { super(client, announceUrl) debug('new udp tracker %s', announceUrl) @@ -41,12 +46,14 @@ class UDPTracker extends Tracker { this.destroyed = true clearInterval(this.interval) + let timeout + // If there are no pending requests, destroy immediately. if (this.cleanupFns.length === 0) return destroyCleanup() // Otherwise, wait a short time for pending requests to complete, then force // destroy them. - var timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) + timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) // But, if all pending requests complete before the timeout fires, do cleanup // right away. @@ -72,43 +79,68 @@ class UDPTracker extends Tracker { const self = this if (!opts) opts = {} - // HACK: Fix for WHATWG URL object not parsing non-standard URL schemes like - // 'udp:'. Just replace it with 'http:' since we only need the `hostname` - // and `port` properties. - // - // Note: Only affects Chrome and Firefox. Works fine in Node.js, Safari, and - // Edge. - // - // Note: UDP trackers aren't used in the normal browser build, but they are - // used in a Chrome App build (i.e. by Brave Browser). - // - // Bug reports: - // - Chrome: https://bugs.chromium.org/p/chromium/issues/detail?id=734880 - // - Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1374505 - let { hostname, port } = new URL(this.announceUrl.replace(/^udp:/, 'http:')) + let { hostname, port } = common.parseUrl(this.announceUrl) if (port === '') port = 80 + let timeout + // Socket used to connect to the socks server to create a relay, null if socks is disabled + let proxySocket + // Socket used to connect to the tracker or to the socks relay if socks is enabled + let socket + // Contains the host/port of the socks relay + let relay + let transactionId = genTransactionId() - let socket = dgram.createSocket('udp4') - let timeout = setTimeout(() => { - // does not matter if `stopped` event arrives, so supress errors - if (opts.event === 'stopped') cleanup() - else onError(new Error(`tracker request timed out (${opts.event})`)) - timeout = null - }, common.REQUEST_TIMEOUT) - if (timeout.unref) timeout.unref() + const proxyOpts = this.client._proxyOpts && clone(this.client._proxyOpts.socksProxy) + if (proxyOpts) { + if (!proxyOpts.proxy) proxyOpts.proxy = {} + // UDP requests uses the associate command + proxyOpts.proxy.command = 'associate' + if (!proxyOpts.target) { + // This should contain client IP and port but can be set to 0 if we don't have this information + proxyOpts.target = { + host: '0.0.0.0', + port: 0 + } + } + + if (proxyOpts.proxy.type === 5) { + Socks.createConnection(proxyOpts, onGotConnection) + } else { + debug('Ignoring Socks proxy for UDP request because type 5 is required') + onGotConnection(null) + } + } else { + onGotConnection(null) + } this.cleanupFns.push(cleanup) - send(Buffer.concat([ - common.CONNECTION_ID, - common.toUInt32(common.ACTIONS.CONNECT), - transactionId - ])) + function onGotConnection (err, s, info) { + if (err) return onError(err) - socket.once('error', onError) - socket.on('message', onSocketMessage) + proxySocket = s + socket = dgram.createSocket('udp4') + relay = info + + timeout = setTimeout(() => { + // does not matter if `stopped` event arrives, so supress errors + if (opts.event === 'stopped') cleanup() + else onError(new Error(`tracker request timed out (${opts.event})`)) + timeout = null + }, common.REQUEST_TIMEOUT) + if (timeout.unref) timeout.unref() + + send(concat([ + common.CONNECTION_ID, + common.toUInt32(common.ACTIONS.CONNECT), + transactionId + ]), relay) + + socket.once('error', onError) + socket.on('message', onSocketMessage) + } function cleanup () { if (timeout) { @@ -122,6 +154,10 @@ class UDPTracker extends Tracker { socket.on('error', noop) // ignore all future errors try { socket.close() } catch (err) {} socket = null + if (proxySocket) { + try { proxySocket.close() } catch (err) {} + proxySocket = null + } } if (self.maybeDestroyCleanup) self.maybeDestroyCleanup() } @@ -139,7 +175,9 @@ class UDPTracker extends Tracker { } function onSocketMessage (msg) { - if (msg.length < 8 || msg.readUInt32BE(4) !== transactionId.readUInt32BE(0)) { + if (proxySocket) msg = msg.slice(10) + const view = new DataView(transactionId.buffer) + if (msg.length < 8 || msg.readUInt32BE(4) !== view.getUint32(0)) { return onError(new Error('tracker sent invalid transaction id')) } @@ -192,7 +230,7 @@ class UDPTracker extends Tracker { return onError(new Error('invalid scrape message')) } const infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0) - ? opts.infoHash.map(infoHash => { return infoHash.toString('hex') }) + ? opts.infoHash.map(infoHash => infoHash.toString('hex')) : [(opts.infoHash && opts.infoHash.toString('hex')) || self.client.infoHash] for (let i = 0, len = (msg.length - 8) / 12; i < len; i += 1) { @@ -222,43 +260,48 @@ class UDPTracker extends Tracker { } } - function send (message) { - socket.send(message, 0, message.length, port, hostname) + function send (message, proxyInfo) { + if (proxyInfo) { + const pack = Socks.createUDPFrame({ host: hostname, port }, message) + socket.send(pack, 0, pack.length, proxyInfo.port, proxyInfo.host) + } else { + socket.send(message, 0, message.length, port, hostname) + } } function announce (connectionId, opts) { transactionId = genTransactionId() - send(Buffer.concat([ + send(concat([ connectionId, common.toUInt32(common.ACTIONS.ANNOUNCE), transactionId, self.client._infoHashBuffer, self.client._peerIdBuffer, toUInt64(opts.downloaded), - opts.left != null ? toUInt64(opts.left) : Buffer.from('FFFFFFFFFFFFFFFF', 'hex'), + opts.left != null ? toUInt64(opts.left) : hex2arr('ffffffffffffffff'), toUInt64(opts.uploaded), common.toUInt32(common.EVENTS[opts.event] || 0), common.toUInt32(0), // ip address (optional) common.toUInt32(0), // key (optional) common.toUInt32(opts.numwant), toUInt16(self.client._port) - ])) + ]), relay) } function scrape (connectionId) { transactionId = genTransactionId() const infoHash = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0) - ? Buffer.concat(opts.infoHash) + ? concat(opts.infoHash) : (opts.infoHash || self.client._infoHashBuffer) - send(Buffer.concat([ + send(concat([ connectionId, common.toUInt32(common.ACTIONS.SCRAPE), transactionId, infoHash - ])) + ]), relay) } } } @@ -266,12 +309,13 @@ class UDPTracker extends Tracker { UDPTracker.prototype.DEFAULT_ANNOUNCE_INTERVAL = 30 * 60 * 1000 // 30 minutes function genTransactionId () { - return randombytes(4) + return randomBytes(4) } function toUInt16 (n) { - const buf = Buffer.allocUnsafe(2) - buf.writeUInt16BE(n, 0) + const buf = new Uint8Array(2) + const view = new DataView(buf.buffer) + view.setUint16(0, n) return buf } @@ -279,15 +323,14 @@ const MAX_UINT = 4294967295 function toUInt64 (n) { if (n > MAX_UINT || typeof n === 'string') { - const bytes = new BN(n).toArray() - while (bytes.length < 8) { - bytes.unshift(0) - } - return Buffer.from(bytes) + const buf = new Uint8Array(8) + const view = new DataView(buf.buffer) + view.setBigUint64(0, BigInt(n)) + return buf } - return Buffer.concat([common.toUInt32(0), common.toUInt32(n)]) + return concat([new Uint8Array(4), common.toUInt32(n)]) } function noop () {} -module.exports = UDPTracker +export default UDPTracker diff --git a/lib/client/websocket-tracker.js b/lib/client/websocket-tracker.js index fec33d209..c4e3e23d7 100644 --- a/lib/client/websocket-tracker.js +++ b/lib/client/websocket-tracker.js @@ -1,10 +1,12 @@ -const debug = require('debug')('bittorrent-tracker:websocket-tracker') -const Peer = require('simple-peer') -const randombytes = require('randombytes') -const Socket = require('simple-websocket') +import Debug from 'debug' +import Peer from '@thaunknown/simple-peer/lite.js' +import Socket from '@thaunknown/simple-websocket' +import { arr2text, arr2hex, hex2bin, bin2hex, randomBytes } from 'uint8-util' -const common = require('../common') -const Tracker = require('./tracker') +import common from '../common.js' +import Tracker from './tracker.js' + +const debug = Debug('bittorrent-tracker:websocket-tracker') // Use a socket pool, so tracker clients share WebSocket objects for the same server. // In practice, WebSockets are pretty slow to establish, so this gives a nice performance @@ -17,7 +19,7 @@ const RECONNECT_VARIANCE = 5 * 60 * 1000 const OFFER_TIMEOUT = 50 * 1000 class WebSocketTracker extends Tracker { - constructor (client, announceUrl, opts) { + constructor (client, announceUrl) { super(client, announceUrl) debug('new websocket tracker %s', announceUrl) @@ -56,7 +58,7 @@ class WebSocketTracker extends Tracker { this._send(params) } else { // Limit the number of offers that are generated, since it can be slow - const numwant = Math.min(opts.numwant, 10) + const numwant = Math.min(opts.numwant, 5) this._generateOffers(numwant, offers => { params.numwant = numwant @@ -76,10 +78,8 @@ class WebSocketTracker extends Tracker { } const infoHashes = (Array.isArray(opts.infoHash) && opts.infoHash.length > 0) - ? opts.infoHash.map(infoHash => { - return infoHash.toString('binary') - }) - : (opts.infoHash && opts.infoHash.toString('binary')) || this.client._infoHashBinary + ? opts.infoHash.map(infoHash => hex2bin(infoHash)) + : (opts.infoHash && hex2bin(opts.infoHash)) || this.client._infoHashBinary const params = { action: 'scrape', info_hash: infoHashes @@ -129,12 +129,14 @@ class WebSocketTracker extends Tracker { socket.on('error', noop) // ignore all future errors socket.once('close', cb) + let timeout + // If there is no data response expected, destroy immediately. if (!this.expectingResponse) return destroyCleanup() // Otherwise, wait a short time for potential responses to come in from the // server, then force close the socket. - var timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) + timeout = setTimeout(destroyCleanup, common.DESTROY_TIMEOUT) // But, if a response comes from the server before the timeout fires, do cleanup // right away. @@ -176,7 +178,15 @@ class WebSocketTracker extends Tracker { this._onSocketConnectBound() } } else { - this.socket = socketPool[this.announceUrl] = new Socket(this.announceUrl) + const parsedUrl = new URL(this.announceUrl) + let agent + if (this.client._proxyOpts) { + agent = parsedUrl.protocol === 'wss:' ? this.client._proxyOpts.httpsAgent : this.client._proxyOpts.httpAgent + if (!agent && this.client._proxyOpts.socksProxy) { + agent = this.client._proxyOpts.socksProxy + } + } + this.socket = socketPool[this.announceUrl] = new Socket({ url: this.announceUrl, agent }) this.socket.consumers = 1 this.socket.once('connect', this._onSocketConnectBound) } @@ -202,7 +212,7 @@ class WebSocketTracker extends Tracker { this.expectingResponse = false try { - data = JSON.parse(data) + data = JSON.parse(arr2text(data)) } catch (err) { this.client.emit('warning', new Error('Invalid tracker response')) return @@ -221,7 +231,7 @@ class WebSocketTracker extends Tracker { if (data.info_hash !== this.client._infoHashBinary) { debug( 'ignoring websocket data from %s for %s (looking for %s: reused socket)', - this.announceUrl, common.binaryToHex(data.info_hash), this.client.infoHash + this.announceUrl, bin2hex(data.info_hash), this.client.infoHash ) return } @@ -254,7 +264,7 @@ class WebSocketTracker extends Tracker { if (data.complete != null) { const response = Object.assign({}, data, { announce: this.announceUrl, - infoHash: common.binaryToHex(data.info_hash) + infoHash: bin2hex(data.info_hash) }) this.client.emit('update', response) } @@ -263,7 +273,7 @@ class WebSocketTracker extends Tracker { if (data.offer && data.peer_id) { debug('creating peer (from remote offer)') peer = this._createPeer() - peer.id = common.binaryToHex(data.peer_id) + peer.id = bin2hex(data.peer_id) peer.once('signal', answer => { const params = { action: 'announce', @@ -276,17 +286,17 @@ class WebSocketTracker extends Tracker { if (this._trackerId) params.trackerid = this._trackerId this._send(params) }) - peer.signal(data.offer) this.client.emit('peer', peer) + peer.signal(data.offer) } if (data.answer && data.peer_id) { - const offerId = common.binaryToHex(data.offer_id) + const offerId = bin2hex(data.offer_id) peer = this.peers[offerId] if (peer) { - peer.id = common.binaryToHex(data.peer_id) - peer.signal(data.answer) + peer.id = bin2hex(data.peer_id) this.client.emit('peer', peer) + peer.signal(data.answer) clearTimeout(peer.trackerTimeout) peer.trackerTimeout = null @@ -311,7 +321,7 @@ class WebSocketTracker extends Tracker { // (separate from announce interval) const response = Object.assign(data[infoHash], { announce: this.announceUrl, - infoHash: common.binaryToHex(infoHash) + infoHash: bin2hex(infoHash) }) this.client.emit('scrape', response) }) @@ -364,13 +374,13 @@ class WebSocketTracker extends Tracker { checkDone() function generateOffer () { - const offerId = randombytes(20).toString('hex') + const offerId = arr2hex(randomBytes(20)) debug('creating peer (from _generateOffers)') const peer = self.peers[offerId] = self._createPeer({ initiator: true }) peer.once('signal', offer => { offers.push({ offer, - offer_id: common.hexToBinary(offerId) + offer_id: hex2bin(offerId) }) checkDone() }) @@ -429,4 +439,4 @@ WebSocketTracker._socketPool = socketPool function noop () {} -module.exports = WebSocketTracker +export default WebSocketTracker diff --git a/lib/common-node.js b/lib/common-node.js index 7a9e87d6b..9fefa5c4d 100644 --- a/lib/common-node.js +++ b/lib/common-node.js @@ -3,46 +3,49 @@ * These are separate from common.js so they can be skipped when bundling for the browser. */ -var querystring = require('querystring') +import querystring from 'querystring' +import { concat } from 'uint8-util' -exports.IPV4_RE = /^[\d.]+$/ -exports.IPV6_RE = /^[\da-fA-F:]+$/ -exports.REMOVE_IPV4_MAPPED_IPV6_RE = /^::ffff:/ +export const IPV4_RE = /^[\d.]+$/ +export const IPV6_RE = /^[\da-fA-F:]+$/ +export const REMOVE_IPV4_MAPPED_IPV6_RE = /^::ffff:/ -exports.CONNECTION_ID = Buffer.concat([toUInt32(0x417), toUInt32(0x27101980)]) -exports.ACTIONS = { CONNECT: 0, ANNOUNCE: 1, SCRAPE: 2, ERROR: 3 } -exports.EVENTS = { update: 0, completed: 1, started: 2, stopped: 3 } -exports.EVENT_IDS = { +export const CONNECTION_ID = concat([toUInt32(0x417), toUInt32(0x27101980)]) +export const ACTIONS = { CONNECT: 0, ANNOUNCE: 1, SCRAPE: 2, ERROR: 3 } +export const EVENTS = { update: 0, completed: 1, started: 2, stopped: 3, paused: 4 } +export const EVENT_IDS = { 0: 'update', 1: 'completed', 2: 'started', - 3: 'stopped' + 3: 'stopped', + 4: 'paused' } -exports.EVENT_NAMES = { +export const EVENT_NAMES = { update: 'update', completed: 'complete', started: 'start', - stopped: 'stop' + stopped: 'stop', + paused: 'pause' } /** * Client request timeout. How long to wait before considering a request to a * tracker server to have timed out. */ -exports.REQUEST_TIMEOUT = 15000 +export const REQUEST_TIMEOUT = 15000 /** * Client destroy timeout. How long to wait before forcibly cleaning up all * pending requests, open sockets, etc. */ -exports.DESTROY_TIMEOUT = 1000 +export const DESTROY_TIMEOUT = 1000 -function toUInt32 (n) { - var buf = Buffer.allocUnsafe(4) - buf.writeUInt32BE(n, 0) +export function toUInt32 (n) { + const buf = new Uint8Array(4) + const view = new DataView(buf.buffer) + view.setUint32(0, n) return buf } -exports.toUInt32 = toUInt32 /** * `querystring.parse` using `unescape` instead of decodeURIComponent, since bittorrent @@ -50,9 +53,7 @@ exports.toUInt32 = toUInt32 * @param {string} q * @return {Object} */ -exports.querystringParse = function (q) { - return querystring.parse(q, null, null, { decodeURIComponent: unescape }) -} +export const querystringParse = q => querystring.parse(q, null, null, { decodeURIComponent: unescape }) /** * `querystring.stringify` using `escape` instead of encodeURIComponent, since bittorrent @@ -60,11 +61,9 @@ exports.querystringParse = function (q) { * @param {Object} obj * @return {string} */ -exports.querystringStringify = function (obj) { - var ret = querystring.stringify(obj, null, null, { encodeURIComponent: escape }) - ret = ret.replace(/[@*/+]/g, function (char) { - // `escape` doesn't encode the characters @*/+ so we do it manually - return '%' + char.charCodeAt(0).toString(16).toUpperCase() - }) +export const querystringStringify = obj => { + let ret = querystring.stringify(obj, null, null, { encodeURIComponent: escape }) + ret = ret.replace(/[@*/+]/g, char => // `escape` doesn't encode the characters @*/+ so we do it manually + `%${char.charCodeAt(0).toString(16).toUpperCase()}`) return ret } diff --git a/lib/common.js b/lib/common.js index f735a8566..ef888579c 100644 --- a/lib/common.js +++ b/lib/common.js @@ -1,23 +1,41 @@ /** * Functions/constants needed by both the client and server. */ +import * as common from './common-node.js' +export * from './common-node.js' -exports.DEFAULT_ANNOUNCE_PEERS = 50 -exports.MAX_ANNOUNCE_PEERS = 82 +export const DEFAULT_ANNOUNCE_PEERS = 50 +export const MAX_ANNOUNCE_PEERS = 82 -exports.binaryToHex = function (str) { - if (typeof str !== 'string') { - str = String(str) - } - return Buffer.from(str, 'binary').toString('hex') -} +// HACK: Fix for WHATWG URL object not parsing non-standard URL schemes like +// 'udp:'. Just replace it with 'http:' since we only need a few properties. +// +// Note: Only affects Chrome and Firefox. Works fine in Node.js, Safari, and +// Edge. +// +// Note: UDP trackers aren't used in the normal browser build, but they are +// used in a Chrome App build (i.e. by Brave Browser). +// +// Bug reports: +// - Chrome: https://bugs.chromium.org/p/chromium/issues/detail?id=734880 +// - Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1374505 +export const parseUrl = str => { + const url = new URL(str.replace(/^udp:/, 'http:')) -exports.hexToBinary = function (str) { - if (typeof str !== 'string') { - str = String(str) + if (str.match(/^udp:/)) { + Object.defineProperties(url, { + href: { value: url.href.replace(/^http/, 'udp') }, + protocol: { value: url.protocol.replace(/^http/, 'udp') }, + origin: { value: url.origin.replace(/^http/, 'udp') } + }) } - return Buffer.from(str, 'hex').toString('binary') + + return url } -var config = require('./common-node') -Object.assign(exports, config) +export default { + DEFAULT_ANNOUNCE_PEERS, + MAX_ANNOUNCE_PEERS, + parseUrl, + ...common +} diff --git a/lib/server/parse-http.js b/lib/server/parse-http.js index 85a1c8723..97b46e307 100644 --- a/lib/server/parse-http.js +++ b/lib/server/parse-http.js @@ -1,11 +1,11 @@ -module.exports = parseHttpRequest +import { bin2hex } from 'uint8-util' -var common = require('../common') +import common from '../common.js' -function parseHttpRequest (req, opts) { +export default function (req, opts) { if (!opts) opts = {} - var s = req.url.split('?') - var params = common.querystringParse(s[1]) + const s = req.url.split('?') + const params = common.querystringParse(s[1]) params.type = 'http' if (opts.action === 'announce' || s[0] === '/announce') { @@ -14,15 +14,15 @@ function parseHttpRequest (req, opts) { if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) { throw new Error('invalid info_hash') } - params.info_hash = common.binaryToHex(params.info_hash) + params.info_hash = bin2hex(params.info_hash) if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) { throw new Error('invalid peer_id') } - params.peer_id = common.binaryToHex(params.peer_id) + params.peer_id = bin2hex(params.peer_id) params.port = Number(params.port) - if (!params.port) throw new Error('invalid port') + if (!params.port || params.port <= 0 || params.port > 65535) throw new Error('invalid port') params.left = Number(params.left) if (Number.isNaN(params.left)) params.left = Infinity @@ -33,10 +33,18 @@ function parseHttpRequest (req, opts) { common.MAX_ANNOUNCE_PEERS ) - params.ip = opts.trustProxy - ? req.headers['x-forwarded-for'] || req.connection.remoteAddress - : req.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4 - params.addr = (common.IPV6_RE.test(params.ip) ? '[' + params.ip + ']' : params.ip) + ':' + params.port + if (opts.trustProxy) { + if (req.headers['x-forwarded-for']) { + const [realIp] = req.headers['x-forwarded-for'].split(',') + params.ip = realIp.trim() + } else { + params.ip = req.connection.remoteAddress + } + } else { + params.ip = req.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4 + } + + params.addr = `${common.IPV6_RE.test(params.ip) ? `[${params.ip}]` : params.ip}:${params.port}` params.headers = req.headers } else if (opts.action === 'scrape' || s[0] === '/scrape') { @@ -44,15 +52,15 @@ function parseHttpRequest (req, opts) { if (typeof params.info_hash === 'string') params.info_hash = [params.info_hash] if (Array.isArray(params.info_hash)) { - params.info_hash = params.info_hash.map(function (binaryInfoHash) { + params.info_hash = params.info_hash.map(binaryInfoHash => { if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) { throw new Error('invalid info_hash') } - return common.binaryToHex(binaryInfoHash) + return bin2hex(binaryInfoHash) }) } } else { - throw new Error('invalid action in HTTP request: ' + req.url) + throw new Error(`invalid action in HTTP request: ${req.url}`) } return params diff --git a/lib/server/parse-udp.js b/lib/server/parse-udp.js index da32bebdf..f73b64917 100644 --- a/lib/server/parse-udp.js +++ b/lib/server/parse-udp.js @@ -1,19 +1,18 @@ -module.exports = parseUdpRequest +import ipLib from 'ip' +import common from '../common.js' +import { equal } from 'uint8-util' -var ipLib = require('ip') -var common = require('../common') - -function parseUdpRequest (msg, rinfo) { +export default function (msg, rinfo) { if (msg.length < 16) throw new Error('received packet is too short') - var params = { + const params = { connectionId: msg.slice(0, 8), // 64-bit action: msg.readUInt32BE(8), transactionId: msg.readUInt32BE(12), type: 'udp' } - if (!common.CONNECTION_ID.equals(params.connectionId)) { + if (!equal(common.CONNECTION_ID, params.connectionId)) { throw new Error('received packet with invalid connection id') } @@ -29,7 +28,7 @@ function parseUdpRequest (msg, rinfo) { params.event = common.EVENT_IDS[msg.readUInt32BE(80)] if (!params.event) throw new Error('invalid event') // early return - var ip = msg.readUInt32BE(84) // optional + const ip = msg.readUInt32BE(84) // optional params.ip = ip ? ipLib.toString(ip) : rinfo.address @@ -44,32 +43,32 @@ function parseUdpRequest (msg, rinfo) { ) params.port = msg.readUInt16BE(96) || rinfo.port // optional - params.addr = params.ip + ':' + params.port // TODO: ipv6 brackets + params.addr = `${params.ip}:${params.port}` // TODO: ipv6 brackets params.compact = 1 // udp is always compact } else if (params.action === common.ACTIONS.SCRAPE) { // scrape message if ((msg.length - 16) % 20 !== 0) throw new Error('invalid scrape message') params.info_hash = [] - for (var i = 0, len = (msg.length - 16) / 20; i < len; i += 1) { - var infoHash = msg.slice(16 + (i * 20), 36 + (i * 20)).toString('hex') // 20 bytes + for (let i = 0, len = (msg.length - 16) / 20; i < len; i += 1) { + const infoHash = msg.slice(16 + (i * 20), 36 + (i * 20)).toString('hex') // 20 bytes params.info_hash.push(infoHash) } } else { - throw new Error('Invalid action in UDP packet: ' + params.action) + throw new Error(`Invalid action in UDP packet: ${params.action}`) } return params } -var TWO_PWR_32 = (1 << 16) * 2 +const TWO_PWR_32 = (1 << 16) * 2 /** * Return the closest floating-point representation to the buffer value. Precision will be * lost for big numbers. */ function fromUInt64 (buf) { - var high = buf.readUInt32BE(0) | 0 // force - var low = buf.readUInt32BE(4) | 0 - var lowUnsigned = (low >= 0) ? low : TWO_PWR_32 + low + const high = buf.readUInt32BE(0) | 0 // force + const low = buf.readUInt32BE(4) | 0 + const lowUnsigned = (low >= 0) ? low : TWO_PWR_32 + low return (high * TWO_PWR_32) + lowUnsigned } diff --git a/lib/server/parse-websocket.js b/lib/server/parse-websocket.js index 4416008a8..744a3c30b 100644 --- a/lib/server/parse-websocket.js +++ b/lib/server/parse-websocket.js @@ -1,8 +1,8 @@ -module.exports = parseWebSocketRequest +import { bin2hex } from 'uint8-util' -var common = require('../common') +import common from '../common.js' -function parseWebSocketRequest (socket, opts, params) { +export default function (socket, opts, params) { if (!opts) opts = {} params = JSON.parse(params) // may throw @@ -14,18 +14,18 @@ function parseWebSocketRequest (socket, opts, params) { if (typeof params.info_hash !== 'string' || params.info_hash.length !== 20) { throw new Error('invalid info_hash') } - params.info_hash = common.binaryToHex(params.info_hash) + params.info_hash = bin2hex(params.info_hash) if (typeof params.peer_id !== 'string' || params.peer_id.length !== 20) { throw new Error('invalid peer_id') } - params.peer_id = common.binaryToHex(params.peer_id) + params.peer_id = bin2hex(params.peer_id) if (params.answer) { if (typeof params.to_peer_id !== 'string' || params.to_peer_id.length !== 20) { throw new Error('invalid `to_peer_id` (required with `answer`)') } - params.to_peer_id = common.binaryToHex(params.to_peer_id) + params.to_peer_id = bin2hex(params.to_peer_id) } params.left = Number(params.left) @@ -41,26 +41,34 @@ function parseWebSocketRequest (socket, opts, params) { if (typeof params.info_hash === 'string') params.info_hash = [params.info_hash] if (Array.isArray(params.info_hash)) { - params.info_hash = params.info_hash.map(function (binaryInfoHash) { + params.info_hash = params.info_hash.map(binaryInfoHash => { if (typeof binaryInfoHash !== 'string' || binaryInfoHash.length !== 20) { throw new Error('invalid info_hash') } - return common.binaryToHex(binaryInfoHash) + return bin2hex(binaryInfoHash) }) } } else { - throw new Error('invalid action in WS request: ' + params.action) + throw new Error(`invalid action in WS request: ${params.action}`) } // On first parse, save important data from `socket.upgradeReq` and delete it // to reduce memory usage. if (socket.upgradeReq) { - socket.ip = opts.trustProxy - ? socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress - : socket.upgradeReq.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4 + if (opts.trustProxy) { + if (socket.upgradeReq.headers['x-forwarded-for']) { + const [realIp] = socket.upgradeReq.headers['x-forwarded-for'].split(',') + socket.ip = realIp.trim() + } else { + socket.ip = socket.upgradeReq.connection.remoteAddress + } + } else { + socket.ip = socket.upgradeReq.connection.remoteAddress.replace(common.REMOVE_IPV4_MAPPED_IPV6_RE, '') // force ipv4 + } + socket.port = socket.upgradeReq.connection.remotePort if (socket.port) { - socket.addr = (common.IPV6_RE.test(socket.ip) ? '[' + socket.ip + ']' : socket.ip) + ':' + socket.port + socket.addr = `${common.IPV6_RE.test(socket.ip) ? `[${socket.ip}]` : socket.ip}:${socket.port}` } socket.headers = socket.upgradeReq.headers diff --git a/lib/server/swarm.js b/lib/server/swarm.js index 5c914c940..9e3a0a204 100644 --- a/lib/server/swarm.js +++ b/lib/server/swarm.js @@ -1,148 +1,164 @@ -module.exports = Swarm +import arrayRemove from 'unordered-array-remove' +import Debug from 'debug' +import LRU from 'lru' +import randomIterate from 'random-iterate' -var arrayRemove = require('unordered-array-remove') -var debug = require('debug')('bittorrent-tracker:swarm') -var LRU = require('lru') -var randomIterate = require('random-iterate') +const debug = Debug('bittorrent-tracker:swarm') // Regard this as the default implementation of an interface that you // need to support when overriding Server.createSwarm() and Server.getSwarm() -function Swarm (infoHash, server) { - var self = this - self.infoHash = infoHash - self.complete = 0 - self.incomplete = 0 - - self.peers = new LRU({ - max: server.peersCacheLength || 1000, - maxAge: server.peersCacheTtl || 20 * 60 * 1000 // 20 minutes - }) - - // When a peer is evicted from the LRU store, send a synthetic 'stopped' event - // so the stats get updated correctly. - self.peers.on('evict', function (data) { - var peer = data.value - var params = { - type: peer.type, - event: 'stopped', - numwant: 0, - peer_id: peer.peerId +class Swarm { + constructor (infoHash, server) { + const self = this + self.infoHash = infoHash + self.complete = 0 + self.incomplete = 0 + + self.peers = new LRU({ + max: server.peersCacheLength || 1000, + maxAge: server.peersCacheTtl || 20 * 60 * 1000 // 20 minutes + }) + + // When a peer is evicted from the LRU store, send a synthetic 'stopped' event + // so the stats get updated correctly. + self.peers.on('evict', data => { + const peer = data.value + const params = { + type: peer.type, + event: 'stopped', + numwant: 0, + peer_id: peer.peerId + } + self._onAnnounceStopped(params, peer, peer.peerId) + peer.socket = null + }) + } + + announce (params, cb) { + const self = this + const id = params.type === 'ws' ? params.peer_id : params.addr + // Mark the source peer as recently used in cache + const peer = self.peers.get(id) + + if (params.event === 'started') { + self._onAnnounceStarted(params, peer, id) + } else if (params.event === 'stopped') { + self._onAnnounceStopped(params, peer, id) + if (!cb) return // when websocket is closed + } else if (params.event === 'completed') { + self._onAnnounceCompleted(params, peer, id) + } else if (params.event === 'update') { + self._onAnnounceUpdate(params, peer, id) + } else if (params.event === 'paused') { + self._onAnnouncePaused(params, peer, id) + } else { + cb(new Error('invalid event')) + return } - self._onAnnounceStopped(params, peer, peer.peerId) - peer.socket = null - }) -} + cb(null, { + complete: self.complete, + incomplete: self.incomplete, + peers: self._getPeers(params.numwant, params.peer_id, !!params.socket) + }) + } -Swarm.prototype.announce = function (params, cb) { - var self = this - var id = params.type === 'ws' ? params.peer_id : params.addr - // Mark the source peer as recently used in cache - var peer = self.peers.get(id) - - if (params.event === 'started') { - self._onAnnounceStarted(params, peer, id) - } else if (params.event === 'stopped') { - self._onAnnounceStopped(params, peer, id) - } else if (params.event === 'completed') { - self._onAnnounceCompleted(params, peer, id) - } else if (params.event === 'update') { - self._onAnnounceUpdate(params, peer, id) - } else { - cb(new Error('invalid event')) - return + scrape (params, cb) { + cb(null, { + complete: this.complete, + incomplete: this.incomplete + }) } - cb(null, { - complete: self.complete, - incomplete: self.incomplete, - peers: self._getPeers(params.numwant, params.peer_id, !!params.socket) - }) -} -Swarm.prototype.scrape = function (params, cb) { - cb(null, { - complete: this.complete, - incomplete: this.incomplete - }) -} + _onAnnounceStarted (params, peer, id) { + if (peer) { + debug('unexpected `started` event from peer that is already in swarm') + return this._onAnnounceUpdate(params, peer, id) // treat as an update + } -Swarm.prototype._onAnnounceStarted = function (params, peer, id) { - if (peer) { - debug('unexpected `started` event from peer that is already in swarm') - return this._onAnnounceUpdate(params, peer, id) // treat as an update + if (params.left === 0) this.complete += 1 + else this.incomplete += 1 + this.peers.set(id, { + type: params.type, + complete: params.left === 0, + peerId: params.peer_id, // as hex + ip: params.ip, + port: params.port, + socket: params.socket // only websocket + }) } - if (params.left === 0) this.complete += 1 - else this.incomplete += 1 - this.peers.set(id, { - type: params.type, - complete: params.left === 0, - peerId: params.peer_id, // as hex - ip: params.ip, - port: params.port, - socket: params.socket // only websocket - }) -} + _onAnnounceStopped (params, peer, id) { + if (!peer) { + debug('unexpected `stopped` event from peer that is not in swarm') + return // do nothing + } -Swarm.prototype._onAnnounceStopped = function (params, peer, id) { - if (!peer) { - debug('unexpected `stopped` event from peer that is not in swarm') - return // do nothing - } + if (peer.complete) this.complete -= 1 + else this.incomplete -= 1 - if (peer.complete) this.complete -= 1 - else this.incomplete -= 1 + // If it's a websocket, remove this swarm's infohash from the list of active + // swarms that this peer is participating in. + if (peer.socket && !peer.socket.destroyed) { + const index = peer.socket.infoHashes.indexOf(this.infoHash) + arrayRemove(peer.socket.infoHashes, index) + } - // If it's a websocket, remove this swarm's infohash from the list of active - // swarms that this peer is participating in. - if (peer.socket && !peer.socket.destroyed) { - var index = peer.socket.infoHashes.indexOf(this.infoHash) - arrayRemove(peer.socket.infoHashes, index) + this.peers.remove(id) } - this.peers.remove(id) -} + _onAnnounceCompleted (params, peer, id) { + if (!peer) { + debug('unexpected `completed` event from peer that is not in swarm') + return this._onAnnounceStarted(params, peer, id) // treat as a start + } + if (peer.complete) { + debug('unexpected `completed` event from peer that is already completed') + return this._onAnnounceUpdate(params, peer, id) // treat as an update + } -Swarm.prototype._onAnnounceCompleted = function (params, peer, id) { - if (!peer) { - debug('unexpected `completed` event from peer that is not in swarm') - return this._onAnnounceStarted(params, peer, id) // treat as a start - } - if (peer.complete) { - debug('unexpected `completed` event from peer that is already completed') - return this._onAnnounceUpdate(params, peer, id) // treat as an update + this.complete += 1 + this.incomplete -= 1 + peer.complete = true + this.peers.set(id, peer) } - this.complete += 1 - this.incomplete -= 1 - peer.complete = true - this.peers.set(id, peer) -} + _onAnnounceUpdate (params, peer, id) { + if (!peer) { + debug('unexpected `update` event from peer that is not in swarm') + return this._onAnnounceStarted(params, peer, id) // treat as a start + } -Swarm.prototype._onAnnounceUpdate = function (params, peer, id) { - if (!peer) { - debug('unexpected `update` event from peer that is not in swarm') - return this._onAnnounceStarted(params, peer, id) // treat as a start + if (!peer.complete && params.left === 0) { + this.complete += 1 + this.incomplete -= 1 + peer.complete = true + } + this.peers.set(id, peer) } - if (!peer.complete && params.left === 0) { - this.complete += 1 - this.incomplete -= 1 - peer.complete = true + _onAnnouncePaused (params, peer, id) { + if (!peer) { + debug('unexpected `paused` event from peer that is not in swarm') + return this._onAnnounceStarted(params, peer, id) // treat as a start + } + + this._onAnnounceUpdate(params, peer, id) } - this.peers.set(id, peer) -} -Swarm.prototype._getPeers = function (numwant, ownPeerId, isWebRTC) { - var peers = [] - var ite = randomIterate(this.peers.keys) - var peerId - while ((peerId = ite()) && peers.length < numwant) { - // Don't mark the peer as most recently used on announce - var peer = this.peers.peek(peerId) - if (!peer) continue - if (isWebRTC && peer.peerId === ownPeerId) continue // don't send peer to itself - if ((isWebRTC && peer.type !== 'ws') || (!isWebRTC && peer.type === 'ws')) continue // send proper peer type - peers.push(peer) + _getPeers (numwant, ownPeerId, isWebRTC) { + const peers = [] + const ite = randomIterate(this.peers.keys) + let peerId + while ((peerId = ite()) && peers.length < numwant) { + // Don't mark the peer as most recently used on announce + const peer = this.peers.peek(peerId) + if (!peer) continue + if (isWebRTC && peer.peerId === ownPeerId) continue // don't send peer to itself + if ((isWebRTC && peer.type !== 'ws') || (!isWebRTC && peer.type === 'ws')) continue // send proper peer type + peers.push(peer) + } + return peers } - return peers } + +export default Swarm diff --git a/package.json b/package.json index 2d1be7ba7..ca9cebd6d 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "bittorrent-tracker", "description": "Simple, robust, BitTorrent tracker (client & server) implementation", - "version": "9.14.5", + "version": "11.2.2", "author": { - "name": "WebTorrent, LLC", + "name": "WebTorrent LLC", "email": "feross@webtorrent.io", "url": "https://webtorrent.io" }, @@ -14,46 +14,68 @@ "./lib/common-node.js": false, "./lib/client/http-tracker.js": false, "./lib/client/udp-tracker.js": false, - "./server.js": false + "./server.js": false, + "socks": false }, "chromeapp": { "./server.js": false, - "dgram": "chrome-dgram" + "dgram": "chrome-dgram", + "socks": false }, "bugs": { "url": "https://github.com/webtorrent/bittorrent-tracker/issues" }, + "type": "module", "dependencies": { - "bencode": "^2.0.1", - "bittorrent-peerid": "^1.3.2", - "bn.js": "^5.1.1", - "chrome-dgram": "^3.0.4", + "@thaunknown/simple-peer": "^10.0.8", + "@thaunknown/simple-websocket": "^9.1.3", + "bencode": "^4.0.0", + "bittorrent-peerid": "^1.3.6", + "chrome-dgram": "^3.0.6", "compact2string": "^1.4.1", - "debug": "^4.1.1", - "ip": "^1.1.5", + "cross-fetch-ponyfill": "^1.0.3", + "debug": "^4.3.4", + "ip": "^2.0.1", "lru": "^3.1.0", - "minimist": "^1.2.5", + "minimist": "^1.2.8", "once": "^1.4.0", + "queue-microtask": "^1.2.3", "random-iterate": "^1.0.1", - "randombytes": "^2.1.0", - "run-parallel": "^1.1.9", - "run-series": "^1.1.8", - "simple-get": "^3.1.0", - "simple-peer": "^9.7.1", - "simple-websocket": "^8.1.1", - "string2compact": "^1.3.0", + "run-parallel": "^1.2.0", + "run-series": "^1.1.9", + "socks": "^2.8.3", + "string2compact": "^2.0.1", + "uint8-util": "^2.2.5", "unordered-array-remove": "^1.0.2", - "ws": "^7.3.0" + "ws": "^8.17.0" }, "devDependencies": { - "magnet-uri": "^5.2.4", + "@mapbox/node-pre-gyp": "1.0.11", + "@webtorrent/semantic-release-config": "1.0.10", + "magnet-uri": "7.0.7", + "semantic-release": "21.1.2", "standard": "*", - "tape": "^5.0.0", - "webtorrent-fixtures": "^1.7.3", - "wrtc": "^0.4.4" + "tape": "5.9.0", + "undici": "^6.16.1", + "webrtc-polyfill": "^1.1.5", + "webtorrent-fixtures": "2.0.2" }, "engines": { - "node": ">=10" + "node": ">=16.0.0" + }, + "exports": { + ".": { + "import": "./index.js" + }, + "./client": { + "import": "./client.js" + }, + "./server": { + "import": "./server.js" + }, + "./websocket-tracker": { + "import": "./lib/client/websocket-tracker.js" + } }, "keywords": [ "bittorrent", @@ -68,15 +90,38 @@ "license": "MIT", "main": "index.js", "optionalDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "bufferutil": "^4.0.8", + "utf-8-validate": "^6.0.4" }, "repository": { "type": "git", "url": "git://github.com/webtorrent/bittorrent-tracker.git" }, "scripts": { + "preversion": "npm run update-authors", "test": "standard && tape test/*.js", "update-authors": "./tools/update-authors.sh" + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "renovate": { + "extends": [ + "github>webtorrent/renovate-config" + ] + }, + "release": { + "extends": "@webtorrent/semantic-release-config" } } diff --git a/server.js b/server.js index 1ec53da46..f7023d99f 100644 --- a/server.js +++ b/server.js @@ -1,19 +1,21 @@ -const bencode = require('bencode') -const debug = require('debug')('bittorrent-tracker:server') -const dgram = require('dgram') -const EventEmitter = require('events') -const http = require('http') -const peerid = require('bittorrent-peerid') -const series = require('run-series') -const string2compact = require('string2compact') -const WebSocketServer = require('ws').Server - -const common = require('./lib/common') -const Swarm = require('./lib/server/swarm') -const parseHttpRequest = require('./lib/server/parse-http') -const parseUdpRequest = require('./lib/server/parse-udp') -const parseWebSocketRequest = require('./lib/server/parse-websocket') - +import bencode from 'bencode' +import Debug from 'debug' +import dgram from 'dgram' +import EventEmitter from 'events' +import http from 'http' +import peerid from 'bittorrent-peerid' +import series from 'run-series' +import string2compact from 'string2compact' +import { WebSocketServer } from 'ws' +import { hex2bin } from 'uint8-util' + +import common from './lib/common.js' +import Swarm from './lib/server/swarm.js' +import parseHttpRequest from './lib/server/parse-http.js' +import parseUdpRequest from './lib/server/parse-udp.js' +import parseWebSocketRequest from './lib/server/parse-websocket.js' + +const debug = Debug('bittorrent-tracker:server') const hasOwnProperty = Object.prototype.hasOwnProperty /** @@ -23,14 +25,14 @@ const hasOwnProperty = Object.prototype.hasOwnProperty * metrics from clients that help the tracker keep overall statistics about the torrent. * Responses include a peer list that helps the client participate in the torrent. * - * @param {Object} opts options object - * @param {Number} opts.interval tell clients to announce on this interval (ms) - * @param {Number} opts.trustProxy trust 'x-forwarded-for' header from reverse proxy - * @param {boolean} opts.http start an http server? (default: true) - * @param {boolean} opts.udp start a udp server? (default: true) - * @param {boolean} opts.ws start a websocket server? (default: true) - * @param {boolean} opts.stats enable web-based statistics? (default: true) - * @param {function} opts.filter black/whitelist fn for disallowing/allowing torrents + * @param {Object} opts options object + * @param {Number} opts.interval tell clients to announce on this interval (ms) + * @param {Number} opts.trustProxy trust 'x-forwarded-for' header from reverse proxy + * @param {boolean|Object} opts.http start an http server?, or options for http.createServer (default: true) + * @param {boolean|Object} opts.udp start a udp server?, or extra options for dgram.createSocket (default: true) + * @param {boolean|Object} opts.ws start a websocket server?, or extra options for new WebSocketServer (default: true) + * @param {boolean} opts.stats enable web-based statistics? (default: true) + * @param {function} opts.filter black/whitelist fn for disallowing/allowing torrents */ class Server extends EventEmitter { constructor (opts = {}) { @@ -59,7 +61,7 @@ class Server extends EventEmitter { // start an http tracker unless the user explictly says no if (opts.http !== false) { - this.http = http.createServer() + this.http = http.createServer(isObject(opts.http) ? opts.http : undefined) this.http.on('error', err => { this._onError(err) }) this.http.on('listening', onListening) @@ -75,18 +77,20 @@ class Server extends EventEmitter { // start a udp tracker unless the user explicitly says no if (opts.udp !== false) { - const isNode10 = /^v0.10./.test(process.version) - - this.udp4 = this.udp = dgram.createSocket( - isNode10 ? 'udp4' : { type: 'udp4', reuseAddr: true } - ) + this.udp4 = this.udp = dgram.createSocket({ + type: 'udp4', + reuseAddr: true, + ...(isObject(opts.udp) ? opts.udp : undefined) + }) this.udp4.on('message', (msg, rinfo) => { this.onUdpRequest(msg, rinfo) }) this.udp4.on('error', err => { this._onError(err) }) this.udp4.on('listening', onListening) - this.udp6 = dgram.createSocket( - isNode10 ? 'udp6' : { type: 'udp6', reuseAddr: true } - ) + this.udp6 = dgram.createSocket({ + type: 'udp6', + reuseAddr: true, + ...(isObject(opts.udp) ? opts.udp : undefined) + }) this.udp6.on('message', (msg, rinfo) => { this.onUdpRequest(msg, rinfo) }) this.udp6.on('error', err => { this._onError(err) }) this.udp6.on('listening', onListening) @@ -94,7 +98,8 @@ class Server extends EventEmitter { // start a websocket tracker (for WebTorrent) unless the user explicitly says no if (opts.ws !== false) { - if (!this.http) { + const noServer = isObject(opts.ws) && opts.ws.noServer + if (!this.http && !noServer) { this.http = http.createServer() this.http.on('error', err => { this._onError(err) }) this.http.on('listening', onListening) @@ -112,13 +117,19 @@ class Server extends EventEmitter { }) } this.ws = new WebSocketServer({ - server: this.http, + server: noServer ? undefined : this.http, perMessageDeflate: false, - clientTracking: false + clientTracking: false, + ...(isObject(opts.ws) ? opts.ws : undefined) }) + this.ws.address = () => { + if (noServer) { + throw new Error('address() unavailable with { noServer: true }') + } return this.http.address() } + this.ws.on('error', err => { this._onError(err) }) this.ws.on('connection', (socket, req) => { // Note: socket.upgradeReq was removed in ws@3.0.0, so re-add it. @@ -230,11 +241,11 @@ class Server extends EventEmitter { }) }) - const isSeederOnly = peer => { return peer.seeder && peer.leecher === false } - const isLeecherOnly = peer => { return peer.leecher && peer.seeder === false } - const isSeederAndLeecher = peer => { return peer.seeder && peer.leecher } - const isIPv4 = peer => { return peer.ipv4 } - const isIPv6 = peer => { return peer.ipv6 } + const isSeederOnly = peer => peer.seeder && peer.leecher === false + const isLeecherOnly = peer => peer.leecher && peer.seeder === false + const isSeederAndLeecher = peer => peer.seeder && peer.leecher + const isIPv4 = peer => peer.ipv4 + const isIPv6 = peer => peer.ipv6 const stats = { torrents: infoHashes.length, @@ -297,10 +308,6 @@ class Server extends EventEmitter { debug('listen (port: %o hostname: %o)', port, hostname) - function isObject (obj) { - return typeof obj === 'object' && obj !== null - } - const httpPort = isObject(port) ? (port.http || 0) : port const udpPort = isObject(port) ? (port.udp || 0) : port @@ -344,7 +351,7 @@ class Server extends EventEmitter { } createSwarm (infoHash, cb) { - if (Buffer.isBuffer(infoHash)) infoHash = infoHash.toString('hex') + if (ArrayBuffer.isView(infoHash)) infoHash = infoHash.toString('hex') process.nextTick(() => { const swarm = this.torrents[infoHash] = new Server.Swarm(infoHash, this) @@ -353,7 +360,7 @@ class Server extends EventEmitter { } getSwarm (infoHash, cb) { - if (Buffer.isBuffer(infoHash)) infoHash = infoHash.toString('hex') + if (ArrayBuffer.isView(infoHash)) infoHash = infoHash.toString('hex') process.nextTick(() => { cb(null, this.torrents[infoHash]) @@ -482,7 +489,7 @@ class Server extends EventEmitter { socket.send(JSON.stringify({ action: params.action === common.ACTIONS.ANNOUNCE ? 'announce' : 'scrape', 'failure reason': err.message, - info_hash: common.hexToBinary(params.info_hash) + info_hash: hex2bin(params.info_hash) }), socket.onSend) this.emit('warning', err) @@ -500,7 +507,7 @@ class Server extends EventEmitter { socket.infoHashes.push(params.info_hash) } - response.info_hash = common.hexToBinary(params.info_hash) + response.info_hash = hex2bin(params.info_hash) // WebSocket tracker should have a shorter interval – default: 2 minutes response.interval = Math.ceil(this.intervalMs / 1000 / 5) @@ -520,8 +527,8 @@ class Server extends EventEmitter { action: 'announce', offer: params.offers[i].offer, offer_id: params.offers[i].offer_id, - peer_id: common.hexToBinary(params.peer_id), - info_hash: common.hexToBinary(params.info_hash) + peer_id: hex2bin(params.peer_id), + info_hash: hex2bin(params.info_hash) }), peer.socket.onSend) debug('sent offer to %s from %s', peer.peerId, params.peer_id) }) @@ -553,8 +560,8 @@ class Server extends EventEmitter { action: 'announce', answer: params.answer, offer_id: params.offer_id, - peer_id: common.hexToBinary(params.peer_id), - info_hash: common.hexToBinary(params.info_hash) + peer_id: hex2bin(params.peer_id), + info_hash: hex2bin(params.info_hash) }), toPeer.socket.onSend) debug('sent answer to %s from %s', toPeer.peerId, params.peer_id) @@ -583,7 +590,7 @@ class Server extends EventEmitter { event: 'stopped', numwant: 0, peer_id: socket.peerId - }, noop) + }) } }) } @@ -673,26 +680,16 @@ class Server extends EventEmitter { const peers = response.peers // Find IPv4 peers - response.peers = string2compact(peers.filter(peer => { - return common.IPV4_RE.test(peer.ip) - }).map(peer => { - return `${peer.ip}:${peer.port}` - })) + response.peers = string2compact(peers.filter(peer => common.IPV4_RE.test(peer.ip)).map(peer => `${peer.ip}:${peer.port}`)) // Find IPv6 peers - response.peers6 = string2compact(peers.filter(peer => { - return common.IPV6_RE.test(peer.ip) - }).map(peer => { - return `[${peer.ip}]:${peer.port}` - })) + response.peers6 = string2compact(peers.filter(peer => common.IPV6_RE.test(peer.ip)).map(peer => `[${peer.ip}]:${peer.port}`)) } else if (params.compact === 0) { // IPv6 peers are not separate for non-compact responses - response.peers = response.peers.map(peer => { - return { - 'peer id': common.hexToBinary(peer.peerId), - ip: peer.ip, - port: peer.port - } - }) + response.peers = response.peers.map(peer => ({ + 'peer id': hex2bin(peer.peerId), + ip: peer.ip, + port: peer.port + })) } // else, return full peer objects (used for websocket responses) cb(null, response) @@ -707,24 +704,22 @@ class Server extends EventEmitter { params.info_hash = Object.keys(this.torrents) } - series(params.info_hash.map(infoHash => { - return cb => { - this.getSwarm(infoHash, (err, swarm) => { - if (err) return cb(err) - if (swarm) { - swarm.scrape(params, (err, scrapeInfo) => { - if (err) return cb(err) - cb(null, { - infoHash, - complete: (scrapeInfo && scrapeInfo.complete) || 0, - incomplete: (scrapeInfo && scrapeInfo.incomplete) || 0 - }) + series(params.info_hash.map(infoHash => cb => { + this.getSwarm(infoHash, (err, swarm) => { + if (err) return cb(err) + if (swarm) { + swarm.scrape(params, (err, scrapeInfo) => { + if (err) return cb(err) + cb(null, { + infoHash, + complete: (scrapeInfo && scrapeInfo.complete) || 0, + incomplete: (scrapeInfo && scrapeInfo.incomplete) || 0 }) - } else { - cb(null, { infoHash, complete: 0, incomplete: 0 }) - } - }) - } + }) + } else { + cb(null, { infoHash, complete: 0, incomplete: 0 }) + } + }) }), (err, results) => { if (err) return cb(err) @@ -735,7 +730,7 @@ class Server extends EventEmitter { } results.forEach(result => { - response.files[common.hexToBinary(result.infoHash)] = { + response.files[hex2bin(result.infoHash)] = { complete: result.complete || 0, incomplete: result.incomplete || 0, downloaded: result.complete || 0 // TODO: this only provides a lower-bound @@ -801,6 +796,10 @@ function makeUdpPacket (params) { return packet } +function isObject (obj) { + return typeof obj === 'object' && obj !== null +} + function toNumber (x) { x = Number(x) return x >= 0 ? x : false @@ -808,4 +807,4 @@ function toNumber (x) { function noop () {} -module.exports = Server +export default Server diff --git a/test/client-large-torrent.js b/test/client-large-torrent.js index 00a62c21e..db0d0fbf9 100644 --- a/test/client-large-torrent.js +++ b/test/client-large-torrent.js @@ -1,41 +1,41 @@ -var Client = require('../') -var common = require('./common') -var fixtures = require('webtorrent-fixtures') -var test = require('tape') +import Client from '../index.js' +import common from './common.js' +import fixtures from 'webtorrent-fixtures' +import test from 'tape' -var peerId = Buffer.from('01234567890123456789') +const peerId = Buffer.from('01234567890123456789') function testLargeTorrent (t, serverType) { t.plan(9) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.sintel.parsedTorrent.infoHash, - peerId: peerId, + peerId, port: 6881, announce: announceUrl, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.update() - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.stop() - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') @@ -50,14 +50,14 @@ function testLargeTorrent (t, serverType) { }) } -test('http: large torrent: client.start()', function (t) { +test('http: large torrent: client.start()', t => { testLargeTorrent(t, 'http') }) -test('udp: large torrent: client.start()', function (t) { +test('udp: large torrent: client.start()', t => { testLargeTorrent(t, 'udp') }) -test('ws: large torrent: client.start()', function (t) { +test('ws: large torrent: client.start()', t => { testLargeTorrent(t, 'ws') }) diff --git a/test/client-magnet.js b/test/client-magnet.js index bb768eab8..a6268b3fc 100644 --- a/test/client-magnet.js +++ b/test/client-magnet.js @@ -1,44 +1,44 @@ -var Client = require('../') -var common = require('./common') -var fixtures = require('webtorrent-fixtures') -var magnet = require('magnet-uri') -var test = require('tape') +import Client from '../index.js' +import common from './common.js' +import fixtures from 'webtorrent-fixtures' +import magnet from 'magnet-uri' +import test from 'tape' -var peerId = Buffer.from('01234567890123456789') +const peerId = Buffer.from('01234567890123456789') function testMagnet (t, serverType) { t.plan(9) - var parsedTorrent = magnet(fixtures.leaves.magnetURI) + const parsedTorrent = magnet(fixtures.leaves.magnetURI) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.update() - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.stop() - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') @@ -53,14 +53,14 @@ function testMagnet (t, serverType) { }) } -test('http: magnet: client.start/update/stop()', function (t) { +test('http: magnet: client.start/update/stop()', t => { testMagnet(t, 'http') }) -test('udp: magnet: client.start/update/stop()', function (t) { +test('udp: magnet: client.start/update/stop()', t => { testMagnet(t, 'udp') }) -test('ws: magnet: client.start/update/stop()', function (t) { +test('ws: magnet: client.start/update/stop()', t => { testMagnet(t, 'ws') }) diff --git a/test/client-ws-socket-pool.js b/test/client-ws-socket-pool.js index a87619f93..6d3fe1a3f 100644 --- a/test/client-ws-socket-pool.js +++ b/test/client-ws-socket-pool.js @@ -1,51 +1,51 @@ -var Client = require('../') -var common = require('./common') -var fixtures = require('webtorrent-fixtures') -var test = require('tape') +import Client from '../index.js' +import common from './common.js' +import fixtures from 'webtorrent-fixtures' +import test from 'tape' -var peerId = Buffer.from('01234567890123456789') -var port = 6681 +const peerId = Buffer.from('01234567890123456789') +const port = 6681 -test('ensure client.destroy() callback is called with re-used websockets in socketPool', function (t) { +test('ensure client.destroy() callback is called with re-used websockets in socketPool', t => { t.plan(4) - common.createServer(t, 'ws', function (server, announceUrl) { - var client1 = new Client({ + common.createServer(t, 'ws', (server, announceUrl) => { + const client1 = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, - port: port, + peerId, + port, wrtc: {} }) common.mockWebsocketTracker(client1) - client1.on('error', function (err) { t.error(err) }) - client1.on('warning', function (err) { t.error(err) }) + client1.on('error', err => { t.error(err) }) + client1.on('warning', err => { t.error(err) }) client1.start() - client1.once('update', function () { + client1.once('update', () => { t.pass('got client1 update') // second ws client using same announce url will re-use the same websocket - var client2 = new Client({ + const client2 = new Client({ infoHash: fixtures.alice.parsedTorrent.infoHash, // different info hash announce: announceUrl, - peerId: peerId, - port: port, + peerId, + port, wrtc: {} }) common.mockWebsocketTracker(client2) - client2.on('error', function (err) { t.error(err) }) - client2.on('warning', function (err) { t.error(err) }) + client2.on('error', err => { t.error(err) }) + client2.on('warning', err => { t.error(err) }) client2.start() - client2.once('update', function () { + client2.once('update', () => { t.pass('got client2 update') - client1.destroy(function (err) { + client1.destroy(err => { t.error(err, 'got client1 destroy callback') - client2.destroy(function (err) { + client2.destroy(err => { t.error(err, 'got client2 destroy callback') server.close() }) diff --git a/test/client.js b/test/client.js index e622c1abf..d81ee55f0 100644 --- a/test/client.js +++ b/test/client.js @@ -1,37 +1,40 @@ -var Client = require('../') -var common = require('./common') -var fixtures = require('webtorrent-fixtures') -var test = require('tape') - -var peerId1 = Buffer.from('01234567890123456789') -var peerId2 = Buffer.from('12345678901234567890') -var peerId3 = Buffer.from('23456789012345678901') -var port = 6881 +import Client from '../index.js' +import common from './common.js' +import http from 'http' +import fixtures from 'webtorrent-fixtures' +import net from 'net' +import test from 'tape' +import undici from 'undici' + +const peerId1 = Buffer.from('01234567890123456789') +const peerId2 = Buffer.from('12345678901234567890') +const peerId3 = Buffer.from('23456789012345678901') +const port = 6881 function testClientStart (t, serverType) { t.plan(4) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.stop() - client.once('update', function () { + client.once('update', () => { t.pass('got response to stop') server.close() client.destroy() @@ -42,42 +45,42 @@ function testClientStart (t, serverType) { }) } -test('http: client.start()', function (t) { +test('http: client.start()', t => { testClientStart(t, 'http') }) -test('udp: client.start()', function (t) { +test('udp: client.start()', t => { testClientStart(t, 'udp') }) -test('ws: client.start()', function (t) { +test('ws: client.start()', t => { testClientStart(t, 'ws') }) function testClientStop (t, serverType) { t.plan(4) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.start() - client.once('update', function () { + client.once('update', () => { t.pass('client received response to "start" message') client.stop() - client.once('update', function (data) { + client.once('update', data => { // receive one final update after calling stop t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') @@ -90,51 +93,51 @@ function testClientStop (t, serverType) { }) } -test('http: client.stop()', function (t) { +test('http: client.stop()', t => { testClientStop(t, 'http') }) -test('udp: client.stop()', function (t) { +test('udp: client.stop()', t => { testClientStop(t, 'udp') }) -test('ws: client.stop()', function (t) { +test('ws: client.stop()', t => { testClientStop(t, 'ws') }) function testClientStopDestroy (t, serverType) { t.plan(2) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.start() - client.once('update', function () { + client.once('update', () => { t.pass('client received response to "start" message') client.stop() - client.on('update', function () { t.fail('client should not receive update after destroy is called') }) + client.on('update', () => { t.fail('client should not receive update after destroy is called') }) // Call destroy() in the same tick as stop(), but the message should still // be received by the server, though obviously the client won't receive the // response. client.destroy() - server.once('stop', function (peer, params) { + server.once('stop', (peer, params) => { t.pass('server received "stop" message') - setTimeout(function () { + setTimeout(() => { // give the websocket server time to finish in progress (stream) messages // to peers server.close() @@ -144,50 +147,50 @@ function testClientStopDestroy (t, serverType) { }) } -test('http: client.stop(); client.destroy()', function (t) { +test('http: client.stop(); client.destroy()', t => { testClientStopDestroy(t, 'http') }) -test('udp: client.stop(); client.destroy()', function (t) { +test('udp: client.stop(); client.destroy()', t => { testClientStopDestroy(t, 'udp') }) -test('ws: client.stop(); client.destroy()', function (t) { +test('ws: client.stop(); client.destroy()', t => { testClientStopDestroy(t, 'ws') }) function testClientUpdate (t, serverType) { t.plan(4) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.setInterval(500) client.start() - client.once('update', function () { + client.once('update', () => { client.setInterval(500) // after interval, we should get another update - client.once('update', function (data) { + client.once('update', data => { // received an update! t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.stop() - client.once('update', function () { + client.once('update', () => { t.pass('got response to stop') server.close() client.destroy() @@ -197,35 +200,35 @@ function testClientUpdate (t, serverType) { }) } -test('http: client.update()', function (t) { +test('http: client.update()', t => { testClientUpdate(t, 'http') }) -test('udp: client.update()', function (t) { +test('udp: client.update()', t => { testClientUpdate(t, 'udp') }) -test('ws: client.update()', function (t) { +test('ws: client.update()', t => { testClientUpdate(t, 'ws') }) function testClientScrape (t, serverType) { t.plan(4) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('scrape', function (data) { + client.once('scrape', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') @@ -239,46 +242,46 @@ function testClientScrape (t, serverType) { }) } -test('http: client.scrape()', function (t) { +test('http: client.scrape()', t => { testClientScrape(t, 'http') }) -test('udp: client.scrape()', function (t) { +test('udp: client.scrape()', t => { testClientScrape(t, 'udp') }) -test('ws: client.scrape()', function (t) { +test('ws: client.scrape()', t => { testClientScrape(t, 'ws') }) function testClientAnnounceWithParams (t, serverType) { t.plan(5) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) - server.on('start', function (peer, params) { + server.on('start', (peer, params) => { t.equal(params.testParam, 'this is a test') }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.stop() - client.once('update', function () { + client.once('update', () => { t.pass('got response to stop') server.close() client.destroy() @@ -291,24 +294,24 @@ function testClientAnnounceWithParams (t, serverType) { }) } -test('http: client.announce() with params', function (t) { +test('http: client.announce() with params', t => { testClientAnnounceWithParams(t, 'http') }) -test('ws: client.announce() with params', function (t) { +test('ws: client.announce() with params', t => { testClientAnnounceWithParams(t, 'ws') }) function testClientGetAnnounceOpts (t, serverType) { t.plan(5) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, - getAnnounceOpts: function () { + port, + getAnnounceOpts () { return { testParam: 'this is a test' } @@ -316,22 +319,22 @@ function testClientGetAnnounceOpts (t, serverType) { wrtc: {} }) - server.on('start', function (peer, params) { + server.on('start', (peer, params) => { t.equal(params.testParam, 'this is a test') }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') client.stop() - client.once('update', function () { + client.once('update', () => { t.pass('got response to stop') server.close() client.destroy() @@ -342,33 +345,33 @@ function testClientGetAnnounceOpts (t, serverType) { }) } -test('http: client `opts.getAnnounceOpts`', function (t) { +test('http: client `opts.getAnnounceOpts`', t => { testClientGetAnnounceOpts(t, 'http') }) -test('ws: client `opts.getAnnounceOpts`', function (t) { +test('ws: client `opts.getAnnounceOpts`', t => { testClientGetAnnounceOpts(t, 'ws') }) function testClientAnnounceWithNumWant (t, serverType) { t.plan(4) - common.createServer(t, serverType, function (server, announceUrl) { - var client1 = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client1 = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: [announceUrl], peerId: peerId1, - port: port, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client1) - client1.on('error', function (err) { t.error(err) }) - client1.on('warning', function (err) { t.error(err) }) + client1.on('error', err => { t.error(err) }) + client1.on('warning', err => { t.error(err) }) client1.start() - client1.once('update', function () { - var client2 = new Client({ + client1.once('update', () => { + const client2 = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId2, @@ -377,12 +380,12 @@ function testClientAnnounceWithNumWant (t, serverType) { }) if (serverType === 'ws') common.mockWebsocketTracker(client2) - client2.on('error', function (err) { t.error(err) }) - client2.on('warning', function (err) { t.error(err) }) + client2.on('error', err => { t.error(err) }) + client2.on('warning', err => { t.error(err) }) client2.start() - client2.once('update', function () { - var client3 = new Client({ + client2.once('update', () => { + const client3 = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId3, @@ -391,33 +394,33 @@ function testClientAnnounceWithNumWant (t, serverType) { }) if (serverType === 'ws') common.mockWebsocketTracker(client3) - client3.on('error', function (err) { t.error(err) }) - client3.on('warning', function (err) { t.error(err) }) + client3.on('error', err => { t.error(err) }) + client3.on('warning', err => { t.error(err) }) client3.start({ numwant: 1 }) - client3.on('peer', function () { + client3.on('peer', () => { t.pass('got one peer (this should only fire once)') - var num = 3 + let num = 3 function tryCloseServer () { num -= 1 if (num === 0) server.close() } client1.stop() - client1.once('update', function () { + client1.once('update', () => { t.pass('got response to stop (client1)') client1.destroy() tryCloseServer() }) client2.stop() - client2.once('update', function () { + client2.once('update', () => { t.pass('got response to stop (client2)') client2.destroy() tryCloseServer() }) client3.stop() - client3.once('update', function () { + client3.once('update', () => { t.pass('got response to stop (client3)') client3.destroy() tryCloseServer() @@ -428,36 +431,36 @@ function testClientAnnounceWithNumWant (t, serverType) { }) } -test('http: client announce with numwant', function (t) { +test('http: client announce with numwant', t => { testClientAnnounceWithNumWant(t, 'http') }) -test('udp: client announce with numwant', function (t) { +test('udp: client announce with numwant', t => { testClientAnnounceWithNumWant(t, 'udp') }) -test('http: userAgent', function (t) { +test('http: userAgent', t => { t.plan(2) - common.createServer(t, 'http', function (server, announceUrl) { + common.createServer(t, 'http', (server, announceUrl) => { // Confirm that user-agent header is set - server.http.on('request', function (req, res) { - t.ok(req.headers['user-agent'].indexOf('WebTorrent') !== -1) + server.http.on('request', (req, res) => { + t.ok(req.headers['user-agent'].includes('WebTorrent')) }) - var client = new Client({ + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, userAgent: 'WebTorrent/0.98.0 (https://webtorrent.io)', wrtc: {} }) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('update', function (data) { + client.once('update', data => { t.equal(data.announce, announceUrl) server.close() @@ -471,22 +474,22 @@ test('http: userAgent', function (t) { function testSupportedTracker (t, serverType) { t.plan(1) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.start() - client.once('update', function (data) { + client.once('update', data => { t.pass('tracker is valid') server.close() @@ -495,49 +498,143 @@ function testSupportedTracker (t, serverType) { }) } -test('http: valid tracker port', function (t) { +test('http: valid tracker port', t => { testSupportedTracker(t, 'http') }) -test('udp: valid tracker port', function (t) { +test('udp: valid tracker port', t => { testSupportedTracker(t, 'udp') }) -test('ws: valid tracker port', function (t) { +test('ws: valid tracker port', t => { testSupportedTracker(t, 'ws') }) function testUnsupportedTracker (t, announceUrl) { t.plan(1) - var client = new Client({ + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: peerId1, - port: port, + port, wrtc: {} }) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.ok(err.message.includes('tracker'), 'got warning') client.destroy() }) } -test('unsupported tracker protocol', function (t) { +test('unsupported tracker protocol', t => { testUnsupportedTracker(t, 'badprotocol://127.0.0.1:8080/announce') }) -test('http: invalid tracker port', function (t) { +test('http: invalid tracker port', t => { testUnsupportedTracker(t, 'http://127.0.0.1:69691337/announce') }) -test('udp: invalid tracker port', function (t) { +test('http: invalid tracker url', t => { + testUnsupportedTracker(t, 'http:') +}) + +test('http: invalid tracker url with slash', t => { + testUnsupportedTracker(t, 'http://') +}) + +test('udp: invalid tracker port', t => { testUnsupportedTracker(t, 'udp://127.0.0.1:69691337') }) -test('ws: invalid tracker port', function (t) { +test('udp: invalid tracker url', t => { + testUnsupportedTracker(t, 'udp:') +}) + +test('udp: invalid tracker url with slash', t => { + testUnsupportedTracker(t, 'udp://') +}) + +test('ws: invalid tracker port', t => { testUnsupportedTracker(t, 'ws://127.0.0.1:69691337') }) + +test('ws: invalid tracker url', t => { + testUnsupportedTracker(t, 'ws:') +}) + +test('ws: invalid tracker url with slash', t => { + testUnsupportedTracker(t, 'ws://') +}) + +function testClientStartHttpAgent (t, serverType) { + t.plan(5) + + common.createServer(t, serverType, function (server, announceUrl) { + let agent + if (global.fetch && serverType !== 'ws') { + const connector = undici.buildConnector({ rejectUnauthorized: false }) + agent = new undici.Agent({ + connect (opts, cb) { + agentUsed = true + connector(opts, (err, socket) => { + if (err) { + cb(err, null) + } else { + cb(null, socket) + } + }) + } + }) + } else { + agent = new http.Agent() + agent.createConnection = function (opts, fn) { + agentUsed = true + return net.createConnection(opts, fn) + } + } + let agentUsed = false + const client = new Client({ + infoHash: fixtures.leaves.parsedTorrent.infoHash, + announce: announceUrl, + peerId: peerId1, + port, + wrtc: {}, + proxyOpts: { + httpAgent: agent + } + }) + + if (serverType === 'ws') common.mockWebsocketTracker(client) + client.on('error', function (err) { t.error(err) }) + client.on('warning', function (err) { t.error(err) }) + + client.once('update', function (data) { + t.equal(data.announce, announceUrl) + t.equal(typeof data.complete, 'number') + t.equal(typeof data.incomplete, 'number') + + t.ok(agentUsed) + + client.stop() + + client.once('update', function () { + t.pass('got response to stop') + server.close() + client.destroy() + }) + }) + + client.start() + }) +} + +test('http: client.start(httpAgent)', function (t) { + testClientStartHttpAgent(t, 'http') +}) + +test('ws: client.start(httpAgent)', function (t) { + testClientStartHttpAgent(t, 'ws') +}) diff --git a/test/common.js b/test/common.js index 0f872e397..5ff6ce39f 100644 --- a/test/common.js +++ b/test/common.js @@ -1,40 +1,42 @@ -var Server = require('../').Server +import { Server } from '../index.js' -exports.createServer = function (t, opts, cb) { +export const createServer = (t, opts, cb) => { if (typeof opts === 'string') opts = { serverType: opts } opts.http = (opts.serverType === 'http') opts.udp = (opts.serverType === 'udp') opts.ws = (opts.serverType === 'ws') - var server = new Server(opts) + const server = new Server(opts) - server.on('error', function (err) { t.error(err) }) - server.on('warning', function (err) { t.error(err) }) + server.on('error', err => { t.error(err) }) + server.on('warning', err => { t.error(err) }) - server.listen(0, function () { - var port = server[opts.serverType].address().port - var announceUrl + server.listen(0, () => { + const port = server[opts.serverType].address().port + let announceUrl if (opts.serverType === 'http') { - announceUrl = 'http://127.0.0.1:' + port + '/announce' + announceUrl = `http://127.0.0.1:${port}/announce` } else if (opts.serverType === 'udp') { - announceUrl = 'udp://127.0.0.1:' + port + announceUrl = `udp://127.0.0.1:${port}` } else if (opts.serverType === 'ws') { - announceUrl = 'ws://127.0.0.1:' + port + announceUrl = `ws://127.0.0.1:${port}` } cb(server, announceUrl) }) } -exports.mockWebsocketTracker = function (client) { - client._trackers[0]._generateOffers = function (numwant, cb) { - var offers = [] - for (var i = 0; i < numwant; i++) { - offers.push({ fake_offer: 'fake_offer_' + i }) +export const mockWebsocketTracker = client => { + client._trackers[0]._generateOffers = (numwant, cb) => { + const offers = [] + for (let i = 0; i < numwant; i++) { + offers.push({ fake_offer: `fake_offer_${i}` }) } - process.nextTick(function () { + process.nextTick(() => { cb(offers) }) } } + +export default { mockWebsocketTracker, createServer } diff --git a/test/destroy.js b/test/destroy.js index 5437b8ac2..e9d7af3a4 100644 --- a/test/destroy.js +++ b/test/destroy.js @@ -1,28 +1,28 @@ -var Client = require('../') -var common = require('./common') -var fixtures = require('webtorrent-fixtures') -var test = require('tape') +import Client from '../index.js' +import common from './common.js' +import fixtures from 'webtorrent-fixtures' +import test from 'tape' -var peerId = Buffer.from('01234567890123456789') -var port = 6881 +const peerId = Buffer.from('01234567890123456789') +const port = 6881 function testNoEventsAfterDestroy (t, serverType) { t.plan(1) - common.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, - port: port, + peerId, + port, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) - client.once('update', function () { + client.once('update', () => { t.fail('no "update" event should fire, since client is destroyed') }) @@ -30,21 +30,21 @@ function testNoEventsAfterDestroy (t, serverType) { client.update() client.destroy() - setTimeout(function () { + setTimeout(() => { t.pass('wait to see if any events are fired') server.close() }, 1000) }) } -test('http: no "update" events after destroy()', function (t) { +test('http: no "update" events after destroy()', t => { testNoEventsAfterDestroy(t, 'http') }) -test('udp: no "update" events after destroy()', function (t) { +test('udp: no "update" events after destroy()', t => { testNoEventsAfterDestroy(t, 'udp') }) -test('ws: no "update" events after destroy()', function (t) { +test('ws: no "update" events after destroy()', t => { testNoEventsAfterDestroy(t, 'ws') }) diff --git a/test/evict.js b/test/evict.js index 3cab3a767..385fae2a6 100644 --- a/test/evict.js +++ b/test/evict.js @@ -1,96 +1,96 @@ -var Client = require('../') -var common = require('./common') -var test = require('tape') -var wrtc = require('wrtc') +import Client from '../index.js' +import common from './common.js' +import test from 'tape' +import wrtc from 'webrtc-polyfill' -var infoHash = '4cb67059ed6bd08362da625b3ae77f6f4a075705' -var peerId = Buffer.from('01234567890123456789') -var peerId2 = Buffer.from('12345678901234567890') -var peerId3 = Buffer.from('23456789012345678901') +const infoHash = '4cb67059ed6bd08362da625b3ae77f6f4a075705' +const peerId = Buffer.from('01234567890123456789') +const peerId2 = Buffer.from('12345678901234567890') +const peerId3 = Buffer.from('23456789012345678901') function serverTest (t, serverType, serverFamily) { t.plan(10) - var hostname = serverFamily === 'inet6' + const hostname = serverFamily === 'inet6' ? '[::1]' : '127.0.0.1' - var opts = { - serverType: serverType, + const opts = { + serverType, peersCacheLength: 2 // LRU cache can only contain a max of 2 peers } - common.createServer(t, opts, function (server) { + common.createServer(t, opts, server => { // Not using announceUrl param from `common.createServer()` since we // want to control IPv4 vs IPv6. - var port = server[serverType].address().port - var announceUrl = serverType + '://' + hostname + ':' + port + '/announce' + const port = server[serverType].address().port + const announceUrl = `${serverType}://${hostname}:${port}/announce` - var client1 = new Client({ - infoHash: infoHash, + const client1 = new Client({ + infoHash, announce: [announceUrl], - peerId: peerId, + peerId, port: 6881, - wrtc: wrtc + wrtc }) if (serverType === 'ws') common.mockWebsocketTracker(client1) client1.start() - client1.once('update', function (data) { - var client2 = new Client({ - infoHash: infoHash, + client1.once('update', data => { + const client2 = new Client({ + infoHash, announce: [announceUrl], peerId: peerId2, port: 6882, - wrtc: wrtc + wrtc }) if (serverType === 'ws') common.mockWebsocketTracker(client2) client2.start() - client2.once('update', function (data) { - server.getSwarm(infoHash, function (err, swarm) { + client2.once('update', data => { + server.getSwarm(infoHash, (err, swarm) => { t.error(err) t.equal(swarm.complete + swarm.incomplete, 2) // Ensure that first peer is evicted when a third one is added - var evicted = false - swarm.peers.once('evict', function (evictedPeer) { + let evicted = false + swarm.peers.once('evict', evictedPeer => { t.equal(evictedPeer.value.peerId, peerId.toString('hex')) t.equal(swarm.complete + swarm.incomplete, 2) evicted = true }) - var client3 = new Client({ - infoHash: infoHash, + const client3 = new Client({ + infoHash, announce: [announceUrl], peerId: peerId3, port: 6880, - wrtc: wrtc + wrtc }) if (serverType === 'ws') common.mockWebsocketTracker(client3) client3.start() - client3.once('update', function (data) { + client3.once('update', data => { t.ok(evicted, 'client1 was evicted from server before client3 gets response') t.equal(swarm.complete + swarm.incomplete, 2) - client1.destroy(function () { + client1.destroy(() => { t.pass('client1 destroyed') }) - client2.destroy(function () { + client2.destroy(() => { t.pass('client3 destroyed') }) - client3.destroy(function () { + client3.destroy(() => { t.pass('client3 destroyed') }) - server.close(function () { + server.close(() => { t.pass('server destroyed') }) }) @@ -100,18 +100,18 @@ function serverTest (t, serverType, serverFamily) { }) } -test('evict: ipv4 server', function (t) { +test('evict: ipv4 server', t => { serverTest(t, 'http', 'inet') }) -test('evict: http ipv6 server', function (t) { +test('evict: http ipv6 server', t => { serverTest(t, 'http', 'inet6') }) -test('evict: udp server', function (t) { +test('evict: udp server', t => { serverTest(t, 'udp', 'inet') }) -test('evict: ws server', function (t) { +test('evict: ws server', t => { serverTest(t, 'ws', 'inet') }) diff --git a/test/filter.js b/test/filter.js index 395e23363..4dc3da02d 100644 --- a/test/filter.js +++ b/test/filter.js @@ -1,16 +1,16 @@ -var Client = require('../') -var common = require('./common') -var fixtures = require('webtorrent-fixtures') -var test = require('tape') +import Client from '../index.js' +import common from './common.js' +import fixtures from 'webtorrent-fixtures' +import test from 'tape' -var peerId = Buffer.from('01234567890123456789') +const peerId = Buffer.from('01234567890123456789') function testFilterOption (t, serverType) { t.plan(8) - var opts = { serverType: serverType } // this is test-suite-only option - opts.filter = function (infoHash, params, cb) { - process.nextTick(function () { + const opts = { serverType } // this is test-suite-only option + opts.filter = (infoHash, params, cb) => { + process.nextTick(() => { if (infoHash === fixtures.alice.parsedTorrent.infoHash) { cb(new Error('disallowed info_hash (Alice)')) } else { @@ -19,43 +19,43 @@ function testFilterOption (t, serverType) { }) } - common.createServer(t, opts, function (server, announceUrl) { - var client1 = new Client({ + common.createServer(t, opts, (server, announceUrl) => { + const client1 = new Client({ infoHash: fixtures.alice.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881, wrtc: {} }) - client1.on('error', function (err) { t.error(err) }) + client1.on('error', err => { t.error(err) }) if (serverType === 'ws') common.mockWebsocketTracker(client1) - client1.once('warning', function (err) { + client1.once('warning', err => { t.ok(err.message.includes('disallowed info_hash (Alice)'), 'got client warning') - client1.destroy(function () { + client1.destroy(() => { t.pass('client1 destroyed') - var client2 = new Client({ + const client2 = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client2) - client2.on('error', function (err) { t.error(err) }) - client2.on('warning', function (err) { t.error(err) }) + client2.on('error', err => { t.error(err) }) + client2.on('warning', err => { t.error(err) }) - client2.on('update', function () { + client2.on('update', () => { t.pass('got announce') - client2.destroy(function () { t.pass('client2 destroyed') }) - server.close(function () { t.pass('server closed') }) + client2.destroy(() => { t.pass('client2 destroyed') }) + server.close(() => { t.pass('server closed') }) }) - server.on('start', function () { + server.on('start', () => { t.equal(Object.keys(server.torrents).length, 1) }) @@ -64,7 +64,7 @@ function testFilterOption (t, serverType) { }) server.removeAllListeners('warning') - server.once('warning', function (err) { + server.once('warning', err => { t.ok(err.message.includes('disallowed info_hash (Alice)'), 'got server warning') t.equal(Object.keys(server.torrents).length, 0) }) @@ -73,24 +73,24 @@ function testFilterOption (t, serverType) { }) } -test('http: filter option blocks tracker from tracking torrent', function (t) { +test('http: filter option blocks tracker from tracking torrent', t => { testFilterOption(t, 'http') }) -test('udp: filter option blocks tracker from tracking torrent', function (t) { +test('udp: filter option blocks tracker from tracking torrent', t => { testFilterOption(t, 'udp') }) -test('ws: filter option blocks tracker from tracking torrent', function (t) { +test('ws: filter option blocks tracker from tracking torrent', t => { testFilterOption(t, 'ws') }) function testFilterCustomError (t, serverType) { t.plan(8) - var opts = { serverType: serverType } // this is test-suite-only option - opts.filter = function (infoHash, params, cb) { - process.nextTick(function () { + const opts = { serverType } // this is test-suite-only option + opts.filter = (infoHash, params, cb) => { + process.nextTick(() => { if (infoHash === fixtures.alice.parsedTorrent.infoHash) { cb(new Error('alice blocked')) } else { @@ -99,42 +99,42 @@ function testFilterCustomError (t, serverType) { }) } - common.createServer(t, opts, function (server, announceUrl) { - var client1 = new Client({ + common.createServer(t, opts, (server, announceUrl) => { + const client1 = new Client({ infoHash: fixtures.alice.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881, wrtc: {} }) - client1.on('error', function (err) { t.error(err) }) + client1.on('error', err => { t.error(err) }) if (serverType === 'ws') common.mockWebsocketTracker(client1) - client1.once('warning', function (err) { + client1.once('warning', err => { t.ok(/alice blocked/.test(err.message), 'got client warning') - client1.destroy(function () { + client1.destroy(() => { t.pass('client1 destroyed') - var client2 = new Client({ + const client2 = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client2) - client2.on('error', function (err) { t.error(err) }) - client2.on('warning', function (err) { t.error(err) }) + client2.on('error', err => { t.error(err) }) + client2.on('warning', err => { t.error(err) }) - client2.on('update', function () { + client2.on('update', () => { t.pass('got announce') - client2.destroy(function () { t.pass('client2 destroyed') }) - server.close(function () { t.pass('server closed') }) + client2.destroy(() => { t.pass('client2 destroyed') }) + server.close(() => { t.pass('server closed') }) }) - server.on('start', function () { + server.on('start', () => { t.equal(Object.keys(server.torrents).length, 1) }) @@ -143,7 +143,7 @@ function testFilterCustomError (t, serverType) { }) server.removeAllListeners('warning') - server.once('warning', function (err) { + server.once('warning', err => { t.ok(/alice blocked/.test(err.message), 'got server warning') t.equal(Object.keys(server.torrents).length, 0) }) @@ -152,14 +152,14 @@ function testFilterCustomError (t, serverType) { }) } -test('http: filter option with custom error', function (t) { +test('http: filter option with custom error', t => { testFilterCustomError(t, 'http') }) -test('udp: filter option filter option with custom error', function (t) { +test('udp: filter option filter option with custom error', t => { testFilterCustomError(t, 'udp') }) -test('ws: filter option filter option with custom error', function (t) { +test('ws: filter option filter option with custom error', t => { testFilterCustomError(t, 'ws') }) diff --git a/test/querystring.js b/test/querystring.js index a8d5bd2eb..85e14e544 100644 --- a/test/querystring.js +++ b/test/querystring.js @@ -1,12 +1,12 @@ -var common = require('../lib/common') -var test = require('tape') +import common from '../lib/common.js' +import test from 'tape' // https://github.com/webtorrent/webtorrent/issues/196 -test('encode special chars +* in http tracker urls', function (t) { - var q = Object.create(null) +test('encode special chars +* in http tracker urls', t => { + const q = Object.create(null) q.info_hash = Buffer.from('a2a15537542b22925ad10486bf7a8b2a9c42f0d1', 'hex').toString('binary') - var encoded = 'info_hash=%A2%A1U7T%2B%22%92Z%D1%04%86%BFz%8B%2A%9CB%F0%D1' + const encoded = 'info_hash=%A2%A1U7T%2B%22%92Z%D1%04%86%BFz%8B%2A%9CB%F0%D1' t.equal(common.querystringStringify(q), encoded) // sanity check that encode-decode matches up diff --git a/test/request-handler.js b/test/request-handler.js index d825a31e8..6532cbbb8 100644 --- a/test/request-handler.js +++ b/test/request-handler.js @@ -1,59 +1,59 @@ -var Client = require('../') -var common = require('./common') -var fixtures = require('webtorrent-fixtures') -var test = require('tape') -var Server = require('../server') +import Client from '../index.js' +import common from './common.js' +import fixtures from 'webtorrent-fixtures' +import test from 'tape' +import Server from '../server.js' -var peerId = Buffer.from('01234567890123456789') +const peerId = Buffer.from('01234567890123456789') function testRequestHandler (t, serverType) { t.plan(5) - var opts = { serverType: serverType } // this is test-suite-only option + const opts = { serverType } // this is test-suite-only option class Swarm extends Server.Swarm { announce (params, cb) { - super.announce(params, function (err, response) { - if (err) return cb(response) + super.announce(params, (err, response) => { + if (cb && err) return cb(response) response.complete = 246 response.extraData = 'hi' - cb(null, response) + if (cb) cb(null, response) }) } } // Use a custom Swarm implementation for this test only - var OldSwarm = Server.Swarm + const OldSwarm = Server.Swarm Server.Swarm = Swarm - t.on('end', function () { + t.on('end', () => { Server.Swarm = OldSwarm }) - common.createServer(t, opts, function (server, announceUrl) { - var client1 = new Client({ + common.createServer(t, opts, (server, announceUrl) => { + const client1 = new Client({ infoHash: fixtures.alice.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881, wrtc: {} }) - client1.on('error', function (err) { t.error(err) }) + client1.on('error', err => { t.error(err) }) if (serverType === 'ws') common.mockWebsocketTracker(client1) - server.once('start', function () { + server.once('start', () => { t.pass('got start message from client1') }) - client1.once('update', function (data) { + client1.once('update', data => { t.equal(data.complete, 246) - t.equal(data.extraData.toString(), 'hi') + t.equal(Buffer.from(data.extraData).toString(), 'hi') - client1.destroy(function () { + client1.destroy(() => { t.pass('client1 destroyed') }) - server.close(function () { + server.close(() => { t.pass('server destroyed') }) }) @@ -62,11 +62,11 @@ function testRequestHandler (t, serverType) { }) } -test('http: request handler option intercepts announce requests and responses', function (t) { +test('http: request handler option intercepts announce requests and responses', t => { testRequestHandler(t, 'http') }) -test('ws: request handler option intercepts announce requests and responses', function (t) { +test('ws: request handler option intercepts announce requests and responses', t => { testRequestHandler(t, 'ws') }) diff --git a/test/scrape.js b/test/scrape.js index b372ec2df..c0c101a5f 100644 --- a/test/scrape.js +++ b/test/scrape.js @@ -1,70 +1,70 @@ -var bencode = require('bencode') -var Client = require('../') -var common = require('./common') -var commonLib = require('../lib/common') -var commonTest = require('./common') -var fixtures = require('webtorrent-fixtures') -var get = require('simple-get') -var test = require('tape') +import bencode from 'bencode' +import Client from '../index.js' +import common from './common.js' +import commonLib from '../lib/common.js' +import fixtures from 'webtorrent-fixtures' +import fetch from 'cross-fetch-ponyfill' +import test from 'tape' +import { hex2bin } from 'uint8-util' -var peerId = Buffer.from('01234567890123456789') +const peerId = Buffer.from('01234567890123456789') function testSingle (t, serverType) { - commonTest.createServer(t, serverType, function (server, announceUrl) { - var client = new Client({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881, wrtc: {} }) if (serverType === 'ws') common.mockWebsocketTracker(client) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.scrape() - client.on('scrape', function (data) { + client.on('scrape', data => { t.equal(data.announce, announceUrl) t.equal(data.infoHash, fixtures.leaves.parsedTorrent.infoHash) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') t.equal(typeof data.downloaded, 'number') client.destroy() - server.close(function () { + server.close(() => { t.end() }) }) }) } -test('http: single info_hash scrape', function (t) { +test('http: single info_hash scrape', t => { testSingle(t, 'http') }) -test('udp: single info_hash scrape', function (t) { +test('udp: single info_hash scrape', t => { testSingle(t, 'udp') }) -test('ws: single info_hash scrape', function (t) { +test('ws: single info_hash scrape', t => { testSingle(t, 'ws') }) function clientScrapeStatic (t, serverType) { - commonTest.createServer(t, serverType, function (server, announceUrl) { - var client = Client.scrape({ + common.createServer(t, serverType, (server, announceUrl) => { + const client = Client.scrape({ announce: announceUrl, infoHash: fixtures.leaves.parsedTorrent.infoHash, wrtc: {} - }, function (err, data) { + }, (err, data) => { t.error(err) t.equal(data.announce, announceUrl) t.equal(data.infoHash, fixtures.leaves.parsedTorrent.infoHash) t.equal(typeof data.complete, 'number') t.equal(typeof data.incomplete, 'number') t.equal(typeof data.downloaded, 'number') - server.close(function () { + server.close(() => { t.end() }) }) @@ -72,55 +72,55 @@ function clientScrapeStatic (t, serverType) { }) } -test('http: scrape using Client.scrape static method', function (t) { +test('http: scrape using Client.scrape static method', t => { clientScrapeStatic(t, 'http') }) -test('udp: scrape using Client.scrape static method', function (t) { +test('udp: scrape using Client.scrape static method', t => { clientScrapeStatic(t, 'udp') }) -test('ws: scrape using Client.scrape static method', function (t) { +test('ws: scrape using Client.scrape static method', t => { clientScrapeStatic(t, 'ws') }) // Ensure the callback function gets called when an invalid url is passed function clientScrapeStaticInvalid (t, serverType) { - var announceUrl = serverType + '://invalid.lol' + let announceUrl = `${serverType}://invalid.lol` if (serverType === 'http') announceUrl += '/announce' - var client = Client.scrape({ + const client = Client.scrape({ announce: announceUrl, infoHash: fixtures.leaves.parsedTorrent.infoHash, wrtc: {} - }, function (err, data) { + }, (err, data) => { t.ok(err instanceof Error) t.end() }) if (serverType === 'ws') common.mockWebsocketTracker(client) } -test('http: scrape using Client.scrape static method (invalid url)', function (t) { +test('http: scrape using Client.scrape static method (invalid url)', t => { clientScrapeStaticInvalid(t, 'http') }) -test('udp: scrape using Client.scrape static method (invalid url)', function (t) { +test('udp: scrape using Client.scrape static method (invalid url)', t => { clientScrapeStaticInvalid(t, 'udp') }) -test('ws: scrape using Client.scrape static method (invalid url)', function (t) { +test('ws: scrape using Client.scrape static method (invalid url)', t => { clientScrapeStaticInvalid(t, 'ws') }) function clientScrapeMulti (t, serverType) { - var infoHash1 = fixtures.leaves.parsedTorrent.infoHash - var infoHash2 = fixtures.alice.parsedTorrent.infoHash + const infoHash1 = fixtures.leaves.parsedTorrent.infoHash + const infoHash2 = fixtures.alice.parsedTorrent.infoHash - commonTest.createServer(t, serverType, function (server, announceUrl) { + common.createServer(t, serverType, (server, announceUrl) => { Client.scrape({ infoHash: [infoHash1, infoHash2], announce: announceUrl - }, function (err, results) { + }, (err, results) => { t.error(err) t.equal(results[infoHash1].announce, announceUrl) @@ -135,96 +135,103 @@ function clientScrapeMulti (t, serverType) { t.equal(typeof results[infoHash2].incomplete, 'number') t.equal(typeof results[infoHash2].downloaded, 'number') - server.close(function () { + server.close(() => { t.end() }) }) }) } -test('http: MULTI scrape using Client.scrape static method', function (t) { +test('http: MULTI scrape using Client.scrape static method', t => { clientScrapeMulti(t, 'http') }) -test('udp: MULTI scrape using Client.scrape static method', function (t) { +test('udp: MULTI scrape using Client.scrape static method', t => { clientScrapeMulti(t, 'udp') }) -test('server: multiple info_hash scrape (manual http request)', function (t) { - t.plan(13) +test('server: multiple info_hash scrape (manual http request)', t => { + t.plan(12) - var binaryInfoHash1 = commonLib.hexToBinary(fixtures.leaves.parsedTorrent.infoHash) - var binaryInfoHash2 = commonLib.hexToBinary(fixtures.alice.parsedTorrent.infoHash) + const binaryInfoHash1 = hex2bin(fixtures.leaves.parsedTorrent.infoHash) + const binaryInfoHash2 = hex2bin(fixtures.alice.parsedTorrent.infoHash) - commonTest.createServer(t, 'http', function (server, announceUrl) { - var scrapeUrl = announceUrl.replace('/announce', '/scrape') + common.createServer(t, 'http', async (server, announceUrl) => { + const scrapeUrl = announceUrl.replace('/announce', '/scrape') - var url = scrapeUrl + '?' + commonLib.querystringStringify({ - info_hash: [binaryInfoHash1, binaryInfoHash2] - }) - - get.concat(url, function (err, res, data) { + const url = `${scrapeUrl}?${commonLib.querystringStringify({ + info_hash: [binaryInfoHash1, binaryInfoHash2] +})}` + let res + try { + res = await fetch(url) + } catch (err) { t.error(err) + } + let data = Buffer.from(await res.arrayBuffer()) - t.equal(res.statusCode, 200) + t.equal(res.status, 200) - data = bencode.decode(data) - t.ok(data.files) - t.equal(Object.keys(data.files).length, 2) + data = bencode.decode(data) + t.ok(data.files) + t.equal(Object.keys(data.files).length, 2) - t.ok(data.files[binaryInfoHash1]) - t.equal(typeof data.files[binaryInfoHash1].complete, 'number') - t.equal(typeof data.files[binaryInfoHash1].incomplete, 'number') - t.equal(typeof data.files[binaryInfoHash1].downloaded, 'number') + t.ok(data.files[binaryInfoHash1]) + t.equal(typeof data.files[binaryInfoHash1].complete, 'number') + t.equal(typeof data.files[binaryInfoHash1].incomplete, 'number') + t.equal(typeof data.files[binaryInfoHash1].downloaded, 'number') - t.ok(data.files[binaryInfoHash2]) - t.equal(typeof data.files[binaryInfoHash2].complete, 'number') - t.equal(typeof data.files[binaryInfoHash2].incomplete, 'number') - t.equal(typeof data.files[binaryInfoHash2].downloaded, 'number') + t.ok(data.files[binaryInfoHash2]) + t.equal(typeof data.files[binaryInfoHash2].complete, 'number') + t.equal(typeof data.files[binaryInfoHash2].incomplete, 'number') + t.equal(typeof data.files[binaryInfoHash2].downloaded, 'number') - server.close(function () { t.pass('server closed') }) - }) + server.close(() => { t.pass('server closed') }) }) }) -test('server: all info_hash scrape (manual http request)', function (t) { - t.plan(10) +test('server: all info_hash scrape (manual http request)', t => { + t.plan(9) - var binaryInfoHash = commonLib.hexToBinary(fixtures.leaves.parsedTorrent.infoHash) + const binaryInfoHash = hex2bin(fixtures.leaves.parsedTorrent.infoHash) - commonTest.createServer(t, 'http', function (server, announceUrl) { - var scrapeUrl = announceUrl.replace('/announce', '/scrape') + common.createServer(t, 'http', (server, announceUrl) => { + const scrapeUrl = announceUrl.replace('/announce', '/scrape') // announce a torrent to the tracker - var client = new Client({ + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881 }) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.start() - server.once('start', function () { + server.once('start', async () => { // now do a scrape of everything by omitting the info_hash param - get.concat(scrapeUrl, function (err, res, data) { + let res + try { + res = await fetch(scrapeUrl) + } catch (err) { t.error(err) + } + let data = Buffer.from(await res.arrayBuffer()) - t.equal(res.statusCode, 200) - data = bencode.decode(data) - t.ok(data.files) - t.equal(Object.keys(data.files).length, 1) + t.equal(res.status, 200) + data = bencode.decode(data) + t.ok(data.files) + t.equal(Object.keys(data.files).length, 1) - t.ok(data.files[binaryInfoHash]) - t.equal(typeof data.files[binaryInfoHash].complete, 'number') - t.equal(typeof data.files[binaryInfoHash].incomplete, 'number') - t.equal(typeof data.files[binaryInfoHash].downloaded, 'number') + t.ok(data.files[binaryInfoHash]) + t.equal(typeof data.files[binaryInfoHash].complete, 'number') + t.equal(typeof data.files[binaryInfoHash].incomplete, 'number') + t.equal(typeof data.files[binaryInfoHash].downloaded, 'number') - client.destroy(function () { t.pass('client destroyed') }) - server.close(function () { t.pass('server closed') }) - }) + client.destroy(() => { t.pass('client destroyed') }) + server.close(() => { t.pass('server closed') }) }) }) }) diff --git a/test/server.js b/test/server.js index ae3d956ac..d4cf493e4 100644 --- a/test/server.js +++ b/test/server.js @@ -1,54 +1,54 @@ -var Client = require('../') -var common = require('./common') -var test = require('tape') -var wrtc = require('wrtc') +import Client from '../index.js' +import common from './common.js' +import test from 'tape' +import wrtc from 'webrtc-polyfill' -var infoHash = '4cb67059ed6bd08362da625b3ae77f6f4a075705' -var peerId = Buffer.from('01234567890123456789') -var peerId2 = Buffer.from('12345678901234567890') -var peerId3 = Buffer.from('23456789012345678901') +const infoHash = '4cb67059ed6bd08362da625b3ae77f6f4a075705' +const peerId = Buffer.from('01234567890123456789') +const peerId2 = Buffer.from('12345678901234567890') +const peerId3 = Buffer.from('23456789012345678901') function serverTest (t, serverType, serverFamily) { t.plan(40) - var hostname = serverFamily === 'inet6' + const hostname = serverFamily === 'inet6' ? '[::1]' : '127.0.0.1' - var clientIp = serverFamily === 'inet6' + const clientIp = serverFamily === 'inet6' ? '::1' : '127.0.0.1' - var opts = { - serverType: serverType + const opts = { + serverType } - common.createServer(t, opts, function (server) { + common.createServer(t, opts, server => { // Not using announceUrl param from `common.createServer()` since we // want to control IPv4 vs IPv6. - var port = server[serverType].address().port - var announceUrl = serverType + '://' + hostname + ':' + port + '/announce' + const port = server[serverType].address().port + const announceUrl = `${serverType}://${hostname}:${port}/announce` - var client1 = new Client({ - infoHash: infoHash, + const client1 = new Client({ + infoHash, announce: [announceUrl], - peerId: peerId, + peerId, port: 6881, - wrtc: wrtc + wrtc }) if (serverType === 'ws') common.mockWebsocketTracker(client1) client1.start() - server.once('start', function () { + server.once('start', () => { t.pass('got start message from client1') }) - client1.once('update', function (data) { + client1.once('update', data => { t.equal(data.announce, announceUrl) t.equal(data.complete, 0) t.equal(data.incomplete, 1) - server.getSwarm(infoHash, function (err, swarm) { + server.getSwarm(infoHash, (err, swarm) => { t.error(err) t.equal(Object.keys(server.torrents).length, 1) @@ -56,11 +56,11 @@ function serverTest (t, serverType, serverFamily) { t.equal(swarm.incomplete, 1) t.equal(swarm.peers.length, 1) - var id = serverType === 'ws' + const id = serverType === 'ws' ? peerId.toString('hex') - : hostname + ':6881' + : `${hostname}:6881` - var peer = swarm.peers.peek(id) + const peer = swarm.peers.peek(id) t.equal(peer.type, serverType) t.equal(peer.ip, clientIp) t.equal(peer.peerId, peerId.toString('hex')) @@ -75,82 +75,82 @@ function serverTest (t, serverType, serverFamily) { client1.complete() - client1.once('update', function (data) { + client1.once('update', data => { t.equal(data.announce, announceUrl) t.equal(data.complete, 1) t.equal(data.incomplete, 0) client1.scrape() - client1.once('scrape', function (data) { + client1.once('scrape', data => { t.equal(data.announce, announceUrl) t.equal(data.complete, 1) t.equal(data.incomplete, 0) t.equal(typeof data.downloaded, 'number') - var client2 = new Client({ - infoHash: infoHash, + const client2 = new Client({ + infoHash, announce: [announceUrl], peerId: peerId2, port: 6882, - wrtc: wrtc + wrtc }) if (serverType === 'ws') common.mockWebsocketTracker(client2) client2.start() - server.once('start', function () { + server.once('start', () => { t.pass('got start message from client2') }) - client2.once('update', function (data) { + client2.once('update', data => { t.equal(data.announce, announceUrl) t.equal(data.complete, 1) t.equal(data.incomplete, 1) - var client3 = new Client({ - infoHash: infoHash, + const client3 = new Client({ + infoHash, announce: [announceUrl], peerId: peerId3, port: 6880, - wrtc: wrtc + wrtc }) if (serverType === 'ws') common.mockWebsocketTracker(client3) client3.start() - server.once('start', function () { + server.once('start', () => { t.pass('got start message from client3') }) - client3.once('update', function (data) { + client3.once('update', data => { t.equal(data.announce, announceUrl) t.equal(data.complete, 1) t.equal(data.incomplete, 2) client2.stop() - client2.once('update', function (data) { + client2.once('update', data => { t.equal(data.announce, announceUrl) t.equal(data.complete, 1) t.equal(data.incomplete, 1) - client2.destroy(function () { + client2.destroy(() => { t.pass('client2 destroyed') client3.stop() - client3.once('update', function (data) { + client3.once('update', data => { t.equal(data.announce, announceUrl) t.equal(data.complete, 1) t.equal(data.incomplete, 0) - client1.destroy(function () { + client1.destroy(() => { t.pass('client1 destroyed') }) - client3.destroy(function () { + client3.destroy(() => { t.pass('client3 destroyed') }) - server.close(function () { + server.close(() => { t.pass('server destroyed') }) }) @@ -165,18 +165,18 @@ function serverTest (t, serverType, serverFamily) { }) } -test('http ipv4 server', function (t) { +test('http ipv4 server', t => { serverTest(t, 'http', 'inet') }) -test('http ipv6 server', function (t) { +test('http ipv6 server', t => { serverTest(t, 'http', 'inet6') }) -test('udp server', function (t) { +test('udp server', t => { serverTest(t, 'udp', 'inet') }) -test('ws server', function (t) { +test('ws server', t => { serverTest(t, 'ws', 'inet') }) diff --git a/test/stats.js b/test/stats.js index 5b14f81b4..7223da014 100644 --- a/test/stats.js +++ b/test/stats.js @@ -1,23 +1,22 @@ -var Client = require('../') -var commonTest = require('./common') -var fixtures = require('webtorrent-fixtures') -var get = require('simple-get') -var test = require('tape') +import Client from '../index.js' +import commonTest from './common.js' +import fixtures from 'webtorrent-fixtures' +import fetch from 'cross-fetch-ponyfill' +import test from 'tape' -var peerId = Buffer.from('-WW0091-4ea5886ce160') -var unknownPeerId = Buffer.from('01234567890123456789') +const peerId = Buffer.from('-WW0091-4ea5886ce160') +const unknownPeerId = Buffer.from('01234567890123456789') function parseHtml (html) { - var extractValue = new RegExp('[^v^h](\\d+)') - var array = html.replace('torrents', '\n').split('\n').filter(function (line) { - return line && line.trim().length > 0 - }).map(function (line) { - var a = extractValue.exec(line) + const extractValue = /[^v^h](\d+)/ + const array = html.replace('torrents', '\n').split('\n').filter(line => line && line.trim().length > 0).map(line => { + const a = extractValue.exec(line) if (a) { return parseInt(a[1]) } + return null }) - var i = 0 + let i = 0 return { torrents: array[i++], activeTorrents: array[i++], @@ -30,166 +29,169 @@ function parseHtml (html) { } } -test('server: get empty stats', function (t) { - t.plan(11) +test('server: get empty stats', t => { + t.plan(10) - commonTest.createServer(t, 'http', function (server, announceUrl) { - var url = announceUrl.replace('/announce', '/stats') + commonTest.createServer(t, 'http', async (server, announceUrl) => { + const url = announceUrl.replace('/announce', '/stats') - get.concat(url, function (err, res, data) { + let res + try { + res = await fetch(url) + } catch (err) { t.error(err) - - var stats = parseHtml(data.toString()) - t.equal(res.statusCode, 200) - t.equal(stats.torrents, 0) - t.equal(stats.activeTorrents, 0) - t.equal(stats.peersAll, 0) - t.equal(stats.peersSeederOnly, 0) - t.equal(stats.peersLeecherOnly, 0) - t.equal(stats.peersSeederAndLeecher, 0) - t.equal(stats.peersIPv4, 0) - t.equal(stats.peersIPv6, 0) - - server.close(function () { t.pass('server closed') }) - }) + } + const data = Buffer.from(await res.arrayBuffer()) + + const stats = parseHtml(data.toString()) + t.equal(res.status, 200) + t.equal(stats.torrents, 0) + t.equal(stats.activeTorrents, 0) + t.equal(stats.peersAll, 0) + t.equal(stats.peersSeederOnly, 0) + t.equal(stats.peersLeecherOnly, 0) + t.equal(stats.peersSeederAndLeecher, 0) + t.equal(stats.peersIPv4, 0) + t.equal(stats.peersIPv6, 0) + + server.close(() => { t.pass('server closed') }) }) }) -test('server: get empty stats with json header', function (t) { - t.plan(11) +test('server: get empty stats with json header', t => { + t.plan(10) - commonTest.createServer(t, 'http', function (server, announceUrl) { - var opts = { + commonTest.createServer(t, 'http', async (server, announceUrl) => { + const opts = { url: announceUrl.replace('/announce', '/stats'), headers: { accept: 'application/json' - }, - json: true + } } - - get.concat(opts, function (err, res, stats) { + let res + try { + res = await fetch(announceUrl.replace('/announce', '/stats'), opts) + } catch (err) { t.error(err) - - t.equal(res.statusCode, 200) - t.equal(stats.torrents, 0) - t.equal(stats.activeTorrents, 0) - t.equal(stats.peersAll, 0) - t.equal(stats.peersSeederOnly, 0) - t.equal(stats.peersLeecherOnly, 0) - t.equal(stats.peersSeederAndLeecher, 0) - t.equal(stats.peersIPv4, 0) - t.equal(stats.peersIPv6, 0) - - server.close(function () { t.pass('server closed') }) - }) + } + const stats = await res.json() + + t.equal(res.status, 200) + t.equal(stats.torrents, 0) + t.equal(stats.activeTorrents, 0) + t.equal(stats.peersAll, 0) + t.equal(stats.peersSeederOnly, 0) + t.equal(stats.peersLeecherOnly, 0) + t.equal(stats.peersSeederAndLeecher, 0) + t.equal(stats.peersIPv4, 0) + t.equal(stats.peersIPv6, 0) + + server.close(() => { t.pass('server closed') }) }) }) -test('server: get empty stats on stats.json', function (t) { - t.plan(11) - - commonTest.createServer(t, 'http', function (server, announceUrl) { - var opts = { - url: announceUrl.replace('/announce', '/stats.json'), - json: true - } +test('server: get empty stats on stats.json', t => { + t.plan(10) - get.concat(opts, function (err, res, stats) { + commonTest.createServer(t, 'http', async (server, announceUrl) => { + let res + try { + res = await fetch(announceUrl.replace('/announce', '/stats.json')) + } catch (err) { t.error(err) - - t.equal(res.statusCode, 200) - t.equal(stats.torrents, 0) - t.equal(stats.activeTorrents, 0) - t.equal(stats.peersAll, 0) - t.equal(stats.peersSeederOnly, 0) - t.equal(stats.peersLeecherOnly, 0) - t.equal(stats.peersSeederAndLeecher, 0) - t.equal(stats.peersIPv4, 0) - t.equal(stats.peersIPv6, 0) - - server.close(function () { t.pass('server closed') }) - }) + } + const stats = await res.json() + + t.equal(res.status, 200) + t.equal(stats.torrents, 0) + t.equal(stats.activeTorrents, 0) + t.equal(stats.peersAll, 0) + t.equal(stats.peersSeederOnly, 0) + t.equal(stats.peersLeecherOnly, 0) + t.equal(stats.peersSeederAndLeecher, 0) + t.equal(stats.peersIPv4, 0) + t.equal(stats.peersIPv6, 0) + + server.close(() => { t.pass('server closed') }) }) }) -test('server: get leecher stats.json', function (t) { - t.plan(11) +test('server: get leecher stats.json', t => { + t.plan(10) - commonTest.createServer(t, 'http', function (server, announceUrl) { + commonTest.createServer(t, 'http', (server, announceUrl) => { // announce a torrent to the tracker - var client = new Client({ + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, - peerId: peerId, + peerId, port: 6881 }) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.start() - server.once('start', function () { - var opts = { - url: announceUrl.replace('/announce', '/stats.json'), - json: true + server.once('start', async () => { + let res + try { + res = await fetch(announceUrl.replace('/announce', '/stats.json')) + } catch (err) { + t.error(err) } + const stats = await res.json() - get.concat(opts, function (err, res, stats) { - t.error(err) + t.equal(res.status, 200) + t.equal(stats.torrents, 1) + t.equal(stats.activeTorrents, 1) + t.equal(stats.peersAll, 1) + t.equal(stats.peersSeederOnly, 0) + t.equal(stats.peersLeecherOnly, 1) + t.equal(stats.peersSeederAndLeecher, 0) + t.equal(stats.clients.WebTorrent['0.91'], 1) - t.equal(res.statusCode, 200) - t.equal(stats.torrents, 1) - t.equal(stats.activeTorrents, 1) - t.equal(stats.peersAll, 1) - t.equal(stats.peersSeederOnly, 0) - t.equal(stats.peersLeecherOnly, 1) - t.equal(stats.peersSeederAndLeecher, 0) - t.equal(stats.clients.WebTorrent['0.91'], 1) - - client.destroy(function () { t.pass('client destroyed') }) - server.close(function () { t.pass('server closed') }) - }) + client.destroy(() => { t.pass('client destroyed') }) + server.close(() => { t.pass('server closed') }) }) }) }) -test('server: get leecher stats.json (unknown peerId)', function (t) { - t.plan(11) +test('server: get leecher stats.json (unknown peerId)', t => { + t.plan(10) - commonTest.createServer(t, 'http', function (server, announceUrl) { + commonTest.createServer(t, 'http', (server, announceUrl) => { // announce a torrent to the tracker - var client = new Client({ + const client = new Client({ infoHash: fixtures.leaves.parsedTorrent.infoHash, announce: announceUrl, peerId: unknownPeerId, port: 6881 }) - client.on('error', function (err) { t.error(err) }) - client.on('warning', function (err) { t.error(err) }) + client.on('error', err => { t.error(err) }) + client.on('warning', err => { t.error(err) }) client.start() - server.once('start', function () { - var opts = { - url: announceUrl.replace('/announce', '/stats.json'), - json: true + server.once('start', async () => { + let res + try { + res = await fetch(announceUrl.replace('/announce', '/stats.json')) + } catch (err) { + t.error(err) } + const stats = await res.json() - get.concat(opts, function (err, res, stats) { - t.error(err) + t.equal(res.status, 200) + t.equal(stats.torrents, 1) + t.equal(stats.activeTorrents, 1) + t.equal(stats.peersAll, 1) + t.equal(stats.peersSeederOnly, 0) + t.equal(stats.peersLeecherOnly, 1) + t.equal(stats.peersSeederAndLeecher, 0) + t.equal(stats.clients.unknown['01234567'], 1) - t.equal(res.statusCode, 200) - t.equal(stats.torrents, 1) - t.equal(stats.activeTorrents, 1) - t.equal(stats.peersAll, 1) - t.equal(stats.peersSeederOnly, 0) - t.equal(stats.peersLeecherOnly, 1) - t.equal(stats.peersSeederAndLeecher, 0) - t.equal(stats.clients.unknown['01234567'], 1) - - client.destroy(function () { t.pass('client destroyed') }) - server.close(function () { t.pass('server closed') }) - }) + client.destroy(() => { t.pass('client destroyed') }) + server.close(() => { t.pass('server closed') }) }) }) })