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
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.
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
```
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
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, 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"
}
});
```