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());
```
Related:
- https://github.com/socketio/engine.io/issues/668
- https://github.com/socketio/engine.io/issues/651
- https://github.com/socketio/socket.io/issues/4609
- https://github.com/socketio/socket.io/issues/3933
- a lot of other issues asking for compatibility with express-session
This reverts commit [1], which was included in `engine.io@5.1.0` and
`socket.io@4.1.0`.
The WebSocket connection was closed before all packets were written
out, so for example when calling `socket.disconnect(true)` on the
Socket.IO server (which disconnect from all namespaces and close the
connection), the client would receive only the first disconnect packet
and kept trying to reconnect to the other namespaces.
The only difference with the previous implementation (pre 5.1.0) is
that the "drain" event gets only called once at the end, and not after
each packet.
[1]: ad5306aeae
Related: https://github.com/socketio/engine.io/issues/648
The package does not have a default export, so importing it from a
project using ES modules would break in some cases.
> Cannot destructure property 'Server' of '_engineIo.default'
Related: https://github.com/socketio/engine.io/issues/657
This major bump creates a lot of noise, but it is necessary for
prettier to be able to parse new syntax such as:
- typed imports: `import { type xxx } from ...`
- private attributes: `class A { #b; #c() {} }`
The "addTrailingSlash" option allows to control whether a trailing
slash is added to the path of the HTTP requests:
- true (default): "/engine.io/"
- false: "/engine.io"
Related: 21a6e1219a
Signed-off-by: iifawzi <iifawzie@gmail.com>
This optimization is only applied if:
- the permessage-deflate extension is disabled (which is the default)
- the "ws" package is used (which is the default)
In that case, the WebSocket frame will only be computed once, when
broadcasting to multiple clients.
Related: 5f7b47d40f
Before this change, receiving an HTTP2 upgrade would make the server
crash:
> Error: read ECONNRESET
> at TCP.onStreamRead (node:internal/stream_base_commons:217:20) {
> errno: -104,
> code: 'ECONNRESET',
> syscall: 'read'
> }
This can be reproduced with Node.js v14.15.3, v16.18.1 and v18.12.1.
A few notes:
- the certificates were recreated because Node.js 18 includes OpenSSL
v3, which has deprecated support for some legacy ciphers (like RC2)
- eiows currently fails to build on Node.js 18, so the tests are
temporarily skipped
See also: https://github.com/nodejs/Release
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}
```
Related: https://github.com/socketio/socket.io-client/issues/1531
With the engine based on µWebSockets.js (introduced in version 6.1.0),
a huge request body split in multiple chunks would throw the following
error:
> node:buffer:254
> TypedArrayPrototypeSet(target, source, targetStart);
> ^
>
> TypeError: Cannot perform %TypedArray%.prototype.set on a detached ArrayBuffer
> at Buffer.set (<anonymous>)
> at _copyActual (node:buffer:254:3)
> node:buffer:254
> TypedArrayPrototypeSet(target, source, targetStart);
> ^
>
> TypeError: Cannot perform %TypedArray%.prototype.set on a detached ArrayBuffer
> at Buffer.set (<anonymous>)
> at _copyActual (node:buffer:254:3)
> at Function.concat (node:buffer:562:12)
> at onEnd (.../node_modules/engine.io/build/transports-uws/polling.js:126:32)
> at .../node_modules/engine.io/build/transports-uws/polling.js:143:17
Note: µWebSockets.js does not currently support chunked transfer
encoding.
Note to future self: URL for dependencies from GitHub must use
"git+https" instead of "git+ssh" to make the CI pass, else you will
encounter the following error:
```
npm ERR! Error while executing:
npm ERR! /usr/bin/git ls-remote -h -t ssh://git@github.com/uNetworking/uWebSockets.js.git
npm ERR!
npm ERR! Warning: Permanently added the RSA host key for IP address '140.82.112.3' to the list of known hosts.
npm ERR! git@github.com: Permission denied (publickey).
npm ERR! fatal: Could not read from remote repository.
npm ERR!
npm ERR! Please make sure you have the correct access rights
npm ERR! and the repository exists.
npm ERR!
npm ERR! exited with error code: 128
```
When binding to an uWebSockets.js application, the server could crash
with the following error:
```
TypeError: res.onData is not a function
at Polling.onDataRequest (build/transports-uws/polling.js:133:13)
at Polling.onRequest (build/transports-uws/polling.js:47:18)
at callback (build/userver.js:80:56)
```
Related: https://github.com/socketio/engine.io/issues/637
When binding to an uWebSockets.js App, there was an unhandled case that
could crash the server:
```
curl "http://localhost:3000/engine.io/?EIO=4&transport=websocket"
```
would result in:
```
Error: Returning from a request handler without responding or attaching an abort handler is forbidden!
terminate called without an active exception
```
Note: this does not apply to the default server based on ws, because
the error was caught elsewhere in the source code.
Related: https://github.com/socketio/socket.io/issues/4250
**IMPORTANT SECURITY FIX**
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 [1], included in `engine.io@4.0.0`, so
previous releases are not impacted.
[1]: f3c291fa61
Thanks to Marcus Wejderot from Mevisio for the responsible disclosure.