456 Commits

Author SHA1 Message Date
Damien Arrachequesne
c840bad43a test: fix flaky tests 2022-01-05 08:00:55 +01:00
Damien Arrachequesne
0f11c4745f chore(release): 4.4.0
Diff: https://github.com/socketio/socket.io/compare/4.3.2...4.4.0
2021-11-18 14:10:19 +01:00
Damien Arrachequesne
b839a3b400 fix: prevent double ack when emitting with a timeout
The ack was not properly removed upon timeout, and could be called
twice.

Related: f0ed42f18c
2021-11-18 14:03:07 +01:00
Damien Arrachequesne
f0ed42f18c feat: add timeout feature
Usage:

```js
socket.timeout(5000).emit("my-event", (err) => {
  if (err) {
    // the client did not acknowledge the event in the given delay
  }
});
```
2021-11-16 20:07:53 +01:00
Damien Arrachequesne
b7213e71e4 test: fix flaky test
`srv.close()` only closes the underlying HTTP server, but this does not
terminate the existing WebSocket connections.

Reference: https://nodejs.org/api/http.html#serverclosecallback
2021-11-16 15:58:55 +01:00
Damien Arrachequesne
2da82103d2 test: add test for volatile packet with binary
See also: 88eee5948a
2021-11-16 15:57:32 +01:00
Damien Arrachequesne
02b0f73e2c fix: only set 'connected' to true after middleware execution
The Socket instance is only considered connected when the "connection"
event is emitted, and not during the middleware(s) execution.

```js
io.use((socket, next) => {
  console.log(socket.connected); // prints "false"
  next();
});

io.on("connection", (socket) => {
  console.log(socket.connected); // prints "true"
});
```

Related: https://github.com/socketio/socket.io/issues/4129
2021-11-12 07:31:52 +01:00
Damien Arrachequesne
c0d8c5ab23 feat: add an implementation based on uWebSockets.js
Usage:

```js
const { App } = require("uWebSockets.js");
const { Server } = require("socket.io");

const app = new App();
const server = new Server();

server.attachApp(app);

app.listen(3000);
```

The Adapter prototype is updated so we can benefit from the publish
functionality of uWebSockets.js, so this will apply to all adapters
extending the default adapter.

Reference: https://github.com/uNetworking/uWebSockets.js

Related:

- https://github.com/socketio/socket.io/issues/3601
- https://github.com/socketio/engine.io/issues/578
2021-11-12 07:01:55 +01:00
Sebastiaan Marynissen
9d86397243 fix: fix race condition in dynamic namespaces (#4137)
Using an async operation with `io.use()` could lead to the creation of
several instances of a same namespace, each of them overriding the
previous one.

Example:

```js
io.use(async (nsp, auth, next) => {
  await anOperationThatTakesSomeTime();
  next();
});
```

Related: https://github.com/socketio/socket.io/pull/4136
2021-10-24 07:46:29 +02:00
Damien Arrachequesne
95810aa62d chore(release): 4.3.0
Diff: https://github.com/socketio/socket.io/compare/4.2.0...4.3.0
2021-10-14 14:59:13 +02:00
Damien Arrachequesne
60edecb3bd feat: serve ESM bundle
Related:

- 0661564dc2
- https://github.com/socketio/socket.io-client/issues/1198
2021-10-13 18:17:12 +02:00
Damien Arrachequesne
7a74b66872 test: remove hardcoded ports
Related: https://github.com/socketio/socket.io/issues/3447
2021-09-09 08:57:11 +02:00
Damien Arrachequesne
dc81fcf461 fix: send volatile packets with binary attachments
The binary attachments of volatile packets were discarded (only the
header packet was sent) due to a bug introduced by [1].

Related: https://github.com/socketio/socket.io/issues/3919

[1]: dc381b72c6
2021-09-09 08:55:51 +02:00
Tim Düsterhus
24fee27ba3 feat: ignore the query string when serving client JavaScript (#4024)
Related: https://github.com/socketio/socket.io/issues/4023
2021-08-30 07:59:47 +02:00
Damien Arrachequesne
94e27cd072 fix: fix io.except() method
Previously, calling `io.except("theroom").emit(...)` did not exclude
the sockets in the given room.

This method was forgotten in [1].

[1]: ac9e8ca6c7
2021-07-10 11:48:46 +02:00
Damien Arrachequesne
a4dffc6527 fix: remove x-sourcemap header
This header is useless, as the client bundle already contains a
sourceMappingURL field.

Besides, Firefox prints the following warning:

> <url> is being assigned a //# sourceMappingURL, but already has one

Related: https://github.com/socketio/socket.io/issues/3958
2021-07-04 00:51:41 +02:00
Damien Arrachequesne
891b1870e9 fix(typings): properly type the adapter attribute
Related: https://github.com/socketio/socket.io/issues/3796
2021-05-11 23:59:44 +02:00
Damien Arrachequesne
b84ed1e41c fix(typings): properly type server-side events
See also: 93cce05fb3
2021-05-11 23:59:18 +02:00
Damien Arrachequesne
95d9e4a42f test: fix randomly failing test 2021-05-11 00:06:03 +02:00
Damien Arrachequesne
499c89250d feat: notify upon namespace creation
A "new_namespace" event will be emitted when a new namespace is created:

```js
io.on("new_namespace", (namespace) => {
  // ...
});
```

This could be used for example for registering the same middleware for
each namespace.

See https://github.com/socketio/socket.io/issues/3851
2021-05-11 00:09:18 +02:00
Damien Arrachequesne
93cce05fb3 feat: add support for inter-server communication
Syntax:

```js
// server A
io.serverSideEmit("hello", "world");

// server B
io.on("hello", (arg) => {
  console.log(arg); // prints "world"
});
```

With acknowledgements:

```js
// server A
io.serverSideEmit("hello", "world", (err, responses) => {
  console.log(responses); // prints ["hi"]
});

// server B
io.on("hello", (arg, callback) => {
  callback("hi");
});
```

This feature replaces the customHook/customRequest API from the Redis
adapter: https://github.com/socketio/socket.io-redis/issues/370
2021-05-11 00:07:20 +02:00
Damien Arrachequesne
dc381b72c6 perf: add support for the "wsPreEncoded" writing option
Packets that are sent to multiple clients will now be pre-encoded for
the WebSocket transport (which means simply prepending "4" - which is
the "message" packet type in Engine.IO).

Note: buffers are not pre-encoded, since they are sent without
modification over the WebSocket connection

See also: 7706b123df

engine.io diff: https://github.com/socketio/engine.io/compare/5.0.0...5.1.0
2021-05-11 00:06:03 +02:00
Damien Arrachequesne
d65b6ee84c fix: properly export the Socket class
Before this change, `require("socket.io").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:36:12 +02:00
Maxime Kjaer
a11152f42b fix(typings): add fallback to untyped event listener (#3834)
Related: https://github.com/socketio/socket.io/issues/3833
2021-03-31 11:37:37 +02:00
Damien Arrachequesne
5eaeffc8e2 chore(release): 4.0.0
Diff: https://github.com/socketio/socket.io/compare/3.1.2...4.0.0
2021-03-10 12:43:53 +01:00
Damien Arrachequesne
1b6d6de4ed chore: include Engine.IO v5
Release notes: https://github.com/socketio/engine.io/releases/tag/5.0.0
2021-03-10 11:14:33 +01:00
Maxime Kjaer
0107510ba8 feat: add support for typed events (#3822)
Syntax:

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

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

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

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

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

  socket.emit("hello", "again");
});
```

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

Note: we could also have reused the method here ([1]) to add types to
the EventEmitter, instead of creating a StrictEventEmitter class.

Related: https://github.com/socketio/socket.io/issues/3742

[1]: https://github.com/binier/tiny-typed-emitter
2021-03-10 00:18:13 +01:00
Damien Arrachequesne
b25495c069 feat: add some utility methods
This commit adds the following methods:

- fetchSockets: returns the matching socket instances

Syntax:

```js
// return all Socket instances
const sockets = await io.fetchSockets();

// return all Socket instances of the "admin" namespace in the "room1" room
const sockets = await io.of("/admin").in("room1").fetchSockets();
```

- socketsJoin: makes the matching socket instances join the specified rooms

Syntax:

```js
// make all Socket instances join the "room1" room
io.socketsJoin("room1");

// make all Socket instances of the "admin" namespace in the "room1" room join the "room2" room
io.of("/admin").in("room1").socketsJoin("room2");
```

- socketsLeave: makes the matching socket instances leave the specified rooms

Syntax:

```js
// make all Socket instances leave the "room1" room
io.socketsLeave("room1");

// make all Socket instances of the "admin" namespace in the "room1" room leave the "room2" room
io.of("/admin").in("room1").socketsLeave("room2");
```

- disconnectSockets: makes the matching socket instances disconnect

Syntax:

```js
// make all Socket instances disconnect
io.disconnectSockets();

// make all Socket instances of the "admin" namespace in the "room1" room disconnect
io.of("/admin").in("room1").disconnectSockets();
```

Those methods share the same semantics as broadcasting. They will also
work with multiple Socket.IO servers when using the Redis adapter. In
that case, the fetchSockets() method will return a list of RemoteSocket
instances, which expose a subset of the methods and attributes of the
Socket class (the "request" attribute cannot be mocked, for example).

Related:

- https://github.com/socketio/socket.io/issues/3042
- https://github.com/socketio/socket.io/issues/3418
- https://github.com/socketio/socket.io/issues/3570
- https://github.com/socketio/socket.io-redis/issues/283
2021-03-02 11:17:21 +01:00
Damien Arrachequesne
085d1de9df feat: allow to pass an array to io.to(...)
In some cases it is necessary to pass an array of rooms instead of a single room.

New syntax:

```
io.to(["room1", "room2"]).except(["room3"]).emit(...);

socket.to(["room1", "room2"]).except(["room3"]).emit(...);
```

Related: https://github.com/socketio/socket.io/issues/3048
2021-03-01 23:20:46 +01:00
Damien Arrachequesne
ac9e8ca6c7 fix: make io.to(...) immutable
Previously, broadcasting to a given room (by calling `io.to()`) would
mutate the io instance, which could lead to surprising behaviors, like:

```js
io.to("room1");
io.to("room2").emit(...); // also sent to room1

// or with async/await
io.to("room3").emit("details", await fetchDetails()); // random behavior: maybe in room3, maybe to all clients
```

Calling `io.to()` (or any other broadcast modifier) will now return an
immutable instance.

Related:

- https://github.com/socketio/socket.io/issues/3431
- https://github.com/socketio/socket.io/issues/3444
2021-03-01 23:17:08 +01:00
Sebastiaan Marynissen
7de2e87e88 feat: allow to exclude specific rooms when broadcasting (#3789)
New syntax:

```
io.except("room1").emit(...);
io.to("room1").except("room2").emit(...);

socket.broadcast.except("room1").emit(...);
socket.to("room1").except("room2").emit(...);
```

Related:

- https://github.com/socketio/socket.io/issues/3629
- https://github.com/socketio/socket.io/issues/3657
2021-03-01 09:30:58 +01:00
Damien Arrachequesne
494c64e44f fix: ignore packet received after disconnection
Related: https://github.com/socketio/socket.io/issues/3095
2021-02-26 00:58:30 +01:00
Damien Arrachequesne
6f4bd7f8e7 fix: properly parse the CONNECT packet in v2 compatibility mode
In Socket.IO v2, the Socket query option was appended to the namespace
in the CONNECT packet:

{
  type: 0,
  nsp: "/my-namespace?abc=123"
}

Note: the "query" option on the client-side (v2) will be found in the
"auth" attribute on the server-side:

```
// client-side
const socket = io("/nsp1", {
  query: {
    abc: 123
  }
});
socket.query = { abc: 456 };

// server-side
const io = require("socket.io")(httpServer, {
  allowEIO3: true // enable compatibility mode
});

io.of("/nsp1").on("connection", (socket) => {
  console.log(socket.handshake.auth); // { abc: 456 } (the Socket query)
  console.log(socket.handshake.query.abc); // 123 (the Manager query)
});

More information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/#Add-a-clear-distinction-between-the-Manager-query-option-and-the-Socket-query-option

Related: https://github.com/socketio/socket.io/issues/3791
2021-02-03 22:54:07 +01:00
Jakob Ackermann
161091dd4c feat: confirm a weak but matching ETag (#3485)
When handling compression at the proxy server level, the client receives a weak ETag.
Weak ETags are prefixed with `W/`, e.g. `W/"2.2.0"`.
Upon cache validation we should take care of these too.

Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
2021-01-15 01:04:55 +01:00
Damien Arrachequesne
9925746c8e feat: add support for Socket.IO v2 clients
In order to ease the migration to Socket.IO v3, the Socket.IO server
can now communicate with v2 clients.

```js
const io = require("socket.io")({
  allowEIO3: true
});
```

This feature is disabled by default.
2021-01-14 23:38:24 +01:00
Damien Arrachequesne
bf54327421 revert: restore the socket middleware functionality
This functionality was removed in [1] (included in 3.0.0), but
catch-all listeners and socket middleware features are complementary
rather than mutually exclusive.

The only difference with the previous implementation is that passing an
error to the `next` handler will create an error on the server-side,
and not on the client-side anymore.

```js
io.on("connection", (socket) => {

  socket.use(([ event, ...args ], next) => {
    next(new Error("stop"));
  });

  socket.on("error", (err) => {
    // to restore the previous behavior
    socket.emit("error", err);

    // or close the connection, depending on your use case
    socket.disconnect(true);
  });
});
```

This creates additional possibilities about custom error handlers, which
may be implemented in the future.

```js
// user-defined error handler
socket.use((err, [ event ], next) => {
  // either handle it
  socket.disconnect();

  // or forward the error to the default error handler
  next(err);
});

// default error handler
socket.use((err, _, next) => {
  socket.emit("error", err);
});
```

Related: https://github.com/socketio/socket.io/issues/3678

[1]: 5c73733985
2021-01-05 11:51:50 +01:00
Damien Arrachequesne
170b739f14 fix: properly clear timeout on connection failure
Related: https://github.com/socketio/socket.io/issues/3720
2021-01-05 11:51:08 +01:00
Damien Arrachequesne
a0a3481c64 test: fix random test failure 2021-01-04 10:27:57 +01:00
David Fong
d1bfe40dbb refactor: add more typing info and upgrade prettier (#3725)
This upgrades prettier to 2.2.0 to gain support for TypeScript's new
type-only-imports feature.
2020-12-11 12:19:20 +01:00
Damien Arrachequesne
9b6f9711da chore(release): 3.0.2
Diff: https://github.com/socketio/socket.io/compare/3.0.1...3.0.2
2020-11-18 00:53:51 +01:00
Damien Arrachequesne
43705d7a91 fix: merge Engine.IO options
So that the following example:

```js
const io = require('socket.io')({
  pingTimeout: 10000
});

io.listen(3000);
```

behaves the same as:

```js
const io = require('socket.io')(3000, {
  pingTimeout: 10000
});
```

Before this change, the options in the first example were not forwarded
to the Engine.IO constructor, which is not really intuitive.

The previous syntax (which is still valid):

```js
const io = require('socket.io')();

io.listen(3000, {
  pingTimeout: 10000
});
```
2020-11-17 23:33:18 +01:00
Damien Arrachequesne
54bf4a44e9 feat: emit an Error object upon middleware error
This commit restores the ability to send additional data in the
middleware functions, which was removed during the rewrite to
Typescript ([1]).

The only difference with the previous implementation is that the client
will now emit a "connect_error" (previously, "error") event with an
actual Error object, with both the message and an optional "data"
attribute.

```js
// server-side
io.use((socket, next) => {
  const err = new Error("not authorized");
  err.data = { content: "Please retry later" };
  next(err);
});

// client-side
socket.on("connect_error", err => {
  console.log(err.message); // not authorized
  console.log(err.data.content); // Please retry later
});
```

[1]: a5581a9789
2020-10-30 22:52:08 +01:00
Damien Arrachequesne
aa7574f884 feat: serve msgpack bundle
See 71d60480af
2020-10-27 23:17:12 +01:00
Damien Arrachequesne
cacad7029a chore(release): 3.0.0-rc3
Diff: https://github.com/socketio/socket.io/compare/3.0.0-rc2...3.0.0-rc3
2020-10-27 00:44:30 +01:00
Damien Arrachequesne
d16c035d25 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.
2020-10-26 00:29:11 +01:00
Damien Arrachequesne
5c73733985 feat: add support for catch-all listeners
Inspired from EventEmitter2 [1]

```js
io.on("connect", socket => {

  socket.onAny((event, ...args) => {});

  socket.prependAny((event, ...args) => {});

  socket.offAny(); // remove all listeners

  socket.offAny(listener);

  const listeners = socket.listenersAny();
});
```

Breaking change: the socket.use() method is removed

This method was introduced in [2] for the same feature (having a
catch-all listener), but there were two issues:

- the API is not very user-friendly, since the user has to know the structure of the packet argument
- it uses an ERROR packet, which is reserved for Namespace authentication issues (see [3])

[1]: https://github.com/EventEmitter2/EventEmitter2
[2]: https://github.com/socketio/socket.io/issues/434
[3]: https://github.com/socketio/socket.io-protocol
2020-10-25 23:44:01 +01:00
Damien Arrachequesne
129c6417bd feat: make Socket#join() and Socket#leave() synchronous
Depending on the adapter, Socket#join() may return:

- nothing (in-memory and Redis adapters)
- a promise (custom adapters)

Breaking change: Socket#join() and Socket#leave() do not accept a
callback argument anymore.

Before:

```js
socket.join("room1", () => {
 io.to("room1").emit("hello");
});
```

After:

```
socket.join("room1");
io.to("room1").emit("hello");
// or await socket.join("room1"); for custom adapters
```

Note: the need for an asynchronous method came from the Redis adapter,
which did override the Adapter#add() method in earlier versions, but
this is not the case anymore.

Reference:

- https://github.com/socketio/socket.io/blob/2.3.0/lib/socket.js#L236-L258
- https://github.com/socketio/socket.io-adapter/blob/1.1.2/index.js#L56-L65
- 05f926e13e

Related: https://github.com/socketio/socket.io/issues/3662
2020-10-22 01:50:13 +02:00
Damien Arrachequesne
0d74f290cd refactor(typings): export Socket class
In order to be able to cast it on the argument of the "connect" event:

```js
import { Socket } from "socket.io";

io.on("connect", (socket: Socket) => {
  // ...
});
```
2020-10-17 03:36:15 +02:00
Damien Arrachequesne
7603da71a5 feat: remove prod dependency to socket.io-client
The client bundles are included in the repository in order to remove
socket.io-client from the list of production dependencies and thus to
reduce the total number of dependencies when installing the server.

This means the release of the client and the server must now be in sync
(which is almost always the case actually).

The minified build is now served:

- /<path>/socket.io.js
- /<path>/socket.io.js.map
- /<path>/socket.io.min.js
- /<path>/socket.io.min.js.map

The content will now be compressed as well.
2020-10-17 02:11:15 +02:00
Damien Arrachequesne
91cd255ba7 fix: close clients with no namespace
After a given timeout, a client that did not join any namespace will be
closed in order to prevent malicious clients from using the server
resources.

The timeout defaults to 45 seconds, in order not to interfere with the
Engine.IO heartbeat mechanism (30 seconds).
2020-10-15 11:54:06 +02:00