Commit Graph

48 Commits

Author SHA1 Message Date
Damien Arrachequesne
34cbfbb532 fix: discard acknowledgements upon disconnection
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
2024-03-14 17:34:29 +01:00
Damien Arrachequesne
f9c16f2265 fix(typings): fix the type of the socket#id attribute
Related: https://github.com/socketio/socket.io/issues/4884
2024-01-02 15:55:56 +01:00
Damien Arrachequesne
121fd7c73d refactor: do not reuse the same packet ID for retries
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.
2023-02-20 17:31:02 +01:00
Damien Arrachequesne
4996f9ee71 fix: do not drain the queue while the socket is offline
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
2023-02-20 17:01:22 +01:00
Damien Arrachequesne
4d6d95e079 fix(typings): do not expose browser-specific types
Related:

- https://github.com/socketio/socket.io-client/issues/1561
- b862924b7f
- 37d7a0aa79
2023-02-04 08:21:59 +01:00
Damien Arrachequesne
c54e09d092 test: add more tests for the retry mechanism 2023-02-04 08:04:38 +01:00
Damien Arrachequesne
5ba0d498dc refactor: minor edit
Following 655dce9755
2023-02-03 08:27:15 +01:00
Damien Arrachequesne
655dce9755 feat: implement retry mechanism
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
2023-02-01 08:19:34 +01:00
Damien Arrachequesne
f27cba5b33 refactor: add recovered flag after a successful recovery
Following b4e20c5c70
2023-01-30 08:14:45 +01:00
Damien Arrachequesne
47b979d573 feat: add promise-based acknowledgements
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
2023-01-30 08:08:06 +01:00
Damien Arrachequesne
b4e20c5c70 feat: implement connection state recovery
Connection state recovery allows a client to reconnect after a
temporary disconnection and restore its state:

- id
- rooms
- data
- missed packets

See also: 54d5ee05a6
2023-01-25 09:21:08 +01:00
Damien Arrachequesne
a1c528b089 fix(typings): properly type emits with timeout (2)
This follows [1], in order to keep the label of each argument.

[1]: 33e417258c

Related:

- https://github.com/socketio/socket.io-client/pull/1570#issuecomment-1384075633
- https://github.com/microsoft/TypeScript/issues/39941
- https://github.com/microsoft/TypeScript/issues/48049
2023-01-17 15:56:37 +01:00
Engin Aydogan
33e417258c fix(typings): properly type emits with timeout (#1570)
When emitting with a timeout (added in version 4.4.0), the "err"
argument was not properly typed and would require to split the client
and server typings. It will now be automatically inferred as an Error
object.

Workaround for previous versions:

```ts
type WithTimeoutAck<isEmitter extends boolean, args extends any[]> = isEmitter extends true ? [Error, ...args] : args;

interface ClientToServerEvents<isEmitter extends boolean = false> {
    withAck: (data: { argName: boolean }, callback: (...args: WithTimeoutAck<isEmitter, [string]>) => void) => void;
}

interface ServerToClientEvents<isEmitter extends boolean = false> {

}

const io = new Server<ClientToServerEvents, ServerToClientEvents<true>>(3000);

io.on("connection", (socket) => {
    socket.on("withAck", (val, cb) => {
        cb("123");
    });
});

const socket: Socket<ServerToClientEvents, ClientToServerEvents<true>> = ioc("http://localhost:3000");

socket.timeout(100).emit("withAck", { argName: true }, (err, val) => {
  // ...
});
```

Related: https://github.com/socketio/socket.io-client/issues/1555
2023-01-16 12:31:01 +01:00
Damien Arrachequesne
7c056889ee docs: add jsdoc for each public method 2022-10-15 06:54:56 +02:00
Damien Arrachequesne
2403b88057 fix: do not swallow user exceptions
Following [1], any exception in a user-provided event listener would
get caught in the try...catch of the decoder and result in the
reconnection of the socket.

[1]: c597023169

Related:

- https://github.com/socketio/socket.io-client/issues/1551
- https://github.com/socketio/socket.io-client/issues/1554
- https://github.com/socketio/socket.io-client/issues/1557
2022-10-13 16:26:35 +02:00
Damien Arrachequesne
8c659bcccf chore: regenerate lockfile
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).
2022-06-26 08:19:46 +02:00
Damien Arrachequesne
e8590188ec refactor: replace the disconnected attribute by a getter 2022-04-24 00:13:56 +02:00
Damien Arrachequesne
74e3e601a4 feat: add support for catch-all listeners for outgoing packets
This is similar to `onAny()`, but for outgoing packets.

Syntax:

```js
socket.onAnyOutgoing((event, ...args) => {
  console.log(event);
});
```

Related: 531104d332
2022-04-23 23:57:03 +02:00
Damien Arrachequesne
b862924b7f feat: add details to the disconnect event
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
2022-04-23 00:57:23 +02:00
Damien Arrachequesne
522ffbe7a8 fix: prevent double ack with timeout
The ack was not properly removed upon timeout, and could be called
twice.

Related: ccf7998cc5
2021-11-18 13:40:46 +01:00
Damien Arrachequesne
d54d12ce63 fix: prevent socket from reconnecting after middleware failure
Related: https://github.com/socketio/socket.io/discussions/4150
2021-11-16 19:57:47 +01:00
Damien Arrachequesne
ccf7998cc5 feat: add timeout feature
Usage:

```js
socket.timeout(5000).emit("my-event", (err) => {
  if (err) {
    // the server did not acknowledge the event in the given delay
  }
});
```
2021-11-16 19:56:44 +01:00
Damien Arrachequesne
91b948b860 refactor: move the typed events to @socket.io/component-emitter
The typed events have been moved to [1] in order to remove the
intermediary class and reduce the bundle size.

Diff: https://github.com/socketio/emitter/compare/2.0.0...3.0.0

[1]: https://github.com/socketio/emitter/
2021-10-14 14:09:23 +02:00
Damien Arrachequesne
16b65698ae feat: provide an ESM build with and without debug
See also: 00d7e7d7ee

Related:

- https://github.com/socketio/socket.io-client/issues/1188
- https://github.com/socketio/socket.io-client/issues/1378
2021-10-13 18:09:41 +02:00
anshul singh
f3acddf997 refactor: remove duplicate initilializations (#1489)
The attributes were already initialized, resulting in duplicate lines
in the final bundle.

Related: https://github.com/socketio/socket.io/issues/4063
2021-08-28 09:25:36 +02:00
Damien Arrachequesne
34f822f783 fix: ensure buffered events are sent in order
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
2021-05-06 14:23:25 +02:00
divlo
c15022347c fix(typings): make auth property public (#1455)
Related: https://github.com/socketio/socket.io-client/issues/1453
2021-04-01 00:32:21 +02:00
Damien Arrachequesne
59023657a0 feat: add support for typed events
Syntax:

```ts
interface ServerToClientEvents {
  "my-event": (a: number, b: string, c: number[]) => void;
}

interface ClientToServerEvents {
  hello: (message: string) => void;
}

const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io();

socket.emit("hello", "world");

socket.on("my-event", (a, b, c) => {
  // ...
});
```

The events are not typed by default (inferred as any), so this change
is backward compatible.

Related: https://github.com/socketio/socket.io/issues/3742
2021-03-10 01:24:56 +01:00
david-fong
47f917afdd fix(typings): add return types and general-case overload signatures (#1440) 2021-02-02 11:29:08 +01:00
Damien Arrachequesne
53c73749a8 fix: emit a connect_error event upon connection failure
Related: https://github.com/socketio/socket.io/issues/3734
2021-01-05 11:42:54 +01:00
Damien Arrachequesne
b83f89c901 fix(typings): make sendBuffer and receiveBuffer public
See also: https://socket.io/docs/v3/client-offline-behavior/#Buffered-events
2021-01-05 10:17:58 +01:00
david-fong
7bc7a7b624 refactor: switch to native javascript bind (#1423)
The polyfill for the bind() method was necessary for IE6/7/8, which we
do not support anymore.

Related: https://caniuse.com/mdn-javascript_builtins_function_bind
2020-12-11 11:55:57 +01:00
Damien Arrachequesne
f8f60fc860 fix: keep track of active sockets
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
2020-12-07 11:24:15 +01:00
Damien Arrachequesne
ec1f8c3474 fix: emit an error when reaching a v2.x server
A WebSocket-only v3.x client is able to reach a v2.x server, as the
Engine.IO handshake are compatible, but the v2.x server does not send
a sid in the Socket.IO handshake, which resulted in:

> Uncaught TypeError: Cannot read property 'sid' of undefined

A 'connect_error' event will now be emitted.

References:

- https://github.com/socketio/engine.io-protocol#difference-between-v3-and-v4
- https://github.com/socketio/socket.io-protocol#difference-between-v5-and-v4
- https://socket.io/docs/v3/migrating-from-2-x-to-3-0/
2020-12-07 11:18:55 +01:00
david-fong
226b491846 refactor(typings): make typing information more specific (#1418) 2020-12-07 09:06:57 +01:00
Damien Arrachequesne
09393952e3 feat: emit an Error object upon middleware error
See 54bf4a44e9
2020-10-30 22:54:13 +01:00
Damien Arrachequesne
13e1db7c94 refactor: rename ERROR to CONNECT_ERROR
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", () => {});
```
2020-10-26 10:04:05 +01:00
Damien Arrachequesne
55f464f59e feat: add support for catch-all listeners
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
2020-10-26 10:03:27 +01:00
Damien Arrachequesne
f3cbe983a1 refactor: additional typings 2020-10-17 03:37:53 +02:00
Damien Arrachequesne
7ddad2c09d feat: add volatile events
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");
```
2020-10-17 03:37:29 +02:00
Damien Arrachequesne
178909471a feat: move binary detection back to the parser
See 285e7cd0d8

Breaking change: the Socket#binary() method is removed, as this use
case is now covered by the ability to provide your own parser.
2020-10-15 10:37:06 +02:00
Damien Arrachequesne
c7998d5446 refactor: add Manager and Socket typings 2020-10-14 22:59:58 +02:00
Damien Arrachequesne
b60e909039 refactor: remove the 'connecting' event
This event was added in 41956806a7

But it does not convey the information that the Socket is actually
sending a CONNECT packet to the server. It should maybe be moved to the
Socket#onopen() method, but let's remove it for now as it is not
documented anywhere.
2020-10-12 15:03:09 +02:00
Damien Arrachequesne
6494f61be0 feat: throw upon reserved event names
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
2020-10-12 15:03:09 +02:00
Damien Arrachequesne
bbe94adb82 feat: do not reuse the Engine.IO id
Related: 3b0cb1158c
2020-10-08 03:04:49 +02:00
Damien Arrachequesne
249e0bef90 feat: remove the implicit connection to the default namespace
Related: 09b6f23339
2020-10-08 02:04:03 +02:00
Damien Arrachequesne
429846b0a1 chore: bump socket.io-parser
Breaking change:

- the encode() method is now synchronous

Please note that the exchange [protocol][1] is left untouched and thus
stays in version 4.

Diff: https://github.com/socketio/socket.io-parser/compare/3.4.1...4.0.0

[1] https://github.com/socketio/socket.io-protocol
2020-10-06 01:20:12 +02:00
Damien Arrachequesne
697bea2d81 refactor: migrate to TypeScript 2020-10-06 00:21:14 +02:00