diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0ef66e1..79c974a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,13 +18,14 @@ jobs: matrix: node-version: - 18 + - 20 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 1df1a2ad..649a6014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,55 +1,126 @@ # History -## 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). + + +### Dependencies + +- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change) + + + +## [6.5.3](https://github.com/socketio/engine.io/compare/6.5.2...6.5.3) (2023-10-06) + + +### Bug Fixes + +* improve compatibility with node16 module resolution ([#689](https://github.com/socketio/engine.io/issues/689)) ([c6bf8c0](https://github.com/socketio/engine.io/commit/c6bf8c0f571aad7a5917f43860c8c3d74a9b429b)) +* **webtransport:** properly handle abruptly closed connections ([ff1c861](https://github.com/socketio/engine.io/commit/ff1c8615483bab25acc9cf04fb40339b0bd78812)) + + +### Dependencies + +- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change) + + + ## [6.5.2](https://github.com/socketio/engine.io/compare/6.5.1...6.5.2) (2023-08-01) 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 5cec3d61..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"); @@ -464,12 +470,6 @@ export abstract class BaseServer extends EventEmitter { } else if ("websocket" === transportName) { transport.perMessageDeflate = this.opts.perMessageDeflate; } - - if (req._query && req._query.b64) { - transport.supportsBinary = false; - } else { - transport.supportsBinary = true; - } } catch (e) { debug('error handshaking to transport "%s"', transportName); this.emit("connection_error", { @@ -596,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); } } @@ -666,7 +666,7 @@ export class Server extends BaseServer { /** * Initialize websocket server * - * @api protected + * @protected */ protected init() { if (!~this.opts.transports.indexOf("websocket")) return; @@ -713,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) => { @@ -751,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); } }; @@ -775,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 ) { @@ -824,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); @@ -862,13 +857,8 @@ export class Server extends BaseServer { websocket.removeListener("error", onUpgradeError); const transport = this.createTransport(req._query.transport, req); - if (req._query && req._query.b64) { - transport.supportsBinary = false; - } else { - transport.supportsBinary = true; - } transport.perMessageDeflate = this.opts.perMessageDeflate; - client.maybeUpgrade(transport); + client._maybeUpgrade(transport); } } else { const closeConnection = (errorCode, errorContext) => @@ -887,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); @@ -908,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; @@ -921,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 @@ -949,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) { @@ -974,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 7afb9622..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"); @@ -12,24 +12,49 @@ export interface SendOptions { compress?: boolean; } +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; - public _readyState: string; + /** + * The current state of the socket. + */ + public _readyState: ReadyState = "opening"; + /** + * The current low-level transport. + */ public transport: Transport; - private server: Server; - private upgrading: boolean; - private upgraded: boolean; - private writeBuffer: Packet[]; - private packetsFn: Array<() => void>; - private sentCallbackFn: any[]; - private cleanupFn: any[]; - private checkIntervalTimer; - private upgradeTimeoutTimer; + private server: BaseServer; + /* private */ upgrading = false; + /* private */ upgraded = false; + private writeBuffer: Packet[] = []; + private packetsFn: SendCallback[] = []; + private sentCallbackFn: SendCallback[][] = []; + private cleanupFn: any[] = []; private pingTimeoutTimer; private pingIntervalTimer; @@ -45,27 +70,21 @@ export class Socket extends EventEmitter { return this._readyState; } - set readyState(state) { + set readyState(state: ReadyState) { debug("readyState updated from %s to %s", this._readyState, state); 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; - this.upgrading = false; - this.upgraded = false; - this.readyState = "opening"; - this.writeBuffer = []; - this.packetsFn = []; - this.sentCallbackFn = []; - this.cleanupFn = []; this.request = req; this.protocol = protocol; @@ -81,8 +100,6 @@ export class Socket extends EventEmitter { // see https://github.com/fails-components/webtransport/issues/114 } - this.checkIntervalTimer = null; - this.upgradeTimeoutTimer = null; this.pingTimeoutTimer = null; this.pingIntervalTimer = null; @@ -93,7 +110,7 @@ export class Socket extends EventEmitter { /** * Called upon transport considered open. * - * @api private + * @private */ private onOpen() { this.readyState = "open"; @@ -119,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(); @@ -132,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) { @@ -142,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; @@ -183,10 +194,10 @@ export class Socket extends EventEmitter { /** * Called upon transport error. * - * @param {Error} error object - * @api private + * @param {Error} err - error object + * @private */ - private onError(err) { + private onError(err: Error) { debug("transport error"); this.onClose("transport error", err); } @@ -195,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(() => { @@ -204,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, @@ -265,7 +299,7 @@ export class Socket extends EventEmitter { this.upgrading = true; // set transport upgrade timer - this.upgradeTimeoutTimer = setTimeout(() => { + const upgradeTimeoutTimer = setTimeout(() => { debug("client did not complete upgrade - closing transport"); cleanup(); if ("open" === transport.readyState) { @@ -273,13 +307,15 @@ export class Socket extends EventEmitter { } }, this.server.opts.upgradeTimeout); + let checkIntervalTimer; + const onPacket = (packet) => { if ("ping" === packet.type && "probe" === packet.data) { debug("got probe ping packet, sending pong"); transport.send([{ type: "pong", data: "probe" }]); this.emit("upgrading", transport); - clearInterval(this.checkIntervalTimer); - this.checkIntervalTimer = setInterval(check, 100); + clearInterval(checkIntervalTimer); + checkIntervalTimer = setInterval(check, 100); } else if ("upgrade" === packet.type && this.readyState !== "closed") { debug("got upgrade packet - upgrading"); cleanup(); @@ -311,11 +347,8 @@ export class Socket extends EventEmitter { const cleanup = () => { this.upgrading = false; - clearInterval(this.checkIntervalTimer); - this.checkIntervalTimer = null; - - clearTimeout(this.upgradeTimeoutTimer); - this.upgradeTimeoutTimer = null; + clearInterval(checkIntervalTimer); + clearTimeout(upgradeTimeoutTimer); transport.removeListener("packet", onPacket); transport.removeListener("close", onTransportClose); @@ -348,7 +381,7 @@ export class Socket extends EventEmitter { /** * Clears listeners and timers associated with current transport. * - * @api private + * @private */ private clearTransport() { let cleanup; @@ -384,9 +417,6 @@ export class Socket extends EventEmitter { clearTimeout(this.pingIntervalTimer); clearTimeout(this.pingTimeoutTimer); - clearInterval(this.checkIntervalTimer); - this.checkIntervalTimer = null; - clearTimeout(this.upgradeTimeoutTimer); // clean writeBuffer in next tick, so developers can still // grab the writeBuffer on 'close' event process.nextTick(() => { @@ -399,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. * @@ -439,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; } @@ -453,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; } @@ -466,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; @@ -498,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(); } @@ -507,7 +503,7 @@ export class Socket extends EventEmitter { /** * Attempts to flush the packets buffer. * - * @api private + * @private */ private flush() { if ( @@ -520,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); @@ -535,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); @@ -556,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; @@ -583,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 4068a31e..5e74813c 100644 --- a/lib/transport.ts +++ b/lib/transport.ts @@ -2,35 +2,69 @@ 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; - public writable: boolean; + /** + * 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; - protected _readyState: string; - protected discarded: boolean; + /** + * 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() { return this._readyState; } - set readyState(state) { + set readyState(state: ReadyState) { debug( "readyState updated from %s to %s (%s)", this._readyState, @@ -43,21 +77,19 @@ export abstract class Transport extends EventEmitter { /** * Transport constructor. * - * @param {http.IncomingMessage} request - * @api public + * @param {EngineRequest} req */ - constructor(req) { + constructor(req: { _query: Record }) { super(); - this.readyState = "open"; - this.discarded = false; this.protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default this.parser = this.protocol === 4 ? parser_v4 : parser_v3; + this.supportsBinary = !(req._query && req._query.b64); } /** * Flags the transport as discarded. * - * @api private + * @package */ discard() { this.discarded = true; @@ -66,20 +98,17 @@ export abstract class Transport extends EventEmitter { /** * Called with an incoming HTTP request. * - * @param {http.IncomingMessage} request - * @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"; @@ -89,9 +118,9 @@ export abstract class Transport extends EventEmitter { /** * Called with a transport error. * - * @param {String} message error - * @param {Object} error description - * @api protected + * @param {String} msg - message error + * @param {Object} desc - error description + * @protected */ protected onError(msg: string, desc?) { if (this.listeners("error").length) { @@ -110,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); @@ -120,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. @@ -152,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 ce503a8a..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,26 +34,22 @@ 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; + // remove the reference to the ServerResponse object (as the first request of the session is kept in memory by default) + req.res = null; if (req.getMethod() === "get") { this.onPollRequest(req, res); @@ -69,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) { @@ -99,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) { @@ -111,7 +106,7 @@ export class Polling extends Transport { /** * The client sends a request with data. * - * @api private + * @private */ onDataRequest(req, res) { if (this.dataReq) { @@ -208,7 +203,7 @@ export class Polling extends Transport { /** * Cleanup request. * - * @api private + * @private */ private onDataRequestCleanup() { this.dataReq = this.dataRes = null; @@ -218,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); @@ -242,7 +237,7 @@ export class Polling extends Transport { /** * Overrides onClose. * - * @api private + * @private */ onClose() { if (this.writable) { @@ -256,7 +251,7 @@ export class Polling extends Transport { * Writes a packet payload. * * @param {Object} packet - * @api private + * @private */ send(packets) { this.writable = false; @@ -287,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 @@ -356,7 +352,7 @@ export class Polling extends Transport { /** * Compresses data. * - * @api private + * @private */ compress(data, encoding, callback) { debug("compressing"); @@ -379,7 +375,7 @@ export class Polling extends Transport { /** * Closes the transport. * - * @api private + * @private */ doClose(fn) { debug("closing"); @@ -411,7 +407,7 @@ export class Polling extends Transport { * * @param req - request * @param {Object} extra headers - * @api private + * @private */ headers(req, headers) { headers = headers || {}; @@ -423,6 +419,8 @@ export class Polling extends Transport { headers["X-XSS-Protection"] = "0"; } + headers["cache-control"] = "no-store"; + this.emit("headers", headers, req); return 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 70be3411..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,25 +35,21 @@ 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; if ("GET" === req.method) { this.onPollRequest(req, res); @@ -68,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' @@ -98,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) { @@ -110,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"); @@ -167,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)); @@ -184,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) { @@ -209,7 +205,7 @@ export class Polling extends Transport { /** * Overrides onClose. * - * @api private + * @private */ onClose() { if (this.writable) { @@ -219,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) { @@ -254,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 @@ -320,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 = []; @@ -343,9 +334,9 @@ export class Polling extends Transport { /** * Closes the transport. * - * @api private + * @private */ - doClose(fn) { + override doClose(fn: () => void) { debug("closing"); let closeTimeoutTimer; @@ -378,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"]; @@ -392,6 +381,8 @@ export class Polling extends Transport { headers["X-XSS-Protection"] = "0"; } + headers["cache-control"] = "no-store"; + this.emit("headers", headers, req); return headers; } 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 4f6f6877..07852282 100644 --- a/lib/transports/webtransport.ts +++ b/lib/transports/webtransport.ts @@ -14,7 +14,9 @@ export class WebTransport extends Transport { super({ _query: { EIO: "4" } }); const transformStream = createPacketEncoderStream(); - transformStream.readable.pipeTo(stream.writable); + transformStream.readable.pipeTo(stream.writable).catch(() => { + debug("the stream was closed"); + }); this.writer = transformStream.writable.getWriter(); (async () => { @@ -42,10 +44,6 @@ export class WebTransport extends Transport { return "webtransport"; } - get supportsFraming() { - return true; - } - async send(packets) { this.writable = false; @@ -58,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 5753cdc1..b20c90f8 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "engine.io", - "version": "6.5.2", + "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", "types": "./build/engine.io.d.ts", "exports": { + "types": "./build/engine.io.d.ts", "import": "./wrapper.mjs", "require": "./build/engine.io.js" }, @@ -40,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 fa0ab9c0..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); @@ -3443,13 +3395,12 @@ describe("server", () => { }); describe("response headers", () => { - function testForHeaders(headers, done) { + function testForHeaders(headers, callback) { const engine = listen((port) => { engine.on("connection", (conn) => { conn.transport.once("headers", (headers) => { - expect(headers["X-XSS-Protection"]).to.be("0"); + callback(headers); conn.close(); - done(); }); conn.send("hi"); }); @@ -3465,7 +3416,10 @@ describe("server", () => { "user-agent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; Tablet PC 2.0)", }; - testForHeaders(headers, done); + testForHeaders(headers, (headers) => { + expect(headers["X-XSS-Protection"]).to.be("0"); + done(); + }); }); it("should contain X-XSS-Protection: 0 for IE11", (done) => { @@ -3473,7 +3427,17 @@ describe("server", () => { "user-agent": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko", }; - testForHeaders(headers, done); + testForHeaders(headers, (headers) => { + expect(headers["X-XSS-Protection"]).to.be("0"); + done(); + }); + }); + + it("should include a 'cache-control' header", (done) => { + testForHeaders({}, (headers) => { + expect(headers["cache-control"]).to.be("no-store"); + done(); + }); }); it("should emit a 'initial_headers' event (polling)", (done) => { 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) => {