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
zuul is now archived [1] and does not support the new W3C WebDriver
protocol, since it relies on the wd package [2] under the hood, which
uses the (now deprecated) JSON Wire Protocol.
We will now use the webdriver.io test framework, which allows to run
our tests in local and on Sauce Labs (cross-browser and mobile tests).
This allows us to run our tests on latest versions of Android and iOS,
since Sauce Labs only supports the W3C WebDriver protocol for these
platforms ([3]).
[1]: https://github.com/defunctzombie/zuul
[2]: https://github.com/admc/wd
[3]: https://docs.saucelabs.com/dev/w3c-webdriver-capabilities/
Tests iPhone and iPad 16 are currently failing.
Saucelabs error message:
> An unknown server-side error occurred while processing the command. Original error: W3C capabilities should be provided
It might be time to migrate from zuul (https://github.com/defunctzombie/zuul).
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