The "drain" event (added in [1]) had two different meanings:
- the transport is ready to be written
- the packets are sent over the wire
For the WebSocket and the WebTransport transports, those two events
happen at the same time, but this is not the case for the HTTP
long-polling transport:
- the transport is ready to be written when the client sends a GET request
- the packets are sent over the wire when the server responds to the GET request
Which caused an issue with send callbacks during an upgrade, since the
packets were written but the client would not open a new GET request.
There are now two distinct events: "ready" and "drain"
Related: https://github.com/socketio/engine.io/issues/695
[1]: 2a93f06e27
Instead of allocating one temporary function for each WebSocket
`send()` call.
Regarding the test removal, the permessage-deflate threshold was
implemented in the "ws" package in [1], so it's not needed anymore.
[1]: 6b3904b42d
The wsPreEncoded option was added in the `socket.io-adapter` package
when broadcasting a message to multiple clients.
It was removed in [1] and is now superseded by the `wsPreEncodedFrame`
option, which directly computes the WebSocket frame once for all
clients (see [2]).
[1]: 88eee5948a
[2]: 5f7b47d40f
With the `websocket` transport, the callbacks which indicate that the
packets are actually written were not properly called.
Example:
```js
socket.send("hello", () => {
// the message has been written to the underlying transport
});
```
The bug was caused by the `websocket` transport (and `webtransport` as
well) having its `supportsFraming` property set to `true`, despite
having been changed in [1] to emit a single `drain` event for each
batch of messages written to the transport like the `polling` transport
always did. Note that although [1] is partially reverted in [2], the
new `drain` event behavior is preserved as called out in that commit's
message.
The `supportsFraming` attribute was introduced in [3] (amended by [4])
as a way to distinguish transports that emit one `drain` per message
from those that emit one `drain` per batch. Since the delivery of
`send` callbacks depends on matching `drain` events with
`transport.send` calls, that distinction is vital to correct behavior.
However, now that all transports have converged to "one `drain` per
batch" behavior, this `supportsFraming` property can be retired (and
the code for calling callbacks simplified).
[1]: https://github.com/socketio/engine.io/pull/618
[2]: a65a047526
[3]: https://github.com/socketio/engine.io/pull/130
[4]: https://github.com/socketio/engine.io/pull/132
Related: https://github.com/socketio/engine.io/issues/698
A reference to the initial IncomingMessage object (the first HTTP
request of the session) is kept in memory by default (`socket.request`),
so its attached ServerResponse object (`req.res`) would not be
garbage-collected. This will now be the case.
Note: the IncomingMessage object is needed in two cases:
- when working with the `express-session` middleware (`request.session`)
- when fetching the certificate of the client with `request.socket.getPeerCertificate()`
That's why removing it would be a breaking change.
This header should not be needed since the client already includes a
cache busting query parameter ("t"), but a misconfigured CDN could
ignore the query parameters and cache the server response.
Related: https://github.com/socketio/socket.io/issues/4842
Refreshing the page with a client connected with WebTransport would
trigger the following exception:
> node:internal/process/promises:288
> triggerUncaughtException(err, true /* fromPromise */);
> ^
>
> [UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "0".] {
> code: 'ERR_UNHANDLED_REJECTION'
> }
Related: https://github.com/socketio/engine.io/issues/688
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
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() {} }`
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
This option will be used when broadcasting a packet to multiple clients,
in order to only encode the packet once.
Usage:
```js
socket.write("test", {
wsPreEncoded: "4test"
});
```
Note: pre-encoding the content with HTTP long-polling is a bit harder,
since the concatenation of the packets is specific to each client.
This change should reduce memory usage when many packets are emitted to
many clients in a burst.
Co-authored-by: Branislav Katreniak <bkatreniak@slido.com>
Those events will be emitted before the response headers are written to
the socket:
- "initial_headers": on the first request of the connection
- "headers": on all requests (HTTP long-polling and WebSocket upgrade)
Syntax:
```js
server.on("initial_headers", (headers, req) => {
headers["test"] = "123";
headers["set-cookie"] = "mycookie=456";
});
server.on("headers", (headers, req) => {
headers["test"] = "789";
});
```
Related:
- https://github.com/socketio/engine.io/issues/557
- https://github.com/socketio/socket.io/issues/3630
We'll now rely on the standard cors module (https://github.com/expressjs/cors),
instead of the custom implementation that is error-prone and not
really user-friendly.
Breaking change: 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
}
})
```
It's now possible to specify an origins value (default value is '*') when initialising the engine. This value will be returned as the Access-Control-Allow-Origin value.
Related: #449
The server often crashes with 'TypeError: "list" argument must be an Array of Buffers' errors,
which is caused by a bug in new versions of node, where setEncoding call does not work for messages
that are already in the queue.
This pull request makes sure that concat is never called in the non binary case, even if
setEncoding does not work properly.