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
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/
This is similar to `onAny()`, but for outgoing packets.
Syntax:
```js
socket.onAnyOutgoing((event, ...args) => {
console.log(event);
});
```
Related: 531104d332
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 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 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
With autoUnref set to true (default: false), the Socket.IO client will
allow the program to exit if there is no other active timer/socket in
the event system.
```js
const socket = io({
autoUnref: true
});
```
Note: this option only applies to Node.js clients.
Related: https://github.com/socketio/socket.io-client/issues/1446
Previously, the following code:
```js
const socket1 = io({
path: "/test1"
});
const socket2 = io({
path: "/test2"
});
```
would result in one single Manager, with the "/test2" path being
silently ignored.
Two distinct Manager instances will now be created.
Related: https://github.com/socketio/socket.io-client/issues/1225
When a given socket was disconnected, either by the server-side or by the client-side, the manager was closed too, regardless of the other connected sockets.
```js
const socket1 = io({
autoConnect: false
});
const socket2 = io("/test");
socket1.disconnect(); // also disconnect socket2
```
This bug was introduced in [1].
[1]: b60e909039
- rename "connect_error" to "error"
- remove "reconnecting" (duplicate of "reconnect_attempt")
The updated list of events emitted by the Manager:
- open: successful (re)connection
- error: (re)connection failure (previously: "connect_error") or error after a successful connection
- close: disconnection
- ping: ping packet
- packet: data packet
- reconnect_attempt: reconnection attempt (previously: "reconnect_attempt" & "reconnecting")
- reconnect: successful reconnection
- reconnect_error: reconnection failure
- reconnect_failed: reconnection failure after all attempts
For reference, the Socket instance emits the following events:
- connect: successful connection to a Namespace
- connect_error: connection failure
- disconnect: disconnection
The meaning is not modified: this packet type is still used by the
server when the connection to a namespace is refused.
Breaking change: the Socket instance will now emit a "connect_error"
event instead of "error" (which is not a reserved event anymore)
```js
// before
socket.on("error", () => {});
// after
socket.on("connect_error", () => {});
```
Inspired from EventEmitter2 [1]
The API is similar to the one on the server-side:
```js
socket.onAny((event, ...args) => {});
socket.prependAny((event, ...args) => {});
socket.offAny(); // remove all listeners
socket.offAny(listener);
const listeners = socket.listenersAny();
```
[1]: https://github.com/EventEmitter2/EventEmitter2
A volatile packet will be dropped if:
- the socket is not connected
- the low-level transport is not ready (for example, a HTTP POST request is already pending)
Syntax:
```js
socket.volatile.emit("volatile event", "might or might not be sent");
```
These events cannot be used by the end users, because they are part of
the Socket.IO public API, so using them will now throw an error
explicitly.
Related: f7ed81e5d2
Previously, most of the events emitted by the Manager were also emitted
by the Socket instances, but it was somehow misleading for the end
users because they don't have the same meaning:
- Manager: the state of the low-level connection (with connection and reconnection events)
- Socket: the state of the connection to the Namespace (only 'connect', 'disconnect' and 'error')
For example, the `reconnect` event:
```js
socket.on("reconnect", () => {
console.log(socket.connected); // might be false, which is a bit surprising
});
```
Breaking change: the Socket instance will no longer forward the events
of its Manager
Those events can still be accessed on the Manager instance though:
```js
socket.io.on("reconnect", () => {
// ...
});
```
It was included by the previous commit, but the test code is not
currently transpiled to ES5 so it breaks IE in the CI.
Let's use a plain function for now.