mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-10 15:37:58 -05:00
Merge remote-tracking branch 'engine.io/main' into monorepo
Source: https://github.com/socketio/engine.io
This commit is contained in:
4
packages/engine.io/.eslintrc.json
Normal file
4
packages/engine.io/.eslintrc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "prettier",
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
1
packages/engine.io/.prettierignore
Normal file
1
packages/engine.io/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
lib/parser-v3/
|
||||
851
packages/engine.io/CHANGELOG.md
Normal file
851
packages/engine.io/CHANGELOG.md
Normal file
@@ -0,0 +1,851 @@
|
||||
# History
|
||||
|
||||
| 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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **webtransport:** add proper framing ([a306db0](https://github.com/socketio/engine.io/commit/a306db09e8ddb367c7d62f45fec920f979580b7c))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.5.1](https://github.com/socketio/engine.io/compare/6.5.0...6.5.1) (2023-06-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent crash when accessing TextDecoder ([#684](https://github.com/socketio/engine.io/issues/684)) ([6dd2bc4](https://github.com/socketio/engine.io/commit/6dd2bc4f68edd7575c3844ae8ceadde649be95b2))
|
||||
|
||||
|
||||
### Credits
|
||||
|
||||
Huge thanks to [@iowaguy](https://github.com/iowaguy) for helping!
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.5.0](https://github.com/socketio/engine.io/compare/6.4.2...6.5.0) (2023-06-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **uws:** discard any write to an aborted uWS response ([#682](https://github.com/socketio/engine.io/issues/682)) ([3144d27](https://github.com/socketio/engine.io/commit/3144d274584ae3b96cca4e609c66c56d534f1715))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
#### Support for WebTransport
|
||||
|
||||
The Engine.IO server can now use WebTransport as the underlying transport.
|
||||
|
||||
WebTransport is a web API that uses the HTTP/3 protocol as a bidirectional transport. It's intended for two-way communications between a web client and an HTTP/3 server.
|
||||
|
||||
References:
|
||||
|
||||
- https://w3c.github.io/webtransport/
|
||||
- https://developer.mozilla.org/en-US/docs/Web/API/WebTransport
|
||||
- https://developer.chrome.com/articles/webtransport/
|
||||
|
||||
Until WebTransport support lands [in Node.js](https://github.com/nodejs/node/issues/38478), you can use the `@fails-components/webtransport` package:
|
||||
|
||||
```js
|
||||
import { readFileSync } from "fs";
|
||||
import { createServer } from "https";
|
||||
import { Server } from "engine.io";
|
||||
import { Http3Server } from "@fails-components/webtransport";
|
||||
|
||||
// WARNING: the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements)
|
||||
const cert = readFileSync("/path/to/my/cert.pem");
|
||||
const key = readFileSync("/path/to/my/key.pem");
|
||||
|
||||
const httpsServer = createServer({
|
||||
key,
|
||||
cert
|
||||
});
|
||||
|
||||
httpsServer.listen(3000);
|
||||
|
||||
const engine = new Server({
|
||||
transports: ["polling", "websocket", "webtransport"] // WebTransport is not enabled by default
|
||||
});
|
||||
|
||||
engine.attach(httpsServer);
|
||||
|
||||
const h3Server = new Http3Server({
|
||||
port: 3000,
|
||||
host: "0.0.0.0",
|
||||
secret: "changeit",
|
||||
cert,
|
||||
privKey: key,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
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);
|
||||
}
|
||||
})();
|
||||
|
||||
h3Server.startServer();
|
||||
```
|
||||
|
||||
Added in [123b68c](https://github.com/socketio/engine.io/commit/123b68c04f9e971f59b526e0f967a488ee6b0116).
|
||||
|
||||
|
||||
### Credits
|
||||
|
||||
Huge thanks to [@OxleyS](https://github.com/OxleyS) for helping!
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.4.2](https://github.com/socketio/engine.io/compare/6.4.1...6.4.2) (2023-05-02)
|
||||
|
||||
:warning: This release contains an important security fix :warning:
|
||||
|
||||
A malicious client could send a specially crafted HTTP request, triggering an uncaught exception and killing the Node.js process:
|
||||
|
||||
```
|
||||
TypeError: Cannot read properties of undefined (reading 'handlesUpgrades')
|
||||
at Server.onWebSocket (build/server.js:515:67)
|
||||
```
|
||||
|
||||
Please upgrade as soon as possible.
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include error handling for Express middlewares ([#674](https://github.com/socketio/engine.io/issues/674)) ([9395782](https://github.com/socketio/engine.io/commit/93957828be1252c83275b56f0c7c0bd145a0ceb9))
|
||||
* prevent crash when provided with an invalid query param ([fc480b4](https://github.com/socketio/engine.io/commit/fc480b4f305e16fe5972cf337d055e598372dc44))
|
||||
* **typings:** make clientsCount public ([#675](https://github.com/socketio/engine.io/issues/675)) ([bd6d471](https://github.com/socketio/engine.io/commit/bd6d4713b02ff646c581872cd9ffe753acff0d73))
|
||||
* **uws:** prevent crash when using with middlewares ([8b22162](https://github.com/socketio/engine.io/commit/8b2216290330b174c9e67be32765bec0c74769f9))
|
||||
|
||||
|
||||
### Credits
|
||||
|
||||
Huge thanks to [@tyilo](https://github.com/tyilo) and [@cieldeville](https://github.com/cieldeville) for helping!
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.4.1](https://github.com/socketio/engine.io/compare/6.4.0...6.4.1) (2023-02-20)
|
||||
|
||||
This release contains [6e78489](https://github.com/socketio/engine.io/commit/6e78489486f0d7570861fd6002a364d1ab87da4a), which exports the `BaseServer` class in order to restore the compatibility with the `nodenext` module resolution strategy of TypeScript.
|
||||
|
||||
Reference: https://www.typescriptlang.org/tsconfig/#moduleResolution
|
||||
|
||||
Related: https://github.com/socketio/socket.io/issues/4621
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
## [6.4.0](https://github.com/socketio/engine.io/compare/6.3.1...6.4.0) (2023-02-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for Express middlewares ([24786e7](https://github.com/socketio/engine.io/commit/24786e77c5403b1c4b5a2bc84e2af06f9187f74a))
|
||||
|
||||
This commit implements middlewares at the Engine.IO level, because Socket.IO middlewares are meant for namespace authorization and are not executed during a classic HTTP request/response cycle.
|
||||
|
||||
A workaround was possible by using the allowRequest option and the "headers" event, but this feels way cleaner and works with upgrade requests too.
|
||||
|
||||
Syntax:
|
||||
|
||||
```js
|
||||
engine.use((req, res, next) => {
|
||||
// do something
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// with express-session
|
||||
import session from "express-session";
|
||||
|
||||
engine.use(session({
|
||||
secret: "keyboard cat",
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: true }
|
||||
}));
|
||||
|
||||
// with helmet
|
||||
import helmet from "helmet";
|
||||
|
||||
engine.use(helmet());
|
||||
```
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.3.1](https://github.com/socketio/engine.io/compare/6.3.0...6.3.1) (2023-01-12)
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [6.3.0](https://github.com/socketio/engine.io/compare/6.2.1...6.3.0) (2023-01-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix the ES module wrapper ([ed87609](https://github.com/socketio/engine.io/commit/ed87609bafca0e844e6b29ea1a895d95df6a544c))
|
||||
* wait for all packets to be sent before closing the WebSocket connection ([a65a047](https://github.com/socketio/engine.io/commit/a65a047526401bebaa113a8c70d03f5d963eaa54))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add the "addTrailingSlash" option ([#655](https://github.com/socketio/engine.io/issues/655)) ([d0fd474](https://github.com/socketio/engine.io/commit/d0fd4746afa396297f07bb62e539b0c1c4018d7c))
|
||||
|
||||
The trailing slash which was added by default can now be disabled:
|
||||
|
||||
```js
|
||||
import { Server } from "engine.io";
|
||||
|
||||
const server = new Server();
|
||||
|
||||
server.attach(httpServer, {
|
||||
addTrailingSlash: false
|
||||
});
|
||||
```
|
||||
|
||||
In the example above, the clients can omit the trailing slash and use `/engine.io` instead of `/engine.io/`.
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* add the wsPreEncodedFrame option ([5e34722](https://github.com/socketio/engine.io/commit/5e34722b0b6564d6207a56d69bc3b0a831e4dc46))
|
||||
|
||||
This will be used when broadcasting packets at the Socket.IO level.
|
||||
|
||||
See also: https://github.com/socketio/socket.io-adapter/commit/5f7b47d40f9daabe4e3c321eda620bbadfe5ce96
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) ([diff](https://github.com/websockets/ws/compare/8.2.3...8.11.0))
|
||||
|
||||
|
||||
|
||||
## [3.6.1](https://github.com/socketio/engine.io/compare/3.6.0...3.6.1) (2022-11-20)
|
||||
|
||||
:warning: This release contains an important security fix :warning:
|
||||
|
||||
A malicious client could send a specially crafted HTTP request, triggering an uncaught exception and killing the Node.js process:
|
||||
|
||||
```
|
||||
Error: read ECONNRESET
|
||||
at TCP.onStreamRead (internal/stream_base_commons.js:209:20)
|
||||
Emitted 'error' event on Socket instance at:
|
||||
at emitErrorNT (internal/streams/destroy.js:106:8)
|
||||
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
|
||||
at processTicksAndRejections (internal/process/task_queues.js:80:21) {
|
||||
errno: -104,
|
||||
code: 'ECONNRESET',
|
||||
syscall: 'read'
|
||||
}
|
||||
```
|
||||
|
||||
Please upgrade as soon as possible.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch errors when destroying invalid upgrades ([83c4071](https://github.com/socketio/engine.io/commit/83c4071af871fc188298d7d591e95670bf9f9085))
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~7.4.2`](https://github.com/websockets/ws/releases/tag/7.4.2) (no change)
|
||||
|
||||
|
||||
## [6.2.1](https://github.com/socketio/engine.io/compare/6.2.0...6.2.1) (2022-11-20)
|
||||
|
||||
:warning: This release contains an important security fix :warning:
|
||||
|
||||
A malicious client could send a specially crafted HTTP request, triggering an uncaught exception and killing the Node.js process:
|
||||
|
||||
```
|
||||
Error: read ECONNRESET
|
||||
at TCP.onStreamRead (internal/stream_base_commons.js:209:20)
|
||||
Emitted 'error' event on Socket instance at:
|
||||
at emitErrorNT (internal/streams/destroy.js:106:8)
|
||||
at emitErrorCloseNT (internal/streams/destroy.js:74:3)
|
||||
at processTicksAndRejections (internal/process/task_queues.js:80:21) {
|
||||
errno: -104,
|
||||
code: 'ECONNRESET',
|
||||
syscall: 'read'
|
||||
}
|
||||
```
|
||||
|
||||
Please upgrade as soon as possible.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* catch errors when destroying invalid upgrades ([#658](https://github.com/socketio/engine.io/issues/658)) ([425e833](https://github.com/socketio/engine.io/commit/425e833ab13373edf1dd5a0706f07100db14e3c6))
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`ws@~8.2.3`](https://github.com/websockets/ws/releases/tag/8.2.3) (no change)
|
||||
|
||||
|
||||
|
||||
## [3.6.0](https://github.com/socketio/engine.io/compare/3.5.0...3.6.0) (2022-06-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add extension in the package.json main entry ([#608](https://github.com/socketio/engine.io/issues/608)) ([3ad0567](https://github.com/socketio/engine.io/commit/3ad0567dbd57cfb7c2ff4e8b7488d80f37022b4a))
|
||||
* do not reset the ping timer after upgrade ([1f5d469](https://github.com/socketio/engine.io/commit/1f5d4699862afee1e410fcb0e1f5e751ebcd2f9f)), closes [/github.com/socketio/socket.io-client-swift/pull/1309#issuecomment-768475704](https://github.com//github.com/socketio/socket.io-client-swift/pull/1309/issues/issuecomment-768475704)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* decrease the default value of maxHttpBufferSize ([58e274c](https://github.com/socketio/engine.io/commit/58e274c437e9cbcf69fd913c813aad8fbd253703))
|
||||
|
||||
This change reduces the default value from 100 mb to a more sane 1 mb.
|
||||
|
||||
This helps protect the server against denial of service attacks by malicious clients sending huge amounts of data.
|
||||
|
||||
See also: https://github.com/advisories/GHSA-j4f2-536g-r55m
|
||||
|
||||
* increase the default value of pingTimeout ([f55a79a](https://github.com/socketio/engine.io/commit/f55a79a28a5fbc6c9edae876dd11308b89cc979e))
|
||||
|
||||
|
||||
|
||||
## [6.2.0](https://github.com/socketio/engine.io/compare/6.1.3...6.2.0) (2022-04-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add the "maxPayload" field in the handshake details ([088dcb4](https://github.com/socketio/engine.io/commit/088dcb4dff60df39785df13d0a33d3ceaa1dff38))
|
||||
|
||||
So that clients in HTTP long-polling can decide how many packets they have to send to stay under the maxHttpBufferSize
|
||||
value.
|
||||
|
||||
This is a backward compatible change which should not mandate a new major revision of the protocol (we stay in v4), as
|
||||
we only add a field in the JSON-encoded handshake data:
|
||||
|
||||
```
|
||||
0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## [6.1.3](https://github.com/socketio/engine.io/compare/6.1.2...6.1.3) (2022-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** allow CorsOptionsDelegate as cors options ([#641](https://github.com/socketio/engine.io/issues/641)) ([a463d26](https://github.com/socketio/engine.io/commit/a463d268ed90064e7863679bda423951de108c36))
|
||||
* **uws:** properly handle chunked content ([#642](https://github.com/socketio/engine.io/issues/642)) ([3367440](https://github.com/socketio/engine.io/commit/33674403084c329dc6ad026c4122333a6f8a9992))
|
||||
|
||||
|
||||
|
||||
## [6.1.2](https://github.com/socketio/engine.io/compare/6.1.1...6.1.2) (2022-01-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **uws:** expose additional uWebSockets.js options ([#634](https://github.com/socketio/engine.io/issues/634)) ([49bb7cf](https://github.com/socketio/engine.io/commit/49bb7cf66518d4b49baf883a16ee1fe1ed8aed28))
|
||||
* **uws:** fix HTTP long-polling with CORS ([45112a3](https://github.com/socketio/engine.io/commit/45112a30d1af4cc25b21a5d658a748583cb64ed4))
|
||||
* **uws:** handle invalid websocket upgrades ([8b4d6a8](https://github.com/socketio/engine.io/commit/8b4d6a8176db72f5c2420c5a45f0d97d33af049b))
|
||||
|
||||
|
||||
|
||||
## [6.1.1](https://github.com/socketio/engine.io/compare/6.1.0...6.1.1) (2022-01-11)
|
||||
|
||||
:warning: This release contains an important security fix :warning:
|
||||
|
||||
A malicious client could send a specially crafted HTTP request, triggering an uncaught exception and killing the Node.js process:
|
||||
|
||||
> RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear
|
||||
> at Receiver.getInfo (/.../node_modules/ws/lib/receiver.js:176:14)
|
||||
> at Receiver.startLoop (/.../node_modules/ws/lib/receiver.js:136:22)
|
||||
> at Receiver._write (/.../node_modules/ws/lib/receiver.js:83:10)
|
||||
> at writeOrBuffer (internal/streams/writable.js:358:12)
|
||||
|
||||
This bug was introduced by [this commit](https://github.com/socketio/engine.io/commit/f3c291fa613a9d50c924d74293035737fdace4f2), included in `engine.io@4.0.0`, so previous releases are not impacted.
|
||||
|
||||
Thanks to Marcus Wejderot from Mevisio for the responsible disclosure.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly handle invalid data sent by a malicious websocket client ([c0e194d](https://github.com/socketio/engine.io/commit/c0e194d44933bd83bf9a4b126fca68ba7bf5098c))
|
||||
|
||||
|
||||
|
||||
## [6.1.0](https://github.com/socketio/engine.io/compare/6.0.0...6.1.0) (2021-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix payload encoding for v3 clients ([ed50fc3](https://github.com/socketio/engine.io/commit/ed50fc346b9c58459bf4e6fe5c45e8d34faac8da))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add an implementation based on uWebSockets.js ([271e2df](https://github.com/socketio/engine.io/commit/271e2df94d39bbd13c33cab98cdd5915f9d28536))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* refresh ping timer ([#628](https://github.com/socketio/engine.io/issues/628)) ([37474c7](https://github.com/socketio/engine.io/commit/37474c7e67be7c5f25f9ca2d4ea99f3a256bd2de))
|
||||
|
||||
|
||||
|
||||
## [6.0.1](https://github.com/socketio/engine.io/compare/6.0.0...6.0.1) (2021-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix payload encoding for v3 clients ([3f42262](https://github.com/socketio/engine.io/commit/3f42262fd27a77a7383cdbb44ede7c6211a9782b))
|
||||
|
||||
|
||||
|
||||
## [6.0.0](https://github.com/socketio/engine.io/compare/5.2.0...6.0.0) (2021-10-08)
|
||||
|
||||
The codebase was migrated to TypeScript ([c0d6eaa](https://github.com/socketio/engine.io/commit/c0d6eaa1ba1291946dc8425d5f533d5f721862dd))
|
||||
|
||||
An ES module wrapper was also added ([401f4b6](https://github.com/socketio/engine.io/commit/401f4b60693fb6702c942692ce42e5bb701d81d7)).
|
||||
|
||||
Please note that the communication protocol was not updated, so a v5 client will be able to reach a v6 server (and vice-versa).
|
||||
|
||||
Reference: https://github.com/socketio/engine.io-protocol
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- the default export was removed, so the following code won't work anymore:
|
||||
|
||||
```js
|
||||
const eioServer = require("engine.io")(httpServer);
|
||||
```
|
||||
|
||||
Please use this instead:
|
||||
|
||||
```js
|
||||
const { Server } = require("engine.io");
|
||||
const eioServer = new Server(httpServer);
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
`ws` version: `~8.2.3` (bumped from `~7.4.2`)
|
||||
|
||||
## [5.2.0](https://github.com/socketio/engine.io/compare/5.1.1...5.2.0) (2021-08-29)
|
||||
|
||||
No change on the server-side, this matches the client release.
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/socketio/engine.io/compare/5.1.0...5.1.1) (2021-05-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly close the websocket connection upon handshake error ([4360686](https://github.com/socketio/engine.io/commit/43606865e5299747cbb31f3ed9baf4567502a879))
|
||||
|
||||
|
||||
## [5.1.0](https://github.com/socketio/engine.io/compare/5.0.0...5.1.0) (2021-05-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a "connection_error" event ([7096e98](https://github.com/socketio/engine.io/commit/7096e98a02295a62c8ea2aa56461d4875887092d))
|
||||
* add the "initial_headers" and "headers" events ([2527543](https://github.com/socketio/engine.io/commit/252754353a0e88eb036ebb3082e9d6a9a5f497db))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **websocket:** add a "wsPreEncoded" writing option ([7706b12](https://github.com/socketio/engine.io/commit/7706b123df914777d19c8179b45ab6932f82916c))
|
||||
* **websocket:** fix write back-pressure ([#618](https://github.com/socketio/engine.io/issues/618)) ([ad5306a](https://github.com/socketio/engine.io/commit/ad5306aeaedf06ac7a49f791e1b76e55c35a564e))
|
||||
|
||||
|
||||
## [5.0.0](https://github.com/socketio/engine.io/compare/4.1.1...5.0.0) (2021-03-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* set default protocol version to 3 ([#616](https://github.com/socketio/engine.io/issues/616)) ([868d891](https://github.com/socketio/engine.io/commit/868d89111de0ab5bd0e147ecaff7983afbf5d087))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* increase the default value of pingTimeout ([5a7fa13](https://github.com/socketio/engine.io/commit/5a7fa132c442bc1e7eefa1cf38168ee951575ded))
|
||||
* remove dynamic require() with wsEngine ([edb7343](https://github.com/socketio/engine.io/commit/edb734316f143bf0f1bbc344e966d18e2676b934))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* the syntax of the "wsEngine" option is updated
|
||||
|
||||
Before:
|
||||
|
||||
```js
|
||||
const eioServer = require("engine.io")(httpServer, {
|
||||
wsEngine: "eiows"
|
||||
});
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
const eioServer = require("engine.io")(httpServer, {
|
||||
wsEngine: require("eiows").Server
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## [4.1.1](https://github.com/socketio/engine.io/compare/4.1.0...4.1.1) (2021-02-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not reset the ping timer after upgrade ([ff2b8ab](https://github.com/socketio/engine.io/commit/ff2b8aba48ebcb0de5626d3b76fddc94c398395f)), closes [/github.com/socketio/socket.io-client-swift/pull/1309#issuecomment-768475704](https://github.com//github.com/socketio/socket.io-client-swift/pull/1309/issues/issuecomment-768475704)
|
||||
|
||||
|
||||
## [4.1.0](https://github.com/socketio/engine.io/compare/4.0.6...4.1.0) (2021-01-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for v3.x clients ([663d326](https://github.com/socketio/engine.io/commit/663d326d18de598318bd2120b2b70cd51adf8955))
|
||||
|
||||
|
||||
## [4.0.6](https://github.com/socketio/engine.io/compare/4.0.5...4.0.6) (2021-01-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correctly pass the options when using the Server constructor ([#610](https://github.com/socketio/engine.io/issues/610)) ([cec2750](https://github.com/socketio/engine.io/commit/cec27502f5b55c8a2ff289db34019629bf6a97ca))
|
||||
|
||||
|
||||
|
||||
## [3.5.0](https://github.com/socketio/engine.io/compare/3.4.2...3.5.0) (2020-12-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for all cookie options ([19cc582](https://github.com/socketio/engine.io/commit/19cc58264a06dca47ed401fbaca32dcdb80a903b)), closes [/github.com/jshttp/cookie#options-1](https://github.com//github.com/jshttp/cookie/issues/options-1)
|
||||
* disable perMessageDeflate by default ([5ad2736](https://github.com/socketio/engine.io/commit/5ad273601eb66c7b318542f87026837bf9dddd21))
|
||||
|
||||
|
||||
|
||||
## [4.0.5](https://github.com/socketio/engine.io/compare/4.0.4...4.0.5) (2020-12-07)
|
||||
|
||||
No change on the server-side, this matches the client release.
|
||||
|
||||
## [4.0.4](https://github.com/socketio/engine.io/compare/4.0.3...4.0.4) (2020-11-17)
|
||||
|
||||
No change on the server-side, this matches the client release.
|
||||
|
||||
## [4.0.3](https://github.com/socketio/engine.io/compare/4.0.2...4.0.3) (2020-11-17)
|
||||
|
||||
No change on the server-side, this matches the client release.
|
||||
|
||||
## [4.0.2](https://github.com/socketio/engine.io/compare/4.0.1...4.0.2) (2020-11-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add extension in the package.json main entry ([#608](https://github.com/socketio/engine.io/issues/608)) ([17b8c2f](https://github.com/socketio/engine.io/commit/17b8c2f199e7a307b6d6294b8599abacb3ec56e7))
|
||||
|
||||
|
||||
## [4.0.1](https://github.com/socketio/engine.io/compare/4.0.0...4.0.1) (2020-10-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not overwrite CORS headers upon error ([fe093ba](https://github.com/socketio/engine.io/commit/fe093bae1adce99e01dfdd3ce7542957785098b5))
|
||||
|
||||
|
||||
|
||||
## [4.0.0](https://github.com/socketio/engine.io/compare/v4.0.0-alpha.1...4.0.0) (2020-09-10)
|
||||
|
||||
More details about this release in the blog post: https://socket.io/blog/engine-io-4-release/
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ignore errors when forcefully closing the socket ([#601](https://github.com/socketio/engine.io/issues/601)) ([dcdbccb](https://github.com/socketio/engine.io/commit/dcdbccb3dd8a7b7db057d23925356034fcd35d48))
|
||||
* remove implicit require of uws ([82cdca2](https://github.com/socketio/engine.io/commit/82cdca23bab0ed69b61b60961900d456a3065e6a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* disable perMessageDeflate by default ([078527a](https://github.com/socketio/engine.io/commit/078527a384b70dc46d99083fa218be5d45213e51))
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [v4.0.0-alpha.1...4.0.0](https://github.com/socketio/engine.io/compare/v4.0.0-alpha.1...4.0.0)
|
||||
- Full diff: [3.4.0...4.0.0](https://github.com/socketio/engine.io/compare/3.4.0...4.0.0)
|
||||
- Client release: [4.0.0](https://github.com/socketio/engine.io-client/releases/tag/4.0.0)
|
||||
- ws version: [^7.1.2](https://github.com/websockets/ws/releases/tag/7.1.2)
|
||||
|
||||
|
||||
## [3.4.2](https://github.com/socketio/engine.io/compare/3.4.1...3.4.2) (2020-06-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove explicit require of uws ([85e544a](https://github.com/socketio/engine.io/commit/85e544afd95a5890761a613263a5eba0c9a18a93))
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [3.4.1...3.4.2](https://github.com/socketio/engine.io/compare/3.4.1...3.4.2)
|
||||
- Client release: -
|
||||
- ws version: [^7.1.2](https://github.com/websockets/ws/releases/tag/7.1.2)
|
||||
|
||||
|
||||
|
||||
## [3.4.1](https://github.com/socketio/engine.io/compare/3.4.0...3.4.1) (2020-04-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ignore errors when forcefully closing the socket ([da851ec](https://github.com/socketio/engine.io/commit/da851ec4ec89d96df2ee5c711f328b5d795423e9))
|
||||
* use SameSite=Strict by default ([001ca62](https://github.com/socketio/engine.io/commit/001ca62cc4a8f511f3b2fbd9e4493ad274a6a0e5))
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [3.4.0...3.4.1](https://github.com/socketio/engine.io/compare/3.4.0...3.4.1)
|
||||
- Client release: [3.4.1](https://github.com/socketio/engine.io-client/releases/tag/3.4.1)
|
||||
- ws version: [^7.1.2](https://github.com/websockets/ws/releases/tag/7.1.2)
|
||||
|
||||
|
||||
|
||||
## [4.0.0-alpha.1](https://github.com/socketio/engine.io/compare/v4.0.0-alpha.0...v4.0.0-alpha.1) (2020-02-12)
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [v4.0.0-alpha.0...v4.0.0-alpha.1](https://github.com/socketio/engine.io-client/compare/v4.0.0-alpha.0...v4.0.0-alpha.1)
|
||||
- Client release: [v4.0.0-alpha.1](https://github.com/socketio/engine.io-client/releases/tag/v4.0.0-alpha.1)
|
||||
- ws version: [^7.1.2](https://github.com/websockets/ws/releases/tag/7.1.2)
|
||||
|
||||
|
||||
|
||||
## [4.0.0-alpha.0](https://github.com/socketio/engine.io/compare/3.4.0...v4.0.0-alpha.0) (2020-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* decrease the default value of maxHttpBufferSize ([734f9d1](https://github.com/socketio/engine.io/commit/734f9d1268840722c41219e69eb58318e0b2ac6b))
|
||||
* disable cookie by default and add sameSite attribute ([a374471](https://github.com/socketio/engine.io/commit/a374471d06e3681a769766a1d068898182f9305f)), closes [/github.com/jshttp/cookie#options-1](https://github.com//github.com/jshttp/cookie/issues/options-1)
|
||||
* generateId method can now return a Promise ([f3c291f](https://github.com/socketio/engine.io/commit/f3c291fa613a9d50c924d74293035737fdace4f2))
|
||||
* reverse the ping-pong mechanism ([31ff875](https://github.com/socketio/engine.io/commit/31ff87593f231b86dc47ec5761936439ebd53c20))
|
||||
* use the cors module to handle cross-origin requests ([61b9492](https://github.com/socketio/engine.io/commit/61b949259ed966ef6fc8bfd61f14d1a2ef06d319))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* the handlePreflightRequest option is removed by the change.
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
new Server({
|
||||
handlePreflightRequest: (req, res) => {
|
||||
res.writeHead(200, {
|
||||
"Access-Control-Allow-Origin": 'https://example.com',
|
||||
"Access-Control-Allow-Methods": 'GET',
|
||||
"Access-Control-Allow-Headers": 'Authorization',
|
||||
"Access-Control-Allow-Credentials": true
|
||||
});
|
||||
res.end();
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
new Server({
|
||||
cors: {
|
||||
origin: "https://example.com",
|
||||
methods: ["GET"],
|
||||
allowedHeaders: ["Authorization"],
|
||||
credentials: true
|
||||
}
|
||||
})
|
||||
```
|
||||
* the syntax has changed from
|
||||
|
||||
```
|
||||
new Server({
|
||||
cookieName: "test",
|
||||
cookieHttpOnly: false,
|
||||
cookiePath: "/custom"
|
||||
})
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```
|
||||
new Server({
|
||||
cookie: {
|
||||
name: "test",
|
||||
httpOnly: false,
|
||||
path: "/custom"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
All other options (domain, maxAge, sameSite, ...) are now supported.
|
||||
|
||||
* v3.x clients will not be able to connect anymore (they will send a ping packet and timeout while waiting for a pong packet).
|
||||
|
||||
#### Links
|
||||
|
||||
- Diff: [3.4.0...v4.0.0-alpha.0](https://github.com/socketio/engine.io-client/compare/3.4.0...v4.0.0-alpha.0)
|
||||
- Client release: [v4.0.0-alpha.0](https://github.com/socketio/engine.io-client/releases/tag/v4.0.0-alpha.0)
|
||||
- ws version: [^7.1.2](https://github.com/websockets/ws/releases/tag/7.1.2)
|
||||
|
||||
19
packages/engine.io/LICENSE
Normal file
19
packages/engine.io/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Guillermo Rauch <guillermo@learnboost.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the 'Software'), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
603
packages/engine.io/README.md
Normal file
603
packages/engine.io/README.md
Normal file
@@ -0,0 +1,603 @@
|
||||
|
||||
# Engine.IO: the realtime engine
|
||||
|
||||
[](https://github.com/socketio/engine.io/actions)
|
||||
[](http://badge.fury.io/js/engine.io)
|
||||
|
||||
`Engine.IO` is the implementation of transport-based
|
||||
cross-browser/cross-device bi-directional communication layer for
|
||||
[Socket.IO](http://github.com/socketio/socket.io).
|
||||
|
||||
## How to use
|
||||
|
||||
### Server
|
||||
|
||||
#### (A) Listening on a port
|
||||
|
||||
```js
|
||||
const engine = require('engine.io');
|
||||
const server = engine.listen(80);
|
||||
|
||||
server.on('connection', socket => {
|
||||
socket.send('utf 8 string');
|
||||
socket.send(Buffer.from([0, 1, 2, 3, 4, 5])); // binary data
|
||||
});
|
||||
```
|
||||
|
||||
#### (B) Intercepting requests for a http.Server
|
||||
|
||||
```js
|
||||
const engine = require('engine.io');
|
||||
const http = require('http').createServer().listen(3000);
|
||||
const server = engine.attach(http);
|
||||
|
||||
server.on('connection', socket => {
|
||||
socket.on('message', data => { });
|
||||
socket.on('close', () => { });
|
||||
});
|
||||
```
|
||||
|
||||
#### (C) Passing in requests
|
||||
|
||||
```js
|
||||
const engine = require('engine.io');
|
||||
const server = new engine.Server();
|
||||
|
||||
server.on('connection', socket => {
|
||||
socket.send('hi');
|
||||
});
|
||||
|
||||
// …
|
||||
httpServer.on('upgrade', (req, socket, head) => {
|
||||
server.handleUpgrade(req, socket, head);
|
||||
});
|
||||
|
||||
httpServer.on('request', (req, res) => {
|
||||
server.handleRequest(req, res);
|
||||
});
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
```html
|
||||
<script src="/path/to/engine.io.js"></script>
|
||||
<script>
|
||||
const socket = new eio.Socket('ws://localhost/');
|
||||
socket.on('open', () => {
|
||||
socket.on('message', data => {});
|
||||
socket.on('close', () => {});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
For more information on the client refer to the
|
||||
[engine-client](http://github.com/socketio/engine.io-client) repository.
|
||||
|
||||
## What features does it have?
|
||||
|
||||
- **Maximum reliability**. Connections are established even in the presence of:
|
||||
- proxies and load balancers.
|
||||
- personal firewall and antivirus software.
|
||||
- for more information refer to **Goals** and **Architecture** sections
|
||||
- **Minimal client size** aided by:
|
||||
- lazy loading of flash transports.
|
||||
- lack of redundant transports.
|
||||
- **Scalable**
|
||||
- load balancer friendly
|
||||
- **Future proof**
|
||||
- **100% Node.JS core style**
|
||||
- No API sugar (left for higher level projects)
|
||||
|
||||
## API
|
||||
|
||||
### Server
|
||||
|
||||
<hr><br>
|
||||
|
||||
#### Top-level
|
||||
|
||||
These are exposed by `require('engine.io')`:
|
||||
|
||||
##### Events
|
||||
|
||||
- `flush`
|
||||
- Called when a socket buffer is being flushed.
|
||||
- **Arguments**
|
||||
- `Socket`: socket being flushed
|
||||
- `Array`: write buffer
|
||||
- `drain`
|
||||
- Called when a socket buffer is drained
|
||||
- **Arguments**
|
||||
- `Socket`: socket being flushed
|
||||
|
||||
##### Properties
|
||||
|
||||
- `protocol` _(Number)_: protocol revision number
|
||||
- `Server`: Server class constructor
|
||||
- `Socket`: Socket class constructor
|
||||
- `Transport` _(Function)_: transport constructor
|
||||
- `transports` _(Object)_: map of available transports
|
||||
|
||||
##### Methods
|
||||
|
||||
- `()`
|
||||
- Returns a new `Server` instance. If the first argument is an `http.Server` then the
|
||||
new `Server` instance will be attached to it. Otherwise, the arguments are passed
|
||||
directly to the `Server` constructor.
|
||||
- **Parameters**
|
||||
- `http.Server`: optional, server to attach to.
|
||||
- `Object`: optional, options object (see `Server#constructor` api docs below)
|
||||
|
||||
The following are identical ways to instantiate a server and then attach it.
|
||||
|
||||
```js
|
||||
const httpServer; // previously created with `http.createServer();` from node.js api.
|
||||
|
||||
// create a server first, and then attach
|
||||
const eioServer = require('engine.io').Server();
|
||||
eioServer.attach(httpServer);
|
||||
|
||||
// or call the module as a function to get `Server`
|
||||
const eioServer = require('engine.io')();
|
||||
eioServer.attach(httpServer);
|
||||
|
||||
// immediately attach
|
||||
const eioServer = require('engine.io')(httpServer);
|
||||
|
||||
// with custom options
|
||||
const eioServer = require('engine.io')(httpServer, {
|
||||
maxHttpBufferSize: 1e3
|
||||
});
|
||||
```
|
||||
|
||||
- `listen`
|
||||
- Creates an `http.Server` which listens on the given port and attaches WS
|
||||
to it. It returns `501 Not Implemented` for regular http requests.
|
||||
- **Parameters**
|
||||
- `Number`: port to listen on.
|
||||
- `Object`: optional, options object
|
||||
- `Function`: callback for `listen`.
|
||||
- **Options**
|
||||
- All options from `Server.attach` method, documented below.
|
||||
- **Additionally** See Server `constructor` below for options you can pass for creating the new Server
|
||||
- **Returns** `Server`
|
||||
|
||||
```js
|
||||
const engine = require('engine.io');
|
||||
const server = engine.listen(3000, {
|
||||
pingTimeout: 2000,
|
||||
pingInterval: 10000
|
||||
});
|
||||
|
||||
server.on('connection', /* ... */);
|
||||
```
|
||||
|
||||
- `attach`
|
||||
- Captures `upgrade` requests for a `http.Server`. In other words, makes
|
||||
a regular http.Server WebSocket-compatible.
|
||||
- **Parameters**
|
||||
- `http.Server`: server to attach to.
|
||||
- `Object`: optional, options object
|
||||
- **Options**
|
||||
- All options from `Server.attach` method, documented below.
|
||||
- **Additionally** See Server `constructor` below for options you can pass for creating the new Server
|
||||
- **Returns** `Server` a new Server instance.
|
||||
|
||||
```js
|
||||
const engine = require('engine.io');
|
||||
const httpServer = require('http').createServer().listen(3000);
|
||||
const server = engine.attach(httpServer, {
|
||||
wsEngine: require('eiows').Server // requires having eiows as dependency
|
||||
});
|
||||
|
||||
server.on('connection', /* ... */);
|
||||
```
|
||||
|
||||
#### Server
|
||||
|
||||
The main server/manager. _Inherits from EventEmitter_.
|
||||
|
||||
##### Events
|
||||
|
||||
- `connection`
|
||||
- Fired when a new connection is established.
|
||||
- **Arguments**
|
||||
- `Socket`: a Socket object
|
||||
|
||||
- `initial_headers`
|
||||
- Fired on the first request of the connection, before writing the response headers
|
||||
- **Arguments**
|
||||
- `headers` (`Object`): a hash of headers
|
||||
- `req` (`http.IncomingMessage`): the request
|
||||
|
||||
- `headers`
|
||||
- Fired on the all requests of the connection, before writing the response headers
|
||||
- **Arguments**
|
||||
- `headers` (`Object`): a hash of headers
|
||||
- `req` (`http.IncomingMessage`): the request
|
||||
|
||||
- `connection_error`
|
||||
- Fired when an error occurs when establishing the connection.
|
||||
- **Arguments**
|
||||
- `error`: an object with following properties:
|
||||
- `req` (`http.IncomingMessage`): the request that was dropped
|
||||
- `code` (`Number`): one of `Server.errors`
|
||||
- `message` (`string`): one of `Server.errorMessages`
|
||||
- `context` (`Object`): extra info about the error
|
||||
|
||||
| Code | Message |
|
||||
| ---- | ------- |
|
||||
| 0 | "Transport unknown"
|
||||
| 1 | "Session ID unknown"
|
||||
| 2 | "Bad handshake method"
|
||||
| 3 | "Bad request"
|
||||
| 4 | "Forbidden"
|
||||
| 5 | "Unsupported protocol version"
|
||||
|
||||
|
||||
##### Properties
|
||||
|
||||
**Important**: if you plan to use Engine.IO in a scalable way, please
|
||||
keep in mind the properties below will only reflect the clients connected
|
||||
to a single process.
|
||||
|
||||
- `clients` _(Object)_: hash of connected clients by id.
|
||||
- `clientsCount` _(Number)_: number of connected clients.
|
||||
|
||||
##### Methods
|
||||
|
||||
- **constructor**
|
||||
- Initializes the server
|
||||
- **Parameters**
|
||||
- `Object`: optional, options object
|
||||
- **Options**
|
||||
- `pingTimeout` (`Number`): how many ms without a pong packet to
|
||||
consider the connection closed (`20000`)
|
||||
- `pingInterval` (`Number`): how many ms before sending a new ping
|
||||
packet (`25000`)
|
||||
- `upgradeTimeout` (`Number`): how many ms before an uncompleted transport upgrade is cancelled (`10000`)
|
||||
- `maxHttpBufferSize` (`Number`): how many bytes or characters a message
|
||||
can be, before closing the session (to avoid DoS). Default
|
||||
value is `1E6`.
|
||||
- `allowRequest` (`Function`): A function that receives a given handshake
|
||||
or upgrade request as its first parameter, and can decide whether to
|
||||
continue or not. The second argument is a function that needs to be
|
||||
called with the decided information: `fn(err, success)`, where
|
||||
`success` is a boolean value where false means that the request is
|
||||
rejected, and err is an error code.
|
||||
- `transports` (`<Array> String`): transports to allow connections
|
||||
to (`['polling', 'websocket']`)
|
||||
- `allowUpgrades` (`Boolean`): whether to allow transport upgrades
|
||||
(`true`)
|
||||
- `perMessageDeflate` (`Object|Boolean`): parameters of the WebSocket permessage-deflate extension
|
||||
(see [ws module](https://github.com/einaros/ws) api docs). Set to `true` to enable. (defaults to `false`)
|
||||
- `threshold` (`Number`): data is compressed only if the byte size is above this value (`1024`)
|
||||
- `httpCompression` (`Object|Boolean`): parameters of the http compression for the polling transports
|
||||
(see [zlib](http://nodejs.org/api/zlib.html#zlib_options) api docs). Set to `false` to disable. (`true`)
|
||||
- `threshold` (`Number`): data is compressed only if the byte size is above this value (`1024`)
|
||||
- `cookie` (`Object|Boolean`): configuration of the cookie that
|
||||
contains the client sid to send as part of handshake response
|
||||
headers. This cookie might be used for sticky-session. Defaults to not sending any cookie (`false`).
|
||||
See [here](https://github.com/jshttp/cookie#options-1) for all supported options.
|
||||
- `wsEngine` (`Function`): what WebSocket server implementation to use. Specified module must conform to the `ws` interface (see [ws module api docs](https://github.com/websockets/ws/blob/master/doc/ws.md)). Default value is `ws`. An alternative c++ addon is also available by installing `eiows` module.
|
||||
- `cors` (`Object`): the options that will be forwarded to the cors module. See [there](https://github.com/expressjs/cors#configuration-options) for all available options. Defaults to no CORS allowed.
|
||||
- `initialPacket` (`Object`): an optional packet which will be concatenated to the handshake packet emitted by Engine.IO.
|
||||
- `allowEIO3` (`Boolean`): whether to support v3 Engine.IO clients (defaults to `false`)
|
||||
- `close`
|
||||
- Closes all clients
|
||||
- **Returns** `Server` for chaining
|
||||
- `handleRequest`
|
||||
- Called internally when a `Engine` request is intercepted.
|
||||
- **Parameters**
|
||||
- `http.IncomingMessage`: a node request object
|
||||
- `http.ServerResponse`: a node response object
|
||||
- **Returns** `Server` for chaining
|
||||
- `handleUpgrade`
|
||||
- Called internally when a `Engine` ws upgrade is intercepted.
|
||||
- **Parameters** (same as `upgrade` event)
|
||||
- `http.IncomingMessage`: a node request object
|
||||
- `net.Stream`: TCP socket for the request
|
||||
- `Buffer`: legacy tail bytes
|
||||
- **Returns** `Server` for chaining
|
||||
- `attach`
|
||||
- Attach this Server instance to an `http.Server`
|
||||
- Captures `upgrade` requests for a `http.Server`. In other words, makes
|
||||
a regular http.Server WebSocket-compatible.
|
||||
- **Parameters**
|
||||
- `http.Server`: server to attach to.
|
||||
- `Object`: optional, options object
|
||||
- **Options**
|
||||
- `path` (`String`): name of the path to capture (`/engine.io`).
|
||||
- `destroyUpgrade` (`Boolean`): destroy unhandled upgrade requests (`true`)
|
||||
- `destroyUpgradeTimeout` (`Number`): milliseconds after which unhandled requests are ended (`1000`)
|
||||
- `generateId`
|
||||
- Generate a socket id.
|
||||
- Overwrite this method to generate your custom socket id.
|
||||
- **Parameters**
|
||||
- `http.IncomingMessage`: a node request object
|
||||
- **Returns** A socket id for connected client.
|
||||
|
||||
<hr><br>
|
||||
|
||||
#### Socket
|
||||
|
||||
A representation of a client. _Inherits from EventEmitter_.
|
||||
|
||||
##### Events
|
||||
|
||||
- `close`
|
||||
- Fired when the client is disconnected.
|
||||
- **Arguments**
|
||||
- `String`: reason for closing
|
||||
- `Object`: description object (optional)
|
||||
- `message`
|
||||
- Fired when the client sends a message.
|
||||
- **Arguments**
|
||||
- `String` or `Buffer`: Unicode string or Buffer with binary contents
|
||||
- `error`
|
||||
- Fired when an error occurs.
|
||||
- **Arguments**
|
||||
- `Error`: error object
|
||||
- `upgrading`
|
||||
- Fired when the client starts the upgrade to a better transport like WebSocket.
|
||||
- **Arguments**
|
||||
- `Object`: the transport
|
||||
- `upgrade`
|
||||
- Fired when the client completes the upgrade to a better transport like WebSocket.
|
||||
- **Arguments**
|
||||
- `Object`: the transport
|
||||
- `flush`
|
||||
- Called when the write buffer is being flushed.
|
||||
- **Arguments**
|
||||
- `Array`: write buffer
|
||||
- `drain`
|
||||
- Called when the write buffer is drained
|
||||
- `packet`
|
||||
- Called when a socket received a packet (`message`, `ping`)
|
||||
- **Arguments**
|
||||
- `type`: packet type
|
||||
- `data`: packet data (if type is message)
|
||||
- `packetCreate`
|
||||
- Called before a socket sends a packet (`message`, `ping`)
|
||||
- **Arguments**
|
||||
- `type`: packet type
|
||||
- `data`: packet data (if type is message)
|
||||
- `heartbeat`
|
||||
- Called when `ping` or `pong` packed is received (depends of client version)
|
||||
|
||||
##### Properties
|
||||
|
||||
- `id` _(String)_: unique identifier
|
||||
- `server` _(Server)_: engine parent reference
|
||||
- `request` _(http.IncomingMessage)_: request that originated the Socket
|
||||
- `upgraded` _(Boolean)_: whether the transport has been upgraded
|
||||
- `readyState` _(String)_: opening|open|closing|closed
|
||||
- `transport` _(Transport)_: transport reference
|
||||
|
||||
##### Methods
|
||||
|
||||
- `send`:
|
||||
- Sends a message, performing `message = toString(arguments[0])` unless
|
||||
sending binary data, which is sent as is.
|
||||
- **Parameters**
|
||||
- `String` | `Buffer` | `ArrayBuffer` | `ArrayBufferView`: a string or any object implementing `toString()`, with outgoing data, or a Buffer or ArrayBuffer with binary data. Also any ArrayBufferView can be sent as is.
|
||||
- `Object`: optional, options object
|
||||
- `Function`: optional, a callback executed when the message gets flushed out by the transport
|
||||
- **Options**
|
||||
- `compress` (`Boolean`): whether to compress sending data. This option might be ignored and forced to be `true` when using polling. (`true`)
|
||||
- **Returns** `Socket` for chaining
|
||||
- `close`
|
||||
- Disconnects the client
|
||||
- **Returns** `Socket` for chaining
|
||||
|
||||
### Client
|
||||
|
||||
<hr><br>
|
||||
|
||||
Exposed in the `eio` global namespace (in the browser), or by
|
||||
`require('engine.io-client')` (in Node.JS).
|
||||
|
||||
For the client API refer to the
|
||||
[engine-client](http://github.com/learnboost/engine.io-client) repository.
|
||||
|
||||
## Debug / logging
|
||||
|
||||
Engine.IO is powered by [debug](http://github.com/visionmedia/debug).
|
||||
In order to see all the debug output, run your app with the environment variable
|
||||
`DEBUG` including the desired scope.
|
||||
|
||||
To see the output from all of Engine.IO's debugging scopes you can use:
|
||||
|
||||
```
|
||||
DEBUG=engine* node myapp
|
||||
```
|
||||
|
||||
## Transports
|
||||
|
||||
- `polling`: XHR / JSONP polling transport.
|
||||
- `websocket`: WebSocket transport.
|
||||
|
||||
## Plugins
|
||||
|
||||
- [engine.io-conflation](https://github.com/EugenDueck/engine.io-conflation): Makes **conflation and aggregation** of messages straightforward.
|
||||
|
||||
## Support
|
||||
|
||||
The support channels for `engine.io` are the same as `socket.io`:
|
||||
- irc.freenode.net **#socket.io**
|
||||
- [Google Groups](http://groups.google.com/group/socket_io)
|
||||
- [Website](http://socket.io)
|
||||
|
||||
## Development
|
||||
|
||||
To contribute patches, run tests or benchmarks, make sure to clone the
|
||||
repository:
|
||||
|
||||
```
|
||||
git clone git://github.com/LearnBoost/engine.io.git
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
cd engine.io
|
||||
npm install
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Tests run with `npm test`. It runs the server tests that are aided by
|
||||
the usage of `engine.io-client`.
|
||||
|
||||
Make sure `npm install` is run first.
|
||||
|
||||
## Goals
|
||||
|
||||
The main goal of `Engine` is ensuring the most reliable realtime communication.
|
||||
Unlike the previous Socket.IO core, it always establishes a long-polling
|
||||
connection first, then tries to upgrade to better transports that are "tested" on
|
||||
the side.
|
||||
|
||||
During the lifetime of the Socket.IO projects, we've found countless drawbacks
|
||||
to relying on `HTML5 WebSocket` or `Flash Socket` as the first connection
|
||||
mechanisms.
|
||||
|
||||
Both are clearly the _right way_ of establishing a bidirectional communication,
|
||||
with HTML5 WebSocket being the way of the future. However, to answer most business
|
||||
needs, alternative traditional HTTP 1.1 mechanisms are just as good as delivering
|
||||
the same solution.
|
||||
|
||||
WebSocket based connections have two fundamental benefits:
|
||||
|
||||
1. **Better server performance**
|
||||
- _A: Load balancers_<br>
|
||||
Load balancing a long polling connection poses a serious architectural nightmare
|
||||
since requests can come from any number of open sockets by the user agent, but
|
||||
they all need to be routed to the process and computer that owns the `Engine`
|
||||
connection. This negatively impacts RAM and CPU usage.
|
||||
- _B: Network traffic_<br>
|
||||
WebSocket is designed around the premise that each message frame has to be
|
||||
surrounded by the least amount of data. In HTTP 1.1 transports, each message
|
||||
frame is surrounded by HTTP headers and chunked encoding frames. If you try to
|
||||
send the message _"Hello world"_ with xhr-polling, the message ultimately
|
||||
becomes larger than if you were to send it with WebSocket.
|
||||
- _C: Lightweight parser_<br>
|
||||
As an effect of **B**, the server has to do a lot more work to parse the network
|
||||
data and figure out the message when traditional HTTP requests are used
|
||||
(as in long polling). This means that another advantage of WebSocket is
|
||||
less server CPU usage.
|
||||
|
||||
2. **Better user experience**
|
||||
|
||||
Due to the reasons stated in point **1**, the most important effect of being able
|
||||
to establish a WebSocket connection is raw data transfer speed, which translates
|
||||
in _some_ cases in better user experience.
|
||||
|
||||
Applications with heavy realtime interaction (such as games) will benefit greatly,
|
||||
whereas applications like realtime chat (Gmail/Facebook), newsfeeds (Facebook) or
|
||||
timelines (Twitter) will have negligible user experience improvements.
|
||||
|
||||
Having said this, attempting to establish a WebSocket connection directly so far has
|
||||
proven problematic:
|
||||
|
||||
1. **Proxies**<br>
|
||||
Many corporate proxies block WebSocket traffic.
|
||||
|
||||
2. **Personal firewall and antivirus software**<br>
|
||||
As a result of our research, we've found that at least 3 personal security
|
||||
applications block WebSocket traffic.
|
||||
|
||||
3. **Cloud application platforms**<br>
|
||||
Platforms like Heroku or No.de have had trouble keeping up with the fast-paced
|
||||
nature of the evolution of the WebSocket protocol. Applications therefore end up
|
||||
inevitably using long polling, but the seamless installation experience of
|
||||
Socket.IO we strive for (_"require() it and it just works"_) disappears.
|
||||
|
||||
Some of these problems have solutions. In the case of proxies and personal programs,
|
||||
however, the solutions many times involve upgrading software. Experience has shown
|
||||
that relying on client software upgrades to deliver a business solution is
|
||||
fruitless: the very existence of this project has to do with a fragmented panorama
|
||||
of user agent distribution, with clients connecting with latest versions of the most
|
||||
modern user agents (Chrome, Firefox and Safari), but others with versions as low as
|
||||
IE 5.5.
|
||||
|
||||
From the user perspective, an unsuccessful WebSocket connection can translate in
|
||||
up to at least 10 seconds of waiting for the realtime application to begin
|
||||
exchanging data. This **perceptively** hurts user experience.
|
||||
|
||||
To summarize, **Engine** focuses on reliability and user experience first, marginal
|
||||
potential UX improvements and increased server performance second. `Engine` is the
|
||||
result of all the lessons learned with WebSocket in the wild.
|
||||
|
||||
## Architecture
|
||||
|
||||
The main premise of `Engine`, and the core of its existence, is the ability to
|
||||
swap transports on the fly. A connection starts as xhr-polling, but it can
|
||||
switch to WebSocket.
|
||||
|
||||
The central problem this poses is: how do we switch transports without losing
|
||||
messages?
|
||||
|
||||
`Engine` only switches from polling to another transport in between polling
|
||||
cycles. Since the server closes the connection after a certain timeout when
|
||||
there's no activity, and the polling transport implementation buffers messages
|
||||
in between connections, this ensures no message loss and optimal performance.
|
||||
|
||||
Another benefit of this design is that we workaround almost all the limitations
|
||||
of **Flash Socket**, such as slow connection times, increased file size (we can
|
||||
safely lazy load it without hurting user experience), etc.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Can I use engine without Socket.IO ?
|
||||
|
||||
Absolutely. Although the recommended framework for building realtime applications
|
||||
is Socket.IO, since it provides fundamental features for real-world applications
|
||||
such as multiplexing, reconnection support, etc.
|
||||
|
||||
`Engine` is to Socket.IO what Connect is to Express. An essential piece for building
|
||||
realtime frameworks, but something you _probably_ won't be using for building
|
||||
actual applications.
|
||||
|
||||
### Does the server serve the client?
|
||||
|
||||
No. The main reason is that `Engine` is meant to be bundled with frameworks.
|
||||
Socket.IO includes `Engine`, therefore serving two clients is not necessary. If
|
||||
you use Socket.IO, including
|
||||
|
||||
```html
|
||||
<script src="/socket.io/socket.io.js">
|
||||
```
|
||||
|
||||
has you covered.
|
||||
|
||||
### Can I implement `Engine` in other languages?
|
||||
|
||||
Absolutely. The [engine.io-protocol](https://github.com/socketio/engine.io-protocol)
|
||||
repository contains the most up-to-date description of the specification
|
||||
at all times.
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Guillermo Rauch <guillermo@learnboost.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
25
packages/engine.io/SECURITY.md
Normal file
25
packages/engine.io/SECURITY.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | `socket.io` version | Supported |
|
||||
|---------|---------------------|--------------------|
|
||||
| 6.x | 4.x | :white_check_mark: |
|
||||
| 4.x | 3.x | :white_check_mark: |
|
||||
| 3.5.x | 2.4.x | :white_check_mark: |
|
||||
| < 3.5.0 | < 2.4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security vulnerability in this package, please send an email to [@darrachequesne](https://github.com/darrachequesne) (see address in profile) describing the vulnerability and how to reproduce it.
|
||||
|
||||
We will get back to you as soon as possible and publish a fix if necessary.
|
||||
|
||||
:warning: IMPORTANT :warning: please do not create an issue in this repository, as attackers might take advantage of it. Thank you in advance for your responsible disclosure.
|
||||
|
||||
## History
|
||||
|
||||
- Feb 2020: [Resource exhaustion in engine.io](https://github.com/advisories/GHSA-j4f2-536g-r55m) (CVE-2020-36048)
|
||||
- Jan 2022: [Uncaught exception in engine.io](https://github.com/advisories/GHSA-273r-mgr4-v34f) (CVE-2022-21676)
|
||||
- Nov 2022: [Uncaught exception in engine.io](https://github.com/advisories/GHSA-r7qp-cfhv-p84w) (CVE-2022-41940)
|
||||
- May 2023: [Uncaught exception in engine.io](https://github.com/advisories/GHSA-q9mw-68c2-j6m5) (CVE-2023-31125)
|
||||
6
packages/engine.io/examples/esm-import/README.md
Normal file
6
packages/engine.io/examples/esm-import/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm link ../..
|
||||
$ node index.js
|
||||
```
|
||||
3
packages/engine.io/examples/esm-import/index.js
Normal file
3
packages/engine.io/examples/esm-import/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Server } from "engine.io";
|
||||
|
||||
console.log(Server);
|
||||
6
packages/engine.io/examples/esm-import/package.json
Normal file
6
packages/engine.io/examples/esm-import/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "esm-import",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
18
packages/engine.io/examples/latency/README.md
Normal file
18
packages/engine.io/examples/latency/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
# eio-latency
|
||||
|
||||
## Running
|
||||
|
||||
First, execute:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Then execute the server:
|
||||
|
||||
```
|
||||
$ node index
|
||||
```
|
||||
|
||||
And point your browser to `localhost:3000` (if PORT is set in your environment, then it will use that instead).
|
||||
15
packages/engine.io/examples/latency/index.html
Normal file
15
packages/engine.io/examples/latency/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>EIO Latency</title>
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>EIO Latency <span id="latency"></span></h1>
|
||||
<h2 id="transport">(connecting)</h2>
|
||||
<canvas id="chart" height="200"></canvas>
|
||||
|
||||
<script src="/engine.io.min.js"></script>
|
||||
<script src="/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
34
packages/engine.io/examples/latency/index.js
Normal file
34
packages/engine.io/examples/latency/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const server = require('http').createServer(app);
|
||||
const enchilada = require('enchilada');
|
||||
const io = require('engine.io').attach(server);
|
||||
|
||||
app.use(enchilada({
|
||||
src: __dirname + '/public',
|
||||
debug: true
|
||||
}));
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(__dirname + '/index.html');
|
||||
});
|
||||
|
||||
app.get('/engine.io.min.js', (req, res) => {
|
||||
res.sendFile(require.resolve('engine.io-client/dist/engine.io.min.js'));
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('message', () => {
|
||||
socket.send('pong');
|
||||
});
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
server.listen(port, () => {
|
||||
console.log('\x1B[96mlistening on localhost:' + port + ' \x1B[39m');
|
||||
});
|
||||
1708
packages/engine.io/examples/latency/package-lock.json
generated
Normal file
1708
packages/engine.io/examples/latency/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
packages/engine.io/examples/latency/package.json
Normal file
11
packages/engine.io/examples/latency/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "eio-latency",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"enchilada": "0.13.0",
|
||||
"engine.io": "^6.4.2",
|
||||
"engine.io-client": "^4.1.4",
|
||||
"express": "^4.19.2",
|
||||
"smoothie": "1.19.0"
|
||||
}
|
||||
}
|
||||
58
packages/engine.io/examples/latency/public/index.js
Normal file
58
packages/engine.io/examples/latency/public/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const SmoothieChart = require('smoothie').SmoothieChart;
|
||||
const TimeSeries = require('smoothie').TimeSeries;
|
||||
|
||||
// helper
|
||||
|
||||
function $ (id) { return document.getElementById(id); }
|
||||
|
||||
// chart
|
||||
|
||||
let smoothie;
|
||||
let time;
|
||||
|
||||
function render () {
|
||||
if (smoothie) smoothie.stop();
|
||||
$('chart').width = document.body.clientWidth;
|
||||
smoothie = new SmoothieChart();
|
||||
smoothie.streamTo($('chart'), 1000);
|
||||
time = new TimeSeries();
|
||||
smoothie.addTimeSeries(time, {
|
||||
strokeStyle: 'rgb(255, 0, 0)',
|
||||
fillStyle: 'rgba(255, 0, 0, 0.4)',
|
||||
lineWidth: 2
|
||||
});
|
||||
}
|
||||
|
||||
// socket
|
||||
const socket = new eio.Socket();
|
||||
let last;
|
||||
function send () {
|
||||
last = new Date();
|
||||
socket.send('ping');
|
||||
$('transport').innerHTML = socket.transport.name;
|
||||
}
|
||||
|
||||
socket.on('open', () => {
|
||||
if ($('chart').getContext) {
|
||||
render();
|
||||
window.onresize = render;
|
||||
}
|
||||
send();
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
if (smoothie) smoothie.stop();
|
||||
$('transport').innerHTML = '(disconnected)';
|
||||
});
|
||||
|
||||
socket.on('message', () => {
|
||||
const latency = new Date() - last;
|
||||
$('latency').innerHTML = latency + 'ms';
|
||||
if (time) time.append(+new Date(), latency);
|
||||
setTimeout(send, 100);
|
||||
});
|
||||
5
packages/engine.io/examples/latency/public/style.css
Normal file
5
packages/engine.io/examples/latency/public/style.css
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
body { margin: 0; padding: 0; font-family: Helvetica Neue; }
|
||||
h1 { margin: 100px 100px 10px; }
|
||||
h2 { color: #999; margin: 0 100px 30px; font-weight: normal; }
|
||||
#latency { color: red; }
|
||||
2
packages/engine.io/examples/memory-usage-webtransport/.gitignore
vendored
Executable file
2
packages/engine.io/examples/memory-usage-webtransport/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
||||
*.pem
|
||||
*.log
|
||||
@@ -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) => {});
|
||||
}
|
||||
6
packages/engine.io/examples/memory-usage-webtransport/generate_cert.sh
Executable file
6
packages/engine.io/examples/memory-usage-webtransport/generate_cert.sh
Executable file
@@ -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'
|
||||
530
packages/engine.io/examples/memory-usage-webtransport/package-lock.json
generated
Normal file
530
packages/engine.io/examples/memory-usage-webtransport/package-lock.json
generated
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
})();
|
||||
1
packages/engine.io/examples/memory-usage/.gitignore
vendored
Executable file
1
packages/engine.io/examples/memory-usage/.gitignore
vendored
Executable file
@@ -0,0 +1 @@
|
||||
*.log
|
||||
15
packages/engine.io/examples/memory-usage/client.js
Normal file
15
packages/engine.io/examples/memory-usage/client.js
Normal file
@@ -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) => {});
|
||||
}
|
||||
89
packages/engine.io/examples/memory-usage/package-lock.json
generated
Normal file
89
packages/engine.io/examples/memory-usage/package-lock.json
generated
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
packages/engine.io/examples/memory-usage/package.json
Normal file
10
packages/engine.io/examples/memory-usage/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "memory-usage",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"engine.io-client": "^6.5.4"
|
||||
}
|
||||
}
|
||||
41
packages/engine.io/examples/memory-usage/server.js
Normal file
41
packages/engine.io/examples/memory-usage/server.js
Normal file
@@ -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);
|
||||
54
packages/engine.io/lib/engine.io.ts
Normal file
54
packages/engine.io/lib/engine.io.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { createServer } from "http";
|
||||
import { Server, AttachOptions, ServerOptions } from "./server";
|
||||
import transports from "./transports/index";
|
||||
import * as parser from "engine.io-parser";
|
||||
|
||||
export { Server, transports, listen, attach, parser };
|
||||
export type { AttachOptions, ServerOptions, BaseServer } from "./server";
|
||||
export { uServer } from "./userver";
|
||||
export { Socket } from "./socket";
|
||||
export { Transport } from "./transport";
|
||||
export const protocol = parser.protocol;
|
||||
|
||||
/**
|
||||
* Creates an http.Server exclusively used for WS upgrades.
|
||||
*
|
||||
* @param {Number} port
|
||||
* @param {Function} callback
|
||||
* @param {Object} options
|
||||
* @return {Server} websocket.io server
|
||||
*/
|
||||
|
||||
function listen(port, options: AttachOptions & ServerOptions, fn) {
|
||||
if ("function" === typeof options) {
|
||||
fn = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
const server = createServer(function (req, res) {
|
||||
res.writeHead(501);
|
||||
res.end("Not Implemented");
|
||||
});
|
||||
|
||||
// create engine server
|
||||
const engine = attach(server, options);
|
||||
engine.httpServer = server;
|
||||
|
||||
server.listen(port, fn);
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures upgrade requests for a http.Server.
|
||||
*
|
||||
* @param {http.Server} server
|
||||
* @param {Object} options
|
||||
* @return {Server} engine server
|
||||
*/
|
||||
|
||||
function attach(server, options: AttachOptions & ServerOptions) {
|
||||
const engine = new Server(options);
|
||||
engine.attach(server, options);
|
||||
return engine;
|
||||
}
|
||||
485
packages/engine.io/lib/parser-v3/index.ts
Normal file
485
packages/engine.io/lib/parser-v3/index.ts
Normal file
@@ -0,0 +1,485 @@
|
||||
// imported from https://github.com/socketio/engine.io-parser/tree/2.2.x
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utf8 = require('./utf8');
|
||||
|
||||
/**
|
||||
* Current protocol version.
|
||||
*/
|
||||
export const protocol = 3;
|
||||
|
||||
const hasBinary = (packets) => {
|
||||
for (const packet of packets) {
|
||||
if (packet.data instanceof ArrayBuffer || ArrayBuffer.isView(packet.data)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packet types.
|
||||
*/
|
||||
|
||||
export const packets = {
|
||||
open: 0 // non-ws
|
||||
, close: 1 // non-ws
|
||||
, ping: 2
|
||||
, pong: 3
|
||||
, message: 4
|
||||
, upgrade: 5
|
||||
, noop: 6
|
||||
};
|
||||
|
||||
var packetslist = Object.keys(packets);
|
||||
|
||||
/**
|
||||
* Premade error packet.
|
||||
*/
|
||||
|
||||
var err = { type: 'error', data: 'parser error' };
|
||||
|
||||
const EMPTY_BUFFER = Buffer.concat([]);
|
||||
|
||||
/**
|
||||
* Encodes a packet.
|
||||
*
|
||||
* <packet type id> [ <data> ]
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* 5hello world
|
||||
* 3
|
||||
* 4
|
||||
*
|
||||
* Binary is encoded in an identical principle
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encodePacket (packet, supportsBinary, utf8encode, callback) {
|
||||
if (typeof supportsBinary === 'function') {
|
||||
callback = supportsBinary;
|
||||
supportsBinary = null;
|
||||
}
|
||||
|
||||
if (typeof utf8encode === 'function') {
|
||||
callback = utf8encode;
|
||||
utf8encode = null;
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(packet.data)) {
|
||||
return encodeBuffer(packet, supportsBinary, callback);
|
||||
} else if (packet.data && (packet.data.buffer || packet.data) instanceof ArrayBuffer) {
|
||||
return encodeBuffer({ type: packet.type, data: arrayBufferToBuffer(packet.data) }, supportsBinary, callback);
|
||||
}
|
||||
|
||||
// Sending data as a utf-8 string
|
||||
var encoded = packets[packet.type];
|
||||
|
||||
// data fragment is optional
|
||||
if (undefined !== packet.data) {
|
||||
encoded += utf8encode ? utf8.encode(String(packet.data), { strict: false }) : String(packet.data);
|
||||
}
|
||||
|
||||
return callback('' + encoded);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode Buffer data
|
||||
*/
|
||||
|
||||
function encodeBuffer(packet, supportsBinary, callback) {
|
||||
if (!supportsBinary) {
|
||||
return encodeBase64Packet(packet, callback);
|
||||
}
|
||||
|
||||
var data = packet.data;
|
||||
var typeBuffer = Buffer.allocUnsafe(1);
|
||||
typeBuffer[0] = packets[packet.type];
|
||||
return callback(Buffer.concat([typeBuffer, data]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a packet with binary data in a base64 string
|
||||
*
|
||||
* @param {Object} packet, has `type` and `data`
|
||||
* @return {String} base64 encoded message
|
||||
*/
|
||||
|
||||
export function encodeBase64Packet (packet, callback){
|
||||
var data = Buffer.isBuffer(packet.data) ? packet.data : arrayBufferToBuffer(packet.data);
|
||||
var message = 'b' + packets[packet.type];
|
||||
message += data.toString('base64');
|
||||
return callback(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a packet. Data also available as an ArrayBuffer if requested.
|
||||
*
|
||||
* @return {Object} with `type` and `data` (if any)
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function decodePacket (data, binaryType, utf8decode) {
|
||||
if (data === undefined) {
|
||||
return err;
|
||||
}
|
||||
|
||||
var type;
|
||||
|
||||
// String data
|
||||
if (typeof data === 'string') {
|
||||
|
||||
type = data.charAt(0);
|
||||
|
||||
if (type === 'b') {
|
||||
return decodeBase64Packet(data.slice(1), binaryType);
|
||||
}
|
||||
|
||||
if (utf8decode) {
|
||||
data = tryDecode(data);
|
||||
if (data === false) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (Number(type) != type || !packetslist[type]) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (data.length > 1) {
|
||||
return { type: packetslist[type], data: data.slice(1) };
|
||||
} else {
|
||||
return { type: packetslist[type] };
|
||||
}
|
||||
}
|
||||
|
||||
// Binary data
|
||||
if (binaryType === 'arraybuffer') {
|
||||
// wrap Buffer/ArrayBuffer data into an Uint8Array
|
||||
var intArray = new Uint8Array(data);
|
||||
type = intArray[0];
|
||||
return { type: packetslist[type], data: intArray.buffer.slice(1) };
|
||||
}
|
||||
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = arrayBufferToBuffer(data);
|
||||
}
|
||||
type = data[0];
|
||||
return { type: packetslist[type], data: data.slice(1) };
|
||||
};
|
||||
|
||||
function tryDecode(data) {
|
||||
try {
|
||||
data = utf8.decode(data, { strict: false });
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a packet encoded in a base64 string.
|
||||
*
|
||||
* @param {String} base64 encoded message
|
||||
* @return {Object} with `type` and `data` (if any)
|
||||
*/
|
||||
|
||||
export function decodeBase64Packet (msg, binaryType) {
|
||||
var type = packetslist[msg.charAt(0)];
|
||||
var data = Buffer.from(msg.slice(1), 'base64');
|
||||
if (binaryType === 'arraybuffer') {
|
||||
var abv = new Uint8Array(data.length);
|
||||
for (var i = 0; i < abv.length; i++){
|
||||
abv[i] = data[i];
|
||||
}
|
||||
// @ts-ignore
|
||||
data = abv.buffer;
|
||||
}
|
||||
return { type: type, data: data };
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload).
|
||||
*
|
||||
* <length>:data
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* 11:hello world2:hi
|
||||
*
|
||||
* If any contents are binary, they will be encoded as base64 strings. Base64
|
||||
* encoded strings are marked with a b before the length specifier
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encodePayload (packets, supportsBinary, callback) {
|
||||
if (typeof supportsBinary === 'function') {
|
||||
callback = supportsBinary;
|
||||
supportsBinary = null;
|
||||
}
|
||||
|
||||
if (supportsBinary && hasBinary(packets)) {
|
||||
return encodePayloadAsBinary(packets, callback);
|
||||
}
|
||||
|
||||
if (!packets.length) {
|
||||
return callback('0:');
|
||||
}
|
||||
|
||||
function encodeOne(packet, doneCallback) {
|
||||
encodePacket(packet, supportsBinary, false, function(message) {
|
||||
doneCallback(null, setLengthHeader(message));
|
||||
});
|
||||
}
|
||||
|
||||
map(packets, encodeOne, function(err, results) {
|
||||
return callback(results.join(''));
|
||||
});
|
||||
};
|
||||
|
||||
function setLengthHeader(message) {
|
||||
return message.length + ':' + message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async array map using after
|
||||
*/
|
||||
|
||||
function map(ary, each, done) {
|
||||
const results = new Array(ary.length);
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < ary.length; i++) {
|
||||
each(ary[i], (error, msg) => {
|
||||
results[i] = msg;
|
||||
if (++count === ary.length) {
|
||||
done(null, results);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Decodes data when a payload is maybe expected. Possible binary contents are
|
||||
* decoded from their base64 representation
|
||||
*
|
||||
* @param {String} data, callback method
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export function decodePayload (data, binaryType, callback) {
|
||||
if (typeof data !== 'string') {
|
||||
return decodePayloadAsBinary(data, binaryType, callback);
|
||||
}
|
||||
|
||||
if (typeof binaryType === 'function') {
|
||||
callback = binaryType;
|
||||
binaryType = null;
|
||||
}
|
||||
|
||||
if (data === '') {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
var length = '', n, msg, packet;
|
||||
|
||||
for (var i = 0, l = data.length; i < l; i++) {
|
||||
var chr = data.charAt(i);
|
||||
|
||||
if (chr !== ':') {
|
||||
length += chr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (length === '' || (length != (n = Number(length)))) {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
msg = data.slice(i + 1, i + 1 + n);
|
||||
|
||||
if (length != msg.length) {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
if (msg.length) {
|
||||
packet = decodePacket(msg, binaryType, false);
|
||||
|
||||
if (err.type === packet.type && err.data === packet.data) {
|
||||
// parser error in individual packet - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
var more = callback(packet, i + n, l);
|
||||
if (false === more) return;
|
||||
}
|
||||
|
||||
// advance cursor
|
||||
i += n;
|
||||
length = '';
|
||||
}
|
||||
|
||||
if (length !== '') {
|
||||
// parser error - ignoring payload
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts a buffer to a utf8.js encoded string
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function bufferToString(buffer) {
|
||||
var str = '';
|
||||
for (var i = 0, l = buffer.length; i < l; i++) {
|
||||
str += String.fromCharCode(buffer[i]);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts a utf8.js encoded string to a buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function stringToBuffer(string) {
|
||||
var buf = Buffer.allocUnsafe(string.length);
|
||||
for (var i = 0, l = string.length; i < l; i++) {
|
||||
buf.writeUInt8(string.charCodeAt(i), i);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Converts an ArrayBuffer to a Buffer
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function arrayBufferToBuffer(data) {
|
||||
// data is either an ArrayBuffer or ArrayBufferView.
|
||||
var length = data.byteLength || data.length;
|
||||
var offset = data.byteOffset || 0;
|
||||
|
||||
return Buffer.from(data.buffer || data, offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes multiple messages (payload) as binary.
|
||||
*
|
||||
* <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
|
||||
* 255><data>
|
||||
*
|
||||
* Example:
|
||||
* 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @return {Buffer} encoded payload
|
||||
* @api private
|
||||
*/
|
||||
|
||||
export function encodePayloadAsBinary (packets, callback) {
|
||||
if (!packets.length) {
|
||||
return callback(EMPTY_BUFFER);
|
||||
}
|
||||
|
||||
map(packets, encodeOneBinaryPacket, function(err, results) {
|
||||
return callback(Buffer.concat(results));
|
||||
});
|
||||
};
|
||||
|
||||
function encodeOneBinaryPacket(p, doneCallback) {
|
||||
|
||||
function onBinaryPacketEncode(packet) {
|
||||
|
||||
var encodingLength = '' + packet.length;
|
||||
var sizeBuffer;
|
||||
|
||||
if (typeof packet === 'string') {
|
||||
sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
|
||||
sizeBuffer[0] = 0; // is a string (not true binary = 0)
|
||||
for (var i = 0; i < encodingLength.length; i++) {
|
||||
sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
|
||||
}
|
||||
sizeBuffer[sizeBuffer.length - 1] = 255;
|
||||
return doneCallback(null, Buffer.concat([sizeBuffer, stringToBuffer(packet)]));
|
||||
}
|
||||
|
||||
sizeBuffer = Buffer.allocUnsafe(encodingLength.length + 2);
|
||||
sizeBuffer[0] = 1; // is binary (true binary = 1)
|
||||
for (var i = 0; i < encodingLength.length; i++) {
|
||||
sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
|
||||
}
|
||||
sizeBuffer[sizeBuffer.length - 1] = 255;
|
||||
|
||||
doneCallback(null, Buffer.concat([sizeBuffer, packet]));
|
||||
}
|
||||
|
||||
encodePacket(p, true, true, onBinaryPacketEncode);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Decodes data when a payload is maybe expected. Strings are decoded by
|
||||
* interpreting each byte as a key code for entries marked to start with 0. See
|
||||
* description of encodePayloadAsBinary
|
||||
|
||||
* @param {Buffer} data, callback method
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export function decodePayloadAsBinary (data, binaryType, callback) {
|
||||
if (typeof binaryType === 'function') {
|
||||
callback = binaryType;
|
||||
binaryType = null;
|
||||
}
|
||||
|
||||
var bufferTail = data;
|
||||
var buffers = [];
|
||||
var i;
|
||||
|
||||
while (bufferTail.length > 0) {
|
||||
var strLen = '';
|
||||
var isString = bufferTail[0] === 0;
|
||||
for (i = 1; ; i++) {
|
||||
if (bufferTail[i] === 255) break;
|
||||
// 310 = char length of Number.MAX_VALUE
|
||||
if (strLen.length > 310) {
|
||||
return callback(err, 0, 1);
|
||||
}
|
||||
strLen += '' + bufferTail[i];
|
||||
}
|
||||
bufferTail = bufferTail.slice(strLen.length + 1);
|
||||
|
||||
var msgLength = parseInt(strLen, 10);
|
||||
|
||||
var msg = bufferTail.slice(1, msgLength + 1);
|
||||
if (isString) msg = bufferToString(msg);
|
||||
buffers.push(msg);
|
||||
bufferTail = bufferTail.slice(msgLength + 1);
|
||||
}
|
||||
|
||||
var total = buffers.length;
|
||||
for (i = 0; i < total; i++) {
|
||||
var buffer = buffers[i];
|
||||
callback(decodePacket(buffer, binaryType, true), i, total);
|
||||
}
|
||||
};
|
||||
210
packages/engine.io/lib/parser-v3/utf8.ts
Normal file
210
packages/engine.io/lib/parser-v3/utf8.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/*! https://mths.be/utf8js v2.1.2 by @mathias */
|
||||
|
||||
var stringFromCharCode = String.fromCharCode;
|
||||
|
||||
// Taken from https://mths.be/punycode
|
||||
function ucs2decode(string) {
|
||||
var output = [];
|
||||
var counter = 0;
|
||||
var length = string.length;
|
||||
var value;
|
||||
var extra;
|
||||
while (counter < length) {
|
||||
value = string.charCodeAt(counter++);
|
||||
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
|
||||
// high surrogate, and there is a next character
|
||||
extra = string.charCodeAt(counter++);
|
||||
if ((extra & 0xFC00) == 0xDC00) { // low surrogate
|
||||
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
|
||||
} else {
|
||||
// unmatched surrogate; only append this code unit, in case the next
|
||||
// code unit is the high surrogate of a surrogate pair
|
||||
output.push(value);
|
||||
counter--;
|
||||
}
|
||||
} else {
|
||||
output.push(value);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Taken from https://mths.be/punycode
|
||||
function ucs2encode(array) {
|
||||
var length = array.length;
|
||||
var index = -1;
|
||||
var value;
|
||||
var output = '';
|
||||
while (++index < length) {
|
||||
value = array[index];
|
||||
if (value > 0xFFFF) {
|
||||
value -= 0x10000;
|
||||
output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
|
||||
value = 0xDC00 | value & 0x3FF;
|
||||
}
|
||||
output += stringFromCharCode(value);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function checkScalarValue(codePoint, strict) {
|
||||
if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
|
||||
if (strict) {
|
||||
throw Error(
|
||||
'Lone surrogate U+' + codePoint.toString(16).toUpperCase() +
|
||||
' is not a scalar value'
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
function createByte(codePoint, shift) {
|
||||
return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
|
||||
}
|
||||
|
||||
function encodeCodePoint(codePoint, strict) {
|
||||
if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
|
||||
return stringFromCharCode(codePoint);
|
||||
}
|
||||
var symbol = '';
|
||||
if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
|
||||
symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
|
||||
}
|
||||
else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
|
||||
if (!checkScalarValue(codePoint, strict)) {
|
||||
codePoint = 0xFFFD;
|
||||
}
|
||||
symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
|
||||
symbol += createByte(codePoint, 6);
|
||||
}
|
||||
else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
|
||||
symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
|
||||
symbol += createByte(codePoint, 12);
|
||||
symbol += createByte(codePoint, 6);
|
||||
}
|
||||
symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
function utf8encode(string, opts) {
|
||||
opts = opts || {};
|
||||
var strict = false !== opts.strict;
|
||||
|
||||
var codePoints = ucs2decode(string);
|
||||
var length = codePoints.length;
|
||||
var index = -1;
|
||||
var codePoint;
|
||||
var byteString = '';
|
||||
while (++index < length) {
|
||||
codePoint = codePoints[index];
|
||||
byteString += encodeCodePoint(codePoint, strict);
|
||||
}
|
||||
return byteString;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
function readContinuationByte() {
|
||||
if (byteIndex >= byteCount) {
|
||||
throw Error('Invalid byte index');
|
||||
}
|
||||
|
||||
var continuationByte = byteArray[byteIndex] & 0xFF;
|
||||
byteIndex++;
|
||||
|
||||
if ((continuationByte & 0xC0) == 0x80) {
|
||||
return continuationByte & 0x3F;
|
||||
}
|
||||
|
||||
// If we end up here, it’s not a continuation byte
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
|
||||
function decodeSymbol(strict) {
|
||||
var byte1;
|
||||
var byte2;
|
||||
var byte3;
|
||||
var byte4;
|
||||
var codePoint;
|
||||
|
||||
if (byteIndex > byteCount) {
|
||||
throw Error('Invalid byte index');
|
||||
}
|
||||
|
||||
if (byteIndex == byteCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read first byte
|
||||
byte1 = byteArray[byteIndex] & 0xFF;
|
||||
byteIndex++;
|
||||
|
||||
// 1-byte sequence (no continuation bytes)
|
||||
if ((byte1 & 0x80) == 0) {
|
||||
return byte1;
|
||||
}
|
||||
|
||||
// 2-byte sequence
|
||||
if ((byte1 & 0xE0) == 0xC0) {
|
||||
byte2 = readContinuationByte();
|
||||
codePoint = ((byte1 & 0x1F) << 6) | byte2;
|
||||
if (codePoint >= 0x80) {
|
||||
return codePoint;
|
||||
} else {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
}
|
||||
|
||||
// 3-byte sequence (may include unpaired surrogates)
|
||||
if ((byte1 & 0xF0) == 0xE0) {
|
||||
byte2 = readContinuationByte();
|
||||
byte3 = readContinuationByte();
|
||||
codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
|
||||
if (codePoint >= 0x0800) {
|
||||
return checkScalarValue(codePoint, strict) ? codePoint : 0xFFFD;
|
||||
} else {
|
||||
throw Error('Invalid continuation byte');
|
||||
}
|
||||
}
|
||||
|
||||
// 4-byte sequence
|
||||
if ((byte1 & 0xF8) == 0xF0) {
|
||||
byte2 = readContinuationByte();
|
||||
byte3 = readContinuationByte();
|
||||
byte4 = readContinuationByte();
|
||||
codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) |
|
||||
(byte3 << 0x06) | byte4;
|
||||
if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
|
||||
return codePoint;
|
||||
}
|
||||
}
|
||||
|
||||
throw Error('Invalid UTF-8 detected');
|
||||
}
|
||||
|
||||
var byteArray;
|
||||
var byteCount;
|
||||
var byteIndex;
|
||||
function utf8decode(byteString, opts) {
|
||||
opts = opts || {};
|
||||
var strict = false !== opts.strict;
|
||||
|
||||
byteArray = ucs2decode(byteString);
|
||||
byteCount = byteArray.length;
|
||||
byteIndex = 0;
|
||||
var codePoints = [];
|
||||
var tmp;
|
||||
while ((tmp = decodeSymbol(strict)) !== false) {
|
||||
codePoints.push(tmp);
|
||||
}
|
||||
return ucs2encode(codePoints);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
version: '2.1.2',
|
||||
encode: utf8encode,
|
||||
decode: utf8decode
|
||||
};
|
||||
1056
packages/engine.io/lib/server.ts
Normal file
1056
packages/engine.io/lib/server.ts
Normal file
File diff suppressed because it is too large
Load Diff
588
packages/engine.io/lib/socket.ts
Normal file
588
packages/engine.io/lib/socket.ts
Normal file
@@ -0,0 +1,588 @@
|
||||
import { EventEmitter } from "events";
|
||||
import debugModule from "debug";
|
||||
import type { IncomingMessage } from "http";
|
||||
import type { EngineRequest, Transport } from "./transport";
|
||||
import type { BaseServer } from "./server";
|
||||
import { setTimeout, clearTimeout } from "timers";
|
||||
import type { Packet, PacketType, RawData } from "engine.io-parser";
|
||||
|
||||
const debug = debugModule("engine:socket");
|
||||
|
||||
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;
|
||||
/**
|
||||
* A reference to the first HTTP request of the session
|
||||
*
|
||||
* TODO for the next major release: remove it
|
||||
*/
|
||||
public request: IncomingMessage;
|
||||
/**
|
||||
* The IP address of the client.
|
||||
*/
|
||||
public readonly remoteAddress: string;
|
||||
|
||||
/**
|
||||
* The current state of the socket.
|
||||
*/
|
||||
public _readyState: ReadyState = "opening";
|
||||
/**
|
||||
* The current low-level transport.
|
||||
*/
|
||||
public transport: Transport;
|
||||
|
||||
private server: BaseServer;
|
||||
/* private */ upgrading = false;
|
||||
/* private */ upgraded = false;
|
||||
private writeBuffer: Packet[] = [];
|
||||
private packetsFn: SendCallback[] = [];
|
||||
private sentCallbackFn: SendCallback[][] = [];
|
||||
private cleanupFn: any[] = [];
|
||||
private pingTimeoutTimer;
|
||||
private pingIntervalTimer;
|
||||
|
||||
/**
|
||||
* This is the session identifier that the client will use in the subsequent HTTP requests. It must not be shared with
|
||||
* others parties, as it might lead to session hijacking.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly id: string;
|
||||
|
||||
get readyState() {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
set readyState(state: ReadyState) {
|
||||
debug("readyState updated from %s to %s", this._readyState, state);
|
||||
this._readyState = state;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
server: BaseServer,
|
||||
transport: Transport,
|
||||
req: EngineRequest,
|
||||
protocol: number
|
||||
) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.server = server;
|
||||
this.request = req;
|
||||
this.protocol = protocol;
|
||||
|
||||
// Cache IP since it might not be in the req later
|
||||
if (req) {
|
||||
if (req.websocket && req.websocket._socket) {
|
||||
this.remoteAddress = req.websocket._socket.remoteAddress;
|
||||
} else {
|
||||
this.remoteAddress = req.connection.remoteAddress;
|
||||
}
|
||||
} else {
|
||||
// TODO there is currently no way to get the IP address of the client when it connects with WebTransport
|
||||
// see https://github.com/fails-components/webtransport/issues/114
|
||||
}
|
||||
|
||||
this.pingTimeoutTimer = null;
|
||||
this.pingIntervalTimer = null;
|
||||
|
||||
this.setTransport(transport);
|
||||
this.onOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport considered open.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onOpen() {
|
||||
this.readyState = "open";
|
||||
|
||||
// sends an `open` packet
|
||||
this.transport.sid = this.id;
|
||||
this.sendPacket(
|
||||
"open",
|
||||
JSON.stringify({
|
||||
sid: this.id,
|
||||
upgrades: this.getAvailableUpgrades(),
|
||||
pingInterval: this.server.opts.pingInterval,
|
||||
pingTimeout: this.server.opts.pingTimeout,
|
||||
maxPayload: this.server.opts.maxHttpBufferSize,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.server.opts.initialPacket) {
|
||||
this.sendPacket("message", this.server.opts.initialPacket);
|
||||
}
|
||||
|
||||
this.emit("open");
|
||||
|
||||
if (this.protocol === 3) {
|
||||
// in protocol v3, the client sends a ping, and the server answers with a pong
|
||||
this.resetPingTimeout();
|
||||
} else {
|
||||
// in protocol v4, the server sends a ping, and the client answers with a pong
|
||||
this.schedulePing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport packet.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @private
|
||||
*/
|
||||
private onPacket(packet: Packet) {
|
||||
if ("open" !== this.readyState) {
|
||||
return debug("packet received with closed socket");
|
||||
}
|
||||
// export packet event
|
||||
debug(`received packet ${packet.type}`);
|
||||
this.emit("packet", packet);
|
||||
|
||||
switch (packet.type) {
|
||||
case "ping":
|
||||
if (this.transport.protocol !== 3) {
|
||||
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(new Error("invalid heartbeat direction"));
|
||||
return;
|
||||
}
|
||||
debug("got pong");
|
||||
clearTimeout(this.pingTimeoutTimer);
|
||||
this.pingIntervalTimer.refresh();
|
||||
this.emit("heartbeat");
|
||||
break;
|
||||
|
||||
case "error":
|
||||
this.onClose("parse error");
|
||||
break;
|
||||
|
||||
case "message":
|
||||
this.emit("data", packet.data);
|
||||
this.emit("message", packet.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport error.
|
||||
*
|
||||
* @param {Error} err - error object
|
||||
* @private
|
||||
*/
|
||||
private onError(err: Error) {
|
||||
debug("transport error");
|
||||
this.onClose("transport error", err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pings client every `this.pingInterval` and expects response
|
||||
* within `this.pingTimeout` or closes connection.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private schedulePing() {
|
||||
this.pingIntervalTimer = setTimeout(() => {
|
||||
debug(
|
||||
"writing ping packet - expecting pong within %sms",
|
||||
this.server.opts.pingTimeout
|
||||
);
|
||||
this.sendPacket("ping");
|
||||
this.resetPingTimeout();
|
||||
}, this.server.opts.pingInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets ping timeout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private resetPingTimeout() {
|
||||
clearTimeout(this.pingTimeoutTimer);
|
||||
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
|
||||
* @private
|
||||
*/
|
||||
private setTransport(transport: Transport) {
|
||||
const onError = this.onError.bind(this);
|
||||
const onReady = () => this.flush();
|
||||
const onPacket = this.onPacket.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", onDrain);
|
||||
this.transport.once("close", onClose);
|
||||
|
||||
this.cleanupFn.push(function () {
|
||||
transport.removeListener("error", onError);
|
||||
transport.removeListener("ready", onReady);
|
||||
transport.removeListener("packet", onPacket);
|
||||
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
|
||||
* @private
|
||||
*/
|
||||
/* private */ _maybeUpgrade(transport: Transport) {
|
||||
debug(
|
||||
'might upgrade socket transport from "%s" to "%s"',
|
||||
this.transport.name,
|
||||
transport.name
|
||||
);
|
||||
|
||||
this.upgrading = true;
|
||||
|
||||
// set transport upgrade timer
|
||||
const upgradeTimeoutTimer = setTimeout(() => {
|
||||
debug("client did not complete upgrade - closing transport");
|
||||
cleanup();
|
||||
if ("open" === transport.readyState) {
|
||||
transport.close();
|
||||
}
|
||||
}, 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(checkIntervalTimer);
|
||||
checkIntervalTimer = setInterval(check, 100);
|
||||
} else if ("upgrade" === packet.type && this.readyState !== "closed") {
|
||||
debug("got upgrade packet - upgrading");
|
||||
cleanup();
|
||||
this.transport.discard();
|
||||
this.upgraded = true;
|
||||
this.clearTransport();
|
||||
this.setTransport(transport);
|
||||
this.emit("upgrade", transport);
|
||||
this.flush();
|
||||
if (this.readyState === "closing") {
|
||||
transport.close(() => {
|
||||
this.onClose("forced close");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cleanup();
|
||||
transport.close();
|
||||
}
|
||||
};
|
||||
|
||||
// we force a polling cycle to ensure a fast upgrade
|
||||
const check = () => {
|
||||
if ("polling" === this.transport.name && this.transport.writable) {
|
||||
debug("writing a noop packet to polling for fast upgrade");
|
||||
this.transport.send([{ type: "noop" }]);
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
this.upgrading = false;
|
||||
|
||||
clearInterval(checkIntervalTimer);
|
||||
clearTimeout(upgradeTimeoutTimer);
|
||||
|
||||
transport.removeListener("packet", onPacket);
|
||||
transport.removeListener("close", onTransportClose);
|
||||
transport.removeListener("error", onError);
|
||||
this.removeListener("close", onClose);
|
||||
};
|
||||
|
||||
const onError = (err) => {
|
||||
debug("client did not complete upgrade - %s", err);
|
||||
cleanup();
|
||||
transport.close();
|
||||
transport = null;
|
||||
};
|
||||
|
||||
const onTransportClose = () => {
|
||||
onError("transport closed");
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
onError("socket closed");
|
||||
};
|
||||
|
||||
transport.on("packet", onPacket);
|
||||
transport.once("close", onTransportClose);
|
||||
transport.once("error", onError);
|
||||
|
||||
this.once("close", onClose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears listeners and timers associated with current transport.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private clearTransport() {
|
||||
let cleanup;
|
||||
|
||||
const toCleanUp = this.cleanupFn.length;
|
||||
|
||||
for (let i = 0; i < toCleanUp; i++) {
|
||||
cleanup = this.cleanupFn.shift();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// silence further transport errors and prevent uncaught exceptions
|
||||
this.transport.on("error", function () {
|
||||
debug("error triggered by discarded transport");
|
||||
});
|
||||
|
||||
// ensure transport won't stay open
|
||||
this.transport.close();
|
||||
|
||||
clearTimeout(this.pingTimeoutTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport considered closed.
|
||||
* Possible reasons: `ping timeout`, `client error`, `parse error`,
|
||||
* `transport error`, `server close`, `transport close`
|
||||
*/
|
||||
private onClose(reason: string, description?) {
|
||||
if ("closed" !== this.readyState) {
|
||||
this.readyState = "closed";
|
||||
|
||||
// clear timers
|
||||
clearTimeout(this.pingIntervalTimer);
|
||||
clearTimeout(this.pingTimeoutTimer);
|
||||
|
||||
// clean writeBuffer in next tick, so developers can still
|
||||
// grab the writeBuffer on 'close' event
|
||||
process.nextTick(() => {
|
||||
this.writeBuffer = [];
|
||||
});
|
||||
this.packetsFn = [];
|
||||
this.sentCallbackFn = [];
|
||||
this.clearTransport();
|
||||
this.emit("close", reason, description);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message packet.
|
||||
*
|
||||
* @param {Object} data
|
||||
* @param {Object} options
|
||||
* @param {Function} callback
|
||||
* @return {Socket} for chaining
|
||||
*/
|
||||
public send(data: RawData, options?: SendOptions, callback?: SendCallback) {
|
||||
this.sendPacket("message", data, options, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of {@link send}.
|
||||
*
|
||||
* @param data
|
||||
* @param options
|
||||
* @param callback
|
||||
*/
|
||||
public write(data: RawData, options?: SendOptions, callback?: SendCallback) {
|
||||
this.sendPacket("message", data, options, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
* @param {String} type - packet type
|
||||
* @param {String} data
|
||||
* @param {Object} options
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private sendPacket(
|
||||
type: PacketType,
|
||||
data?: RawData,
|
||||
options: SendOptions = {},
|
||||
callback?: SendCallback
|
||||
) {
|
||||
if ("function" === typeof options) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if ("closing" !== this.readyState && "closed" !== this.readyState) {
|
||||
debug('sending packet "%s" (%s)', type, data);
|
||||
|
||||
// compression is enabled by default
|
||||
options.compress = options.compress !== false;
|
||||
|
||||
const packet: Packet = {
|
||||
type,
|
||||
options: options as { compress: boolean },
|
||||
};
|
||||
|
||||
if (data) packet.data = data;
|
||||
|
||||
// exports packetCreate event
|
||||
this.emit("packetCreate", packet);
|
||||
|
||||
this.writeBuffer.push(packet);
|
||||
|
||||
// add send callback to object, if defined
|
||||
if ("function" === typeof callback) this.packetsFn.push(callback);
|
||||
|
||||
this.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to flush the packets buffer.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private flush() {
|
||||
if (
|
||||
"closed" !== this.readyState &&
|
||||
this.transport.writable &&
|
||||
this.writeBuffer.length
|
||||
) {
|
||||
debug("flushing buffer to transport");
|
||||
this.emit("flush", this.writeBuffer);
|
||||
this.server.emit("flush", this, this.writeBuffer);
|
||||
const wbuf = this.writeBuffer;
|
||||
this.writeBuffer = [];
|
||||
|
||||
if (this.packetsFn.length) {
|
||||
this.sentCallbackFn.push(this.packetsFn);
|
||||
this.packetsFn = [];
|
||||
} else {
|
||||
this.sentCallbackFn.push(null);
|
||||
}
|
||||
|
||||
this.transport.send(wbuf);
|
||||
this.emit("drain");
|
||||
this.server.emit("drain", this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available upgrades for this socket.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private getAvailableUpgrades() {
|
||||
const availableUpgrades = [];
|
||||
const allUpgrades = this.server.upgrades(this.transport.name);
|
||||
for (let i = 0; i < allUpgrades.length; ++i) {
|
||||
const upg = allUpgrades[i];
|
||||
if (this.server.opts.transports.indexOf(upg) !== -1) {
|
||||
availableUpgrades.push(upg);
|
||||
}
|
||||
}
|
||||
return availableUpgrades;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the socket and underlying transport.
|
||||
*
|
||||
* @param {Boolean} discard - optional, discard the transport
|
||||
* @return {Socket} for chaining
|
||||
*/
|
||||
public close(discard?: boolean) {
|
||||
if ("open" !== this.readyState) return;
|
||||
|
||||
this.readyState = "closing";
|
||||
|
||||
if (this.writeBuffer.length) {
|
||||
debug(
|
||||
"there are %d remaining packets in the buffer, waiting for the 'drain' event",
|
||||
this.writeBuffer.length
|
||||
);
|
||||
this.once("drain", () => {
|
||||
debug("all packets have been sent, closing the transport");
|
||||
this.closeTransport(discard);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
debug("the buffer is empty, closing the transport right away", discard);
|
||||
this.closeTransport(discard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying transport.
|
||||
*
|
||||
* @param {Boolean} discard
|
||||
* @private
|
||||
*/
|
||||
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"));
|
||||
}
|
||||
}
|
||||
185
packages/engine.io/lib/transport.ts
Normal file
185
packages/engine.io/lib/transport.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
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 type { IncomingMessage, ServerResponse } from "http";
|
||||
import { Packet, RawData } from "engine.io-parser";
|
||||
|
||||
const debug = debugModule("engine:transport");
|
||||
|
||||
function noop() {}
|
||||
|
||||
type ReadyState = "open" | "closing" | "closed";
|
||||
|
||||
export type EngineRequest = IncomingMessage & {
|
||||
_query: Record<string, string>;
|
||||
res?: ServerResponse;
|
||||
cleanup?: Function;
|
||||
websocket?: any;
|
||||
};
|
||||
|
||||
export abstract class Transport extends EventEmitter {
|
||||
/**
|
||||
* The session ID.
|
||||
*/
|
||||
public sid: string;
|
||||
/**
|
||||
* Whether the transport is currently ready to send packets.
|
||||
*/
|
||||
public writable = false;
|
||||
/**
|
||||
* The revision of the protocol:
|
||||
*
|
||||
* - 3 is used in Engine.IO v3 / Socket.IO v2
|
||||
* - 4 is used in Engine.IO v4 and above / Socket.IO v3 and above
|
||||
*
|
||||
* It is found in the `EIO` query parameters of the HTTP requests.
|
||||
*
|
||||
* @see https://github.com/socketio/engine.io-protocol
|
||||
*/
|
||||
public protocol: number;
|
||||
|
||||
/**
|
||||
* The current state of the transport.
|
||||
* @protected
|
||||
*/
|
||||
protected _readyState: ReadyState = "open";
|
||||
/**
|
||||
* Whether the transport is discarded and can be safely closed (used during upgrade).
|
||||
* @protected
|
||||
*/
|
||||
protected discarded = false;
|
||||
/**
|
||||
* The parser to use (depends on the revision of the {@link Transport#protocol}.
|
||||
* @protected
|
||||
*/
|
||||
protected parser: any;
|
||||
/**
|
||||
* Whether the transport supports binary payloads (else it will be base64-encoded)
|
||||
* @protected
|
||||
*/
|
||||
protected supportsBinary: boolean;
|
||||
|
||||
get readyState() {
|
||||
return this._readyState;
|
||||
}
|
||||
|
||||
set readyState(state: ReadyState) {
|
||||
debug(
|
||||
"readyState updated from %s to %s (%s)",
|
||||
this._readyState,
|
||||
state,
|
||||
this.name
|
||||
);
|
||||
this._readyState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport constructor.
|
||||
*
|
||||
* @param {EngineRequest} req
|
||||
*/
|
||||
constructor(req: { _query: Record<string, string> }) {
|
||||
super();
|
||||
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.
|
||||
*
|
||||
* @package
|
||||
*/
|
||||
discard() {
|
||||
this.discarded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with an incoming HTTP request.
|
||||
*
|
||||
* @param req
|
||||
* @package
|
||||
*/
|
||||
onRequest(req: any) {}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @package
|
||||
*/
|
||||
close(fn?: () => void) {
|
||||
if ("closed" === this.readyState || "closing" === this.readyState) return;
|
||||
|
||||
this.readyState = "closing";
|
||||
this.doClose(fn || noop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with a transport error.
|
||||
*
|
||||
* @param {String} msg - message error
|
||||
* @param {Object} desc - error description
|
||||
* @protected
|
||||
*/
|
||||
protected onError(msg: string, desc?) {
|
||||
if (this.listeners("error").length) {
|
||||
const err = new Error(msg);
|
||||
// @ts-ignore
|
||||
err.type = "TransportError";
|
||||
// @ts-ignore
|
||||
err.description = desc;
|
||||
this.emit("error", err);
|
||||
} else {
|
||||
debug("ignored transport error %s (%s)", msg, desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with parsed out a packets from the data stream.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @protected
|
||||
*/
|
||||
protected onPacket(packet: Packet) {
|
||||
this.emit("packet", packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with the encoded packet data.
|
||||
*
|
||||
* @param {String} data
|
||||
* @protected
|
||||
*/
|
||||
protected onData(data: RawData) {
|
||||
this.onPacket(this.parser.decodePacket(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called upon transport close.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
protected onClose() {
|
||||
this.readyState = "closed";
|
||||
this.emit("close");
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the transport.
|
||||
*/
|
||||
abstract get name(): string;
|
||||
|
||||
/**
|
||||
* Sends an array of packets.
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @package
|
||||
*/
|
||||
abstract send(packets: Packet[]): void;
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*/
|
||||
abstract doClose(fn?: () => void): void;
|
||||
}
|
||||
7
packages/engine.io/lib/transports-uws/index.ts
Normal file
7
packages/engine.io/lib/transports-uws/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Polling } from "./polling";
|
||||
import { WebSocket } from "./websocket";
|
||||
|
||||
export default {
|
||||
polling: Polling,
|
||||
websocket: WebSocket,
|
||||
};
|
||||
427
packages/engine.io/lib/transports-uws/polling.ts
Normal file
427
packages/engine.io/lib/transports-uws/polling.ts
Normal file
@@ -0,0 +1,427 @@
|
||||
import { Transport } from "../transport";
|
||||
import { createGzip, createDeflate } from "zlib";
|
||||
import * as accepts from "accepts";
|
||||
import debugModule from "debug";
|
||||
import { HttpRequest, HttpResponse } from "uWebSockets.js";
|
||||
|
||||
const debug = debugModule("engine:polling");
|
||||
|
||||
const compressionMethods = {
|
||||
gzip: createGzip,
|
||||
deflate: createDeflate,
|
||||
};
|
||||
|
||||
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;
|
||||
private shouldClose: Function;
|
||||
|
||||
private readonly closeTimeout: number;
|
||||
|
||||
/**
|
||||
* HTTP polling constructor.
|
||||
*/
|
||||
constructor(req) {
|
||||
super(req);
|
||||
|
||||
this.closeTimeout = 30 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*/
|
||||
get name() {
|
||||
return "polling";
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides onRequest.
|
||||
*
|
||||
* @param req
|
||||
*
|
||||
* @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);
|
||||
} else if (req.getMethod() === "post") {
|
||||
this.onDataRequest(req, res);
|
||||
} else {
|
||||
res.writeStatus("500 Internal Server Error");
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The client sends a request awaiting for us to send data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
onPollRequest(req, res) {
|
||||
if (this.req) {
|
||||
debug("request overlap");
|
||||
// assert: this.res, '.req and .res should be (un)set together'
|
||||
this.onError("overlap from client");
|
||||
res.writeStatus("500 Internal Server Error");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
debug("setting request");
|
||||
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
|
||||
const onClose = () => {
|
||||
this.writable = false;
|
||||
this.onError("poll connection closed prematurely");
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
this.req = this.res = null;
|
||||
};
|
||||
|
||||
req.cleanup = cleanup;
|
||||
res.onAborted(onClose);
|
||||
|
||||
this.writable = true;
|
||||
this.emit("ready");
|
||||
|
||||
// if we're still writable but had a pending close, trigger an empty send
|
||||
if (this.writable && this.shouldClose) {
|
||||
debug("triggering empty send to append close packet");
|
||||
this.send([{ type: "noop" }]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The client sends a request with data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
onDataRequest(req, res) {
|
||||
if (this.dataReq) {
|
||||
// assert: this.dataRes, '.dataReq and .dataRes should be (un)set together'
|
||||
this.onError("data request overlap from client");
|
||||
res.writeStatus("500 Internal Server Error");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedContentLength = Number(req.headers["content-length"]);
|
||||
|
||||
if (!expectedContentLength) {
|
||||
this.onError("content-length header required");
|
||||
res.writeStatus("411 Length Required").end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (expectedContentLength > this.maxHttpBufferSize) {
|
||||
this.onError("payload too large");
|
||||
res.writeStatus("413 Payload Too Large").end();
|
||||
return;
|
||||
}
|
||||
|
||||
const isBinary = "application/octet-stream" === req.headers["content-type"];
|
||||
|
||||
if (isBinary && this.protocol === 4) {
|
||||
return this.onError("invalid content");
|
||||
}
|
||||
|
||||
this.dataReq = req;
|
||||
this.dataRes = res;
|
||||
|
||||
let buffer;
|
||||
let offset = 0;
|
||||
|
||||
const headers = {
|
||||
// text/html is required instead of text/plain to avoid an
|
||||
// unwanted download dialog on certain user-agents (GH-43)
|
||||
"Content-Type": "text/html",
|
||||
};
|
||||
|
||||
this.headers(req, headers);
|
||||
for (let key in headers) {
|
||||
res.writeHeader(key, String(headers[key]));
|
||||
}
|
||||
|
||||
const onEnd = (buffer) => {
|
||||
this.onData(buffer.toString());
|
||||
this.onDataRequestCleanup();
|
||||
res.cork(() => {
|
||||
res.end("ok");
|
||||
});
|
||||
};
|
||||
|
||||
res.onAborted(() => {
|
||||
this.onDataRequestCleanup();
|
||||
this.onError("data request connection closed prematurely");
|
||||
});
|
||||
|
||||
res.onData((arrayBuffer, isLast) => {
|
||||
const totalLength = offset + arrayBuffer.byteLength;
|
||||
if (totalLength > expectedContentLength) {
|
||||
this.onError("content-length mismatch");
|
||||
res.close(); // calls onAborted
|
||||
return;
|
||||
}
|
||||
|
||||
if (!buffer) {
|
||||
if (isLast) {
|
||||
onEnd(Buffer.from(arrayBuffer));
|
||||
return;
|
||||
}
|
||||
buffer = Buffer.allocUnsafe(expectedContentLength);
|
||||
}
|
||||
|
||||
Buffer.from(arrayBuffer).copy(buffer, offset);
|
||||
|
||||
if (isLast) {
|
||||
if (totalLength != expectedContentLength) {
|
||||
this.onError("content-length mismatch");
|
||||
res.writeStatus("400 Content-Length Mismatch").end();
|
||||
this.onDataRequestCleanup();
|
||||
return;
|
||||
}
|
||||
onEnd(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
offset = totalLength;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup request.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onDataRequestCleanup() {
|
||||
this.dataReq = this.dataRes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the incoming data payload.
|
||||
*
|
||||
* @param {String} encoded payload
|
||||
* @private
|
||||
*/
|
||||
onData(data) {
|
||||
debug('received "%s"', data);
|
||||
const callback = (packet) => {
|
||||
if ("close" === packet.type) {
|
||||
debug("got xhr close packet");
|
||||
this.onClose();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.onPacket(packet);
|
||||
};
|
||||
|
||||
if (this.protocol === 3) {
|
||||
this.parser.decodePayload(data, callback);
|
||||
} else {
|
||||
this.parser.decodePayload(data).forEach(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides onClose.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
onClose() {
|
||||
if (this.writable) {
|
||||
// close pending poll request
|
||||
this.send([{ type: "noop" }]);
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet payload.
|
||||
*
|
||||
* @param {Object} packet
|
||||
* @private
|
||||
*/
|
||||
send(packets) {
|
||||
this.writable = false;
|
||||
|
||||
if (this.shouldClose) {
|
||||
debug("appending close packet to payload");
|
||||
packets.push({ type: "close" });
|
||||
this.shouldClose();
|
||||
this.shouldClose = null;
|
||||
}
|
||||
|
||||
const doWrite = (data) => {
|
||||
const compress = packets.some((packet) => {
|
||||
return packet.options && packet.options.compress;
|
||||
});
|
||||
this.write(data, { compress });
|
||||
};
|
||||
|
||||
if (this.protocol === 3) {
|
||||
this.parser.encodePayload(packets, this.supportsBinary, doWrite);
|
||||
} else {
|
||||
this.parser.encodePayload(packets, doWrite);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data as response to poll request.
|
||||
*
|
||||
* @param {String} data
|
||||
* @param {Object} options
|
||||
* @private
|
||||
*/
|
||||
write(data, options) {
|
||||
debug('writing "%s"', data);
|
||||
this.doWrite(data, options, () => {
|
||||
this.req.cleanup();
|
||||
this.emit("drain");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the write.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
doWrite(data, options, callback) {
|
||||
// explicit UTF-8 is required for pages not served under utf
|
||||
const isString = typeof data === "string";
|
||||
const contentType = isString
|
||||
? "text/plain; charset=UTF-8"
|
||||
: "application/octet-stream";
|
||||
|
||||
const headers = {
|
||||
"Content-Type": contentType,
|
||||
};
|
||||
|
||||
const respond = (data) => {
|
||||
this.headers(this.req, headers);
|
||||
this.res.cork(() => {
|
||||
Object.keys(headers).forEach((key) => {
|
||||
this.res.writeHeader(key, String(headers[key]));
|
||||
});
|
||||
this.res.end(data);
|
||||
});
|
||||
callback();
|
||||
};
|
||||
|
||||
if (!this.httpCompression || !options.compress) {
|
||||
respond(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const len = isString ? Buffer.byteLength(data) : data.length;
|
||||
if (len < this.httpCompression.threshold) {
|
||||
respond(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const encoding = accepts(this.req).encodings(["gzip", "deflate"]);
|
||||
if (!encoding) {
|
||||
respond(data);
|
||||
return;
|
||||
}
|
||||
|
||||
this.compress(data, encoding, (err, data) => {
|
||||
if (err) {
|
||||
this.res.writeStatus("500 Internal Server Error");
|
||||
this.res.end();
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
headers["Content-Encoding"] = encoding;
|
||||
respond(data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
compress(data, encoding, callback) {
|
||||
debug("compressing");
|
||||
|
||||
const buffers = [];
|
||||
let nread = 0;
|
||||
|
||||
compressionMethods[encoding](this.httpCompression)
|
||||
.on("error", callback)
|
||||
.on("data", function (chunk) {
|
||||
buffers.push(chunk);
|
||||
nread += chunk.length;
|
||||
})
|
||||
.on("end", function () {
|
||||
callback(null, Buffer.concat(buffers, nread));
|
||||
})
|
||||
.end(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
doClose(fn) {
|
||||
debug("closing");
|
||||
|
||||
let closeTimeoutTimer;
|
||||
|
||||
const onClose = () => {
|
||||
clearTimeout(closeTimeoutTimer);
|
||||
fn();
|
||||
this.onClose();
|
||||
};
|
||||
|
||||
if (this.writable) {
|
||||
debug("transport writable - closing right away");
|
||||
this.send([{ type: "close" }]);
|
||||
onClose();
|
||||
} else if (this.discarded) {
|
||||
debug("transport discarded - closing right away");
|
||||
onClose();
|
||||
} else {
|
||||
debug("transport not writable - buffering orderly close");
|
||||
this.shouldClose = onClose;
|
||||
closeTimeoutTimer = setTimeout(onClose, this.closeTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns headers for a response.
|
||||
*
|
||||
* @param req - request
|
||||
* @param {Object} extra headers
|
||||
* @private
|
||||
*/
|
||||
headers(req, headers) {
|
||||
headers = headers || {};
|
||||
|
||||
// prevent XSS warnings on IE
|
||||
// https://github.com/LearnBoost/socket.io/pull/1333
|
||||
const ua = req.headers["user-agent"];
|
||||
if (ua && (~ua.indexOf(";MSIE") || ~ua.indexOf("Trident/"))) {
|
||||
headers["X-XSS-Protection"] = "0";
|
||||
}
|
||||
|
||||
headers["cache-control"] = "no-store";
|
||||
|
||||
this.emit("headers", headers, req);
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
83
packages/engine.io/lib/transports-uws/websocket.ts
Normal file
83
packages/engine.io/lib/transports-uws/websocket.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Transport } from "../transport";
|
||||
import debugModule from "debug";
|
||||
|
||||
const debug = debugModule("engine:ws");
|
||||
|
||||
export class WebSocket extends Transport {
|
||||
protected perMessageDeflate: any;
|
||||
private socket: any;
|
||||
|
||||
/**
|
||||
* WebSocket transport
|
||||
*
|
||||
* @param req
|
||||
*/
|
||||
constructor(req) {
|
||||
super(req);
|
||||
this.writable = false;
|
||||
this.perMessageDeflate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*/
|
||||
get name() {
|
||||
return "websocket";
|
||||
}
|
||||
|
||||
/**
|
||||
* Advertise upgrade support.
|
||||
*/
|
||||
get handlesUpgrades() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a packet payload.
|
||||
*
|
||||
* @param {Array} packets
|
||||
* @private
|
||||
*/
|
||||
send(packets) {
|
||||
this.writable = false;
|
||||
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
const isLast = i + 1 === packets.length;
|
||||
|
||||
const send = (data) => {
|
||||
const isBinary = typeof data !== "string";
|
||||
const compress =
|
||||
this.perMessageDeflate &&
|
||||
Buffer.byteLength(data) > this.perMessageDeflate.threshold;
|
||||
|
||||
debug('writing "%s"', data);
|
||||
this.socket.send(data, isBinary, compress);
|
||||
|
||||
if (isLast) {
|
||||
this.emit("drain");
|
||||
this.writable = true;
|
||||
this.emit("ready");
|
||||
}
|
||||
};
|
||||
|
||||
if (packet.options && typeof packet.options.wsPreEncoded === "string") {
|
||||
send(packet.options.wsPreEncoded);
|
||||
} else {
|
||||
this.parser.encodePacket(packet, this.supportsBinary, send);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
doClose(fn) {
|
||||
debug("closing");
|
||||
fn && fn();
|
||||
// call fn first since socket.end() immediately emits a "close" event
|
||||
this.socket.end();
|
||||
}
|
||||
}
|
||||
24
packages/engine.io/lib/transports/index.ts
Normal file
24
packages/engine.io/lib/transports/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Polling as XHR } from "./polling";
|
||||
import { JSONP } from "./polling-jsonp";
|
||||
import { WebSocket } from "./websocket";
|
||||
import { WebTransport } from "./webtransport";
|
||||
|
||||
export default {
|
||||
polling: polling,
|
||||
websocket: WebSocket,
|
||||
webtransport: WebTransport,
|
||||
};
|
||||
|
||||
/**
|
||||
* Polling polymorphic constructor.
|
||||
*/
|
||||
|
||||
function polling(req) {
|
||||
if ("string" === typeof req._query.j) {
|
||||
return new JSONP(req);
|
||||
} else {
|
||||
return new XHR(req);
|
||||
}
|
||||
}
|
||||
|
||||
polling.upgradesTo = ["websocket", "webtransport"];
|
||||
48
packages/engine.io/lib/transports/polling-jsonp.ts
Normal file
48
packages/engine.io/lib/transports/polling-jsonp.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Polling } from "./polling";
|
||||
import * as qs from "querystring";
|
||||
import type { RawData } from "engine.io-parser";
|
||||
|
||||
const rDoubleSlashes = /\\\\n/g;
|
||||
const rSlashes = /(\\)?\\n/g;
|
||||
|
||||
export class JSONP extends Polling {
|
||||
private readonly head: string;
|
||||
private readonly foot: string;
|
||||
|
||||
/**
|
||||
* JSON-P polling transport.
|
||||
*/
|
||||
constructor(req) {
|
||||
super(req);
|
||||
|
||||
this.head = "___eio[" + (req._query.j || "").replace(/[^0-9]/g, "") + "](";
|
||||
this.foot = ");";
|
||||
}
|
||||
|
||||
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 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
|
||||
data = data.replace(rSlashes, function (match, slashes) {
|
||||
return slashes ? match : "\n";
|
||||
});
|
||||
super.onData(data.replace(rDoubleSlashes, "\\n"));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
.replace(/\u2028/g, "\\u2028")
|
||||
.replace(/\u2029/g, "\\u2029");
|
||||
|
||||
// prepare response
|
||||
data = this.head + js + this.foot;
|
||||
|
||||
super.doWrite(data, options, callback);
|
||||
}
|
||||
}
|
||||
389
packages/engine.io/lib/transports/polling.ts
Normal file
389
packages/engine.io/lib/transports/polling.ts
Normal file
@@ -0,0 +1,389 @@
|
||||
import { EngineRequest, Transport } from "../transport";
|
||||
import { createGzip, createDeflate } from "zlib";
|
||||
import * as accepts from "accepts";
|
||||
import debugModule from "debug";
|
||||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import type { Packet, RawData } from "engine.io-parser";
|
||||
|
||||
const debug = debugModule("engine:polling");
|
||||
|
||||
const compressionMethods = {
|
||||
gzip: createGzip,
|
||||
deflate: createDeflate,
|
||||
};
|
||||
|
||||
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: () => void;
|
||||
|
||||
private readonly closeTimeout: number;
|
||||
|
||||
/**
|
||||
* HTTP polling constructor.
|
||||
*/
|
||||
constructor(req) {
|
||||
super(req);
|
||||
|
||||
this.closeTimeout = 30 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*/
|
||||
get name() {
|
||||
return "polling";
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides onRequest.
|
||||
*
|
||||
* @param {EngineRequest} req
|
||||
* @package
|
||||
*/
|
||||
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);
|
||||
} else if ("POST" === req.method) {
|
||||
this.onDataRequest(req, res);
|
||||
} else {
|
||||
res.writeHead(500);
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The client sends a request awaiting for us to send data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onPollRequest(req: EngineRequest, res: ServerResponse) {
|
||||
if (this.req) {
|
||||
debug("request overlap");
|
||||
// assert: this.res, '.req and .res should be (un)set together'
|
||||
this.onError("overlap from client");
|
||||
res.writeHead(400);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
debug("setting request");
|
||||
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
|
||||
const onClose = () => {
|
||||
this.onError("poll connection closed prematurely");
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
req.removeListener("close", onClose);
|
||||
this.req = this.res = null;
|
||||
};
|
||||
|
||||
req.cleanup = cleanup;
|
||||
req.on("close", onClose);
|
||||
|
||||
this.writable = true;
|
||||
this.emit("ready");
|
||||
|
||||
// if we're still writable but had a pending close, trigger an empty send
|
||||
if (this.writable && this.shouldClose) {
|
||||
debug("triggering empty send to append close packet");
|
||||
this.send([{ type: "noop" }]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The client sends a request with data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
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");
|
||||
res.writeHead(400);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const isBinary = "application/octet-stream" === req.headers["content-type"];
|
||||
|
||||
if (isBinary && this.protocol === 4) {
|
||||
return this.onError("invalid content");
|
||||
}
|
||||
|
||||
this.dataReq = req;
|
||||
this.dataRes = res;
|
||||
|
||||
let chunks = isBinary ? Buffer.concat([]) : "";
|
||||
|
||||
const cleanup = () => {
|
||||
req.removeListener("data", onData);
|
||||
req.removeListener("end", onEnd);
|
||||
req.removeListener("close", onClose);
|
||||
this.dataReq = this.dataRes = chunks = null;
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
cleanup();
|
||||
this.onError("data request connection closed prematurely");
|
||||
};
|
||||
|
||||
const onData = (data) => {
|
||||
let contentLength;
|
||||
if (isBinary) {
|
||||
chunks = Buffer.concat([chunks, data]);
|
||||
contentLength = chunks.length;
|
||||
} else {
|
||||
chunks += data;
|
||||
contentLength = Buffer.byteLength(chunks);
|
||||
}
|
||||
|
||||
if (contentLength > this.maxHttpBufferSize) {
|
||||
res.writeHead(413).end();
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
const onEnd = () => {
|
||||
this.onData(chunks);
|
||||
|
||||
const headers = {
|
||||
// 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",
|
||||
};
|
||||
|
||||
res.writeHead(200, this.headers(req, headers));
|
||||
res.end("ok");
|
||||
cleanup();
|
||||
};
|
||||
|
||||
req.on("close", onClose);
|
||||
if (!isBinary) req.setEncoding("utf8");
|
||||
req.on("data", onData);
|
||||
req.on("end", onEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the incoming data payload.
|
||||
*
|
||||
* @param data - encoded payload
|
||||
* @protected
|
||||
*/
|
||||
override onData(data: RawData) {
|
||||
debug('received "%s"', data);
|
||||
const callback = (packet) => {
|
||||
if ("close" === packet.type) {
|
||||
debug("got xhr close packet");
|
||||
this.onClose();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.onPacket(packet);
|
||||
};
|
||||
|
||||
if (this.protocol === 3) {
|
||||
this.parser.decodePayload(data, callback);
|
||||
} else {
|
||||
this.parser.decodePayload(data).forEach(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides onClose.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
onClose() {
|
||||
if (this.writable) {
|
||||
// close pending poll request
|
||||
this.send([{ type: "noop" }]);
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
send(packets: Packet[]) {
|
||||
this.writable = false;
|
||||
|
||||
if (this.shouldClose) {
|
||||
debug("appending close packet to payload");
|
||||
packets.push({ type: "close" });
|
||||
this.shouldClose();
|
||||
this.shouldClose = null;
|
||||
}
|
||||
|
||||
const doWrite = (data) => {
|
||||
const compress = packets.some((packet) => {
|
||||
return packet.options && packet.options.compress;
|
||||
});
|
||||
this.write(data, { compress });
|
||||
};
|
||||
|
||||
if (this.protocol === 3) {
|
||||
this.parser.encodePayload(packets, this.supportsBinary, doWrite);
|
||||
} else {
|
||||
this.parser.encodePayload(packets, doWrite);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data as response to poll request.
|
||||
*
|
||||
* @param {String} data
|
||||
* @param {Object} options
|
||||
* @private
|
||||
*/
|
||||
private write(data, options) {
|
||||
debug('writing "%s"', data);
|
||||
this.doWrite(data, options, () => {
|
||||
this.req.cleanup();
|
||||
this.emit("drain");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the write.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
protected doWrite(data, options, callback) {
|
||||
// explicit UTF-8 is required for pages not served under utf
|
||||
const isString = typeof data === "string";
|
||||
const contentType = isString
|
||||
? "text/plain; charset=UTF-8"
|
||||
: "application/octet-stream";
|
||||
|
||||
const headers = {
|
||||
"Content-Type": contentType,
|
||||
};
|
||||
|
||||
const respond = (data) => {
|
||||
headers["Content-Length"] =
|
||||
"string" === typeof data ? Buffer.byteLength(data) : data.length;
|
||||
this.res.writeHead(200, this.headers(this.req, headers));
|
||||
this.res.end(data);
|
||||
callback();
|
||||
};
|
||||
|
||||
if (!this.httpCompression || !options.compress) {
|
||||
respond(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const len = isString ? Buffer.byteLength(data) : data.length;
|
||||
if (len < this.httpCompression.threshold) {
|
||||
respond(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const encoding = accepts(this.req).encodings(["gzip", "deflate"]);
|
||||
if (!encoding) {
|
||||
respond(data);
|
||||
return;
|
||||
}
|
||||
|
||||
this.compress(data, encoding, (err, data) => {
|
||||
if (err) {
|
||||
this.res.writeHead(500);
|
||||
this.res.end();
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
headers["Content-Encoding"] = encoding;
|
||||
respond(data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses data.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private compress(data, encoding, callback) {
|
||||
debug("compressing");
|
||||
|
||||
const buffers = [];
|
||||
let nread = 0;
|
||||
|
||||
compressionMethods[encoding](this.httpCompression)
|
||||
.on("error", callback)
|
||||
.on("data", function (chunk) {
|
||||
buffers.push(chunk);
|
||||
nread += chunk.length;
|
||||
})
|
||||
.on("end", function () {
|
||||
callback(null, Buffer.concat(buffers, nread));
|
||||
})
|
||||
.end(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the transport.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
override doClose(fn: () => void) {
|
||||
debug("closing");
|
||||
|
||||
let closeTimeoutTimer;
|
||||
|
||||
if (this.dataReq) {
|
||||
debug("aborting ongoing data request");
|
||||
this.dataReq.destroy();
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
clearTimeout(closeTimeoutTimer);
|
||||
fn();
|
||||
this.onClose();
|
||||
};
|
||||
|
||||
if (this.writable) {
|
||||
debug("transport writable - closing right away");
|
||||
this.send([{ type: "close" }]);
|
||||
onClose();
|
||||
} else if (this.discarded) {
|
||||
debug("transport discarded - closing right away");
|
||||
onClose();
|
||||
} else {
|
||||
debug("transport not writable - buffering orderly close");
|
||||
this.shouldClose = onClose;
|
||||
closeTimeoutTimer = setTimeout(onClose, this.closeTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns headers for a response.
|
||||
*
|
||||
* @param {http.IncomingMessage} req
|
||||
* @param {Object} headers - extra headers
|
||||
* @private
|
||||
*/
|
||||
private headers(req: IncomingMessage, headers: Record<string, string> = {}) {
|
||||
// prevent XSS warnings on IE
|
||||
// https://github.com/LearnBoost/socket.io/pull/1333
|
||||
const ua = req.headers["user-agent"];
|
||||
if (ua && (~ua.indexOf(";MSIE") || ~ua.indexOf("Trident/"))) {
|
||||
headers["X-XSS-Protection"] = "0";
|
||||
}
|
||||
|
||||
headers["cache-control"] = "no-store";
|
||||
|
||||
this.emit("headers", headers, req);
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
112
packages/engine.io/lib/transports/websocket.ts
Normal file
112
packages/engine.io/lib/transports/websocket.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { EngineRequest, Transport } from "../transport";
|
||||
import debugModule from "debug";
|
||||
import type { Packet, RawData } from "engine.io-parser";
|
||||
|
||||
const debug = debugModule("engine:ws");
|
||||
|
||||
export class WebSocket extends Transport {
|
||||
protected perMessageDeflate: any;
|
||||
private socket: any;
|
||||
|
||||
/**
|
||||
* WebSocket transport
|
||||
*
|
||||
* @param {EngineRequest} req
|
||||
*/
|
||||
constructor(req: EngineRequest) {
|
||||
super(req);
|
||||
this.socket = req.websocket;
|
||||
this.socket.on("message", (data, isBinary) => {
|
||||
const message = isBinary ? data : data.toString();
|
||||
debug('received "%s"', message);
|
||||
super.onData(message);
|
||||
});
|
||||
this.socket.once("close", this.onClose.bind(this));
|
||||
this.socket.on("error", this.onError.bind(this));
|
||||
this.writable = true;
|
||||
this.perMessageDeflate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport name
|
||||
*/
|
||||
get name() {
|
||||
return "websocket";
|
||||
}
|
||||
|
||||
/**
|
||||
* Advertise upgrade support.
|
||||
*/
|
||||
get handlesUpgrades() {
|
||||
return true;
|
||||
}
|
||||
|
||||
send(packets: Packet[]) {
|
||||
this.writable = false;
|
||||
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
const isLast = i + 1 === packets.length;
|
||||
|
||||
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(
|
||||
// @ts-ignore
|
||||
packet.options.wsPreEncodedFrame,
|
||||
isLast ? this._onSentLast : this._onSent
|
||||
);
|
||||
} else {
|
||||
this.parser.encodePacket(
|
||||
packet,
|
||||
this.supportsBinary,
|
||||
isLast ? this._doSendLast : this._doSend
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the encoding of the WebSocket frame can be skipped.
|
||||
* @param packet
|
||||
* @private
|
||||
*/
|
||||
private _canSendPreEncodedFrame(packet: Packet) {
|
||||
return (
|
||||
!this.perMessageDeflate &&
|
||||
typeof this.socket?._sender?.sendFrame === "function" &&
|
||||
// @ts-ignore
|
||||
packet.options?.wsPreEncodedFrame !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
69
packages/engine.io/lib/transports/webtransport.ts
Normal file
69
packages/engine.io/lib/transports/webtransport.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Transport } from "../transport";
|
||||
import debugModule from "debug";
|
||||
import { createPacketEncoderStream } from "engine.io-parser";
|
||||
|
||||
const debug = debugModule("engine:webtransport");
|
||||
|
||||
/**
|
||||
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API
|
||||
*/
|
||||
export class WebTransport extends Transport {
|
||||
private readonly writer;
|
||||
|
||||
constructor(private readonly session, stream, reader) {
|
||||
super({ _query: { EIO: "4" } });
|
||||
|
||||
const transformStream = createPacketEncoderStream();
|
||||
transformStream.readable.pipeTo(stream.writable).catch(() => {
|
||||
debug("the stream was closed");
|
||||
});
|
||||
this.writer = transformStream.writable.getWriter();
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
debug("session is closed");
|
||||
break;
|
||||
}
|
||||
debug("received chunk: %o", value);
|
||||
this.onPacket(value);
|
||||
}
|
||||
} catch (e) {
|
||||
debug("error while reading: %s", e.message);
|
||||
}
|
||||
})();
|
||||
|
||||
session.closed.then(() => this.onClose());
|
||||
|
||||
this.writable = true;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "webtransport";
|
||||
}
|
||||
|
||||
async send(packets) {
|
||||
this.writable = false;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
await this.writer.write(packet);
|
||||
}
|
||||
} catch (e) {
|
||||
debug("error while writing: %s", e.message);
|
||||
}
|
||||
|
||||
this.emit("drain");
|
||||
this.writable = true;
|
||||
this.emit("ready");
|
||||
}
|
||||
|
||||
doClose(fn) {
|
||||
debug("closing WebTransport session");
|
||||
this.session.close();
|
||||
fn && fn();
|
||||
}
|
||||
}
|
||||
358
packages/engine.io/lib/userver.ts
Normal file
358
packages/engine.io/lib/userver.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
import debugModule from "debug";
|
||||
import { AttachOptions, BaseServer, Server } from "./server";
|
||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import transports from "./transports-uws";
|
||||
|
||||
const debug = debugModule("engine:uws");
|
||||
|
||||
export interface uOptions {
|
||||
/**
|
||||
* What permessage-deflate compression to use. uWS.DISABLED, uWS.SHARED_COMPRESSOR or any of the uWS.DEDICATED_COMPRESSOR_xxxKB.
|
||||
* @default uWS.DISABLED
|
||||
*/
|
||||
compression?: number;
|
||||
/**
|
||||
* Maximum amount of seconds that may pass without sending or getting a message. Connection is closed if this timeout passes. Resolution (granularity) for timeouts are typically 4 seconds, rounded to closest. Disable by using 0.
|
||||
* @default 120
|
||||
*/
|
||||
idleTimeout?: number;
|
||||
/**
|
||||
* Maximum length of allowed backpressure per socket when publishing or sending messages. Slow receivers with too high backpressure will be skipped until they catch up or timeout.
|
||||
* @default 1024 * 1024
|
||||
*/
|
||||
maxBackpressure?: number;
|
||||
}
|
||||
|
||||
export class uServer extends BaseServer {
|
||||
protected init() {}
|
||||
protected cleanup() {}
|
||||
|
||||
/**
|
||||
* Prepares a request by processing the query string.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private prepare(req, res: HttpResponse) {
|
||||
req.method = req.getMethod().toUpperCase();
|
||||
req.url = req.getUrl();
|
||||
|
||||
const params = new URLSearchParams(req.getQuery());
|
||||
req._query = Object.fromEntries(params.entries());
|
||||
|
||||
req.headers = {};
|
||||
req.forEach((key, value) => {
|
||||
req.headers[key] = value;
|
||||
});
|
||||
|
||||
req.connection = {
|
||||
remoteAddress: Buffer.from(res.getRemoteAddressAsText()).toString(),
|
||||
};
|
||||
|
||||
res.onAborted(() => {
|
||||
debug("response has been aborted");
|
||||
});
|
||||
}
|
||||
|
||||
protected createTransport(transportName, req) {
|
||||
return new transports[transportName](req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the engine to a µWebSockets.js server
|
||||
* @param app
|
||||
* @param options
|
||||
*/
|
||||
public attach(
|
||||
app /* : TemplatedApp */,
|
||||
options: AttachOptions & uOptions = {}
|
||||
) {
|
||||
const path = this._computePath(options);
|
||||
(app as TemplatedApp)
|
||||
.any(path, this.handleRequest.bind(this))
|
||||
//
|
||||
.ws<{ transport: any }>(path, {
|
||||
compression: options.compression,
|
||||
idleTimeout: options.idleTimeout,
|
||||
maxBackpressure: options.maxBackpressure,
|
||||
maxPayloadLength: this.opts.maxHttpBufferSize,
|
||||
upgrade: this.handleUpgrade.bind(this),
|
||||
open: (ws) => {
|
||||
const transport = ws.getUserData().transport;
|
||||
transport.socket = ws;
|
||||
transport.writable = true;
|
||||
transport.emit("ready");
|
||||
},
|
||||
message: (ws, message, isBinary) => {
|
||||
ws.getUserData().transport.onData(
|
||||
isBinary ? message : Buffer.from(message).toString()
|
||||
);
|
||||
},
|
||||
close: (ws, code, message) => {
|
||||
ws.getUserData().transport.onClose(code, message);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override _applyMiddlewares(
|
||||
req: any,
|
||||
res: any,
|
||||
callback: (err?: any) => void
|
||||
): void {
|
||||
if (this.middlewares.length === 0) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// needed to buffer headers until the status is computed
|
||||
req.res = new ResponseWrapper(res);
|
||||
|
||||
super._applyMiddlewares(req, req.res, (err) => {
|
||||
// some middlewares (like express-session) wait for the writeHead() call to flush their headers
|
||||
// see https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L220-L244
|
||||
req.res.writeHead();
|
||||
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
private handleRequest(
|
||||
res: HttpResponse,
|
||||
req: HttpRequest & { res: any; _query: any }
|
||||
) {
|
||||
debug('handling "%s" http request "%s"', req.getMethod(), req.getUrl());
|
||||
this.prepare(req, res);
|
||||
|
||||
req.res = res;
|
||||
|
||||
const callback = (errorCode, errorContext) => {
|
||||
if (errorCode !== undefined) {
|
||||
this.emit("connection_error", {
|
||||
req,
|
||||
code: errorCode,
|
||||
message: Server.errorMessages[errorCode],
|
||||
context: errorContext,
|
||||
});
|
||||
this.abortRequest(req.res, errorCode, errorContext);
|
||||
return;
|
||||
}
|
||||
|
||||
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) =>
|
||||
this.abortRequest(res, errorCode, errorContext);
|
||||
this.handshake(req._query.transport, req, closeConnection);
|
||||
}
|
||||
};
|
||||
|
||||
this._applyMiddlewares(req, res, (err) => {
|
||||
if (err) {
|
||||
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
|
||||
} else {
|
||||
this.verify(req, false, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleUpgrade(
|
||||
res: HttpResponse,
|
||||
req: HttpRequest & { res: any; _query: any },
|
||||
context
|
||||
) {
|
||||
debug("on upgrade");
|
||||
|
||||
this.prepare(req, res);
|
||||
|
||||
req.res = res;
|
||||
|
||||
const callback = async (errorCode, errorContext) => {
|
||||
if (errorCode !== undefined) {
|
||||
this.emit("connection_error", {
|
||||
req,
|
||||
code: errorCode,
|
||||
message: Server.errorMessages[errorCode],
|
||||
context: errorContext,
|
||||
});
|
||||
this.abortRequest(res, errorCode, errorContext);
|
||||
return;
|
||||
}
|
||||
|
||||
const id = req._query.sid;
|
||||
let transport;
|
||||
|
||||
if (id) {
|
||||
const client = this.clients[id];
|
||||
if (!client) {
|
||||
debug("upgrade attempt for closed client");
|
||||
res.close();
|
||||
} else if (client.upgrading) {
|
||||
debug("transport has already been trying to upgrade");
|
||||
res.close();
|
||||
} else if (client.upgraded) {
|
||||
debug("transport had already been upgraded");
|
||||
res.close();
|
||||
} else {
|
||||
debug("upgrading existing transport");
|
||||
transport = this.createTransport(req._query.transport, req);
|
||||
client._maybeUpgrade(transport);
|
||||
}
|
||||
} else {
|
||||
transport = await this.handshake(
|
||||
req._query.transport,
|
||||
req,
|
||||
(errorCode, errorContext) =>
|
||||
this.abortRequest(res, errorCode, errorContext)
|
||||
);
|
||||
if (!transport) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// calling writeStatus() triggers the flushing of any header added in a middleware
|
||||
req.res.writeStatus("101 Switching Protocols");
|
||||
|
||||
res.upgrade(
|
||||
{
|
||||
transport,
|
||||
},
|
||||
req.getHeader("sec-websocket-key"),
|
||||
req.getHeader("sec-websocket-protocol"),
|
||||
req.getHeader("sec-websocket-extensions"),
|
||||
context
|
||||
);
|
||||
};
|
||||
|
||||
this._applyMiddlewares(req, res, (err) => {
|
||||
if (err) {
|
||||
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
|
||||
} else {
|
||||
this.verify(req, true, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private abortRequest(
|
||||
res: HttpResponse | ResponseWrapper,
|
||||
errorCode,
|
||||
errorContext
|
||||
) {
|
||||
const statusCode =
|
||||
errorCode === Server.errors.FORBIDDEN
|
||||
? "403 Forbidden"
|
||||
: "400 Bad Request";
|
||||
const message =
|
||||
errorContext && errorContext.message
|
||||
? errorContext.message
|
||||
: Server.errorMessages[errorCode];
|
||||
|
||||
res.writeStatus(statusCode);
|
||||
res.writeHeader("Content-Type", "application/json");
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
code: errorCode,
|
||||
message,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseWrapper {
|
||||
private statusWritten: boolean = false;
|
||||
private headers = [];
|
||||
private isAborted = false;
|
||||
|
||||
constructor(readonly res: HttpResponse) {}
|
||||
|
||||
public set statusCode(status: number) {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
// FIXME: handle all status codes?
|
||||
this.writeStatus(status === 200 ? "200 OK" : "204 No Content");
|
||||
}
|
||||
|
||||
public writeHead(status: number) {
|
||||
this.statusCode = status;
|
||||
}
|
||||
|
||||
public setHeader(key, value) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
this.writeHeader(key, val);
|
||||
});
|
||||
} else {
|
||||
this.writeHeader(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public removeHeader() {
|
||||
// FIXME: not implemented
|
||||
}
|
||||
|
||||
// needed by vary: https://github.com/jshttp/vary/blob/5d725d059b3871025cf753e9dfa08924d0bcfa8f/index.js#L134
|
||||
public getHeader() {}
|
||||
|
||||
public writeStatus(status: string) {
|
||||
if (this.isAborted) return;
|
||||
|
||||
this.res.writeStatus(status);
|
||||
this.statusWritten = true;
|
||||
this.writeBufferedHeaders();
|
||||
return this;
|
||||
}
|
||||
|
||||
public writeHeader(key: string, value: string) {
|
||||
if (this.isAborted) return;
|
||||
|
||||
if (key === "Content-Length") {
|
||||
// the content length is automatically added by uWebSockets.js
|
||||
return;
|
||||
}
|
||||
if (this.statusWritten) {
|
||||
this.res.writeHeader(key, value);
|
||||
} else {
|
||||
this.headers.push([key, value]);
|
||||
}
|
||||
}
|
||||
|
||||
private writeBufferedHeaders() {
|
||||
this.headers.forEach(([key, value]) => {
|
||||
this.res.writeHeader(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
public end(data) {
|
||||
if (this.isAborted) return;
|
||||
|
||||
this.res.cork(() => {
|
||||
if (!this.statusWritten) {
|
||||
// status will be inferred as "200 OK"
|
||||
this.writeBufferedHeaders();
|
||||
}
|
||||
this.res.end(data);
|
||||
});
|
||||
}
|
||||
|
||||
public onData(fn) {
|
||||
if (this.isAborted) return;
|
||||
|
||||
this.res.onData(fn);
|
||||
}
|
||||
|
||||
public onAborted(fn) {
|
||||
if (this.isAborted) return;
|
||||
|
||||
this.res.onAborted(() => {
|
||||
// Any attempt to use the UWS response object after abort will throw!
|
||||
this.isAborted = true;
|
||||
fn();
|
||||
});
|
||||
}
|
||||
|
||||
public cork(fn) {
|
||||
if (this.isAborted) return;
|
||||
|
||||
this.res.cork(fn);
|
||||
}
|
||||
}
|
||||
4485
packages/engine.io/package-lock.json
generated
Normal file
4485
packages/engine.io/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
85
packages/engine.io/package.json
Normal file
85
packages/engine.io/package.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "engine.io",
|
||||
"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"
|
||||
},
|
||||
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
||||
"homepage": "https://github.com/socketio/engine.io",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Eugen Dueck",
|
||||
"web": "https://github.com/EugenDueck"
|
||||
},
|
||||
{
|
||||
"name": "Afshin Mehrabani",
|
||||
"web": "https://github.com/afshinm"
|
||||
},
|
||||
{
|
||||
"name": "Christoph Dorn",
|
||||
"web": "https://github.com/cadorn"
|
||||
},
|
||||
{
|
||||
"name": "Mark Mokryn",
|
||||
"email": "mokesmokes@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fails-components/webtransport": "^0.1.7",
|
||||
"babel-eslint": "^8.0.2",
|
||||
"eiows": "^4.1.2",
|
||||
"engine.io-client": "6.5.0",
|
||||
"engine.io-client-v3": "npm:engine.io-client@3.5.2",
|
||||
"expect.js": "^0.3.1",
|
||||
"express-session": "^1.17.3",
|
||||
"helmet": "^6.0.1",
|
||||
"mocha": "^9.1.3",
|
||||
"node-forge": "^1.3.1",
|
||||
"prettier": "^2.8.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"superagent": "^3.8.1",
|
||||
"typescript": "^4.4.3",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.30.0"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "rimraf ./build && tsc",
|
||||
"test": "npm run compile && npm run format:check && npm run test:default && npm run test:compat-v3",
|
||||
"test:default": "mocha --bail --exit",
|
||||
"test:compat-v3": "EIO_CLIENT=3 mocha --exit",
|
||||
"test:eiows": "EIO_WS_ENGINE=eiows mocha --exit",
|
||||
"test:uws": "EIO_WS_ENGINE=uws mocha --exit",
|
||||
"format:check": "prettier --check \"wrapper.mjs\" \"lib/**/*.ts\" \"test/**/*.js\" \"test/webtransport.mjs\"",
|
||||
"format:fix": "prettier --write \"wrapper.mjs\" \"lib/**/*.ts\" \"test/**/*.js\" \"test/webtransport.mjs\"",
|
||||
"prepack": "npm run compile"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:socketio/engine.io.git"
|
||||
},
|
||||
"files": [
|
||||
"build/",
|
||||
"wrapper.mjs"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
}
|
||||
5
packages/engine.io/test/.eslintrc.json
Normal file
5
packages/engine.io/test/.eslintrc.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"env": {
|
||||
"mocha": true
|
||||
}
|
||||
}
|
||||
57
packages/engine.io/test/common.js
Normal file
57
packages/engine.io/test/common.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const { listen, uServer } = require("..");
|
||||
const { Socket } =
|
||||
process.env.EIO_CLIENT === "3"
|
||||
? require("engine.io-client-v3")
|
||||
: require("engine.io-client");
|
||||
|
||||
/**
|
||||
* Listen shortcut that fires a callback on an ephemeral port.
|
||||
*/
|
||||
|
||||
exports.listen = (opts, fn) => {
|
||||
if ("function" === typeof opts) {
|
||||
fn = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
opts.allowEIO3 = true;
|
||||
|
||||
if (process.env.EIO_WS_ENGINE === "uws") {
|
||||
const { App, us_socket_local_port } = require("uWebSockets.js");
|
||||
const engine = new uServer(opts);
|
||||
const app = App();
|
||||
engine.attach(app, opts);
|
||||
|
||||
app.listen(0, (listenSocket) => {
|
||||
const port = us_socket_local_port(listenSocket);
|
||||
process.nextTick(() => {
|
||||
fn(port);
|
||||
});
|
||||
});
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
if (process.env.EIO_WS_ENGINE === "eiows") {
|
||||
opts.wsEngine = require("eiows").Server;
|
||||
}
|
||||
|
||||
const e = listen(0, opts, () => {
|
||||
fn(e.httpServer.address().port);
|
||||
});
|
||||
|
||||
return e;
|
||||
};
|
||||
|
||||
exports.ClientSocket = Socket;
|
||||
|
||||
exports.createPartialDone = (done, count) => {
|
||||
let i = 0;
|
||||
return () => {
|
||||
if (++i === count) {
|
||||
done();
|
||||
} else if (i > count) {
|
||||
done(new Error(`partialDone() called too many times: ${i} > ${count}`));
|
||||
}
|
||||
};
|
||||
};
|
||||
253
packages/engine.io/test/engine.io.js
Normal file
253
packages/engine.io/test/engine.io.js
Normal file
@@ -0,0 +1,253 @@
|
||||
const net = require("net");
|
||||
const { Server, protocol, attach } = require("..");
|
||||
const listen = require("./common").listen;
|
||||
const expect = require("expect.js");
|
||||
const request = require("superagent");
|
||||
const http = require("http");
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
|
||||
describe("engine", () => {
|
||||
it("should expose protocol number", () => {
|
||||
expect(protocol).to.be.a("number");
|
||||
});
|
||||
|
||||
it.skip("should be the same version as client", () => {
|
||||
const version = require("../package.json").version;
|
||||
expect(version).to.be(require("engine.io-client/package.json").version);
|
||||
});
|
||||
|
||||
describe("engine()", () => {
|
||||
it("should create a Server when require called with no arguments", () => {
|
||||
const engine = new Server();
|
||||
expect(engine).to.be.an(Server);
|
||||
expect(engine.ws).to.be.ok();
|
||||
});
|
||||
|
||||
it("should pass options correctly to the Server", () => {
|
||||
const engine = new Server({ cors: true });
|
||||
expect(engine.opts).to.have.property("cors", true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("listen", () => {
|
||||
it("should open a http server that returns 501", function (done) {
|
||||
if (process.env.EIO_WS_ENGINE === "uws") {
|
||||
return this.skip();
|
||||
}
|
||||
listen((port) => {
|
||||
request.get(`http://localhost:${port}`, (err, res) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(res.status).to.be(501);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("attach()", () => {
|
||||
it("should work from require()", () => {
|
||||
const server = http.createServer();
|
||||
const engine = new Server(server);
|
||||
|
||||
expect(engine).to.be.an(Server);
|
||||
});
|
||||
|
||||
it("should return an engine.Server", () => {
|
||||
const server = http.createServer();
|
||||
const engine = attach(server);
|
||||
|
||||
expect(engine).to.be.an(Server);
|
||||
});
|
||||
|
||||
it("should attach engine to an http server", (done) => {
|
||||
const server = http.createServer();
|
||||
attach(server);
|
||||
|
||||
server.listen(() => {
|
||||
const uri = `http://localhost:${server.address().port}/engine.io/`;
|
||||
request.get(uri, (err, res) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(res.status).to.be(400);
|
||||
expect(res.body.code).to.be(0);
|
||||
expect(res.body.message).to.be("Transport unknown");
|
||||
server.once("close", done);
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should destroy upgrades not handled by engine", (done) => {
|
||||
const server = http.createServer();
|
||||
attach(server, { destroyUpgradeTimeout: 50 });
|
||||
|
||||
server.listen(() => {
|
||||
const client = net.createConnection(server.address().port);
|
||||
client.setEncoding("ascii");
|
||||
client.write(
|
||||
[
|
||||
"GET / HTTP/1.1",
|
||||
"Connection: Upgrade",
|
||||
"Upgrade: IRC/6.9",
|
||||
"",
|
||||
"",
|
||||
].join("\r\n")
|
||||
);
|
||||
|
||||
const check = setTimeout(() => {
|
||||
done(new Error("Client should have ended"));
|
||||
}, 100);
|
||||
|
||||
client.on("end", () => {
|
||||
clearTimeout(check);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should not destroy unhandled upgrades with destroyUpgrade:false", (done) => {
|
||||
const server = http.createServer();
|
||||
attach(server, { destroyUpgrade: false, destroyUpgradeTimeout: 50 });
|
||||
|
||||
server.listen(() => {
|
||||
const client = net.createConnection(server.address().port);
|
||||
client.on("connect", () => {
|
||||
client.setEncoding("ascii");
|
||||
client.write(
|
||||
[
|
||||
"GET / HTTP/1.1",
|
||||
"Connection: Upgrade",
|
||||
"Upgrade: IRC/6.9",
|
||||
"",
|
||||
"",
|
||||
].join("\r\n")
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
client.removeListener("end", onEnd);
|
||||
done();
|
||||
}, 100);
|
||||
|
||||
function onEnd() {
|
||||
done(new Error("Client should not end"));
|
||||
}
|
||||
|
||||
client.on("end", onEnd);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should destroy unhandled upgrades with after a timeout", (done) => {
|
||||
const server = http.createServer();
|
||||
attach(server, { destroyUpgradeTimeout: 200 });
|
||||
|
||||
server.listen(() => {
|
||||
const client = net.createConnection(server.address().port);
|
||||
client.on("connect", () => {
|
||||
client.setEncoding("ascii");
|
||||
client.write(
|
||||
[
|
||||
"GET / HTTP/1.1",
|
||||
"Connection: Upgrade",
|
||||
"Upgrade: IRC/6.9",
|
||||
"",
|
||||
"",
|
||||
].join("\r\n")
|
||||
);
|
||||
|
||||
// send from client to server
|
||||
// tests that socket is still alive
|
||||
// this will not keep the socket open as the server does not handle it
|
||||
setTimeout(() => {
|
||||
client.write("foo");
|
||||
}, 100);
|
||||
|
||||
function onEnd() {
|
||||
done();
|
||||
}
|
||||
|
||||
client.on("end", onEnd);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should not destroy handled upgrades with after a timeout", (done) => {
|
||||
const server = http.createServer();
|
||||
attach(server, { destroyUpgradeTimeout: 100 });
|
||||
|
||||
// write to the socket to keep engine.io from closing it by writing before the timeout
|
||||
server.on("upgrade", (req, socket) => {
|
||||
socket.write("foo");
|
||||
socket.on("data", (chunk) => {
|
||||
expect(chunk.toString()).to.be("foo");
|
||||
socket.end();
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(() => {
|
||||
const client = net.createConnection(server.address().port);
|
||||
|
||||
client.on("connect", () => {
|
||||
client.setEncoding("ascii");
|
||||
client.write(
|
||||
[
|
||||
"GET / HTTP/1.1",
|
||||
"Connection: Upgrade",
|
||||
"Upgrade: IRC/6.9",
|
||||
"",
|
||||
"",
|
||||
].join("\r\n")
|
||||
);
|
||||
|
||||
// test that socket is still open by writing after the timeout period
|
||||
setTimeout(() => {
|
||||
client.write("foo");
|
||||
}, 200);
|
||||
|
||||
client.on("data", (data) => {});
|
||||
|
||||
client.on("end", done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should preserve original request listeners", (done) => {
|
||||
let listeners = 0;
|
||||
const server = http.createServer((req, res) => {
|
||||
expect(req && res).to.be.ok();
|
||||
listeners++;
|
||||
});
|
||||
|
||||
server.on("request", (req, res) => {
|
||||
expect(req && res).to.be.ok();
|
||||
res.writeHead(200);
|
||||
res.end("");
|
||||
listeners++;
|
||||
});
|
||||
|
||||
attach(server);
|
||||
|
||||
server.listen(() => {
|
||||
const port = server.address().port;
|
||||
request.get(
|
||||
`http://localhost:${port}/engine.io/default/`,
|
||||
(err, res) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(res.status).to.be(400);
|
||||
expect(res.body.code).to.be(0);
|
||||
expect(res.body.message).to.be("Transport unknown");
|
||||
request.get(`http://localhost:${port}/test`, (err, res) => {
|
||||
expect(err).to.be(null);
|
||||
expect(res.status).to.be(200);
|
||||
expect(listeners).to.eql(2);
|
||||
server.once("close", done);
|
||||
server.close();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
33
packages/engine.io/test/fixtures/ca.crt
vendored
Normal file
33
packages/engine.io/test/fixtures/ca.crt
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFtTCCA52gAwIBAgIJAJKBPV3nMXjsMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTUxMTE4MTczODAwWhcNMjUxMTE1MTczODAwWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
|
||||
CgKCAgEAmNWKTumE1f+ptArhPTOcaUReoqBlri/ujIzm1N8Qr0hghS6B8eXGngAS
|
||||
YM7ziTlLZmLKgZg7TYOs+qK+xNNjSMbkA4Tions7vX3FYAokfh1iSiQigAw3TAwg
|
||||
brUaA0phucJBjvWI2mDuwzTLhQp1wmGrliAJhXag2ZQt817m6wrsFWuwiviMIHlq
|
||||
mQhC+vwd2SvW4xGf5zxjzCM8m7pOiJCLjxXwvNphiTR3tb807W00mi5cMFwhmAUT
|
||||
uSiVkVERubIYEKNSW2ynxzGFfb+GF/ddUxCKsnMDfM+SpMrsTBv9BzJzXU7Hc9jP
|
||||
NPFtrZiVo9aKn8csTSvKifmfiNwl2YGuWlW++1+ew6Q9rqEqvKHnKU+Cuwt3y37U
|
||||
ryqrBS47cz1xxFb3fCn+a72ytcHjI9lMqIQ0+IZ0/4cf0TK80ECEQ0CyrCk0E9Qz
|
||||
eMEzIALRa/pI8uTXdoAtQIlOsfALWeni+QphZ1BVjwZRmr+F1Px2/R30+gAcZHKc
|
||||
D+0Bm6owvpBWDe1s0DrkwtY3fyZ+OPS5/3eQtyhy9/3vnz9WBw0BGZyN2nzs5HsB
|
||||
RB5qDBRx+NQz1QYp/Ba3WeVmZURe2NMnS4uEypkWahW1XNQ+g+JJhK1p01s0+v/B
|
||||
f4DodYEcsw/3fRU0AKdsAkabQ68VIJAYyfQyinpNR9sHDKZ6Dx8CAwEAAaOBpzCB
|
||||
pDAdBgNVHQ4EFgQUdwTc4idMFJo0xYmoLTJQeD7A59kwdQYDVR0jBG4wbIAUdwTc
|
||||
4idMFJo0xYmoLTJQeD7A59mhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
|
||||
b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQCS
|
||||
gT1d5zF47DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQBRLzphOdJO
|
||||
ekU+iwFLGXJiLfUQi3EORaA3mBtozvyRRUyxu0FqWdGGINcyq7xaZxkQ0P+eBMDv
|
||||
V3F7FvidhaVe6TR7VJB2gyaWeLd1BSOi5gGBk5kuuqV3VbusNzY+WhJip98HEK+y
|
||||
XP+Lt/LXJpPwOORF9TiR6IBFQn3fBVhPjsqQRzT468QuZ5DCGQ5UW+wWl43I8OxS
|
||||
PDiUmHTlwLnLqqVFgSE+VnX4vSUZD8kDf0kkxssg1r56IzneSlRBegSVXIuRCbRf
|
||||
QmWaxz+D6ffM1eNiE3nQxsgJy3dPL1Lfsaidgz39yAC099pjLyVH23cfmFmT/l5b
|
||||
OdhRE5D75rL8eXAiI2/voz1M+v7XznHjZEhcVUlFsBXsk3zHb2vQQZRNPLnybTb8
|
||||
biFpReSIWdpno+F5IrT7z0L8JI3LU0leEFV+Rf525Q+78Rffohxd51fUj0vLKoy9
|
||||
un0IEkOcaJhHTPc2dMXb2qGcV4MaUEUsERrnanGZmdNd1aD3YAG3C+nJ8gxrEGHO
|
||||
veD6Xbyf1K8e7H2sqhGCm8eyHgCFGQiul6yQ41ZwjKgoSCJvOJaYUFE18k0S9k/I
|
||||
rWYvYWRYbDj4GYx+6LUTfyo30KK5jl4KAil92LrGlIfWK4IjUJyPlOJkb6gXkj0l
|
||||
lfbUHTmnKthFwJS0958xBq7UM7+RzyFIOg==
|
||||
-----END CERTIFICATE-----
|
||||
51
packages/engine.io/test/fixtures/ca.key
vendored
Normal file
51
packages/engine.io/test/fixtures/ca.key
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKQIBAAKCAgEAmNWKTumE1f+ptArhPTOcaUReoqBlri/ujIzm1N8Qr0hghS6B
|
||||
8eXGngASYM7ziTlLZmLKgZg7TYOs+qK+xNNjSMbkA4Tions7vX3FYAokfh1iSiQi
|
||||
gAw3TAwgbrUaA0phucJBjvWI2mDuwzTLhQp1wmGrliAJhXag2ZQt817m6wrsFWuw
|
||||
iviMIHlqmQhC+vwd2SvW4xGf5zxjzCM8m7pOiJCLjxXwvNphiTR3tb807W00mi5c
|
||||
MFwhmAUTuSiVkVERubIYEKNSW2ynxzGFfb+GF/ddUxCKsnMDfM+SpMrsTBv9BzJz
|
||||
XU7Hc9jPNPFtrZiVo9aKn8csTSvKifmfiNwl2YGuWlW++1+ew6Q9rqEqvKHnKU+C
|
||||
uwt3y37UryqrBS47cz1xxFb3fCn+a72ytcHjI9lMqIQ0+IZ0/4cf0TK80ECEQ0Cy
|
||||
rCk0E9QzeMEzIALRa/pI8uTXdoAtQIlOsfALWeni+QphZ1BVjwZRmr+F1Px2/R30
|
||||
+gAcZHKcD+0Bm6owvpBWDe1s0DrkwtY3fyZ+OPS5/3eQtyhy9/3vnz9WBw0BGZyN
|
||||
2nzs5HsBRB5qDBRx+NQz1QYp/Ba3WeVmZURe2NMnS4uEypkWahW1XNQ+g+JJhK1p
|
||||
01s0+v/Bf4DodYEcsw/3fRU0AKdsAkabQ68VIJAYyfQyinpNR9sHDKZ6Dx8CAwEA
|
||||
AQKCAgAg6z3TKXE3Ns4yvXUuXYN/GP7ZQHsmPaTAGUlO6I0LdCd2CEJs+/T/6zwK
|
||||
JglGsVSQRQ8hQszjMU183rkAZBeqgUxzhZfbL3f6pLByszyQ/XtCRO45bmgqtSH3
|
||||
NoLX2pmaDUFZrYFAqEhFO4XqrgoXSDpRJ61lVdvngYc0OGi8j6myI3PvOwHTrNNN
|
||||
Cv6CWPOE53BtkEpE4DkOqzhOwp5Pw/KLa0pjIxaHGwn916Vqzm7aFso8kFucBtvs
|
||||
sdUla7TJrpaIXuVKU+j/eqcqIqqbVuh/D70QGr3RkFQhsqOa8RxbBH7cxi8nwLdA
|
||||
zA+3qHnyxC8voxLjvF7vwRifvetYzOP1YunDU3wraU4sHQn4OXh0TEOhm4QhI2W2
|
||||
XSUt9B7zDm2AQIukJPxXoKsCd7D91l8m/suDGlHv3zZoJ6qgLuEZDOThhRq+wCIs
|
||||
wgzRDgDuQ6CVU1gVnT0FUDj5LZ68qiX9+vA/w3Yky6xSRSTnTvgLaWBsPUBytX4h
|
||||
eqfo39R1Ztm3UQypx0VyPJIDxVt5pbRMNxb7mqjzGh62fcH4fasl0spt97KKAtJq
|
||||
3BraN2EP3TeBB4eaHtyZY/aCoOpNqrL0ajEzN9wS2hrS+j8ZIEMdEfADVOGGfnZo
|
||||
ABl/gRo1m09zAadK5JZlaGx1bZS3ag5ftM+V6S6Ku/LjkINwgQKCAQEAxm4TiJxb
|
||||
k2taQWwPYaPrkulCjrDbIj1boli8uh4h1JtXvrCQxQ9JFoXJtZmezBkJ7Vz9flr+
|
||||
OxR+EGUIc+949bSexwDhVCc1SL5YXVYPu1oeYgOjoVMfh+mzYCgyfK8de8Ijq+gF
|
||||
Egj77UKsfN5ejG6i1Vs4F+Z+zZzsP95qfE5dPieACzwo0igM8HVZMGavO67T1KhY
|
||||
oa7e+Jk7Lcw3KL4vHQQK8UAKHwE1/TOgi0KvSQ250hfJBbWUnLFTbHOXelgg1Fpw
|
||||
sqde/M240Pd2ltKdWxM+awyowiNPkMCHira0RrXdBT0vDbNBy3lvMtm1UpYUoCl0
|
||||
MMrpk2G7zeuqDwKCAQEAxSzwbKuK4guIn/FeoLHiMEazhpHub1OY2yBwnE0j44LQ
|
||||
wIo9G70GTnBaH9gJDj7wOL1xwIdRdNoeWdEQ4wXH/rEH93JrZ1y9jv7LcIegJqQh
|
||||
C0U4RPE1JC0pic6f1Gw58p0q85Rkkee8oaJfeLZ4eJqy52XJyKRzktiQ1mN5ABYU
|
||||
gwS7GXee/tcWobWqN2Sq/4TcW/nysxQ5dsKKJPide0zKeNwhxPlahBupwjlsuoa2
|
||||
wAsUeXttfY17R/vS0KXbxSzoLII/ClrT2n0OoTxmUK7ht4OuBXgg6ZkjIBQ0ki/n
|
||||
CZTvClYziLk5NkGR2tw53I/zlXslXKbuTX4ByUSZ8QKCAQEAmLCPe2nF1fSfqQP7
|
||||
+ghm989ileZlWT2Zy5047IbPRYibxnKbk+elOB2PD5y8YxVJXEtYDOj8BH5KW1dD
|
||||
X+MAUyG/pCZ7PYRGLkm6OWhGBsbb5lQij7sk4jLlArMr1mHx8A9934RUkoIzSWkq
|
||||
zZNXcfyYdFETIuEM5i9AZA1EJ48tlOxUTVDnoH+NJWNHVEVPxj9LZbJ9MT0c+nL+
|
||||
5MjmEQX3vv4jZWz/3MfTwZj+iuqvcymKua3v0+LcDo8tQKDaCRzTdlR5sB+2qhWr
|
||||
h7FEod5Dk5eFSl6dZXZCfYKJSiY5Jsg+4Q8prAMqN+ajuJ9qNbii+nOrovghMHXe
|
||||
TCBx5QKCAQBas2BpbMO3VbzkbkCsRQeaU3uTxJ9c4KSo8BQ9IhMHPg7O8whHMT2s
|
||||
aWxbx6HqxrL0NtkTymuDCC77+/r7o5YrJ75VanHTm0qrc7ObsRfPjqKQr6fBtv9O
|
||||
A+Reuwi0y5AgdYHjiHh20ZXo+GtYeP+T4v23ChC3Vka/3xVJOXrYuk93MX7rqSYf
|
||||
bku/2XRShOFQJwrC2Ih3Li983OJ1PVQb+ugMjp6OIHIt4RfG+2lzqDJ6xt4FP+zO
|
||||
231BUKraReGBozWt+8AKAFwB3pMTQliCdt/n7g/n/imNq18IC6NfN9/cfYE0TRDp
|
||||
rOKPfbwdZD7Nof5X3c0DANsQFI23yvHRAoIBAQCIdbiXKkrgv0fbSNaTpP8XNROV
|
||||
M+BROigjiQnvAILKCenp1MSKcnIfL24ZfWzRhC5s0WtbsABswK+6pP4lXiXZqvyi
|
||||
5SJ3/omT13CyNjTDw1LlpSE33FHJKAIpYfP8QVTkOG/8GclR/JUFXicujess2fhw
|
||||
9F4sUA9txqiyWzhHauU0R4V78jsq1V4VEtGhpapzVNtvpWeCEB33WUiU/EwdLsdz
|
||||
RnKkvCA0WAFuwhH3bELyFj5sVy8L6kS8QQt6w4T2L/gNkwO01RmCNXSQbYkdYA1Y
|
||||
9t8FefPnf1Ry86PLdKyg8/LNLS023MpDgt7eCa2/ysnbhDZ5RepZJymcy0rP
|
||||
-----END RSA PRIVATE KEY-----
|
||||
54
packages/engine.io/test/fixtures/ca.key.org
vendored
Normal file
54
packages/engine.io/test/fixtures/ca.key.org
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,47A0291BAB679B36
|
||||
|
||||
KuGdIvVLu07u5l1aC/NdXvqLFAbI6NHHosGKs+CuuvnqSd4Yo1XG19SR+irE4vvQ
|
||||
EWwMypyTy0HN81WEvwx5PFNT+PGokjz6KBJsUwYo6pgS9pXfx9hk/Uo5NKSrH3Qm
|
||||
3rBVWciPABUiprJaz5PyYZHX/EdzvDmNUpC3HhO0Q4Bfy3DXp8sPvKaoHXYIreKd
|
||||
SpApbeP3bc2N/mg3qFLPC4tntmo2oYID6PEknrMHgBjvQvRDWHhzISdUTcgYmg2n
|
||||
p7aQa0HUbixFpDfTKjBn808iYa76AFj5gfG1NaIZ839h1jq1DlT3y4wcNxpEXY8Z
|
||||
UMvtAtLSEOZYDgJt7kzTJ7tqNYKPluDEoaljLGZeIOYYKDgU6d3Zs67aL036CVSr
|
||||
YdM3mIl65r196NIYnBeXT0metNMWYEAzuF7xTpaeO5JGDECvKQTYumPWwNjXxO75
|
||||
YqWxkmmiJ4TK7ECKfCvefy31wVi1T1+fdGYI3X6F9md/r290URyVfI8AFUkXpJ5c
|
||||
ubWF82dJ9TwMWfdwPnlxQRlzAh18YuTbzK7MJYE3UOLJuzqU9natueIh0Ek2YOtk
|
||||
cVuW0VtPoLP7ApfZJoF0LS2YRV6cWDA3XxiSCWhz0m0cvU1xyts44ej5eYaRvScU
|
||||
Ec3PFliFQrbnKzXwvdZvlhUoEpV+8ldZ6rJTMrZe0WTyQ3r50uLdsc76W/6nwK2/
|
||||
lA8tH3zHBjMDgQ3J+KluxajEgU+fyGpucX63BpCs29XBHFvBLkck8YYNO3xvSKpC
|
||||
7fVu/Vrk89LlQZ9VoX2phidEpgsIji5xK0csXuQ11Zfqu+WCQKO6NS2MUCUFPLmY
|
||||
ZmWN3BvCo9uSOchfs5vnS3+ismLm2Juzs/gmBgdWVd5MyJtmk8U45Tklo3aVJuow
|
||||
6I9kZrAE3x9ZEhLgYUgG93DAEGDD2bFfB2HXbfg1FV0mgOqKU3cuh/VUzbtHfiVX
|
||||
nIoEbue4t76gy2U5gBMfH1hHwM/ONJ9y0LaviWMLX4rEGzgE9nf+ONwykLZqzRdU
|
||||
1XMPCwfxETR5XnnwvUg1IuYU6PPrQ8KpQyDzq3WNYI4ghUGrXsEJeCX+RXFA8GbS
|
||||
nlKIYcsbyeRZGipItB9CBB6HX2tL2dyde2jyXgBFl9mk+cLkttT5l0oVFDhp7xUR
|
||||
b8Z3rFy9XcAbFJ55jjScE+UolKp3jWJUrFmFmHT5ZtLUK3iwtw5jmDCsrWW8mVSV
|
||||
6daZLwOPc77BYSvlWa5DYSApdfvc4QxMxaXFzvk0q36yPCey0z2RriHTkns+yXVZ
|
||||
Oxyjj67w8hKh+nLUd6rSVJ1nFPups11btzIMD46VP1+CoieBh23Qr4Jo5tPfHKZ+
|
||||
hV6RrQcX0umiEceNPK6xgfUnUecYYbRJNcGhdPLPLdSleITiNmAtBBVKdsD/pdHc
|
||||
rj7IabZ7/3SAAQv3fYHxfXgmGcCIF3KDuFpRT0BjtdNH9K3XTXUg7xCvCQiCyYBv
|
||||
UXOsU9F+9VMYe1f+4+5AuAuC8/vhHt3i+h4wGf6feD6/Uq9IN6SWNL6b7Iwwdndl
|
||||
6/QQDtk13glPlEfxC2m+D9FiqyjFcFwt1dlq/hwmoweJ7PM52Y6+6VJyV2ZUm7NU
|
||||
OtOeXT53As8tqVKvwH1OE12OFEPoHO3dkbDM8x21uGCaRf+SePK4wWAS4uQJMCUH
|
||||
CV5BtLUW4CDSZO8TwsLqal3qcL7mlCA7XreFcYiTF6OD0a+b10pZ3NorHwYogkww
|
||||
3tNr6kFD7LdhCBkS53xcXa7js1jH6LhEaNevFPW5O8I24106LBFmW0blcxpZnCIw
|
||||
SaqCpy+o3lMQ/Wqpqz3+pKDAArMsR0mPQ2tws0ER/PzzsuOycrrbEouWJVQTI9zl
|
||||
QTlM09INY/u6uLzLJ35OOU2VXdgpeSvWl1khHGShfZnxa6NjeqNF4Y9lsi876z2g
|
||||
0iS69qftFGuFgl3YFTZJru/ssfaf4d6cHS+LpPIJiY/q/lFthogJ7rjXKvDK0XR4
|
||||
ajnUSplSP8I4Pf45B7KEYaGY6IzQVGgqkcou+tJyWse4Tt5k39Nvahwb6TM3Va2/
|
||||
ho1TFFgjWMc/KS/vqdnzFNdBeBHOADoEaFmhYgOzujGu6m8vnMxjH7mWtQHNbNNI
|
||||
ygwUmnPvfZlPUXvLxFr/OZL+zFZKW2shXWgpt2tdby0tB9Ve7HnBGq2q2OeG7t1U
|
||||
FEjxMYOW7+bHqkAmW28zIV4vLqdMqr830li6dz0iHM9fBcOL4M0UcqD6qbk4aexD
|
||||
Up22bATpExoT43voK+JOzZjvAhuTVScasJNGjtkjRG0DpESjOevlgrSIuAg6ygCt
|
||||
zCXJa0njCBsY12+Mw7kY6rH5ulJXFPBRn+fJaDp0XquKnCVhuktS2M5c3Jjg9LL5
|
||||
v7xmRA+Mc0B9OgA6yEHhDtNUYZpW3wk9fUrY2afDXXOT2G4RJc90i/Vh+7NWNGli
|
||||
gXp2Dd4rbC+M/GAEj6wuycddde6M2dNrvLWce2Eh1IDZZaAEfsJJ0WN+13eUtiZv
|
||||
8cZMb1/HpUy8hrqUw2wuRTWbG1V9PeaBioMBENCn/Zorlh+l2UHuGECCy9aaxhSH
|
||||
ufxp67BCF5RopYjf5QFUsYH1M+DbUO5PqryWhD+wGuQ5Eu8n5Xhrva0ny/VO8Csj
|
||||
2kyFRrgMJStQ66hCj1+cH9rvBqQIrSRcU3zw9iuGYMnAITPvMPNp+hzsY0ttSGlK
|
||||
xk7ZNDN2XaUipED71H9NkzjrdCIarKCQ2VtTzH9L8DKPcSlPAwSCiKhPVaAwSNZs
|
||||
k44ZJSAQTLtUFjDQUNQDkEjnrf/xdZglhLfaOAjlvXyZEv4JC89GUccaRfDUHGJL
|
||||
rbEdfHlD0L2gwHGoRNoYGZA+C161CFlx9lOwNDtOD0nQYhMRHUn69jJIKeJhdK05
|
||||
ZxmrGkqBHQR4agctfeVHUcnF7hbqqfKYEGMHc984XnTUAPBAsjFoGYQ65JmhvFva
|
||||
aDMa8GeMzNMdYNTsJljhYbKlELGMhurJJ7ckkAg6UKQrpUQ7FwmBpU+zaDHKPQ6h
|
||||
8acak5aJfC/OtIpnPDYTBcC3zLNEOvs2QDtjKSVYK7/6AcD9tiYjo0Q95F1aex+M
|
||||
uqp0yoL83Oq/KxPnkGMo67ukON66Xt8hrSgVIVzL4PX5Xl1PtLSN4lleNN69S7ic
|
||||
-----END RSA PRIVATE KEY-----
|
||||
22
packages/engine.io/test/fixtures/client.crt
vendored
Normal file
22
packages/engine.io/test/fixtures/client.crt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtTCCAZ0CFCt+tjtA9647yZp8eNZurQtNp4x+MA0GCSqGSIb3DQEBDQUAMEUx
|
||||
CzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRl
|
||||
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIxMTE4MjEyMzU4WhcNMzIxMTE1MjEy
|
||||
MzU4WjBtMQswCQYDVQQGEwJGSTETMBEGA1UECBMKU29tZS1TdGF0ZTERMA8GA1UE
|
||||
BxMISGVsc2lua2kxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDET
|
||||
MBEGA1UEAxMKRm9vIENsaWVudDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
|
||||
+ukZ/D/DvjCtd7D8SCBDB1w0WYXkS6hjZTbF82gS1pykpVflsYtTnQoprQIeGQFU
|
||||
bdSZCvZkBABncQvE0cAzEtNvhFCtR9c74e+xJOGXB1vKeQlRJudxU5jvo4S/uwUG
|
||||
F1tcov11EQb9sZt0PeSO/PeWoKazBSPB4jrvx3Yv4g0CAwEAATANBgkqhkiG9w0B
|
||||
AQ0FAAOCAgEAMgC3j2XJ5IPB1raLrnJh8vTncnqMe6OmXpaxk0ZYb42Y66BJKlaE
|
||||
UpLmLYIiICmuH6R4lc00W5nexPyCT4g+1CUs3PhOJGEwWOBodv6dFJ4ayGWln1aD
|
||||
QX+W+PRuJAazd7wruVnPxVoEspVO+hcr5byX0F3Auqd9jdQZwFXsWvAo7tZxUnvC
|
||||
gdjnHt5QgMxqeqzZPTw7dreMsIjN6NrUPWaa26VCvLH0Nv+Jgs+RSVwBKp8tO3e+
|
||||
763bi8Htpzt4YfAB7EuRykGlAI42C5ZDzcsq30NpSGgOwveHnlvdl6KhC0QaK71h
|
||||
QmXwBmEUNX1f+XRnvk+fNb1acfddLLYoPP0zS1BEYOOs7KkyScagsUMsnUSOfv3d
|
||||
+etklFvaXFD3+b/KwljH3WH1dG4ro3J6GHXX05ncDydDDksYi6aC3wpPZYY7eMFx
|
||||
RWSxMZHX/bD1YH80a2+jBoskTqz3ZFkkGySMfUcpDCUwQuiwjhLp4sew9RDRB/lv
|
||||
kJezNSoYgnT44CT+IPoPEL1m5Evkm3C7fVzvnldO3TsWmOoza99xrQ+9gtzlWxgb
|
||||
Av6jNbnGG1HgDYcvxpRMKWe+6fUAHCcP0PuO+2rcygemNtEKzfMY6Py66w5L9/WW
|
||||
t0UJWU1rR+kLDS3qLfQqvnbvUMroZ9zxE9CJq6+aKEQEpc79lfiv464=
|
||||
-----END CERTIFICATE-----
|
||||
12
packages/engine.io/test/fixtures/client.csr
vendored
Normal file
12
packages/engine.io/test/fixtures/client.csr
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBrTCCARYCAQAwbTELMAkGA1UEBhMCRkkxEzARBgNVBAgTClNvbWUtU3RhdGUx
|
||||
ETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
|
||||
eSBMdGQxEzARBgNVBAMTCkZvbyBDbGllbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
|
||||
MIGJAoGBAPrpGfw/w74wrXew/EggQwdcNFmF5EuoY2U2xfNoEtacpKVX5bGLU50K
|
||||
Ka0CHhkBVG3UmQr2ZAQAZ3ELxNHAMxLTb4RQrUfXO+HvsSThlwdbynkJUSbncVOY
|
||||
76OEv7sFBhdbXKL9dREG/bGbdD3kjvz3lqCmswUjweI678d2L+INAgMBAAGgADAN
|
||||
BgkqhkiG9w0BAQUFAAOBgQDKGUqjkUxGOisFN70X7ZOW7H99veR9QlixKl5e0W+7
|
||||
UtJ+GUtH2WQEb4F72+ruHrdDWQI1VaH9hPOvTRCjlgXiT0RHXpGPbJK/Nc+Eq5dm
|
||||
kuk/tQeXv6+S1fgYOm0w09rE7pBjQtuAybB55lGZ7k84UE2xTc97Ru14nYFCsZ4z
|
||||
RA==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
15
packages/engine.io/test/fixtures/client.key
vendored
Normal file
15
packages/engine.io/test/fixtures/client.key
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQD66Rn8P8O+MK13sPxIIEMHXDRZheRLqGNlNsXzaBLWnKSlV+Wx
|
||||
i1OdCimtAh4ZAVRt1JkK9mQEAGdxC8TRwDMS02+EUK1H1zvh77Ek4ZcHW8p5CVEm
|
||||
53FTmO+jhL+7BQYXW1yi/XURBv2xm3Q95I7895agprMFI8HiOu/Hdi/iDQIDAQAB
|
||||
AoGBAOFHTXd4YO1wky82Dy1LGiOPm8kNOC7d33BOv2iN9uwN9J4nzymbqNUE/OpD
|
||||
TnaxBPcfvNFk6+PT4QxUvsB8ytzDMZ3YC4xyJf5GPP/hfzyWCRjB557WZl1cx7nC
|
||||
2gA93PBZE7WT1SySXmjsiC7o/2T/0cUaawXOBczHP8oXoEkBAkEA/c1MHs13ojxh
|
||||
oOj/ibCpYpd2Zv5Hrc5tsh+otDdIrb79IAHnNw7WhMkLs6cLk1MY6jLeCvQtjlUY
|
||||
H5C/6Ez84QJBAP0VZMgWPw3FVNXPrj833OA6XjyWO+TADpnlrahuDQqWnR3C29Uc
|
||||
Iq/ApVX2pt2cNIZpiuJ4BYNc44cHjvu6vq0CQQChan1cJc9NhluNLELBfnLsOmpa
|
||||
bKSH3P8VR19TZsm5fvub7Lnx4WT7xKXFl5scEsCIyts/WjbTDDmwca4r/zLhAkB4
|
||||
wkeHbY4CnSDgsKr9AUPEPjWPBURo3vdYmY4mKvTQE5O+iqboZfdrEyoQ/ZMbdRhe
|
||||
9mdNrmU7DAyI9qNUHAQ1AkAlq/vdkrcq5SRR9uti/1M0/Jaw7l3JutBaW93kdvXx
|
||||
BX568ezO1PQtXwVSv+uJEkDoST1bkvhqt7hlMu/RkmfG
|
||||
-----END RSA PRIVATE KEY-----
|
||||
18
packages/engine.io/test/fixtures/client.key.orig
vendored
Normal file
18
packages/engine.io/test/fixtures/client.key.orig
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,081B123E7DBC9687
|
||||
|
||||
c8gE0LQMMtV7GhUcP7GHBotlNNAsbyG/gERB//BFlvRPjs43ALvm5xMGUkcsZkw9
|
||||
H6ljvvgyRkud06x1mGxqlZ4fLlty0/mdXf6791R9gA/9bKLakHGlwnoVUrEPIeh3
|
||||
Iu/3IF0Uz8o3uljSp9eehR8sW5V4o+Z1MszQ+GkcpCyYnDPykyN52QLTM3RcUJ0v
|
||||
2iJrgxYEAyMA14iIpYM8IsFDYIBvwVfQxyBZnQ+8dDQgVOokz56g+/9emgOsVjWj
|
||||
bdUKZ7+95RLxCq0hjJd8GnoDn7qtxIPOGVqYcdEwwNyzO43FnpjOFhNDWwsrNrgf
|
||||
vUHKhZRHB99CkuCvGg4fjVbFd/1/n3eE5VmiQuqoHzPnXH3RDIx2rZSjFo/NkzYx
|
||||
4XzZ4YOe6vOP7kR18CL+bl32WneMELoh+TPLmsSC3rIP48M8+oQINSZaBrl59Ocw
|
||||
NW36xgDNSQoia2splVNo51vtZomq1Hb3co6hD43D4xnrc6Aqucm3BsW6UCc/PVKv
|
||||
HAXLCb+3awIy5NJSYb0qRETE2rKB6LjmKfILtOrto6QYSlkJmQUxqoPXgRAWWNlw
|
||||
1Ngws84+6UjmGWDBlpZHn0hcO/B7KJAAS/xNSFYDoSu6dAbabxTI/dZCWhw1aN5c
|
||||
QeYPihCi66F6Vuq/QT89dHtZE4IMPH7R95Yp18tCcyVGxaEiphw3HJYepMxoJesQ
|
||||
YH8tWQwvD5LaADzNJIKBxMjCOK23GuXWQLJJRf4QWiXaQar69qXULxBT3iqBp/rQ
|
||||
VKsQByBwJlEo/YSEFjhhMgo0zSbqIRAY1XCDo+dgB2IB1KPAobhSCQ==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
BIN
packages/engine.io/test/fixtures/client.pfx
vendored
Normal file
BIN
packages/engine.io/test/fixtures/client.pfx
vendored
Normal file
Binary file not shown.
9
packages/engine.io/test/fixtures/server-close-upgraded.js
vendored
Normal file
9
packages/engine.io/test/fixtures/server-close-upgraded.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const { ClientSocket, listen } = require("../common");
|
||||
|
||||
const engine = listen((port) => {
|
||||
const socket = new ClientSocket("ws://localhost:" + port);
|
||||
socket.on("upgrade", () => {
|
||||
engine.httpServer.close();
|
||||
engine.close();
|
||||
});
|
||||
});
|
||||
9
packages/engine.io/test/fixtures/server-close-upgrading.js
vendored
Normal file
9
packages/engine.io/test/fixtures/server-close-upgrading.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const { ClientSocket, listen } = require("../common");
|
||||
|
||||
const engine = listen((port) => {
|
||||
const socket = new ClientSocket("ws://localhost:" + port);
|
||||
socket.on("upgrading", () => {
|
||||
engine.httpServer.close();
|
||||
engine.close();
|
||||
});
|
||||
});
|
||||
9
packages/engine.io/test/fixtures/server-close.js
vendored
Normal file
9
packages/engine.io/test/fixtures/server-close.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const { ClientSocket, listen } = require("../common");
|
||||
|
||||
const engine = listen((port) => {
|
||||
const socket = new ClientSocket("ws://localhost:" + port);
|
||||
socket.on("open", () => {
|
||||
engine.httpServer.close();
|
||||
engine.close();
|
||||
});
|
||||
});
|
||||
22
packages/engine.io/test/fixtures/server.crt
vendored
Normal file
22
packages/engine.io/test/fixtures/server.crt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtDCCAZwCFCt+tjtA9647yZp8eNZurQtNp4x/MA0GCSqGSIb3DQEBDQUAMEUx
|
||||
CzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRl
|
||||
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIxMTE4MjEyNDExWhcNMzIxMTE1MjEy
|
||||
NDExWjBsMQswCQYDVQQGEwJGSTETMBEGA1UECBMKU29tZS1TdGF0ZTERMA8GA1UE
|
||||
BxMISGVsc2lua2kxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDES
|
||||
MBAGA1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA
|
||||
QLNPDILqMIulvPga6ogci69xYaSp50U7Tv8Hww3m4D9w2ubyGqR7dDW20iGJI7tT
|
||||
Ncv/UtLqgknEiCBXkq/MDr5gdXNI0V/j+qAOqfJSVxvxxHxLrt7ivjpZHbO7B3K7
|
||||
bkWmCxAFzWAlE5U9FXX1b8VfetJv+cDEaXsYwuYfowIDAQABMA0GCSqGSIb3DQEB
|
||||
DQUAA4ICAQCDSuFPJ++HY5WBhpnumbZ7+T0ReWKaerdNnQ2Xgrna5mZfB2xeRkvY
|
||||
XeJ9VBCpGgEZKCKkhZCjomn/kLkYzRk4Sqr1ivN5NWl6G/9UTttHdRa3xiR1NhKI
|
||||
AMYghpel30w5e+cWtsdR06P2FvZMuiMFCyqsbPf1xcEIAXN7HJDswq6g0ppTVZ4L
|
||||
sXljG/J0vp+jAst4XKGLaGqnt8JaBnpNX9NO2Up3h5j7Pa4Nhm/LZ3Ku5ZVDmS1d
|
||||
B98Bsgr6tQSSyPNfZW0tGXELsNX1I+wUFw9IXFadRTHkhjeT/GhFw3i12uY7rqzm
|
||||
uJegTtWDkp1QOajhYhLD9WGXb9teldkAAgZawD6ax/uAzqx/4mBFvsUa3FMcua8k
|
||||
HF9P2lLzKAcyaKt1cvlfUYmDVZ2Gh+9PgM8SqRpMIqK5jMRvFgemxJXS9BMBrQLp
|
||||
TCvgRwQZD4mUloRlGNewKfJ0oQ1rY29vwdjTL8+BBS/GR8EuzYnqJG/D2nK0guIN
|
||||
ze+cSDghA5N2pp/ffnpLWmkIDO+fsGAj3eApLhbPQ1xCXnEv6fOjgUmnxdt41m8d
|
||||
+pEVBICohnvYgoEERDNAi1onJlBd/eyk0Jn37QiwqhQyrmfgwncvlt2SyzS1IZ7s
|
||||
cEYreG6QHghBhgYiYo0FMuDCjT6g6Ga+T8nOp0xpZtGEWvHwjLjxvQ==
|
||||
-----END CERTIFICATE-----
|
||||
11
packages/engine.io/test/fixtures/server.csr
vendored
Normal file
11
packages/engine.io/test/fixtures/server.csr
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBrDCCARUCAQAwbDELMAkGA1UEBhMCRkkxEzARBgNVBAgTClNvbWUtU3RhdGUx
|
||||
ETAPBgNVBAcTCEhlbHNpbmtpMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
|
||||
eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
|
||||
gYkCgYEAwECzTwyC6jCLpbz4GuqIHIuvcWGkqedFO07/B8MN5uA/cNrm8hqke3Q1
|
||||
ttIhiSO7UzXL/1LS6oJJxIggV5KvzA6+YHVzSNFf4/qgDqnyUlcb8cR8S67e4r46
|
||||
WR2zuwdyu25FpgsQBc1gJROVPRV19W/FX3rSb/nAxGl7GMLmH6MCAwEAAaAAMA0G
|
||||
CSqGSIb3DQEBBQUAA4GBAClj/K2DAH5S64T6s7jervmk4N956Ho3aTLBgE+ReXLj
|
||||
btcTdk3vFbQApAlG6MrSKys4HjpKpP/RENx3Js0HHeb8ELmWtIQNxRhwIpl0K5AD
|
||||
xorKj+mwngLtVyARb/M7O3E8jYHzBPzpsolKWIY4AavYdmHu+Zhgm4hPKUcW+bAv
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
15
packages/engine.io/test/fixtures/server.key
vendored
Normal file
15
packages/engine.io/test/fixtures/server.key
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQDAQLNPDILqMIulvPga6ogci69xYaSp50U7Tv8Hww3m4D9w2uby
|
||||
GqR7dDW20iGJI7tTNcv/UtLqgknEiCBXkq/MDr5gdXNI0V/j+qAOqfJSVxvxxHxL
|
||||
rt7ivjpZHbO7B3K7bkWmCxAFzWAlE5U9FXX1b8VfetJv+cDEaXsYwuYfowIDAQAB
|
||||
AoGBAL7tQmXl2fmz/mu5kHhCpKwcuT6TpxEo4aN132aY+qxn1flBHAwiE2mbTmDi
|
||||
rHViq/2GNrK5UUed3p60RdJSlgwIkyqtcGxWhUJGYCR/hU60qeeLp3MhhOoOFbiV
|
||||
YTDsoC7V/SuWbX+1qG5FxnHSnTZhAIRkZXS4uTZ5WDcQm/7BAkEA+TlZ1IT9CeU/
|
||||
FpHpqc8RgR8377Ehjy8o4Z4EGFnxQlAUWASnhs6dw4isr3+c7hA1OEmqmcRClPVZ
|
||||
t1JbHAPC4QJBAMV60WSJzPUccCF47T9Ao2CeWFl/9dmgQQe9idpTNuKMXNtPJN44
|
||||
0MQvnb+xS828woJOoRI+/UTVLLBc4xwMtwMCQQDZTadExTw4v5l1nX5GoJUbp9PG
|
||||
/ARN64nSx0u8y9evwVErucs0oL0we+BOGZAEhz9QN/M3pceESDWUwYtNbv4hAkBB
|
||||
Ku2MqvjK7k6GjTxlgjQn/zkSl+qOnZa4MjEarhlPm5hM+wokl0U1aK07BAwK4b6i
|
||||
d8YpmkXEAEEWFiEQMZX3AkEA1SkdiFj1u7HnzO7onLJsnFzowX3pm1UFl0azOMlM
|
||||
2GkjYxWeJ/4VL7Y6QkhHE0Nj3my2+MJQI9xpYgMbw/l11w==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
18
packages/engine.io/test/fixtures/server.key.orig
vendored
Normal file
18
packages/engine.io/test/fixtures/server.key.orig
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,8A14F3F31BF3CDF7
|
||||
|
||||
84UpLjX5GbfCoP/+ZRLwHZHqmgKyc2cjQOQKhp2YTk+4E0JSO7KsKwJax55r5VqE
|
||||
ECGvbd8yvVZ3GRpU6bDCLelDN+Ob5ZSGhynI0f9is6Qle1/SbvaK+qL6MLf4H38l
|
||||
Y91eZxjE8hLvOMHBlbobLmy9UUzl2osBDAM1nm5d1X7pF7mN4xgY8s2G7UPq3ZZS
|
||||
NWwMsnwtb/9Ahm7WXfsJbebyKspkTSHYp/ozaOGW58fuKxkwRFd1UlyblZWU3ezP
|
||||
JCpvJQ7HS6Dfy6+GUaxcC6pyxqnoJHYccB5usJ2h4QD4Es3sT7Vw7M80JKw3vAWm
|
||||
TH7VkFX3yGfJ1p1jNzifN5687QqrjeI3/ecTs1rFhIC4TUPN9EDvw86Y6l6Mvo04
|
||||
Hl2cVzCnCrZYq0ICD0op3+7f9kuKl7bz54S/iRG2qQdICohPs7ra2yaUy+NFVDs1
|
||||
XdXyF5/xew+Rr2z7ygEd+OrvXxPV0zTFbicg9GXGeB/pIYAclmoSNXD5T3voN7y9
|
||||
5MjSGL375N3z+kqPzMNawYCnZLwQ5jYTDUkDTATYpjcIDVDQkzbl8mFp4i+Z9GhL
|
||||
H8FRAgmBbkgy3dxhFjxr/WzuaTkUCAbGhrtPd8WCPhBXJBzWdrBVR9SE8zT9n8lq
|
||||
ZwaPwkPLBrLe/XZFEQJ5cdvMVNy4QQwsyxHnCgO3VUc68UYXb39MvyM6t8+S1rSm
|
||||
SyvVAT+jB8T4VlE7tedQGuvyKMmeNIJe9znd3u4S+Aq2+vw6bOKeNYBMaupR5Gyl
|
||||
bJrscuTG1aLUe+XH9BuFBUoIwdXuBv4Ko/pDL0MYPghDAEGkp4Acmg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
321
packages/engine.io/test/middlewares.js
Normal file
321
packages/engine.io/test/middlewares.js
Normal file
@@ -0,0 +1,321 @@
|
||||
const listen = require("./common").listen;
|
||||
const expect = require("expect.js");
|
||||
const request = require("superagent");
|
||||
const { WebSocket } = require("ws");
|
||||
const helmet = require("helmet");
|
||||
const session = require("express-session");
|
||||
const { ClientSocket } = require("./common");
|
||||
|
||||
describe("middlewares", () => {
|
||||
it("should apply middleware (polling)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use((req, res, next) => {
|
||||
res.setHeader("foo", "bar");
|
||||
next();
|
||||
});
|
||||
|
||||
request
|
||||
.get(`http://localhost:${port}/engine.io/`)
|
||||
.query({ EIO: 4, transport: "polling" })
|
||||
.end((err, res) => {
|
||||
expect(err).to.be(null);
|
||||
expect(res.status).to.eql(200);
|
||||
expect(res.headers["foo"]).to.eql("bar");
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should apply middleware (websocket)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use((req, res, next) => {
|
||||
res.setHeader("foo", "bar");
|
||||
next();
|
||||
});
|
||||
|
||||
const socket = new WebSocket(
|
||||
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
|
||||
);
|
||||
|
||||
socket.on("upgrade", (res) => {
|
||||
expect(res.headers["foo"]).to.eql("bar");
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should apply all middlewares in order", (done) => {
|
||||
const engine = listen((port) => {
|
||||
let count = 0;
|
||||
|
||||
engine.use((req, res, next) => {
|
||||
expect(++count).to.eql(1);
|
||||
next();
|
||||
});
|
||||
|
||||
engine.use((req, res, next) => {
|
||||
expect(++count).to.eql(2);
|
||||
next();
|
||||
});
|
||||
|
||||
engine.use((req, res, next) => {
|
||||
expect(++count).to.eql(3);
|
||||
next();
|
||||
});
|
||||
|
||||
request
|
||||
.get(`http://localhost:${port}/engine.io/`)
|
||||
.query({ EIO: 4, transport: "polling" })
|
||||
.end((err, res) => {
|
||||
expect(err).to.be(null);
|
||||
expect(res.status).to.eql(200);
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should end the request (polling)", function (done) {
|
||||
if (process.env.EIO_WS_ENGINE === "uws") {
|
||||
return this.skip();
|
||||
}
|
||||
const engine = listen((port) => {
|
||||
engine.use((req, res, _next) => {
|
||||
res.writeHead(503);
|
||||
res.end();
|
||||
});
|
||||
|
||||
engine.on("connection", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
|
||||
request
|
||||
.get(`http://localhost:${port}/engine.io/`)
|
||||
.query({ EIO: 4, transport: "polling" })
|
||||
.end((err, res) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(res.status).to.eql(503);
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should end the request (websocket)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use((req, res, _next) => {
|
||||
res.writeHead(503);
|
||||
res.end();
|
||||
});
|
||||
|
||||
engine.on("connection", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
|
||||
const socket = new WebSocket(
|
||||
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
|
||||
);
|
||||
|
||||
socket.addEventListener("error", () => {
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with helmet (polling)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use(helmet());
|
||||
|
||||
request
|
||||
.get(`http://localhost:${port}/engine.io/`)
|
||||
.query({ EIO: 4, transport: "polling" })
|
||||
.end((err, res) => {
|
||||
expect(err).to.be(null);
|
||||
expect(res.status).to.eql(200);
|
||||
expect(res.headers["x-download-options"]).to.eql("noopen");
|
||||
expect(res.headers["x-content-type-options"]).to.eql("nosniff");
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with helmet (websocket)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use(helmet());
|
||||
|
||||
const socket = new WebSocket(
|
||||
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
|
||||
);
|
||||
|
||||
socket.on("upgrade", (res) => {
|
||||
expect(res.headers["x-download-options"]).to.eql("noopen");
|
||||
expect(res.headers["x-content-type-options"]).to.eql("nosniff");
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with express-session (polling)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use(
|
||||
session({
|
||||
secret: "keyboard cat",
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {},
|
||||
})
|
||||
);
|
||||
|
||||
request
|
||||
.get(`http://localhost:${port}/engine.io/`)
|
||||
.query({ EIO: 4, transport: "polling" })
|
||||
.end((err, res) => {
|
||||
expect(err).to.be(null);
|
||||
// expect(res.status).to.eql(200);
|
||||
expect(res.headers["set-cookie"][0].startsWith("connect.sid=")).to.be(
|
||||
true
|
||||
);
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with express-session (websocket)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use(
|
||||
session({
|
||||
secret: "keyboard cat",
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {},
|
||||
})
|
||||
);
|
||||
|
||||
const socket = new WebSocket(
|
||||
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
|
||||
);
|
||||
|
||||
socket.on("upgrade", (res) => {
|
||||
expect(res.headers["set-cookie"][0].startsWith("connect.sid=")).to.be(
|
||||
true
|
||||
);
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail on errors (polling)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use((req, res, next) => {
|
||||
next(new Error("will always fail"));
|
||||
});
|
||||
|
||||
request
|
||||
.get(`http://localhost:${port}/engine.io/`)
|
||||
.query({ EIO: 4, transport: "polling" })
|
||||
.end((err, res) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(res.status).to.eql(400);
|
||||
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail on errors (websocket)", (done) => {
|
||||
const engine = listen((port) => {
|
||||
engine.use((req, res, next) => {
|
||||
next(new Error("will always fail"));
|
||||
});
|
||||
|
||||
engine.on("connection", () => {
|
||||
done(new Error("should not connect"));
|
||||
});
|
||||
|
||||
const socket = new WebSocket(
|
||||
`ws://localhost:${port}/engine.io/?EIO=4&transport=websocket`
|
||||
);
|
||||
|
||||
socket.addEventListener("error", () => {
|
||||
if (engine.httpServer) {
|
||||
engine.httpServer.close();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should not be receiving data when getting a message longer than maxHttpBufferSize when polling (with a middleware)", (done) => {
|
||||
const opts = {
|
||||
allowUpgrades: false,
|
||||
transports: ["polling"],
|
||||
maxHttpBufferSize: 5,
|
||||
};
|
||||
const engine = listen(opts, (port) => {
|
||||
engine.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
const socket = new ClientSocket(`ws://localhost:${port}`);
|
||||
engine.on("connection", (conn) => {
|
||||
conn.on("message", () => {
|
||||
done(new Error("Test invalidation (message is longer than allowed)"));
|
||||
});
|
||||
});
|
||||
socket.on("open", () => {
|
||||
socket.send("aasdasdakjhasdkjhasdkjhasdkjhasdkjhasdkjhasdkjha");
|
||||
});
|
||||
socket.on("close", (reason) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
22
packages/engine.io/test/parser.js
Normal file
22
packages/engine.io/test/parser.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const expect = require("expect.js");
|
||||
const parser = require("../build/parser-v3/index.js");
|
||||
|
||||
describe("parser", () => {
|
||||
it("properly encodes a mixed payload", (done) => {
|
||||
parser.encodePayload(
|
||||
[
|
||||
{ type: "message", data: "€€€€" },
|
||||
{ type: "message", data: Buffer.from([1, 2, 3]) },
|
||||
],
|
||||
true,
|
||||
(encoded) => {
|
||||
expect(encoded).to.be.a(Buffer);
|
||||
|
||||
parser.decodePayload(encoded, (decoded) => {
|
||||
expect(decoded.data).to.eql("€€€€");
|
||||
done();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
3806
packages/engine.io/test/server.js
Normal file
3806
packages/engine.io/test/server.js
Normal file
File diff suppressed because it is too large
Load Diff
404
packages/engine.io/test/util.mjs
Normal file
404
packages/engine.io/test/util.mjs
Normal file
@@ -0,0 +1,404 @@
|
||||
// imported from https://github.com/fails-components/webtransport/blob/master/test/fixtures/certificate.js
|
||||
|
||||
// @ts-expect-error node-forge has no types and @types/node-forge do not include oids
|
||||
import forge from 'node-forge'
|
||||
import { webcrypto as crypto, X509Certificate } from 'crypto'
|
||||
|
||||
const { pki, asn1, oids } = forge
|
||||
// taken from node-forge
|
||||
/**
|
||||
* Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
|
||||
*
|
||||
* @param {any} obj the subject or issuer (distinguished name).
|
||||
*
|
||||
* @return the ASN.1 RDNSequence.
|
||||
*/
|
||||
function _dnToAsn1(obj) {
|
||||
// create an empty RDNSequence
|
||||
const rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [])
|
||||
|
||||
// iterate over attributes
|
||||
let attr, set
|
||||
const attrs = obj.attributes
|
||||
for (let i = 0; i < attrs.length; ++i) {
|
||||
attr = attrs[i]
|
||||
let value = attr.value
|
||||
|
||||
// reuse tag class for attribute value if available
|
||||
let valueTagClass = asn1.Type.PRINTABLESTRING
|
||||
if ('valueTagClass' in attr) {
|
||||
valueTagClass = attr.valueTagClass
|
||||
|
||||
if (valueTagClass === asn1.Type.UTF8) {
|
||||
value = forge.util.encodeUtf8(value)
|
||||
}
|
||||
// FIXME: handle more encodings
|
||||
}
|
||||
|
||||
// create a RelativeDistinguishedName set
|
||||
// each value in the set is an AttributeTypeAndValue first
|
||||
// containing the type (an OID) and second the value
|
||||
set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
// AttributeType
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(attr.type).getBytes()
|
||||
),
|
||||
// AttributeValue
|
||||
asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value)
|
||||
])
|
||||
])
|
||||
rval.value.push(set)
|
||||
}
|
||||
|
||||
return rval
|
||||
}
|
||||
|
||||
const jan_1_1950 = new Date('1950-01-01T00:00:00Z') // eslint-disable-line camelcase
|
||||
const jan_1_2050 = new Date('2050-01-01T00:00:00Z') // eslint-disable-line camelcase
|
||||
// taken from node-forge almost not modified
|
||||
/**
|
||||
* Converts a Date object to ASN.1
|
||||
* Handles the different format before and after 1st January 2050
|
||||
*
|
||||
* @param {Date} date date object.
|
||||
*
|
||||
* @return the ASN.1 object representing the date.
|
||||
*/
|
||||
function _dateToAsn1(date) {
|
||||
// eslint-disable-next-line camelcase
|
||||
if (date >= jan_1_1950 && date < jan_1_2050) {
|
||||
return asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.UTCTIME,
|
||||
false,
|
||||
asn1.dateToUtcTime(date)
|
||||
)
|
||||
} else {
|
||||
return asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.GENERALIZEDTIME,
|
||||
false,
|
||||
asn1.dateToGeneralizedTime(date)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// taken from node-forge almost not modified
|
||||
/**
|
||||
* Convert signature parameters object to ASN.1
|
||||
*
|
||||
* @param {string} oid Signature algorithm OID
|
||||
* @param {any} params The signature parameters object
|
||||
* @return ASN.1 object representing signature parameters
|
||||
*/
|
||||
function _signatureParametersToAsn1(oid, params) {
|
||||
const parts = []
|
||||
|
||||
switch (oid) {
|
||||
case oids['RSASSA-PSS']:
|
||||
if (params.hash.algorithmOid !== undefined) {
|
||||
parts.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(params.hash.algorithmOid).getBytes()
|
||||
),
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||||
])
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (params.mgf.algorithmOid !== undefined) {
|
||||
parts.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(params.mgf.algorithmOid).getBytes()
|
||||
),
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()
|
||||
),
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||||
])
|
||||
])
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (params.saltLength !== undefined) {
|
||||
parts.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.INTEGER,
|
||||
false,
|
||||
asn1.integerToDer(params.saltLength).getBytes()
|
||||
)
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts)
|
||||
|
||||
default:
|
||||
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
||||
}
|
||||
}
|
||||
|
||||
// taken from node-forge and modified to work with ECDSA
|
||||
/**
|
||||
* Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
|
||||
*
|
||||
* @param {any} cert the certificate.
|
||||
*
|
||||
* @return the asn1 TBSCertificate.
|
||||
*/
|
||||
function getTBSCertificate(cert) {
|
||||
// TBSCertificate
|
||||
const notBefore = _dateToAsn1(cert.validity.notBefore)
|
||||
const notAfter = _dateToAsn1(cert.validity.notAfter)
|
||||
|
||||
const tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
// version
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
||||
// integer
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.INTEGER,
|
||||
false,
|
||||
asn1.integerToDer(cert.version).getBytes()
|
||||
)
|
||||
]),
|
||||
// serialNumber
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.INTEGER,
|
||||
false,
|
||||
forge.util.hexToBytes(cert.serialNumber)
|
||||
),
|
||||
// signature
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
// algorithm
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.OID,
|
||||
false,
|
||||
asn1.oidToDer(cert.siginfo.algorithmOid).getBytes()
|
||||
),
|
||||
// parameters
|
||||
_signatureParametersToAsn1(
|
||||
cert.siginfo.algorithmOid,
|
||||
cert.siginfo.parameters
|
||||
)
|
||||
]),
|
||||
// issuer
|
||||
_dnToAsn1(cert.issuer),
|
||||
// validity
|
||||
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
||||
notBefore,
|
||||
notAfter
|
||||
]),
|
||||
// subject
|
||||
_dnToAsn1(cert.subject),
|
||||
// SubjectPublicKeyInfo
|
||||
// here comes our modification, we are other objects here
|
||||
asn1.fromDer(
|
||||
new forge.util.ByteBuffer(
|
||||
cert.publicKey
|
||||
) /* is in already SPKI format but in DER encoding */
|
||||
)
|
||||
])
|
||||
|
||||
if (cert.issuer.uniqueId) {
|
||||
// issuerUniqueID (optional)
|
||||
tbs.value.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.BITSTRING,
|
||||
false,
|
||||
// TODO: support arbitrary bit length ids
|
||||
String.fromCharCode(0x00) + cert.issuer.uniqueId
|
||||
)
|
||||
])
|
||||
)
|
||||
}
|
||||
if (cert.subject.uniqueId) {
|
||||
// subjectUniqueID (optional)
|
||||
tbs.value.push(
|
||||
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
|
||||
asn1.create(
|
||||
asn1.Class.UNIVERSAL,
|
||||
asn1.Type.BITSTRING,
|
||||
false,
|
||||
// TODO: support arbitrary bit length ids
|
||||
String.fromCharCode(0x00) + cert.subject.uniqueId
|
||||
)
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
if (cert.extensions.length > 0) {
|
||||
// extensions (optional)
|
||||
tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions))
|
||||
}
|
||||
|
||||
return tbs
|
||||
}
|
||||
|
||||
// function taken form selfsigned
|
||||
// a hexString is considered negative if it's most significant bit is 1
|
||||
// because serial numbers use ones' complement notation
|
||||
// this RFC in section 4.1.2.2 requires serial numbers to be positive
|
||||
// http://www.ietf.org/rfc/rfc5280.txt
|
||||
/**
|
||||
* @param {string} hexString
|
||||
* @returns
|
||||
*/
|
||||
function toPositiveHex(hexString) {
|
||||
let mostSiginficativeHexAsInt = parseInt(hexString[0], 16)
|
||||
if (mostSiginficativeHexAsInt < 8) {
|
||||
return hexString
|
||||
}
|
||||
|
||||
mostSiginficativeHexAsInt -= 8
|
||||
return mostSiginficativeHexAsInt.toString() + hexString.substring(1)
|
||||
}
|
||||
|
||||
// the next is an edit of the selfsigned function reduced to the function necessary for webtransport
|
||||
/**
|
||||
* @typedef {object} Certificate
|
||||
* @property {string} public
|
||||
* @property {string} private
|
||||
* @property {string} cert
|
||||
* @property {Uint8Array} hash
|
||||
* @property {string} fingerprint
|
||||
*
|
||||
* @param {*} attrs
|
||||
* @param {*} options
|
||||
* @returns {Promise<Certificate | null>}
|
||||
*/
|
||||
export async function generateWebTransportCertificate(attrs, options) {
|
||||
try {
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-256'
|
||||
},
|
||||
true,
|
||||
['sign', 'verify']
|
||||
)
|
||||
|
||||
const cert = pki.createCertificate()
|
||||
|
||||
cert.serialNumber = toPositiveHex(
|
||||
forge.util.bytesToHex(forge.random.getBytesSync(9))
|
||||
) // the serial number can be decimal or hex (if preceded by 0x)
|
||||
cert.validity.notBefore = new Date()
|
||||
cert.validity.notAfter = new Date()
|
||||
cert.validity.notAfter.setDate(
|
||||
cert.validity.notBefore.getDate() + (options.days || 14)
|
||||
) // per spec only 14 days allowed
|
||||
|
||||
cert.setSubject(attrs)
|
||||
cert.setIssuer(attrs)
|
||||
|
||||
const privateKey = crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
|
||||
const publicKey = (cert.publicKey = await crypto.subtle.exportKey(
|
||||
'spki',
|
||||
keyPair.publicKey
|
||||
))
|
||||
|
||||
cert.setExtensions(
|
||||
options.extensions || [
|
||||
{
|
||||
name: 'basicConstraints',
|
||||
cA: true
|
||||
},
|
||||
{
|
||||
name: 'keyUsage',
|
||||
keyCertSign: true,
|
||||
digitalSignature: true,
|
||||
nonRepudiation: true,
|
||||
keyEncipherment: true,
|
||||
dataEncipherment: true
|
||||
},
|
||||
{
|
||||
name: 'subjectAltName',
|
||||
altNames: [
|
||||
{
|
||||
type: 6, // URI
|
||||
value: 'http://example.org/webid#me'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
// to signing
|
||||
// patch oids object
|
||||
oids['1.2.840.10045.4.3.2'] = 'ecdsa-with-sha256'
|
||||
oids['ecdsa-with-sha256'] = '1.2.840.10045.4.3.2'
|
||||
|
||||
cert.siginfo.algorithmOid = cert.signatureOid = '1.2.840.10045.4.3.2' // 'ecdsa-with-sha256'
|
||||
|
||||
cert.tbsCertificate = getTBSCertificate(cert)
|
||||
const encoded = Buffer.from(
|
||||
asn1.toDer(cert.tbsCertificate).getBytes(),
|
||||
'binary'
|
||||
)
|
||||
cert.md = crypto.subtle.digest('SHA-256', encoded)
|
||||
cert.signature = crypto.subtle.sign(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
hash: { name: 'SHA-256' }
|
||||
},
|
||||
keyPair.privateKey,
|
||||
encoded
|
||||
)
|
||||
cert.md = await cert.md
|
||||
cert.signature = await cert.signature
|
||||
|
||||
const pemcert = pki.certificateToPem(cert)
|
||||
|
||||
const x509cert = new X509Certificate(pemcert)
|
||||
|
||||
const certhash = Buffer.from(
|
||||
x509cert.fingerprint256.split(':').map((el) => parseInt(el, 16))
|
||||
)
|
||||
|
||||
const pem = {
|
||||
private: forge.pem.encode({
|
||||
type: 'PRIVATE KEY',
|
||||
body: new forge.util.ByteBuffer(await privateKey).getBytes()
|
||||
}),
|
||||
public: forge.pem.encode({
|
||||
type: 'PUBLIC KEY',
|
||||
body: new forge.util.ByteBuffer(publicKey).getBytes()
|
||||
}),
|
||||
cert: pemcert,
|
||||
hash: certhash,
|
||||
fingerprint: x509cert.fingerprint256
|
||||
}
|
||||
|
||||
return pem
|
||||
} catch (error) {
|
||||
console.log('error in generate certificate', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
456
packages/engine.io/test/webtransport.mjs
Normal file
456
packages/engine.io/test/webtransport.mjs
Normal file
@@ -0,0 +1,456 @@
|
||||
import * as eio from "../build/server.js";
|
||||
import { Http3Server, WebTransport } from "@fails-components/webtransport";
|
||||
import { Http3EventLoop } from "@fails-components/webtransport/lib/event-loop.js";
|
||||
import expect from "expect.js";
|
||||
import request from "superagent";
|
||||
import { createServer } from "http";
|
||||
import { generateWebTransportCertificate } from "./util.mjs";
|
||||
|
||||
const TEXT_ENCODER = new TextEncoder();
|
||||
const TEXT_DECODER = new TextDecoder();
|
||||
|
||||
function success(engine, h3server, done) {
|
||||
engine.close();
|
||||
h3server.stopServer();
|
||||
done();
|
||||
}
|
||||
|
||||
function createPartialDone(done, count) {
|
||||
let i = 0;
|
||||
return () => {
|
||||
if (++i === count) {
|
||||
done();
|
||||
} else if (i > count) {
|
||||
done(new Error(`partialDone() called too many times: ${i} > ${count}`));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function setupServer(opts, cb) {
|
||||
const certificate = await generateWebTransportCertificate(
|
||||
[{ shortName: "CN", value: "localhost" }],
|
||||
{
|
||||
days: 14, // the total length of the validity period MUST NOT exceed two weeks (https://w3c.github.io/webtransport/#custom-certificate-requirements)
|
||||
}
|
||||
);
|
||||
|
||||
const engine = new eio.Server(opts);
|
||||
|
||||
const h3Server = new Http3Server({
|
||||
port: 0, // random port
|
||||
host: "0.0.0.0",
|
||||
secret: "changeit",
|
||||
cert: certificate.cert,
|
||||
privKey: certificate.private,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error("Server error", ex);
|
||||
}
|
||||
})();
|
||||
|
||||
h3Server.startServer();
|
||||
h3Server.onServerListening = () => cb({ engine, h3Server, certificate });
|
||||
}
|
||||
|
||||
function setup(opts, cb) {
|
||||
setupServer(opts, async ({ engine, h3Server, certificate }) => {
|
||||
const client = new WebTransport(
|
||||
`https://127.0.0.1:${h3Server.port}/engine.io/`,
|
||||
{
|
||||
serverCertificateHashes: [
|
||||
{
|
||||
algorithm: "sha-256",
|
||||
value: certificate.hash,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
await client.ready;
|
||||
|
||||
const stream = await client.createBidirectionalStream();
|
||||
const reader = stream.readable.getReader();
|
||||
const writer = stream.writable.getWriter();
|
||||
|
||||
engine.on("connection", async (socket) => {
|
||||
await reader.read(); // header
|
||||
await reader.read(); // payload (handshake)
|
||||
|
||||
cb({ engine, h3Server, socket, client, stream, reader, writer });
|
||||
});
|
||||
|
||||
await writer.write(Uint8Array.of(1));
|
||||
await writer.write(TEXT_ENCODER.encode("0"));
|
||||
});
|
||||
}
|
||||
|
||||
describe("WebTransport", () => {
|
||||
after(() => {
|
||||
Http3EventLoop.globalLoop.shutdownEventLoop(); // manually shutdown the event loop, instead of waiting 20s
|
||||
});
|
||||
|
||||
it("should allow to connect with WebTransport directly", (done) => {
|
||||
setupServer({}, async ({ engine, h3Server, certificate }) => {
|
||||
const partialDone = createPartialDone(
|
||||
() => success(engine, h3Server, done),
|
||||
2
|
||||
);
|
||||
|
||||
engine.on("connection", (socket) => {
|
||||
expect(socket.transport.name).to.eql("webtransport");
|
||||
partialDone();
|
||||
});
|
||||
|
||||
const client = new WebTransport(
|
||||
`https://127.0.0.1:${h3Server.port}/engine.io/`,
|
||||
{
|
||||
serverCertificateHashes: [
|
||||
{
|
||||
algorithm: "sha-256",
|
||||
value: certificate.hash,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
await client.ready;
|
||||
|
||||
const stream = await client.createBidirectionalStream();
|
||||
const reader = stream.readable.getReader();
|
||||
const writer = stream.writable.getWriter();
|
||||
|
||||
(async function read() {
|
||||
const header = await reader.read();
|
||||
|
||||
expect(header.value).to.eql(Uint8Array.of(107));
|
||||
|
||||
const { value } = await reader.read();
|
||||
|
||||
const handshake = TEXT_DECODER.decode(value);
|
||||
expect(handshake.startsWith("0{")).to.be(true);
|
||||
|
||||
partialDone();
|
||||
})();
|
||||
|
||||
writer.write(Uint8Array.of(1));
|
||||
writer.write(TEXT_ENCODER.encode("0"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow to upgrade to WebTransport", (done) => {
|
||||
setupServer(
|
||||
{
|
||||
transports: ["polling", "websocket", "webtransport"],
|
||||
},
|
||||
async ({ engine, h3Server, certificate }) => {
|
||||
const httpServer = createServer();
|
||||
engine.attach(httpServer);
|
||||
httpServer.listen(h3Server.port);
|
||||
|
||||
const partialDone = createPartialDone(() => {
|
||||
httpServer.close();
|
||||
success(engine, h3Server, done);
|
||||
}, 2);
|
||||
|
||||
engine.on("connection", (socket) => {
|
||||
socket.on("upgrade", (transport) => {
|
||||
expect(transport.name).to.eql("webtransport");
|
||||
partialDone();
|
||||
});
|
||||
});
|
||||
|
||||
request(`http://localhost:${h3Server.port}/engine.io/`)
|
||||
.query({ EIO: 4, transport: "polling" })
|
||||
.end(async (_, res) => {
|
||||
const payload = JSON.parse(res.text.substring(1));
|
||||
|
||||
expect(payload.upgrades).to.eql(["websocket", "webtransport"]);
|
||||
|
||||
const client = new WebTransport(
|
||||
`https://127.0.0.1:${h3Server.port}/engine.io/`,
|
||||
{
|
||||
serverCertificateHashes: [
|
||||
{
|
||||
algorithm: "sha-256",
|
||||
value: certificate.hash,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
await client.ready;
|
||||
|
||||
const stream = await client.createBidirectionalStream();
|
||||
const reader = stream.readable.getReader();
|
||||
const writer = stream.writable.getWriter();
|
||||
|
||||
(async function read() {
|
||||
const header = await reader.read();
|
||||
|
||||
expect(header.value).to.eql(Uint8Array.of(6));
|
||||
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
|
||||
const probeValue = TEXT_DECODER.decode(value);
|
||||
expect(probeValue).to.eql("3probe");
|
||||
|
||||
partialDone();
|
||||
})();
|
||||
|
||||
await writer.write(Uint8Array.of(31));
|
||||
await writer.write(
|
||||
TEXT_ENCODER.encode(`0{"sid":"${payload.sid}"}`)
|
||||
);
|
||||
await writer.write(Uint8Array.of(6));
|
||||
await writer.write(TEXT_ENCODER.encode(`2probe`));
|
||||
await writer.write(Uint8Array.of(1));
|
||||
await writer.write(TEXT_ENCODER.encode(`5`));
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should close a connection that fails to open a bidirectional stream", (done) => {
|
||||
setupServer(
|
||||
{
|
||||
upgradeTimeout: 50,
|
||||
},
|
||||
async ({ engine, h3Server, certificate }) => {
|
||||
const client = new WebTransport(
|
||||
`https://127.0.0.1:${h3Server.port}/engine.io/`,
|
||||
{
|
||||
serverCertificateHashes: [
|
||||
{
|
||||
algorithm: "sha-256",
|
||||
value: certificate.hash,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
await client.ready;
|
||||
|
||||
client.closed.then(() => {
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should close a connection that sends an invalid handshake", (done) => {
|
||||
setupServer(
|
||||
{
|
||||
upgradeTimeout: 50,
|
||||
},
|
||||
async ({ engine, h3Server, certificate }) => {
|
||||
const client = new WebTransport(
|
||||
`https://127.0.0.1:${h3Server.port}/engine.io/`,
|
||||
{
|
||||
serverCertificateHashes: [
|
||||
{
|
||||
algorithm: "sha-256",
|
||||
value: certificate.hash,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
await client.ready;
|
||||
const stream = await client.createBidirectionalStream();
|
||||
const writer = stream.writable.getWriter();
|
||||
|
||||
await writer.write(Uint8Array.of(1, 2, 3));
|
||||
|
||||
client.closed.then(() => {
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should send ping/pong packets", (done) => {
|
||||
setup(
|
||||
{
|
||||
pingInterval: 20,
|
||||
},
|
||||
async ({ engine, h3Server, reader, writer }) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const header = await reader.read();
|
||||
expect(header.value).to.eql(Uint8Array.of(1));
|
||||
|
||||
const packet = await reader.read();
|
||||
const value = TEXT_DECODER.decode(packet.value);
|
||||
expect(value).to.eql("2");
|
||||
|
||||
writer.write(Uint8Array.of(1));
|
||||
writer.write(TEXT_ENCODER.encode("3"));
|
||||
}
|
||||
|
||||
success(engine, h3Server, done);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should close on ping timeout", (done) => {
|
||||
setup(
|
||||
{
|
||||
pingInterval: 20,
|
||||
pingTimeout: 30,
|
||||
},
|
||||
async ({ engine, h3Server, socket, client }) => {
|
||||
const partialDone = createPartialDone(done, 2);
|
||||
socket.on("close", (reason) => {
|
||||
expect(reason).to.eql("ping timeout");
|
||||
partialDone();
|
||||
});
|
||||
|
||||
client.closed.then(() => success(engine, h3Server, partialDone));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle connections closed by the server", (done) => {
|
||||
setup({}, async ({ engine, h3Server, socket, client }) => {
|
||||
client.closed.then(() => success(engine, h3Server, done));
|
||||
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle connections closed by the client", (done) => {
|
||||
setup({}, async ({ engine, h3Server, socket, client }) => {
|
||||
socket.on("close", (reason) => {
|
||||
expect(reason).to.eql("transport close");
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some plaintext data (client to server)", (done) => {
|
||||
setup({}, async ({ engine, h3Server, socket, writer }) => {
|
||||
socket.on("data", (data) => {
|
||||
expect(data).to.eql("hello");
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
|
||||
writer.write(Uint8Array.of(6));
|
||||
writer.write(TEXT_ENCODER.encode("4hello"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some plaintext data (server to client)", (done) => {
|
||||
setup({}, async ({ engine, h3Server, socket, reader }) => {
|
||||
socket.send("hello");
|
||||
|
||||
const header = await reader.read();
|
||||
expect(header.value).to.eql(Uint8Array.of(6));
|
||||
|
||||
const { value } = await reader.read();
|
||||
const decoded = TEXT_DECODER.decode(value);
|
||||
expect(decoded).to.eql("4hello");
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
|
||||
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) => {
|
||||
expect(Buffer.isBuffer(data)).to.be(true);
|
||||
expect(data).to.eql(Buffer.of(1, 2, 3));
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
|
||||
writer.write(Uint8Array.of(131));
|
||||
writer.write(Uint8Array.of(1, 2, 3));
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some binary data (server to client)", (done) => {
|
||||
setup({}, async ({ engine, h3Server, socket, reader }) => {
|
||||
socket.send(Buffer.of(1, 2, 3));
|
||||
|
||||
const header = await reader.read();
|
||||
expect(header.value).to.eql(Uint8Array.of(131));
|
||||
|
||||
const { value } = await reader.read();
|
||||
expect(value).to.eql(Uint8Array.of(1, 2, 3));
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some big binary data (client to server)", (done) => {
|
||||
setup({}, async ({ engine, h3Server, socket, writer }) => {
|
||||
const payload = Buffer.allocUnsafe(1e6);
|
||||
|
||||
socket.on("data", (data) => {
|
||||
expect(Buffer.isBuffer(data)).to.be(true);
|
||||
expect(data).to.eql(payload);
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
|
||||
writer.write(Uint8Array.of(255, 0, 0, 0, 0, 0, 15, 66, 64));
|
||||
writer.write(payload);
|
||||
});
|
||||
});
|
||||
|
||||
it("should send some big binary data (server to client)", (done) => {
|
||||
setup({}, async ({ engine, h3Server, socket, reader }) => {
|
||||
const payload = Buffer.allocUnsafe(1e6);
|
||||
|
||||
socket.send(payload);
|
||||
|
||||
const header = await reader.read();
|
||||
expect(header.value).to.eql(
|
||||
Uint8Array.of(255, 0, 0, 0, 0, 0, 15, 66, 64)
|
||||
);
|
||||
|
||||
const chunk1 = await reader.read();
|
||||
// the size of the chunk is implementation-specific (maxDatagramSize)
|
||||
expect(chunk1.value).to.eql(payload.slice(0, 1228));
|
||||
|
||||
const chunk2 = await reader.read();
|
||||
expect(chunk2.value).to.eql(payload.slice(1228, 2456));
|
||||
|
||||
success(engine, h3Server, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
11
packages/engine.io/tsconfig.json
Normal file
11
packages/engine.io/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/",
|
||||
"target": "es2018", // Node.js 10 (https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping)
|
||||
"module": "commonjs",
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"./lib/**/*"
|
||||
]
|
||||
}
|
||||
10
packages/engine.io/wrapper.mjs
Normal file
10
packages/engine.io/wrapper.mjs
Normal file
@@ -0,0 +1,10 @@
|
||||
export {
|
||||
Server,
|
||||
Socket,
|
||||
Transport,
|
||||
transports,
|
||||
listen,
|
||||
attach,
|
||||
parser,
|
||||
protocol,
|
||||
} from "./build/engine.io.js";
|
||||
Reference in New Issue
Block a user