diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc3f4ece..79c974a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8754a06d..649a6014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,57 +1,100 @@ # History -## 2023 - -- [6.5.4](#654-2023-11-09) (Nov 2023) -- [6.5.3](#653-2023-10-06) (Oct 2023) -- [6.5.2](#652-2023-08-01) (Aug 2023) -- [6.5.1](#651-2023-06-27) (Jun 2023) -- [6.5.0](#650-2023-06-16) (Jun 2023) -- [6.4.2](#642-2023-05-02) (May 2023) -- [6.4.1](#641-2023-02-20) (Feb 2023) -- [6.4.0](#640-2023-02-06) (Feb 2023) -- [6.3.1](#631-2023-01-12) (Jan 2023) -- [6.3.0](#630-2023-01-10) (Jan 2023) - -## 2022 - -- [3.6.1](#361-2022-11-20) (Nov 2022) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) -- [6.2.1](#621-2022-11-20) (Nov 2022) -- [3.6.0](#360-2022-06-06) (Jun 2022) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) -- [6.2.0](#620-2022-04-17) (Apr 2022) -- [6.1.3](#613-2022-02-23) (Feb 2022) -- [6.1.2](#612-2022-01-18) (Jan 2022) -- [6.1.1](#611-2022-01-11) (Jan 2022) - -## 2021 - -- [6.1.0](#610-2021-11-08) (Nov 2021) -- [6.0.1](#601-2021-11-06) (Nov 2021) -- [**6.0.0**](#600-2021-10-08) (Oct 2021) -- [5.2.0](#520-2021-08-29) (Aug 2021) -- [5.1.1](#511-2021-05-16) (May 2021) -- [5.1.0](#510-2021-05-04) (May 2021) -- [**5.0.0**](#500-2021-03-10) (Mar 2021) -- [4.1.1](#411-2021-02-02) (Feb 2021) -- [4.1.0](#410-2021-01-14) (Jan 2021) -- [4.0.6](#406-2021-01-04) (Jan 2021) - -## 2020 - -- [3.5.0](#350-2020-12-30) (Dec 2020) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) -- [4.0.5](#405-2020-12-07) (Dec 2020) -- [4.0.4](#404-2020-11-17) (Nov 2020) -- [4.0.3](#403-2020-11-17) (Nov 2020) -- [4.0.2](#402-2020-11-09) (Nov 2020) -- [4.0.1](#401-2020-10-21) (Oct 2020) -- [**4.0.0**](#400-2020-09-10) (Sep 2020) -- [3.4.2](#342-2020-06-04) (Jun 2020) -- [3.4.1](#341-2020-04-17) (Apr 2020) - +| Version | Release date | +|------------------------------------------------------------------------------------------------------|----------------| +| [6.6.0](#660-2024-06-21) | June 2024 | +| [6.5.5](#655-2024-06-18) (from the [6.5.x](https://github.com/socketio/engine.io/tree/6.5.x) branch) | June 2024 | +| [3.6.2](#362-2024-06-18) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | June 2024 | +| [6.5.4](#654-2023-11-09) | November 2023 | +| [6.5.3](#653-2023-10-06) | October 2023 | +| [6.5.2](#652-2023-08-01) | August 2023 | +| [6.5.1](#651-2023-06-27) | June 2023 | +| [6.5.0](#650-2023-06-16) | June 2023 | +| [6.4.2](#642-2023-05-02) | May 2023 | +| [6.4.1](#641-2023-02-20) | February 2023 | +| [6.4.0](#640-2023-02-06) | February 2023 | +| [6.3.1](#631-2023-01-12) | January 2023 | +| [6.3.0](#630-2023-01-10) | January 2023 | +| [3.6.1](#361-2022-11-20) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | November 2022 | +| [6.2.1](#621-2022-11-20) | November 2022 | +| [3.6.0](#360-2022-06-06) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | June 2022 | +| [6.2.0](#620-2022-04-17) | April 2022 | +| [6.1.3](#613-2022-02-23) | February 2022 | +| [6.1.2](#612-2022-01-18) | January 2022 | +| [6.1.1](#611-2022-01-11) | January 2021 | +| [6.1.0](#610-2021-11-08) | November 2022 | +| [6.0.1](#601-2021-11-06) | November 2021 | +| [**6.0.0**](#600-2021-10-08) | October 2021 | +| [5.2.0](#520-2021-08-29) | August 2021 | +| [5.1.1](#511-2021-05-16) | May 2021 | +| [5.1.0](#510-2021-05-04) | May 2021 | +| [**5.0.0**](#500-2021-03-10) | March 2021 | +| [4.1.1](#411-2021-02-02) | February 2021 | +| [4.1.0](#410-2021-01-14) | January 2021 | +| [4.0.6](#406-2021-01-04) | January 2021 | +| [3.5.0](#350-2020-12-30) (from the [3.x](https://github.com/socketio/engine.io/tree/3.x) branch) | December 2020 | +| [4.0.5](#405-2020-12-07) | December 2020 | +| [4.0.4](#404-2020-11-17) | November 2020 | +| [4.0.3](#403-2020-11-17) | November 2020 | +| [4.0.2](#402-2020-11-09) | November 2020 | +| [4.0.1](#401-2020-10-21) | October 2020 | +| [**4.0.0**](#400-2020-09-10) | September 2020 | +| [3.4.2](#342-2020-06-04) | June 2020 | +| [3.4.1](#341-2020-04-17) | April 2020 | # Release notes +## [6.6.0](https://github.com/socketio/engine.io/compare/6.5.4...6.6.0) (2024-06-21) + + +### Bug Fixes + +* fix `websocket` and `webtransport` send callbacks ([#699](https://github.com/socketio/engine.io/issues/699)) ([fc21c4a](https://github.com/socketio/engine.io/commit/fc21c4a05f9d50d7efd62aa7a937fadce385e919)) +* properly call the send callback during upgrade ([362bc78](https://github.com/socketio/engine.io/commit/362bc78191c607e6b7c7f2b2e7e7ddb2fe53101c)) +* **types:** make socket.request writable ([#697](https://github.com/socketio/engine.io/issues/697)) ([0efa04b](https://github.com/socketio/engine.io/commit/0efa04b5841816d18b0c6ebf7c5f592f8382978a)) + + +### Performance Improvements + +* do not reset the hearbeat timer on each packet ([5359bae](https://github.com/socketio/engine.io/commit/5359bae683e2a25742bd4989d0355a8fc10d294e)) +* **websocket:** use bound callbacks ([9a68c8c](https://github.com/socketio/engine.io/commit/9a68c8ce93cc1bc0bc1a30548558da49860f4acd)) + + +### Dependencies + +- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) (no change) + + + +## [6.5.5](https://github.com/socketio/engine.io/compare/6.5.4...6.5.5) (2024-06-18) + +This release contains a bump of the `ws` dependency, which includes an important [security fix](https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c). + +Advisory: https://github.com/advisories/GHSA-3h5v-q93c-6h6q + +### Bug Fixes + +* **types:** make socket.request writable ([#697](https://github.com/socketio/engine.io/issues/697)) ([0efa04b](https://github.com/socketio/engine.io/commit/0efa04b5841816d18b0c6ebf7c5f592f8382978a)) + +### Dependencies + +- [`ws@~8.17.1`](https://github.com/websockets/ws/releases/tag/8.17.1) ([diff](https://github.com/websockets/ws/compare/8.11.0...8.17.1)) + + + +## [3.6.2](https://github.com/socketio/engine.io/compare/3.6.1...3.6.2) (2024-06-18) + +This release contains a bump of the `ws` dependency, which includes an important [security fix](https://github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52c). + +Advisory: https://github.com/advisories/GHSA-3h5v-q93c-6h6q + +### Dependencies + +- [`ws@~7.5.10`](https://github.com/websockets/ws/releases/tag/7.5.10) ([diff](https://github.com/websockets/ws/compare/7.4.2...7.5.10)) + + + ## [6.5.4](https://github.com/socketio/engine.io/compare/6.5.3...6.5.4) (2023-11-09) This release contains some minor changes which should improve the memory usage of the server, notably [this](https://github.com/socketio/engine.io/commit/f27a6c35017e4eb37546949f754e09933102837a). diff --git a/examples/latency/package-lock.json b/examples/latency/package-lock.json index 73658c3e..407cb591 100644 --- a/examples/latency/package-lock.json +++ b/examples/latency/package-lock.json @@ -127,12 +127,12 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -140,7 +140,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -287,12 +287,15 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsite": { @@ -361,9 +364,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "convert-source-map": { "version": "1.1.1", @@ -371,9 +374,9 @@ "integrity": "sha1-dOUYJHMFhBOwkN1zd3rLxKD/88w=" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -422,6 +425,16 @@ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", "integrity": "sha1-skbCuApXCkfBG+HZvRBw7IeLh84=" }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "defined": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", @@ -610,6 +623,19 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.1.tgz", "integrity": "sha512-v5aZK1hlckcJDGmHz3W8xvI3NUHYc9t8QtTbqdR5OaH3S9iJZilPubauOm+vLWOMMWzpE3hiq92l9lTAHamRCg==" }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -689,16 +715,16 @@ "integrity": "sha1-dYSdz+k9EPsFfDAFWv29UdBqjiQ=" }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -726,15 +752,6 @@ "vary": "~1.1.2" }, "dependencies": { - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -743,28 +760,10 @@ "ms": "2.0.0" } }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" } } }, @@ -816,18 +815,20 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "glob": { @@ -839,12 +840,12 @@ "minimatch": "0.3" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "requires": { - "function-bind": "^1.1.1" + "get-intrinsic": "^1.1.3" } }, "has-cors": { @@ -852,11 +853,32 @@ "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "http-browserify": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.3.2.tgz", @@ -998,16 +1020,16 @@ "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" } }, "minimatch": { @@ -1081,9 +1103,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "object-keys": { "version": "0.4.0", @@ -1197,9 +1219,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -1339,6 +1361,19 @@ "send": "0.18.0" } }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1355,13 +1390,14 @@ "integrity": "sha1-GkEZbzwDM8SCMjWT1ohuzxU92YY=" }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "sigmund": { diff --git a/examples/latency/package.json b/examples/latency/package.json index d90c57c1..6737cb2b 100644 --- a/examples/latency/package.json +++ b/examples/latency/package.json @@ -5,7 +5,7 @@ "enchilada": "0.13.0", "engine.io": "^6.4.2", "engine.io-client": "^4.1.4", - "express": "^4.18.2", + "express": "^4.19.2", "smoothie": "1.19.0" } } diff --git a/examples/memory-usage-webtransport/.gitignore b/examples/memory-usage-webtransport/.gitignore new file mode 100755 index 00000000..cfb6da4f --- /dev/null +++ b/examples/memory-usage-webtransport/.gitignore @@ -0,0 +1,2 @@ +*.pem +*.log diff --git a/examples/memory-usage-webtransport/client.js b/examples/memory-usage-webtransport/client.js new file mode 100644 index 00000000..db81dc19 --- /dev/null +++ b/examples/memory-usage-webtransport/client.js @@ -0,0 +1,35 @@ +import { Socket } from "engine.io-client"; +import { X509Certificate } from "crypto"; +import { readFileSync } from "node:fs"; +import { WebTransport } from "@fails-components/webtransport"; + +const cert = readFileSync("./cert.pem"); +const CLIENTS_COUNT = 100; + +global.WebTransport = WebTransport; + +for (let i = 0; i < CLIENTS_COUNT; i++) { + const socket = new Socket("ws://localhost:3000", { + transports: ["webtransport"], + transportOptions: { + webtransport: { + serverCertificateHashes: [ + { + algorithm: "sha-256", + value: Buffer.from( + new X509Certificate(cert).fingerprint256 + .split(":") + .map((el) => parseInt(el, 16)) + ), + }, + ], + }, + }, + }); + + socket.on("open", () => {}); + + socket.on("message", () => {}); + + socket.on("close", (reason) => {}); +} diff --git a/examples/memory-usage-webtransport/generate_cert.sh b/examples/memory-usage-webtransport/generate_cert.sh new file mode 100755 index 00000000..bbc1e6ea --- /dev/null +++ b/examples/memory-usage-webtransport/generate_cert.sh @@ -0,0 +1,6 @@ +#!/bin/bash +openssl req -new -x509 -nodes \ + -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ + -days 14 \ + -out cert.pem -keyout key.pem \ + -subj '/CN=127.0.0.1' diff --git a/examples/memory-usage-webtransport/package-lock.json b/examples/memory-usage-webtransport/package-lock.json new file mode 100644 index 00000000..99f3318c --- /dev/null +++ b/examples/memory-usage-webtransport/package-lock.json @@ -0,0 +1,530 @@ +{ + "name": "memory-usage-webtransport", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "memory-usage-webtransport", + "version": "0.0.1", + "dependencies": { + "@fails-components/webtransport": "^0.3.1", + "engine.io-client": "^6.5.3" + } + }, + "node_modules/@fails-components/webtransport": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@fails-components/webtransport/-/webtransport-0.3.1.tgz", + "integrity": "sha512-pbFhPSDCg1vLXiUMq3Q9D9O/tI8g4KpuPFAnPREAYoOw/wU4f/U4e93g0wnxIKyrmnz0ZM7lzf3VMJuHP8SzYw==", + "hasInstallScript": true, + "dependencies": { + "@types/debug": "^4.1.7", + "bindings": "^1.5.0", + "debug": "^4.3.4", + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": ">=16.5" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/node-abi": { + "version": "3.65.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", + "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/examples/memory-usage-webtransport/package.json b/examples/memory-usage-webtransport/package.json new file mode 100644 index 00000000..5f0b7bdd --- /dev/null +++ b/examples/memory-usage-webtransport/package.json @@ -0,0 +1,11 @@ +{ + "name": "memory-usage-webtransport", + "version": "0.0.1", + "private": true, + "description": "", + "type": "module", + "dependencies": { + "@fails-components/webtransport": "^0.3.1", + "engine.io-client": "^6.5.3" + } +} diff --git a/examples/memory-usage-webtransport/server.js b/examples/memory-usage-webtransport/server.js new file mode 100644 index 00000000..2128ec38 --- /dev/null +++ b/examples/memory-usage-webtransport/server.js @@ -0,0 +1,65 @@ +import { readFileSync } from "node:fs"; +import { Http3Server } from "@fails-components/webtransport"; +import { Server } from "../../build/engine.io.js"; + +const key = readFileSync("./key.pem"); +const cert = readFileSync("./cert.pem"); + +const PACKETS_COUNT = 100; +const PACKET_SIZE = 100; + +const engine = new Server({ + transports: ["polling", "websocket", "webtransport"], +}); + +const h3Server = new Http3Server({ + port: 3000, + host: "0.0.0.0", + secret: "changeit", + cert, + privKey: key, +}); + +const packets = []; + +for (let i = 0; i < PACKETS_COUNT; i++) { + packets.push("a".repeat(PACKET_SIZE)); +} + +setInterval(() => { + Object.keys(engine.clients).forEach((id) => { + const client = engine.clients[id]; + packets.forEach((packet) => { + client.send(packet); + }); + }); +}, 10000); + +function formatSize(val) { + return Math.floor(val / 1024); +} + +setInterval(() => { + const mem = process.memoryUsage(); + console.log( + `${Math.floor(process.uptime())}; ${formatSize(mem.heapUsed)}; ${formatSize( + mem.heapTotal + )}` + ); +}, 1000); + +h3Server.startServer(); + +(async () => { + // const stream = await h3Server.sessionStream("/engine.io/"); + const stream = await h3Server.sessionStream("/engine.io/", {}); + const sessionReader = stream.getReader(); + + while (true) { + const { done, value } = await sessionReader.read(); + if (done) { + break; + } + engine.onWebTransportSession(value); + } +})(); diff --git a/examples/memory-usage/.gitignore b/examples/memory-usage/.gitignore new file mode 100755 index 00000000..397b4a76 --- /dev/null +++ b/examples/memory-usage/.gitignore @@ -0,0 +1 @@ +*.log diff --git a/examples/memory-usage/client.js b/examples/memory-usage/client.js new file mode 100644 index 00000000..90a1ebea --- /dev/null +++ b/examples/memory-usage/client.js @@ -0,0 +1,15 @@ +import { Socket } from "engine.io-client"; + +const CLIENTS_COUNT = 100; + +for (let i = 0; i < CLIENTS_COUNT; i++) { + const socket = new Socket("ws://localhost:3000", { + transports: ["websocket"], + }); + + socket.on("open", () => {}); + + socket.on("message", () => {}); + + socket.on("close", (reason) => {}); +} diff --git a/examples/memory-usage/package-lock.json b/examples/memory-usage/package-lock.json new file mode 100644 index 00000000..1acf8dea --- /dev/null +++ b/examples/memory-usage/package-lock.json @@ -0,0 +1,89 @@ +{ + "name": "memory-usage", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "memory-usage", + "version": "0.0.1", + "dependencies": { + "engine.io-client": "^6.5.4" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/examples/memory-usage/package.json b/examples/memory-usage/package.json new file mode 100644 index 00000000..030c8769 --- /dev/null +++ b/examples/memory-usage/package.json @@ -0,0 +1,10 @@ +{ + "name": "memory-usage", + "version": "0.0.1", + "private": true, + "description": "", + "type": "module", + "dependencies": { + "engine.io-client": "^6.5.4" + } +} diff --git a/examples/memory-usage/server.js b/examples/memory-usage/server.js new file mode 100644 index 00000000..c051220b --- /dev/null +++ b/examples/memory-usage/server.js @@ -0,0 +1,41 @@ +import { createServer } from "node:http"; +import { Server } from "../../build/engine.io.js"; + +const EMIT_INTERVAL_MS = 10_000; +const PACKETS_COUNT = 100; +const PACKET_SIZE = 100; + +const httpServer = createServer(); +const engine = new Server(); + +engine.attach(httpServer); + +const packets = []; + +for (let i = 0; i < PACKETS_COUNT; i++) { + packets.push("a".repeat(PACKET_SIZE)); +} + +setInterval(() => { + Object.keys(engine.clients).forEach((id) => { + const client = engine.clients[id]; + packets.forEach((packet) => { + client.send(packet); + }); + }); +}, EMIT_INTERVAL_MS); + +function formatSize(val) { + return Math.floor(val / 1024); +} + +setInterval(() => { + const mem = process.memoryUsage(); + console.log( + `${Math.floor(process.uptime())}; ${formatSize(mem.heapUsed)}; ${formatSize( + mem.heapTotal + )}` + ); +}, 1000); + +httpServer.listen(3000); diff --git a/lib/engine.io.ts b/lib/engine.io.ts index f6d4cb1d..3ba9e67f 100644 --- a/lib/engine.io.ts +++ b/lib/engine.io.ts @@ -17,7 +17,6 @@ export const protocol = parser.protocol; * @param {Function} callback * @param {Object} options * @return {Server} websocket.io server - * @api public */ function listen(port, options: AttachOptions & ServerOptions, fn) { @@ -46,7 +45,6 @@ function listen(port, options: AttachOptions & ServerOptions, fn) { * @param {http.Server} server * @param {Object} options * @return {Server} engine server - * @api public */ function attach(server, options: AttachOptions & ServerOptions) { diff --git a/lib/server.ts b/lib/server.ts index 99b3f471..a010092f 100644 --- a/lib/server.ts +++ b/lib/server.ts @@ -17,12 +17,13 @@ import type { CorsOptions, CorsOptionsDelegate } from "cors"; import type { Duplex } from "stream"; import { WebTransport } from "./transports/webtransport"; import { createPacketDecoderStream } from "engine.io-parser"; +import type { EngineRequest } from "./transport"; const debug = debugModule("engine"); const kResponseHeaders = Symbol("responseHeaders"); -type Transport = "polling" | "websocket"; +type Transport = "polling" | "websocket" | "webtransport"; export interface AttachOptions { /** @@ -160,7 +161,8 @@ function parseSessionId(data: string) { export abstract class BaseServer extends EventEmitter { public opts: ServerOptions; - protected clients: any; + // TODO for the next major release: use a Map instead + protected clients: Record; public clientsCount: number; protected middlewares: Middleware[] = []; @@ -168,7 +170,6 @@ export abstract class BaseServer extends EventEmitter { * Server constructor. * * @param {Object} opts - options - * @api public */ constructor(opts: ServerOptions = {}) { super(); @@ -245,9 +246,8 @@ export abstract class BaseServer extends EventEmitter { * Returns a list of available transports for upgrade given a certain transport. * * @return {Array} - * @api public */ - public upgrades(transport) { + public upgrades(transport: string) { if (!this.opts.allowUpgrades) return []; return transports[transport].upgradesTo || []; } @@ -255,11 +255,16 @@ export abstract class BaseServer extends EventEmitter { /** * Verifies a request. * - * @param {http.IncomingMessage} - * @return {Boolean} whether the request is valid - * @api private + * @param {EngineRequest} req + * @param upgrade - whether it's an upgrade request + * @param fn + * @protected */ - protected verify(req, upgrade, fn) { + protected verify( + req: any, + upgrade: boolean, + fn: (errorCode?: number, errorContext?: any) => void + ) { // transport check const transport = req._query.transport; // WebTransport does not go through the verify() method, see the onWebTransportSession() method @@ -383,8 +388,6 @@ export abstract class BaseServer extends EventEmitter { /** * Closes all clients. - * - * @api public */ public close() { debug("closing all open clients"); @@ -403,23 +406,26 @@ export abstract class BaseServer extends EventEmitter { * generate a socket id. * Overwrite this method to generate your custom socket id * - * @param {Object} request object - * @api public + * @param {IncomingMessage} req - the request object */ - public generateId(req) { + public generateId(req: IncomingMessage) { return base64id.generateId(); } /** * Handshakes a new client. * - * @param {String} transport name - * @param {Object} request object + * @param {String} transportName + * @param {Object} req - the request object * @param {Function} closeConnection * - * @api protected + * @protected */ - protected async handshake(transportName, req, closeConnection) { + protected async handshake( + transportName: string, + req: any, + closeConnection: (errorCode?: number, errorContext?: any) => void + ) { const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default if (protocol === 3 && !this.opts.allowEIO3) { debug("unsupported protocol version"); @@ -590,7 +596,7 @@ export abstract class BaseServer extends EventEmitter { debug("upgrading existing transport"); const transport = new WebTransport(session, stream, reader); - client.maybeUpgrade(transport); + client._maybeUpgrade(transport); } } @@ -660,7 +666,7 @@ export class Server extends BaseServer { /** * Initialize websocket server * - * @api protected + * @protected */ protected init() { if (!~this.opts.transports.indexOf("websocket")) return; @@ -707,30 +713,30 @@ export class Server extends BaseServer { /** * Prepares a request by processing the query string. * - * @api private + * @private */ - private prepare(req) { + private prepare(req: EngineRequest) { // try to leverage pre-existing `req._query` (e.g: from connect) if (!req._query) { - req._query = ~req.url.indexOf("?") ? qs.parse(parse(req.url).query) : {}; + req._query = ( + ~req.url.indexOf("?") ? qs.parse(parse(req.url).query) : {} + ) as Record; } } - protected createTransport(transportName, req) { + protected createTransport(transportName: string, req: IncomingMessage) { return new transports[transportName](req); } /** * Handles an Engine.IO HTTP request. * - * @param {IncomingMessage} req + * @param {EngineRequest} req * @param {ServerResponse} res - * @api public */ - public handleRequest(req: IncomingMessage, res: ServerResponse) { + public handleRequest(req: EngineRequest, res: ServerResponse) { debug('handling "%s" http request "%s"', req.method, req.url); this.prepare(req); - // @ts-ignore req.res = res; const callback = (errorCode, errorContext) => { @@ -745,15 +751,12 @@ export class Server extends BaseServer { return; } - // @ts-ignore if (req._query.sid) { debug("setting new request for existing client"); - // @ts-ignore this.clients[req._query.sid].transport.onRequest(req); } else { const closeConnection = (errorCode, errorContext) => abortRequest(res, errorCode, errorContext); - // @ts-ignore this.handshake(req._query.transport, req, closeConnection); } }; @@ -769,11 +772,9 @@ export class Server extends BaseServer { /** * Handles an Engine.IO HTTP Upgrade. - * - * @api public */ public handleUpgrade( - req: IncomingMessage, + req: EngineRequest, socket: Duplex, upgradeHead: Buffer ) { @@ -818,7 +819,7 @@ export class Server extends BaseServer { * Called upon a ws.io connection. * * @param {ws.Socket} websocket - * @api private + * @private */ private onWebSocket(req, socket, websocket) { websocket.on("error", onUpgradeError); @@ -857,7 +858,7 @@ export class Server extends BaseServer { const transport = this.createTransport(req._query.transport, req); transport.perMessageDeflate = this.opts.perMessageDeflate; - client.maybeUpgrade(transport); + client._maybeUpgrade(transport); } } else { const closeConnection = (errorCode, errorContext) => @@ -876,7 +877,6 @@ export class Server extends BaseServer { * * @param {http.Server} server * @param {Object} options - * @api public */ public attach(server: HttpServer, options: AttachOptions = {}) { const path = this._computePath(options); @@ -897,7 +897,7 @@ export class Server extends BaseServer { server.on("request", (req, res) => { if (check(req)) { debug('intercepting request for path "%s"', path); - this.handleRequest(req, res); + this.handleRequest(req as EngineRequest, res); } else { let i = 0; const l = listeners.length; @@ -910,7 +910,7 @@ export class Server extends BaseServer { if (~this.opts.transports.indexOf("websocket")) { server.on("upgrade", (req, socket, head) => { if (check(req)) { - this.handleUpgrade(req, socket, head); + this.handleUpgrade(req as EngineRequest, socket, head); } else if (false !== options.destroyUpgrade) { // default node behavior is to disconnect when no handlers // but by adding a handler, we prevent that @@ -938,7 +938,7 @@ export class Server extends BaseServer { * @param errorCode - the error code * @param errorContext - additional error context * - * @api private + * @private */ function abortRequest(res, errorCode, errorContext) { @@ -963,8 +963,6 @@ function abortRequest(res, errorCode, errorContext) { * @param {net.Socket} socket * @param {string} errorCode - the error code * @param {object} errorContext - additional error context - * - * @api private */ function abortUpgrade( diff --git a/lib/socket.ts b/lib/socket.ts index 6fc2f0d7..d9f58a68 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -1,10 +1,10 @@ import { EventEmitter } from "events"; import debugModule from "debug"; -import { IncomingMessage } from "http"; -import { Transport } from "./transport"; -import { Server } from "./server"; +import type { IncomingMessage } from "http"; +import type { EngineRequest, Transport } from "./transport"; +import type { BaseServer } from "./server"; import { setTimeout, clearTimeout } from "timers"; -import { Packet, PacketType, RawData } from "engine.io-parser"; +import type { Packet, PacketType, RawData } from "engine.io-parser"; const debug = debugModule("engine:socket"); @@ -14,21 +14,46 @@ export interface SendOptions { type ReadyState = "opening" | "open" | "closing" | "closed"; +type SendCallback = (transport: Transport) => void; + export class Socket extends EventEmitter { + /** + * The revision of the protocol: + * + * - 3rd is used in Engine.IO v3 / Socket.IO v2 + * - 4th is used in Engine.IO v4 and above / Socket.IO v3 and above + * + * It is found in the `EIO` query parameters of the HTTP requests. + * + * @see https://github.com/socketio/engine.io-protocol + */ public readonly protocol: number; - // TODO for the next major release: do not keep the reference to the first HTTP request, as it stays in memory - public readonly request: IncomingMessage; + /** + * A reference to the first HTTP request of the session + * + * TODO for the next major release: remove it + */ + public request: IncomingMessage; + /** + * The IP address of the client. + */ public readonly remoteAddress: string; + /** + * The current state of the socket. + */ public _readyState: ReadyState = "opening"; + /** + * The current low-level transport. + */ public transport: Transport; - private server: Server; - private upgrading = false; - private upgraded = false; + private server: BaseServer; + /* private */ upgrading = false; + /* private */ upgraded = false; private writeBuffer: Packet[] = []; - private packetsFn: Array<() => void> = []; - private sentCallbackFn: any[] = []; + private packetsFn: SendCallback[] = []; + private sentCallbackFn: SendCallback[][] = []; private cleanupFn: any[] = []; private pingTimeoutTimer; private pingIntervalTimer; @@ -50,12 +75,13 @@ export class Socket extends EventEmitter { this._readyState = state; } - /** - * Client class (abstract). - * - * @api private - */ - constructor(id, server, transport, req, protocol) { + constructor( + id: string, + server: BaseServer, + transport: Transport, + req: EngineRequest, + protocol: number + ) { super(); this.id = id; this.server = server; @@ -84,7 +110,7 @@ export class Socket extends EventEmitter { /** * Called upon transport considered open. * - * @api private + * @private */ private onOpen() { this.readyState = "open"; @@ -110,9 +136,7 @@ export class Socket extends EventEmitter { if (this.protocol === 3) { // in protocol v3, the client sends a ping, and the server answers with a pong - this.resetPingTimeout( - this.server.opts.pingInterval + this.server.opts.pingTimeout - ); + this.resetPingTimeout(); } else { // in protocol v4, the server sends a ping, and the client answers with a pong this.schedulePing(); @@ -123,7 +147,7 @@ export class Socket extends EventEmitter { * Called upon transport packet. * * @param {Object} packet - * @api private + * @private */ private onPacket(packet: Packet) { if ("open" !== this.readyState) { @@ -133,29 +157,25 @@ export class Socket extends EventEmitter { debug(`received packet ${packet.type}`); this.emit("packet", packet); - // Reset ping timeout on any packet, incoming data is a good sign of - // other side's liveness - this.resetPingTimeout( - this.server.opts.pingInterval + this.server.opts.pingTimeout - ); - switch (packet.type) { case "ping": if (this.transport.protocol !== 3) { - this.onError("invalid heartbeat direction"); + this.onError(new Error("invalid heartbeat direction")); return; } debug("got ping"); + this.pingTimeoutTimer.refresh(); this.sendPacket("pong"); this.emit("heartbeat"); break; case "pong": if (this.transport.protocol === 3) { - this.onError("invalid heartbeat direction"); + this.onError(new Error("invalid heartbeat direction")); return; } debug("got pong"); + clearTimeout(this.pingTimeoutTimer); this.pingIntervalTimer.refresh(); this.emit("heartbeat"); break; @@ -175,9 +195,9 @@ export class Socket extends EventEmitter { * Called upon transport error. * * @param {Error} err - error object - * @api private + * @private */ - private onError(err) { + private onError(err: Error) { debug("transport error"); this.onClose("transport error", err); } @@ -186,7 +206,7 @@ export class Socket extends EventEmitter { * Pings client every `this.pingInterval` and expects response * within `this.pingTimeout` or closes connection. * - * @api private + * @private */ private schedulePing() { this.pingIntervalTimer = setTimeout(() => { @@ -195,58 +215,81 @@ export class Socket extends EventEmitter { this.server.opts.pingTimeout ); this.sendPacket("ping"); - this.resetPingTimeout(this.server.opts.pingTimeout); + this.resetPingTimeout(); }, this.server.opts.pingInterval); } /** * Resets ping timeout. * - * @api private + * @private */ - private resetPingTimeout(timeout) { + private resetPingTimeout() { clearTimeout(this.pingTimeoutTimer); - this.pingTimeoutTimer = setTimeout(() => { - if (this.readyState === "closed") return; - this.onClose("ping timeout"); - }, timeout); + this.pingTimeoutTimer = setTimeout( + () => { + if (this.readyState === "closed") return; + this.onClose("ping timeout"); + }, + this.protocol === 3 + ? this.server.opts.pingInterval + this.server.opts.pingTimeout + : this.server.opts.pingTimeout + ); } /** * Attaches handlers for the given transport. * * @param {Transport} transport - * @api private + * @private */ - private setTransport(transport) { + private setTransport(transport: Transport) { const onError = this.onError.bind(this); + const onReady = () => this.flush(); const onPacket = this.onPacket.bind(this); - const flush = this.flush.bind(this); + const onDrain = this.onDrain.bind(this); const onClose = this.onClose.bind(this, "transport close"); this.transport = transport; this.transport.once("error", onError); + this.transport.on("ready", onReady); this.transport.on("packet", onPacket); - this.transport.on("drain", flush); + this.transport.on("drain", onDrain); this.transport.once("close", onClose); - // this function will manage packet events (also message callbacks) - this.setupSendCallback(); this.cleanupFn.push(function () { transport.removeListener("error", onError); + transport.removeListener("ready", onReady); transport.removeListener("packet", onPacket); - transport.removeListener("drain", flush); + transport.removeListener("drain", onDrain); transport.removeListener("close", onClose); }); } + /** + * Upon transport "drain" event + * + * @private + */ + private onDrain() { + if (this.sentCallbackFn.length > 0) { + debug("executing batch send callback"); + const seqFn = this.sentCallbackFn.shift(); + if (seqFn) { + for (let i = 0; i < seqFn.length; i++) { + seqFn[i](this.transport); + } + } + } + } + /** * Upgrades socket to the given transport * * @param {Transport} transport - * @api private + * @private */ - private maybeUpgrade(transport) { + /* private */ _maybeUpgrade(transport: Transport) { debug( 'might upgrade socket transport from "%s" to "%s"', this.transport.name, @@ -338,7 +381,7 @@ export class Socket extends EventEmitter { /** * Clears listeners and timers associated with current transport. * - * @api private + * @private */ private clearTransport() { let cleanup; @@ -386,39 +429,6 @@ export class Socket extends EventEmitter { } } - /** - * Setup and manage send callback - * - * @api private - */ - private setupSendCallback() { - // the message was sent successfully, execute the callback - const onDrain = () => { - if (this.sentCallbackFn.length > 0) { - const seqFn = this.sentCallbackFn.splice(0, 1)[0]; - if ("function" === typeof seqFn) { - debug("executing send callback"); - seqFn(this.transport); - } else if (Array.isArray(seqFn)) { - debug("executing batch send callback"); - const l = seqFn.length; - let i = 0; - for (; i < l; i++) { - if ("function" === typeof seqFn[i]) { - seqFn[i](this.transport); - } - } - } - } - }; - - this.transport.on("drain", onDrain); - - this.cleanupFn.push(() => { - this.transport.removeListener("drain", onDrain); - }); - } - /** * Sends a message packet. * @@ -426,9 +436,8 @@ export class Socket extends EventEmitter { * @param {Object} options * @param {Function} callback * @return {Socket} for chaining - * @api public */ - public send(data: RawData, options?: SendOptions, callback?: () => void) { + public send(data: RawData, options?: SendOptions, callback?: SendCallback) { this.sendPacket("message", data, options, callback); return this; } @@ -440,7 +449,7 @@ export class Socket extends EventEmitter { * @param options * @param callback */ - public write(data: RawData, options?: SendOptions, callback?: () => void) { + public write(data: RawData, options?: SendOptions, callback?: SendCallback) { this.sendPacket("message", data, options, callback); return this; } @@ -453,13 +462,13 @@ export class Socket extends EventEmitter { * @param {Object} options * @param {Function} callback * - * @api private + * @private */ private sendPacket( type: PacketType, data?: RawData, options: SendOptions = {}, - callback?: () => void + callback?: SendCallback ) { if ("function" === typeof options) { callback = options; @@ -485,7 +494,7 @@ export class Socket extends EventEmitter { this.writeBuffer.push(packet); // add send callback to object, if defined - if (callback) this.packetsFn.push(callback); + if ("function" === typeof callback) this.packetsFn.push(callback); this.flush(); } @@ -494,7 +503,7 @@ export class Socket extends EventEmitter { /** * Attempts to flush the packets buffer. * - * @api private + * @private */ private flush() { if ( @@ -507,12 +516,14 @@ export class Socket extends EventEmitter { this.server.emit("flush", this, this.writeBuffer); const wbuf = this.writeBuffer; this.writeBuffer = []; - if (!this.transport.supportsFraming) { + + if (this.packetsFn.length) { this.sentCallbackFn.push(this.packetsFn); + this.packetsFn = []; } else { - this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn); + this.sentCallbackFn.push(null); } - this.packetsFn = []; + this.transport.send(wbuf); this.emit("drain"); this.server.emit("drain", this); @@ -522,14 +533,12 @@ export class Socket extends EventEmitter { /** * Get available upgrades for this socket. * - * @api private + * @private */ private getAvailableUpgrades() { const availableUpgrades = []; const allUpgrades = this.server.upgrades(this.transport.name); - let i = 0; - const l = allUpgrades.length; - for (; i < l; ++i) { + for (let i = 0; i < allUpgrades.length; ++i) { const upg = allUpgrades[i]; if (this.server.opts.transports.indexOf(upg) !== -1) { availableUpgrades.push(upg); @@ -543,7 +552,6 @@ export class Socket extends EventEmitter { * * @param {Boolean} discard - optional, discard the transport * @return {Socket} for chaining - * @api public */ public close(discard?: boolean) { if ("open" !== this.readyState) return; @@ -570,9 +578,9 @@ export class Socket extends EventEmitter { * Closes the underlying transport. * * @param {Boolean} discard - * @api private + * @private */ - private closeTransport(discard) { + private closeTransport(discard: boolean) { debug("closing the transport (discard? %s)", discard); if (discard) this.transport.discard(); this.transport.close(this.onClose.bind(this, "forced close")); diff --git a/lib/transport.ts b/lib/transport.ts index b0777bca..5e74813c 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -2,30 +2,62 @@ import { EventEmitter } from "events"; import * as parser_v4 from "engine.io-parser"; import * as parser_v3 from "./parser-v3/index"; import debugModule from "debug"; -import { IncomingMessage } from "http"; -import { Packet } from "engine.io-parser"; +import type { IncomingMessage, ServerResponse } from "http"; +import { Packet, RawData } from "engine.io-parser"; const debug = debugModule("engine:transport"); -/** - * Noop function. - * - * @api private - */ - function noop() {} type ReadyState = "open" | "closing" | "closed"; +export type EngineRequest = IncomingMessage & { + _query: Record; + res?: ServerResponse; + cleanup?: Function; + websocket?: any; +}; + export abstract class Transport extends EventEmitter { + /** + * The session ID. + */ public sid: string; + /** + * Whether the transport is currently ready to send packets. + */ public writable = false; + /** + * The revision of the protocol: + * + * - 3 is used in Engine.IO v3 / Socket.IO v2 + * - 4 is used in Engine.IO v4 and above / Socket.IO v3 and above + * + * It is found in the `EIO` query parameters of the HTTP requests. + * + * @see https://github.com/socketio/engine.io-protocol + */ public protocol: number; + /** + * The current state of the transport. + * @protected + */ protected _readyState: ReadyState = "open"; + /** + * Whether the transport is discarded and can be safely closed (used during upgrade). + * @protected + */ protected discarded = false; + /** + * The parser to use (depends on the revision of the {@link Transport#protocol}. + * @protected + */ protected parser: any; - protected req: IncomingMessage & { cleanup: Function }; + /** + * Whether the transport supports binary payloads (else it will be base64-encoded) + * @protected + */ protected supportsBinary: boolean; get readyState() { @@ -45,10 +77,9 @@ export abstract class Transport extends EventEmitter { /** * Transport constructor. * - * @param {http.IncomingMessage} req - * @api public + * @param {EngineRequest} req */ - constructor(req) { + constructor(req: { _query: Record }) { super(); this.protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default this.parser = this.protocol === 4 ? parser_v4 : parser_v3; @@ -58,7 +89,7 @@ export abstract class Transport extends EventEmitter { /** * Flags the transport as discarded. * - * @api private + * @package */ discard() { this.discarded = true; @@ -67,20 +98,17 @@ export abstract class Transport extends EventEmitter { /** * Called with an incoming HTTP request. * - * @param {http.IncomingMessage} req - * @api protected + * @param req + * @package */ - protected onRequest(req) { - debug("setting request"); - this.req = req; - } + onRequest(req: any) {} /** * Closes the transport. * - * @api private + * @package */ - close(fn?) { + close(fn?: () => void) { if ("closed" === this.readyState || "closing" === this.readyState) return; this.readyState = "closing"; @@ -92,7 +120,7 @@ export abstract class Transport extends EventEmitter { * * @param {String} msg - message error * @param {Object} desc - error description - * @api protected + * @protected */ protected onError(msg: string, desc?) { if (this.listeners("error").length) { @@ -111,7 +139,7 @@ export abstract class Transport extends EventEmitter { * Called with parsed out a packets from the data stream. * * @param {Object} packet - * @api protected + * @protected */ protected onPacket(packet: Packet) { this.emit("packet", packet); @@ -121,31 +149,26 @@ export abstract class Transport extends EventEmitter { * Called with the encoded packet data. * * @param {String} data - * @api protected + * @protected */ - protected onData(data) { + protected onData(data: RawData) { this.onPacket(this.parser.decodePacket(data)); } /** * Called upon transport close. * - * @api protected + * @protected */ protected onClose() { this.readyState = "closed"; this.emit("close"); } - /** - * Advertise framing support. - */ - abstract get supportsFraming(); - /** * The name of the transport. */ - abstract get name(); + abstract get name(): string; /** * Sends an array of packets. @@ -153,10 +176,10 @@ export abstract class Transport extends EventEmitter { * @param {Array} packets * @package */ - abstract send(packets); + abstract send(packets: Packet[]): void; /** * Closes the transport. */ - abstract doClose(fn?); + abstract doClose(fn?: () => void): void; } diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 16e72c00..090270ec 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -15,6 +15,7 @@ export class Polling extends Transport { public maxHttpBufferSize: number; public httpCompression: any; + private req: HttpRequest & { cleanup: () => void }; private res: HttpResponse; private dataReq: HttpRequest; private dataRes: HttpResponse; @@ -24,8 +25,6 @@ export class Polling extends Transport { /** * HTTP polling constructor. - * - * @api public. */ constructor(req) { super(req); @@ -35,23 +34,17 @@ export class Polling extends Transport { /** * Transport name - * - * @api public */ get name() { return "polling"; } - get supportsFraming() { - return false; - } - /** * Overrides onRequest. * * @param req * - * @api private + * @private */ onRequest(req) { const res = req.res; @@ -71,7 +64,7 @@ export class Polling extends Transport { /** * The client sends a request awaiting for us to send data. * - * @api private + * @private */ onPollRequest(req, res) { if (this.req) { @@ -101,7 +94,7 @@ export class Polling extends Transport { res.onAborted(onClose); this.writable = true; - this.emit("drain"); + this.emit("ready"); // if we're still writable but had a pending close, trigger an empty send if (this.writable && this.shouldClose) { @@ -113,7 +106,7 @@ export class Polling extends Transport { /** * The client sends a request with data. * - * @api private + * @private */ onDataRequest(req, res) { if (this.dataReq) { @@ -210,7 +203,7 @@ export class Polling extends Transport { /** * Cleanup request. * - * @api private + * @private */ private onDataRequestCleanup() { this.dataReq = this.dataRes = null; @@ -220,7 +213,7 @@ export class Polling extends Transport { * Processes the incoming data payload. * * @param {String} encoded payload - * @api private + * @private */ onData(data) { debug('received "%s"', data); @@ -244,7 +237,7 @@ export class Polling extends Transport { /** * Overrides onClose. * - * @api private + * @private */ onClose() { if (this.writable) { @@ -258,7 +251,7 @@ export class Polling extends Transport { * Writes a packet payload. * * @param {Object} packet - * @api private + * @private */ send(packets) { this.writable = false; @@ -289,19 +282,20 @@ export class Polling extends Transport { * * @param {String} data * @param {Object} options - * @api private + * @private */ write(data, options) { debug('writing "%s"', data); this.doWrite(data, options, () => { this.req.cleanup(); + this.emit("drain"); }); } /** * Performs the write. * - * @api private + * @private */ doWrite(data, options, callback) { // explicit UTF-8 is required for pages not served under utf @@ -358,7 +352,7 @@ export class Polling extends Transport { /** * Compresses data. * - * @api private + * @private */ compress(data, encoding, callback) { debug("compressing"); @@ -381,7 +375,7 @@ export class Polling extends Transport { /** * Closes the transport. * - * @api private + * @private */ doClose(fn) { debug("closing"); @@ -413,7 +407,7 @@ export class Polling extends Transport { * * @param req - request * @param {Object} extra headers - * @api private + * @private */ headers(req, headers) { headers = headers || {}; diff --git a/lib/transports-uws/websocket.ts b/lib/transports-uws/websocket.ts index 9fc43ce3..aa5e1c93 100644 --- a/lib/transports-uws/websocket.ts +++ b/lib/transports-uws/websocket.ts @@ -11,7 +11,6 @@ export class WebSocket extends Transport { * WebSocket transport * * @param req - * @api public */ constructor(req) { super(req); @@ -21,8 +20,6 @@ export class WebSocket extends Transport { /** * Transport name - * - * @api public */ get name() { return "websocket"; @@ -30,27 +27,16 @@ export class WebSocket extends Transport { /** * Advertise upgrade support. - * - * @api public */ get handlesUpgrades() { return true; } - /** - * Advertise framing support. - * - * @api public - */ - get supportsFraming() { - return true; - } - /** * Writes a packet payload. * * @param {Array} packets - * @api private + * @private */ send(packets) { this.writable = false; @@ -69,8 +55,9 @@ export class WebSocket extends Transport { this.socket.send(data, isBinary, compress); if (isLast) { - this.writable = true; this.emit("drain"); + this.writable = true; + this.emit("ready"); } }; @@ -85,7 +72,7 @@ export class WebSocket extends Transport { /** * Closes the transport. * - * @api private + * @private */ doClose(fn) { debug("closing"); diff --git a/lib/transports/index.ts b/lib/transports/index.ts index 5c8449d4..e585112b 100644 --- a/lib/transports/index.ts +++ b/lib/transports/index.ts @@ -11,8 +11,6 @@ export default { /** * Polling polymorphic constructor. - * - * @api private */ function polling(req) { diff --git a/lib/transports/polling-jsonp.ts b/lib/transports/polling-jsonp.ts index d94ba23a..05806008 100644 --- a/lib/transports/polling-jsonp.ts +++ b/lib/transports/polling-jsonp.ts @@ -1,5 +1,6 @@ import { Polling } from "./polling"; import * as qs from "querystring"; +import type { RawData } from "engine.io-parser"; const rDoubleSlashes = /\\\\n/g; const rSlashes = /(\\)?\\n/g; @@ -10,8 +11,6 @@ export class JSONP extends Polling { /** * JSON-P polling transport. - * - * @api public */ constructor(req) { super(req); @@ -20,16 +19,10 @@ export class JSONP extends Polling { this.foot = ");"; } - /** - * Handles incoming data. - * Due to a bug in \n handling by browsers, we expect a escaped string. - * - * @api private - */ - onData(data) { + override onData(data: RawData) { // we leverage the qs module so that we get built-in DoS protection // and the fast alternative to decodeURIComponent - data = qs.parse(data).d; + data = qs.parse(data).d as string; if ("string" === typeof data) { // client will send already escaped newlines as \\\\n and newlines as \\n // \\n must be replaced with \n and \\\\n with \\n @@ -40,12 +33,7 @@ export class JSONP extends Polling { } } - /** - * Performs the write. - * - * @api private - */ - doWrite(data, options, callback) { + override doWrite(data, options, callback) { // we must output valid javascript, not valid json // see: http://timelessrepo.com/json-isnt-a-javascript-subset const js = JSON.stringify(data) diff --git a/lib/transports/polling.ts b/lib/transports/polling.ts index e5ea24cf..1f463359 100644 --- a/lib/transports/polling.ts +++ b/lib/transports/polling.ts @@ -1,8 +1,9 @@ -import { Transport } from "../transport"; +import { EngineRequest, Transport } from "../transport"; import { createGzip, createDeflate } from "zlib"; import * as accepts from "accepts"; import debugModule from "debug"; -import { IncomingMessage, ServerResponse } from "http"; +import type { IncomingMessage, ServerResponse } from "http"; +import type { Packet, RawData } from "engine.io-parser"; const debug = debugModule("engine:polling"); @@ -15,17 +16,16 @@ export class Polling extends Transport { public maxHttpBufferSize: number; public httpCompression: any; + private req: EngineRequest; private res: ServerResponse; private dataReq: IncomingMessage; private dataRes: ServerResponse; - private shouldClose: Function; + private shouldClose: () => void; private readonly closeTimeout: number; /** * HTTP polling constructor. - * - * @api public. */ constructor(req) { super(req); @@ -35,24 +35,18 @@ export class Polling extends Transport { /** * Transport name - * - * @api public */ get name() { return "polling"; } - get supportsFraming() { - return false; - } - /** * Overrides onRequest. * - * @param {http.IncomingMessage} - * @api private + * @param {EngineRequest} req + * @package */ - onRequest(req: IncomingMessage & { res: ServerResponse }) { + onRequest(req: EngineRequest) { const res = req.res; // remove the reference to the ServerResponse object (as the first request of the session is kept in memory by default) req.res = null; @@ -70,9 +64,9 @@ export class Polling extends Transport { /** * The client sends a request awaiting for us to send data. * - * @api private + * @private */ - onPollRequest(req, res) { + private onPollRequest(req: EngineRequest, res: ServerResponse) { if (this.req) { debug("request overlap"); // assert: this.res, '.req and .res should be (un)set together' @@ -100,7 +94,7 @@ export class Polling extends Transport { req.on("close", onClose); this.writable = true; - this.emit("drain"); + this.emit("ready"); // if we're still writable but had a pending close, trigger an empty send if (this.writable && this.shouldClose) { @@ -112,9 +106,9 @@ export class Polling extends Transport { /** * The client sends a request with data. * - * @api private + * @private */ - onDataRequest(req: IncomingMessage, res: ServerResponse) { + private onDataRequest(req: IncomingMessage, res: ServerResponse) { if (this.dataReq) { // assert: this.dataRes, '.dataReq and .dataRes should be (un)set together' this.onError("data request overlap from client"); @@ -169,7 +163,7 @@ export class Polling extends Transport { // text/html is required instead of text/plain to avoid an // unwanted download dialog on certain user-agents (GH-43) "Content-Type": "text/html", - "Content-Length": 2, + "Content-Length": "2", }; res.writeHead(200, this.headers(req, headers)); @@ -186,10 +180,10 @@ export class Polling extends Transport { /** * Processes the incoming data payload. * - * @param {String} encoded payload - * @api private + * @param data - encoded payload + * @protected */ - onData(data) { + override onData(data: RawData) { debug('received "%s"', data); const callback = (packet) => { if ("close" === packet.type) { @@ -211,7 +205,7 @@ export class Polling extends Transport { /** * Overrides onClose. * - * @api private + * @private */ onClose() { if (this.writable) { @@ -221,13 +215,7 @@ export class Polling extends Transport { super.onClose(); } - /** - * Writes a packet payload. - * - * @param {Object} packet - * @api private - */ - send(packets) { + send(packets: Packet[]) { this.writable = false; if (this.shouldClose) { @@ -256,21 +244,22 @@ export class Polling extends Transport { * * @param {String} data * @param {Object} options - * @api private + * @private */ - write(data, options) { + private write(data, options) { debug('writing "%s"', data); this.doWrite(data, options, () => { this.req.cleanup(); + this.emit("drain"); }); } /** * Performs the write. * - * @api private + * @protected */ - doWrite(data, options, callback) { + protected doWrite(data, options, callback) { // explicit UTF-8 is required for pages not served under utf const isString = typeof data === "string"; const contentType = isString @@ -322,9 +311,9 @@ export class Polling extends Transport { /** * Compresses data. * - * @api private + * @private */ - compress(data, encoding, callback) { + private compress(data, encoding, callback) { debug("compressing"); const buffers = []; @@ -345,9 +334,9 @@ export class Polling extends Transport { /** * Closes the transport. * - * @api private + * @private */ - doClose(fn) { + override doClose(fn: () => void) { debug("closing"); let closeTimeoutTimer; @@ -380,13 +369,11 @@ export class Polling extends Transport { /** * Returns headers for a response. * - * @param {http.IncomingMessage} request - * @param {Object} extra headers - * @api private + * @param {http.IncomingMessage} req + * @param {Object} headers - extra headers + * @private */ - headers(req, headers) { - headers = headers || {}; - + private headers(req: IncomingMessage, headers: Record = {}) { // prevent XSS warnings on IE // https://github.com/LearnBoost/socket.io/pull/1333 const ua = req.headers["user-agent"]; diff --git a/lib/transports/websocket.ts b/lib/transports/websocket.ts index 57c4a7b3..71ac94a7 100644 --- a/lib/transports/websocket.ts +++ b/lib/transports/websocket.ts @@ -1,5 +1,6 @@ -import { Transport } from "../transport"; +import { EngineRequest, Transport } from "../transport"; import debugModule from "debug"; +import type { Packet, RawData } from "engine.io-parser"; const debug = debugModule("engine:ws"); @@ -10,10 +11,9 @@ export class WebSocket extends Transport { /** * WebSocket transport * - * @param {http.IncomingMessage} - * @api public + * @param {EngineRequest} req */ - constructor(req) { + constructor(req: EngineRequest) { super(req); this.socket = req.websocket; this.socket.on("message", (data, isBinary) => { @@ -29,8 +29,6 @@ export class WebSocket extends Transport { /** * Transport name - * - * @api public */ get name() { return "websocket"; @@ -38,70 +36,32 @@ export class WebSocket extends Transport { /** * Advertise upgrade support. - * - * @api public */ get handlesUpgrades() { return true; } - /** - * Advertise framing support. - * - * @api public - */ - get supportsFraming() { - return true; - } - - /** - * Writes a packet payload. - * - * @param {Array} packets - * @api private - */ - send(packets) { + send(packets: Packet[]) { this.writable = false; for (let i = 0; i < packets.length; i++) { const packet = packets[i]; const isLast = i + 1 === packets.length; - // always creates a new object since ws modifies it - const opts: { compress?: boolean } = {}; - if (packet.options) { - opts.compress = packet.options.compress; - } - - const onSent = (err) => { - if (err) { - return this.onError("write error", err.stack); - } else if (isLast) { - this.writable = true; - this.emit("drain"); - } - }; - - const send = (data) => { - if (this.perMessageDeflate) { - const len = - "string" === typeof data ? Buffer.byteLength(data) : data.length; - if (len < this.perMessageDeflate.threshold) { - opts.compress = false; - } - } - debug('writing "%s"', data); - this.socket.send(data, opts, onSent); - }; - - if (packet.options && typeof packet.options.wsPreEncoded === "string") { - send(packet.options.wsPreEncoded); - } else if (this._canSendPreEncodedFrame(packet)) { + if (this._canSendPreEncodedFrame(packet)) { // the WebSocket frame was computed with WebSocket.Sender.frame() // see https://github.com/websockets/ws/issues/617#issuecomment-283002469 - this.socket._sender.sendFrame(packet.options.wsPreEncodedFrame, onSent); + this.socket._sender.sendFrame( + // @ts-ignore + packet.options.wsPreEncodedFrame, + isLast ? this._onSentLast : this._onSent + ); } else { - this.parser.encodePacket(packet, this.supportsBinary, send); + this.parser.encodePacket( + packet, + this.supportsBinary, + isLast ? this._doSendLast : this._doSend + ); } } } @@ -111,20 +71,40 @@ export class WebSocket extends Transport { * @param packet * @private */ - private _canSendPreEncodedFrame(packet) { + private _canSendPreEncodedFrame(packet: Packet) { return ( !this.perMessageDeflate && typeof this.socket?._sender?.sendFrame === "function" && + // @ts-ignore packet.options?.wsPreEncodedFrame !== undefined ); } - /** - * Closes the transport. - * - * @api private - */ - doClose(fn) { + private _doSend = (data: RawData) => { + this.socket.send(data, this._onSent); + }; + + private _doSendLast = (data: RawData) => { + this.socket.send(data, this._onSentLast); + }; + + private _onSent = (err?: Error) => { + if (err) { + this.onError("write error", err.stack); + } + }; + + private _onSentLast = (err?: Error) => { + if (err) { + this.onError("write error", err.stack); + } else { + this.emit("drain"); + this.writable = true; + this.emit("ready"); + } + }; + + doClose(fn?: () => void) { debug("closing"); this.socket.close(); fn && fn(); diff --git a/lib/transports/webtransport.ts b/lib/transports/webtransport.ts index 5922fab0..07852282 100644 --- a/lib/transports/webtransport.ts +++ b/lib/transports/webtransport.ts @@ -44,10 +44,6 @@ export class WebTransport extends Transport { return "webtransport"; } - get supportsFraming() { - return true; - } - async send(packets) { this.writable = false; @@ -60,8 +56,9 @@ export class WebTransport extends Transport { debug("error while writing: %s", e.message); } - this.writable = true; this.emit("drain"); + this.writable = true; + this.emit("ready"); } doClose(fn) { diff --git a/lib/userver.ts b/lib/userver.ts index 98380fbd..ce56575a 100644 --- a/lib/userver.ts +++ b/lib/userver.ts @@ -30,7 +30,7 @@ export class uServer extends BaseServer { /** * Prepares a request by processing the query string. * - * @api private + * @private */ private prepare(req, res: HttpResponse) { req.method = req.getMethod().toUpperCase(); @@ -80,7 +80,7 @@ export class uServer extends BaseServer { const transport = ws.getUserData().transport; transport.socket = ws; transport.writable = true; - transport.emit("drain"); + transport.emit("ready"); }, message: (ws, message, isBinary) => { ws.getUserData().transport.onData( @@ -137,6 +137,7 @@ export class uServer extends BaseServer { if (req._query.sid) { debug("setting new request for existing client"); + // @ts-ignore this.clients[req._query.sid].transport.onRequest(req); } else { const closeConnection = (errorCode, errorContext) => @@ -194,7 +195,7 @@ export class uServer extends BaseServer { } else { debug("upgrading existing transport"); transport = this.createTransport(req._query.transport, req); - client.maybeUpgrade(transport); + client._maybeUpgrade(transport); } } else { transport = await this.handshake( diff --git a/package-lock.json b/package-lock.json index 7508dec7..cad4c345 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "engine.io", - "version": "6.5.1", + "version": "6.5.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "engine.io", - "version": "6.5.1", + "version": "6.5.4", "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", @@ -18,7 +18,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "devDependencies": { "@fails-components/webtransport": "^0.1.7", @@ -408,12 +408,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -828,6 +828,27 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/engine.io-parser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", @@ -997,9 +1018,9 @@ "dev": true }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2461,15 +2482,15 @@ "dev": true }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -2881,12 +2902,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -3131,6 +3152,13 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==", "dev": true + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "requires": {} } } }, @@ -3322,9 +3350,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -4385,9 +4413,9 @@ "dev": true }, "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "requires": {} }, "xmlhttprequest-ssl": { diff --git a/package.json b/package.json index aa53c4ac..b20c90f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "engine.io", - "version": "6.5.4", + "version": "6.6.0", "description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server", "type": "commonjs", "main": "./build/engine.io.js", @@ -41,7 +41,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "devDependencies": { "@fails-components/webtransport": "^0.1.7", diff --git a/test/server.js b/test/server.js index a373837e..129b07c3 100644 --- a/test/server.js +++ b/test/server.js @@ -2701,6 +2701,29 @@ describe("server", () => { }); }); + it("should execute when message sent during polling upgrade window", (done) => { + const engine = listen((port) => { + const socket = new ClientSocket(`ws://localhost:${port}`, { + transports: ["polling", "websocket"], + }); + + const partialDone = createPartialDone(() => { + engine.httpServer?.close(); + socket.close(); + done(); + }, 2); + + engine.on("connection", (conn) => { + conn.on("upgrading", () => { + conn.send("a", partialDone); + }); + }); + socket.on("open", () => { + socket.on("message", partialDone); + }); + }); + }); + it("should execute when message sent (websocket)", (done) => { const engine = listen({ allowUpgrades: false }, (port) => { const socket = new ClientSocket(`ws://localhost:${port}`, { @@ -2759,13 +2782,23 @@ describe("server", () => { }); }); - it("should execute in multipart packet", (done) => { + it("should execute in multipart packet (websocket)", (done) => { const engine = listen((port) => { - const socket = new ClientSocket(`ws://localhost:${port}`); + const socket = new ClientSocket(`ws://localhost:${port}`, { + transports: ["websocket"], + }); let i = 0; let j = 0; engine.on("connection", (conn) => { + conn.send("d", (transport) => { + i++; + }); + + conn.send("c", (transport) => { + i++; + }); + conn.send("b", (transport) => { i++; }); @@ -2879,25 +2912,6 @@ describe("server", () => { }); describe("pre-encoded content", () => { - it("should use the pre-encoded content", (done) => { - engine = listen((port) => { - client = new ClientSocket(`ws://localhost:${port}`, { - transports: ["websocket"], - }); - - engine.on("connection", (conn) => { - conn.send("test", { - wsPreEncoded: "4test pre-encoded", - }); - }); - - client.on("message", (msg) => { - expect(msg).to.be("test pre-encoded"); - done(); - }); - }); - }); - it("should use the pre-encoded frame", function (done) { if (process.env.EIO_WS_ENGINE === "uws") { return this.skip(); @@ -3346,68 +3360,6 @@ describe("server", () => { }); }); - describe("permessage-deflate", () => { - it("should set threshold", function (done) { - if (process.env.EIO_WS_ENGINE === "uws") { - return this.skip(); - } - const engine = listen( - { transports: ["websocket"], perMessageDeflate: { threshold: 0 } }, - (port) => { - engine.on("connection", (conn) => { - const socket = conn.transport.socket; - const send = socket.send; - socket.send = (data, opts, callback) => { - socket.send = send; - socket.send(data, opts, callback); - - expect(opts.compress).to.be(true); - conn.close(); - done(); - }; - - const buf = Buffer.allocUnsafe(100); - for (let i = 0; i < buf.length; i++) buf[i] = i % 0xff; - conn.send(buf, { compress: true }); - }); - new ClientSocket(`http://localhost:${port}`, { - transports: ["websocket"], - }); - } - ); - }); - - it("should not compress when the byte size is below threshold", function (done) { - if (process.env.EIO_WS_ENGINE === "uws") { - return this.skip(); - } - const engine = listen( - { transports: ["websocket"], perMessageDeflate: true }, - (port) => { - engine.on("connection", (conn) => { - const socket = conn.transport.socket; - const send = socket.send; - socket.send = (data, opts, callback) => { - socket.send = send; - socket.send(data, opts, callback); - - expect(opts.compress).to.be(false); - conn.close(); - done(); - }; - - const buf = Buffer.allocUnsafe(100); - for (let i = 0; i < buf.length; i++) buf[i] = i % 0xff; - conn.send(buf, { compress: true }); - }); - new ClientSocket(`http://localhost:${port}`, { - transports: ["websocket"], - }); - } - ); - }); - }); - describe("extraHeaders", function () { this.timeout(5000); diff --git a/test/webtransport.mjs b/test/webtransport.mjs index 3d54e554..10948ba2 100644 --- a/test/webtransport.mjs +++ b/test/webtransport.mjs @@ -373,6 +373,21 @@ describe("WebTransport", () => { }); }); + it("should invoke send callbacks (server to client)", (done) => { + setup({}, async ({ engine, h3Server, socket, reader }) => { + const messageCount = 4; + let receivedCallbacks = 0; + + for (let i = 0; i < messageCount; i++) { + socket.send("hello", () => { + if (++receivedCallbacks === messageCount) { + success(engine, h3Server, done); + } + }); + } + }); + }); + it("should send some binary data (client to server)", (done) => { setup({}, async ({ engine, h3Server, socket, writer }) => { socket.on("data", (data) => { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy