Commit Graph

751 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
5a3eafed1c fix(typings): accept string | undefined as init argument
Related: https://github.com/socketio/socket.io/issues/4873
2023-11-24 08:48:08 +01:00
Max Stepanov
f2892aba0b fix: use same scope for setTimeout and clearTimeout calls (#1568)
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)
2023-06-22 09:33:11 +02:00
Damien Arrachequesne
5bc94b56bc fix: properly report timeout error when connecting
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
2023-06-20 18:48:11 +02: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
46213a647e fix: prevent duplicate connections when multiplexing
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
2023-02-20 17:09:50 +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
b7dd891e89 fix: ensure manager.socket() returns an active socket
Related: https://github.com/socketio/socket.io-client/issues/1460
2023-02-03 08:16:38 +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
c597023169 fix: handle ill-formatted packet from server
The decoder can throw an error when trying to decode an invalid payload
sent by the server, so the manager will now catch it, close the
connection and then reconnect instead of crashing.

Related:

- https://github.com/socketio/socket.io/issues/4392
- https://github.com/socketio/socket.io-client/issues/1551
2022-09-02 23:28:35 +01: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
6fdf3c9bfa refactor: import single-file 3rd party modules
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
2022-04-23 22:55:39 +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
99c2cb8421 fix: fix socket.disconnect().connect() usage
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
2021-11-18 13:39:40 +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
6780f29624 fix: restore the default export (bis)
The previous commit, while successfully restoring support for:

```js
const socket = require("socket.io-client")(...);
```

breaks for some other cases:

- https://github.com/socketio/socket.io/issues/4128
- https://github.com/socketio/socket.io-client/issues/1509

According to [1], we should use `export = `, but this is not supported
by module "esnext":

> Export assignment cannot be used when targeting ECMAScript modules

So we'll go for this ugly workaround, at least until we remove the
default export in the next major release.

[1]: https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require
2021-10-17 01:00:28 +02:00
Damien Arrachequesne
f0aae8457a fix: restore the default export
The default export was accidentally removed in [1].

Note: that being said, users are encouraged to use the named exports,
because the default export has a different meaning for CommonJS and ES
modules users.

```js
// BAD
import io from "socket.io-client";

// GOOD
import { io } from "socket.io-client";
```

Related:

- https://github.com/socketio/socket.io-client/issues/1505
- https://github.com/socketio/socket.io-client/issues/1507
- https://github.com/socketio/socket.io-client/issues/1508

[1]: 16b65698ae
2021-10-16 01:09:43 +02:00
Chris Krycho
8737d0ae6f fix: restore the namespace export
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
2021-10-16 00:52:31 +02: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
Abd Ul-Hameed Maree
a9e5b85580 feat(typings): add missing types for some emitter methods (#1502)
Co-authored-by: Abd UlHameed Maree <abd.maree@scandinaviatech.com>
2021-10-14 10:58:32 +02:00
Damien Arrachequesne
0661564dc2 chore: migrate to rollup
This change allows us to:

- reduce the size of the bundle
- provide an ESM bundle (for usage in <script type="module">)

Related: https://github.com/socketio/socket.io-client/issues/1198
2021-10-13 18:09:41 +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
Damien Arrachequesne
91fbd47e1e chore: bump engine.io-client to version 6.0.0
Release notes: https://github.com/socketio/engine.io-client/releases/6.0.0
Diff: https://github.com/socketio/engine.io-client/compare/5.2.0...6.0.0
2021-10-11 23:19:51 +02:00
Damien Arrachequesne
66e00b7dd7 fix(typings): allow async listener in typed events
So that:

```ts
socket.on("my-event", async () => {
  // ...
});
```

is valid under the @typescript-eslint/no-misused-promises rule.

Related: https://github.com/socketio/socket.io-client/issues/1486
2021-08-30 08:21:47 +02:00
Michael Vartan
4e1b65699d feat: add an option to use native timer functions (#1479)
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
2021-08-29 09:31:25 +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
vks-jrobertson
dfb46b55a5 fix: allow to set randomizationFactor to 0 (#1447)
Documentation: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing

Related: https://github.com/socketio/socket.io-client/issues/1231
2021-07-29 09:59:03 +02:00
javiergonzalezGenially
35d27df2ae fix(typings): add missing closeOnBeforeunload option (#1469) 2021-05-17 22:01:39 +02:00
Poyan Nabati
c8dfbb1c1d fix(typings): add missing requestTimeout option (#1467) 2021-05-17 00:39:29 +02:00
Damien Arrachequesne
5394669488 fix(typings): add fallback to untyped event listener
See also: a11152f42b

Related:

- https://github.com/socketio/socket.io/issues/3885
- https://github.com/socketio/socket.io/issues/3872
- https://github.com/socketio/socket.io/issues/3833
2021-05-06 14:24:48 +02:00
Damien Arrachequesne
e20d487ac0 fix: properly export the Socket class
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
2021-05-06 14:24:08 +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
Damien Arrachequesne
dd2a8fce00 fix: ensure connections are properly multiplexed
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
2021-05-06 14:22:54 +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
alex.zeng
48f573f6f6 fix(typings): update definition to match wrapper.mjs (#1456) 2021-04-01 00:31:43 +02:00