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
});
```
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
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
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) => {
// ...
});
```
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.
There are two issues with the typings:
- on the client-side, the Emitter class is not properly imported (hence the @ts-ignore)
- on the server-side, the Socket class is not exported (in order to cast it in the "connect" event)
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"
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).
The underlying Engine.IO server now supports a 'cors' option, which
will be forwarded to the cors module.
Breaking change: the 'origins' option is removed
Before:
```js
new Server(3000, {
origins: ["https://example.com"]
});
```
The 'origins' option was used in the allowRequest method, in order to
determine whether the request should pass or not. And the Engine.IO
server would implicitly add the necessary Access-Control-Allow-xxx
headers.
After:
```js
new Server(3000, {
cors: {
origin: "https://example.com",
methods: ["GET", "POST"],
allowedHeaders: ["content-type"]
}
});
```
The already existing 'allowRequest' option can be used for validation:
```js
new Server(3000, {
allowRequest: (req, callback) => {
callback(null, req.headers.referer.startsWith("https://example.com"));
}
});
```
Both CommonJS and ES6 import are now supported:
- with `{ "type": "commonjs" }` in the package.json file
```js
const io = require("socket.io")(8080);
// or
const { Server } = require("socket.io");
const io = new Server(8080);
```
- with `{ "type": "module" }`
```js
import { Server } from "socket.io";
const io = new Server(8080);
```
Related: https://nodejs.org/api/packages.html#packages_dual_commonjs_es_module_packages
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