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
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
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
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
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
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.
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
The previous signature was not compatible with EventEmitter.emit(). The typescript compilation threw:
```
node_modules/socket.io/dist/namespace.d.ts(89,5): error TS2416: Property 'emit' in type 'Namespace' is not assignable to the same property in base type 'EventEmitter'.
Type '(ev: string, ...args: any[]) => Namespace' is not assignable to type '(event: string | symbol, ...args: any[]) => boolean'.
Type 'Namespace' is not assignable to type 'boolean'.
node_modules/socket.io/dist/socket.d.ts(84,5): error TS2416: Property 'emit' in type 'Socket' is not assignable to the same property in base type 'EventEmitter'.
Type '(ev: string, ...args: any[]) => this' is not assignable to type '(event: string | symbol, ...args: any[]) => boolean'.
Type 'this' is not assignable to type 'boolean'.
Type 'Socket' is not assignable to type 'boolean'.
```
Note: the emit calls cannot be chained anymore:
```js
socket.emit("hello").emit("world"); // will not work anymore
```
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
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
Both the "connected" and the "_sockets" maps were used to track the
Socket instances in the namespace.
Let's merge them into "sockets". It's a breaking change, but:
- the "sockets" object did already exist in Socket.IO v2 (and appears in some examples/tutorials)
- "sockets" makes more sense than "connected" in my opinion
- there was already a breaking change regarding the "connected" property (from object to Map)
Breaking change: the "connected" map is renamed to "sockets"
In previous versions, the Socket#id attribute was equal (or derived,
for a non-default namespace) to the underlying Engine.IO id, which is
used as a mean to authenticate the user throughout the Engine.IO
session and thus is sensitive information that should be kept secret.
The problem with reusing the Engine.IO id is that users could be
tempted to transmit this id to other clients, in order to implement
private messaging for example.
So we'll now generate a new random id for each new socket.
Please note that this id will now be different from the one found in
the query parameters of the HTTP requests.
In previous versions, a client was always connected to the default
namespace, even if it requested access to another namespace.
This meant that the middlewares registered for the default namespace
were triggered in any case, which is a surprising behavior for end
users.
This also meant that the query option of the Socket on the client-side
was not sent in the Socket.IO CONNECT packet for the default namespace:
```js
// default namespace: query sent in the query params
const socket = io({
query: {
abc: "def"
}
});
// another namespace: query sent in the query params + the CONNECT packet
const socket = io("/admin", {
query: {
abc: "def"
}
});
```
The client will now send a CONNECT packet in any case, and the query
option of the Socket is renamed to "auth", in order to make a clear
distinction with the query option of the Manager (included in the query
parameters of the HTTP requests).
```js
// server-side
io.use((socket, next) => {
// not triggered anymore
});
io.of("/admin").use((socket, next => {
// triggered
console.log(socket.handshake.query.abc); // "def"
console.log(socket.handshake.auth.abc); // "123"
});
// client-side
const socket = io("/admin", {
query: {
abc: "def"
},
auth: {
abc: "123"
}
});
```
The value stored in the adapter will now be used, instead of
duplicating it in the Socket class.
Breaking change: Socket#rooms is now a Set instead of an object
Closes https://github.com/socketio/socket.io/issues/2890