Previously, getting disconnected while waiting for an acknowledgement
would create a memory leak, as the acknowledgement was never received
and the handler would stay in memory forever.
This commit fixes the issue:
- handlers that do accept an error as first argument, such as:
* `socket.emit("test", (err, value) => { ... })` with `ackTimeout` option
* `socket.timeout(5000).emit("test", (err, value) => { ... })`
* `const value = await socket.emitWithAck("test")`
will now properly resolve with an error and get discarded.
- handlers that don't like `socket.emit("test", (value) => { ... });`
will simply be discarded upon disconnection
Note: the structure of the 'acks' attribute has been left untouched, in
order to prevent any breaking change.
Related:
- https://github.com/socketio/socket.io-client/issues/1546
- https://github.com/socketio/socket.io/issues/4964
Details:
- engine.io-client sets clearTimeoutFn and setTimeoutFn function
depending on settings passed to manager
- socker.io-client is using manager.setTimeoutFn to start connection
monitoring timer, but is using regular clearTimeout function to
stop it when connection is established
- in some setups it is causing timer fail to stop and it will break
connection every _timeout_ milliseconds (which is 20000 by default)
In some specific cases (Node.js client with WebSocket only), the reason
attached to the "connect_error" event was "websocket error" instead of
"timeout".
Related: https://github.com/socketio/socket.io/issues/4062
The packet ID cannot be used for deduplication, because it's only
unique for the given session. If you reconnect on another server and
try to resend a packet, then the server won't be able to know whether
the packet has already been processed or not.
This bug was introduced in [1]: a multiplexed socket could in some
cases send multiple CONNECT packets, resulting in duplicate connections
on the server side.
A cached socket will now be reopened only if it was inactive, that is,
if one had explicitly called socket.disconnect() before.
Related: https://github.com/socketio/socket.io-client/issues/1460
[1]: b7dd891e89
In the previous implementation added in [1], the socket would try to
send the packet even if it was disconnected, which would needlessly
exhaust the number of retries.
[1]: 655dce9755
Syntax:
```js
const socket = io({
retries: 3,
ackTimeout: 10000
});
// "my-event" will be sent up to 4 times (1 + 3), until the server sends an acknowledgement
socket.emit("my-event", (err) => {});
```
Notes:
- the order of the packets is guaranteed, as we send packets one by one
- the same packet id is reused for consecutive retries, in order to
allow deduplication on the server side
This commit adds some syntactic sugar around acknowledgements:
```js
// without timeout
const response = await socket.emitWithAck("hello", "world");
// with a specific timeout
try {
const response = await socket.timeout(1000).emitWithAck("hello", "world");
} catch (err) {
// the server did not acknowledge the event in the given delay
}
```
Note: enviroments that do not support Promises ([1]) will need to add a
polyfill in order to use this feature
See also: 184f3cf7af
[1]: https://caniuse.com/promises
Connection state recovery allows a client to reconnect after a
temporary disconnection and restore its state:
- id
- rooms
- data
- missed packets
See also: 54d5ee05a6
For some reason, the lockfile was not in sync anymore with the
package.json file:
> `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync.
That may be linked to a new version of Node.js (v16.15.1).
This is similar to `onAny()`, but for outgoing packets.
Syntax:
```js
socket.onAnyOutgoing((event, ...args) => {
console.log(event);
});
```
Related: 531104d332
This commit allows to:
- provide an ESM version of those modules ([1])
- reduce the attack surface in case of supply chain attacks
- reduce the size of the bundle with tree-shaking
As a downside, we won't receive security updates for those modules
anymore.
[1]: socketio/socket.io-client#1536
Related: df32277c3f
The "disconnect" event will now include additional details to help
debugging if anything has gone wrong.
Example when a payload is over the maxHttpBufferSize value in HTTP
long-polling mode:
```js
socket.on("disconnect", (reason, details) => {
console.log(reason); // "transport error"
// in that case, details is an error object
console.log(details.message); "xhr post error"
console.log(details.description); // 413 (the HTTP status of the response)
// details.context refers to the XMLHttpRequest object
console.log(details.context.status); // 413
console.log(details.context.responseText); // ""
});
```
Related: b9252e2074
Previously, calling `socket.disconnect().connect()` could, if the
connection was upgraded to WebSocket, result in "disconnect" being
emitted twice, and an engine being leaked.
Here's what happened:
> socket.disconnect()
- calls `socket.destroy()` so the socket doesn't listen to the manager events anymore
- then calls `manager._close()` which closes the underlying engine but not the manager itself (it waits for the "close" event of the engine)
> socket.connect()
- calls `socket.subEvents()` so the socket does listen to the manager events
- calls `manager.open()` which creates a new engine
And then the first engine emits a "close" event, which is forwarded to
the socket, hence the second "disconnect" event.
Related: https://github.com/socketio/socket.io-client/issues/1014
This restores the previous behavior, where the "io" object available in
the browser could be used as a function (`io()`) or as a namespace
(`io.connect()`).
The breaking change was introduced in [1].
Related: https://github.com/socketio/socket.io/issues/4121
[1]: 16b65698ae
This allows to control the behavior of mocked timers (@sinonjs/fake-timers),
depending on the value of the "useNativeTimers" option:
- true: use native setTimeout function
- false (default): use classic timers, that may be mocked
Related: 5d1d5bea11
Before this change, `require("socket.io-client").Socket` would return
"undefined".
Note: having access to the Socket class allows users to modify its
prototype.
Related: https://github.com/socketio/socket.io/issues/3726
Before this commit, an event sent in the "connect" handler could be
sent before the events that were buffered while disconnected.
```js
socket.on("connect", () => {
socket.emit("bar");
});
socket.emit("foo"); // buffered while disconnected
```
In the example above, the "bar" event was sent first, which is not
correct.
Related: https://github.com/socketio/socket.io-client/issues/1458
When passing the same `options` argument for two distinct Socket
instances, a new Manager was created:
```js
const opts = {};
const socket1 = io("/foo", opts);
const socket2 = io("/bar", opts);
console.log(socket1.io === socket2.io); // false
```
This bug was introduced by [1].
Note: the `options` argument is modified at the `socket.io-client`
level (path, query) and at the `engine.io-client` level (host, port),
which may not be optimal.
[1]: 7a0c2b504f
Related: https://github.com/socketio/socket.io/issues/3898