Socket.io: Roadmap for v3

Created on 18 May 2018  Β·  51Comments  Β·  Source: socketio/socket.io

This list is open to suggestions!

  • [ ] Improve the documentation

That is obviously the major pain point of the project.

  • [x] Reverse the direction of the ping-pong mechanism

Currently the client emits a ping and waits for a pong from the server, relying on setTimeout() to check whether the connection is still alive or not. But there are reports of throttled timers on the browser side, which may trigger random disconnections.

A solution would be that the ping is emitted from the server to the client, but that is a breaking change which will also break other client implementations..

Related: https://github.com/socketio/engine.io/issues/312

  • [x] Update the source code to ES6 in every project

  • [x] Migrate to webpack 4 (or another bundler, if need be)

  • [ ] Remove the sticky-session requirement when using multiple nodes

  • [ ] Default to websocket, and use XHR polling as fallback

Currently polling is established first, and then upgraded to websocket if possible.

  • [x] Make the generateId method asynchronous

This one is also a breaking change. Related: https://github.com/socketio/engine.io/pull/535

  • [ ] Update the Typescript bindings, if need be

  • [ ] Triage issues

No release date for now, as I'm not sure how much time I will be able to dedicate to those points in the following weeks. But any help is welcome!

Most helpful comment

[email protected] and [email protected] have been published :fire:

A few notes:

  • the public API does not change much (a few methods have been removed though, see here and here for reference)
  • the codebase has been migrated to TypeScript, but some typings are missing, notably on the client-side.
  • the breaking changes in Engine.IO v4 (listed here) have been included
  • the client bundle size has increased a bit, despite having less dependencies, I'll dig into this

Any feedback is welcome!

All 51 comments

Looks like a great plan.

I'm switching from nodejs to golang, and found there is no socket.io 2 implementation.
https://github.com/googollee/go-socket.io/issues/188

What do you think of golang server support for v3? I'll be happy to take part in this development.

@theromis are you talking about a golang client (which will connect to the Node.js server) or an actual golang server? (I think some work has been done here too: https://github.com/graarh/golang-socketio)

@darrachequesne Thank you for quick reply, yes I'm talking about server side golang-socketio.
Project mentioned by you is almost dead (last commit more than a year).
https://github.com/googollee/go-socket.io project looking for maintainer.
And non of them support socket.io v2.
I thought may be original greatest nodejs socketio developers could be interested in golang development too :)

Updated Java client would be nice too.

A socket.io package for server side swift like https://github.com/vapor would be very nice too since there is a swift client already.

Sounds like if server side socket.io will be written in some native language like C/C++ it can be used everywhere including nodejs, golang, java, rust and what so ever...
What's wrong with this approach?
My personal opinion socket.io is like de facto standard for modern world. Why not to make it ideal?

One thing that has always bothered me about socket.io which could be addressed in the next major release could be https://github.com/socketio/socket.io/issues/2124 and https://github.com/socketio/socket.io/issues/2343

There has been a historical inconsistency in the handling of namespaces and middlewares.
Also, the client emitting a reconnect event makes more sense in my opinion, after it connects to the namespace rather than it being just the Manager's reconnect event... For example, if there is some authenication error in a sub namespace middleware, the client will still get a reconnect event which I think is counter-intuitive.

From node version 10.5 there is a new Worker Threads, which is currently in experimental state, but when it released, I think it can be a better way for use socket.io instead of using redis server.

Pro:

  • less dependency (redis)
  • native support of multi threading
  • more control

Con:

  • only will work from that nodejs version what release the worker thread (10.x || 11). But this can be handled.

@Twois interesting! It would work for workers on the same host, but what about multiple hosts?

@perrin4869 good idea :+1:

I left a lot of ideas here: #3311 please read :) Will close that one and continue discussion here.

@MickL would you have some time to migrate the socket.io repository to Typescript, as an example?

I am afraid my knowledge of network technology and information security is limited. I rely on high level frameworks like Express or Socket.io.

As far as i see socket.io is already written OOP and documented well so it would be a no brainer to simply port it. My idea was to go as modular as possible so someone like me could contribute more easy and the client ist treeshakable. Also going more reactive could be an idea to make things simpler. Anyway im not enough into the code so both ideas could possibly make no sense in this project tho.

@darrachequesne its a good, question. I will thinking on it.

Require Nodejs 8 and above since earlier versions are at maintenance at best, see https://nodejs.org/en/about/releases/, at the very least nodejs 4 should be dropped(I noticed from .travis.yml that you support it)

Has 3.0 related work started in any branch/repo? Just checking if there's a place to stay informed on how it's going or contributing.

I would like to see improved support for error-handling on the server-side. This has been brought up in a few other issues, but more specifically, it would be great if:

  1. There were support for a custom error-handling middleware in addition to the default. This could look something like Express's (which can be appended as the last in the chain of app.use(), similar to socket.use(), middlewares): https://expressjs.com/en/guide/error-handling.html

The problem, at present, is that there's no way to add any kind of tracking or logging to an error once it has been nexted.

  1. Additionally, since the 'error' namespace is blacklisted, socket.emit('error', err) won't be heard by the client), I can't emit an error from somewhere on the server that doesn't have easy access to next() (i.e. from inside of socket.on('event')). This makes it difficult to create a uniform error-handling solution.

  2. next(err) should dispatch an Error object rather than a string. I understand that there is a workaround for this (https://github.com/socketio/socket.io/issues/3371) by adding a magical.data property to whatever object you call next() on. This is not documented, however, and is unintuitive.

Generally speaking, next(err) seems like an unnecessary blackbox of very limited error-handling capacity.

Thanks for your hard work, and keeping at it!

@darrachequesne Any update on v3 being a reality? I haven't seen any progress on this since 2018.

A short update: my current contract was recently updated, and I should be able to dedicate one day per week (1/5th) to the project maintenance. Thus work on v3 should be resumed shortly.

Really keen to help out on this project in one way or another.

If there is a way to identify how I can do this let me know - I just don't want to potentially fix things for them to not be merged! So would rather wait for some guidance, if help is wanted. Cheers

  • compression (gzip, zlib, defalte/inflate)
  • rework mechanism of sending binary data (2 packets now... ws text and ws binary)
  • send metadata on connection (use compression?)
  • coders/encoders as plugins include custom one (messagepack, protobuf, etc), we could use generics if code will be rewriten to typescript
  • cert pinning (server/client-side)

@blackkopcap

compression (gzip, zlib, defalte/inflate)

Compression is already supported (https://github.com/socketio/engine.io/blob/a05379b1e87d2e4cde40d3e30b134355883f4108/lib/transports/polling.js#L249-L298). The WebSocket compression (perMessageDeflate) will be disabled by default in v3 though, as it requires a lot of extra memory.

rework mechanism of sending binary data (2 packets now... ws text and ws binary)

Totally agree. Card added here

send metadata on connection (use compression?)

I'm not sure I understand. Could you please explain the use case?

coders/encoders as plugins include custom one (messagepack, protobuf, etc), we could use generics if code will be rewriten to typescript

You should already be able provide your own parser, like socket.io-msgpack-parser

cert pinning (server/client-side)

:+1:, added here.

@michaelegregious

  1. There were support for a custom error-handling middleware in addition to the default. This could look something like Express's (which can be appended as the last in the chain of app.use(), similar to socket.use(), middlewares): https://expressjs.com/en/guide/error-handling.html

That would be great indeed. Added here

  1. Additionally, since the 'error' namespace is blacklisted (socket.emit('error', err) won't be heard by the client), I can't emit an error from somewhere on the server that doesn't have easy access to next() (i.e. from inside of socket.on('event')). This makes it difficult to create a uniform error-handling solution.

Not sure what we could do. Do you have a suggestion?

  1. next(err) should dispatch an Error object rather than a string. I understand that there is a workaround for this (https://github.com/socketio/socket.io/issues/3371) by adding a magical.data property to whatever object you call next() on. This is not documented, however, and is unintuitive.

Totally agree :+1: , added here

We've already released Engine.IO v4, which will be included in Socket.IO v3. You can find the release notes here.

I've added what I could find in this thread to the project here, to give an overview of the progress. Do not hesitate to comment!

Here's what I had in mind for fixing https://github.com/socketio/socket.io/issues/2124

The client will now send a CONNECT packet when it wants to get access to the default namespace (/) (no more implicit connection).

Which would mean that the middlewares that are registered for the default namespace won't apply anymore to clients that wants to get access to a non-default namespace

// server-side
io.use((socket, next) => {
  // not triggered anymore
});

io.of('/admin').use((socket, next => {
  // triggered
});

// client-side
const socket = io('/admin');

On the client-side, I think we should also make a clear distinction between the query option for the Manager (which is included in the query params) and the query option for the Socket (which is sent in the CONNECT packet).

Right now:

const socket = io('/admin', {
  query: {
    abc: 'def'
  }
});

results in requests like GET /socket.io/?transport=polling&abc=def and also a CONNECT packet like { "type": 0, "nsp": "admin?abc=def"}

Besides, since there is no CONNECT packet for the default namespace, the query of the Socket is currently ignored in that case.

I propose to rename it to authQuery instead.

const socket = io({
  query: {
    abc: 'def'
  },
  authQuery: {
    abc: '123'
  }
});

would result in requests like GET /socket.io/?transport=polling&abc=def and a CONNECT packet like { "type": 0, "nsp": "/", "data": { abc: '123' } }

@perrin4869 would it make sense?

Edit: regarding tradeoffs, there will be more HTTP requests on startup...

Server > Client: Engine.IO handshake
Client > Server: Socket.IO connect
Server > Client: Socket.IO connect

While we currently have:

// without middleware on the default namespace (only 1 GET request)
Server > Client: Engine.IO handshake + Socket.IO connect
// with at least a middleware (2 GET requests)
Server > Client: Engine.IO handshake
Server > Client: Socket.IO connect

@darrachequesne Thank you for taking this into consideration! It's nice to get some closure here, finally able to simplify the code I had back then :D
It's been a long time so I don't remember exactly, but this sounds like the solution I was going for
Out of curiosity, what's the reason for the trade-offs?

Following the idea of how js shapes works: https://web.archive.org/web/20200201163000/https://mathiasbynens.be/notes/shapes-ics. To improve performance, wouldn't it be better to use arrays to store references for socket connections? More and more connections, countless ids and using objects to do this with each new connection, a new shape is generated and more memory is consumed, depending on the lifetime of a node and the number of connections it can have, this is probably will have some negative performance impact.

@perrin4869 the trade-offs come from the current implementation, as we first establish the Engine.IO connection and then we proceed with the Socket.IO connection. Which gives, if the HTTP long-polling transport is enabled (which is the default):

  • an HTTP GET request to retrieve the Engine.IO handshake
  • an HTTP POST request to send the Socket.IO CONNECT packet
  • an HTTP GET request to retrieve the Socket.IO CONNECT packet from the server
  • and then the upgrade to WebSocket

But it surely could be improved. For reference, the change was implemented here and here.

Besides, the reconnect event will not be emitted by the Socket instance (client-side) anymore (here).

@ferco0 we'll now use a Map instead of a plain object (https://github.com/socketio/socket.io/commit/84437dc2a682add44bb57d03f703cfc955607352). Does your comment still apply, in that case?

[email protected] and [email protected] have been published :fire:

A few notes:

  • the public API does not change much (a few methods have been removed though, see here and here for reference)
  • the codebase has been migrated to TypeScript, but some typings are missing, notably on the client-side.
  • the breaking changes in Engine.IO v4 (listed here) have been included
  • the client bundle size has increased a bit, despite having less dependencies, I'll dig into this

Any feedback is welcome!

@darrachequesne that's ok.

@darrachequesne Many npm open source projects try to decrease the amount of dependencies. It is somewhat of a trend within the community. The main reason is that many organisations require checking licenses of all used dependencies when they are used in projects, to not run into legal issues. Having fewer dependencies helps with this of course. As an example, Helmet (a great security package for the Express web server) went to a zero-dependency install in the latest release 4.0.

With that in mind, is there a plan to decrease the amount of dependencies in socket.io in release 3?

@thernstig that's a good question! We've indeed tried to reduce the number of dependencies in Socket.IO v3:

npm i [email protected] => 48 packages
npm i [email protected] => 33 packages

npm i [email protected] => 37 packages
npm i [email protected] => 20 packages

Here is the dependency tree for the server:

[email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”¬ [email protected]
β”‚ └── [email protected]
β”œβ”€β”¬ [email protected]
β”‚ β”œβ”€β”¬ [email protected]
β”‚ β”‚ β”œβ”€β”¬ [email protected]
β”‚ β”‚ β”‚ └── [email protected]
β”‚ β”‚ └── [email protected]
β”‚ β”œβ”€β”€ [email protected] deduped
β”‚ β”œβ”€β”€ [email protected]
β”‚ β”œβ”€β”¬ [email protected]
β”‚ β”‚ β”œβ”€β”€ [email protected]
β”‚ β”‚ └── [email protected]
β”‚ β”œβ”€β”€ [email protected] deduped
β”‚ β”œβ”€β”€ [email protected]
β”‚ └── [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”¬ [email protected]
β”‚ β”œβ”€β”€ @types/[email protected]
β”‚ β”œβ”€β”€ [email protected]
β”‚ β”œβ”€β”€ [email protected]
β”‚ β”œβ”€β”€ [email protected]
β”‚ β”œβ”€β”€ [email protected] deduped
β”‚ β”œβ”€β”¬ [email protected]
β”‚ β”‚ β”œβ”€β”€ [email protected]
β”‚ β”‚ β”œβ”€β”€ [email protected] deduped
β”‚ β”‚ β”œβ”€β”€ [email protected] deduped
β”‚ β”‚ β”œβ”€β”€ [email protected] deduped
β”‚ β”‚ β”œβ”€β”€ [email protected]
β”‚ β”‚ β”œβ”€β”€ [email protected]
β”‚ β”‚ β”œβ”€β”¬ [email protected]
β”‚ β”‚ β”‚ └─┬ [email protected]
β”‚ β”‚ β”‚   └── [email protected]
β”‚ β”‚ β”œβ”€β”€ [email protected]
β”‚ β”‚ β”œβ”€β”€ [email protected]
β”‚ β”‚ └── [email protected]
β”‚ β”œβ”€β”€ [email protected]
β”‚ └── [email protected] deduped
└─┬ [email protected]
  β”œβ”€β”€ [email protected] deduped
  └── [email protected] deduped

I think that we only need the dist/ folder of the socket.io-client package, so it might make sense to create a socket.io-client-dist without dependencies. What do you think?

Note: socket.[email protected] and socket.[email protected] have been published

I've added some examples with ES modules and TypeScript.

@darrachequesne I leave that entirely up to your discretion πŸ˜„

@michaelegregious regarding your comment, I know it's been quite a while but did you have specific API in mind?

I agree that the current API could be improved. It was implemented as a way to have a catch-all listener (discussed here), and not with error handling in mind.

We could also add a way to warn users about unhandled events (404 responses in Express, http://expressjs.com/en/starter/faq.html#how-do-i-handle-404-responses).

@darrachequesne great to hear your working on the next version of socket.io !! Thanks in advance for all your work on this library.

I am currently testing socket.[email protected] and socket.[email protected] and I am trying to use the new syntax for joining a room and emitting to sockets in that room.

I can not get the server to emit to the client socket using your example in the change log file.

socket.join("room1"); io.to("room1").emit("hello");

Attached are screenshots that show the socket js file being used on the client and the code for the node.js server

client side js file
Screen Shot 2020-10-28 at 12 04 43 AM

client side socket.on event
Screen Shot 2020-10-28 at 12 07 08 AM

server side join syntax inside the io.on("connect",
Screen Shot 2020-10-28 at 12 06 36 AM

I tried all the methods in the last sceenshot to get it to emit to the client and nothing works.

Please let me know if you need anymore info.

Thank You

@darrachequesne I downgraded to 3.0.0-rc2 and join the room using join with the callback method called "room1" which is triggered but none of the emit events get sent to the client

socket.join("room1", () => { console.log('old way to join'); io.to("room1").emit("hello", {}); io.in("room1").emit("hello", {}); });

I also uninstalled 3.0.0-rc2 and installed socket.io 2.3.0 for Server and Client and confirmed the above code works as expected. So I believe something is broke with the 3.0.0 rc versions

@szarkowicz Hmm... I can't reproduce the behavior you are describing: https://github.com/socketio/socket.io-fiddle/tree/issue/v3

The following code seems to work as expected:

socket.join("room1");

io.to("room1").emit('hello', 1, '2', {
  hello: 'you'
});

Do you get any error in the console? Do you get a connect event on the client-side?

@darrachequesne thanks for confirming. I have traced it back to using the redisAdapter causing the issue. Once I comment out the following code the rc3 version of socket.io works as expected.

let redisAdapter = require('socket.io-redis');
io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));

Have you tried using redis with the v3 of socket.io?

Thanks

@szarkowicz yes, you are absolutely right, the Redis adapter needs to be updated to be compliant with Socket.IO v3, it will be updated right after the 3.0.0 release (which should be soon now).

In any case, thanks a lot for the feedback :+1:

@darrachequesne okay sounds good!!! I will just not use if for now and continue to test without it.

Do you have a time when you think v3 will be released?

No worries - thanks again for getting back to me and for working on all enhancements for the next version!!

@darrachequesne Thanks so much for these updates! Sorry for my delayed response! We're not using socket.io in my current project (though we may integrate it in the future), but the workaround I used in my last project was to use socket.emit('err', err) instead of using the privileged namespace socket.emit('error', err).

Here's the note I put in our app's readme to explain the issue:

#### Error Handling Patterns
Most of the communication between client and server in the app happens via `socket.io`. For server-side error-handling, `socket.io` exposes [Express](https://expressjs.com/en/guide/error-handling.html)-like middleware via `socket.use(socket, next)`. Middlewares can be chained to separate concerns for authentication, logging, etc.

Unlike `Express`, however, `socket.io`'s native `next(err)` does not support addition of custom error handlers. Calling `next(err)` immediately breaks out of the middleware chain. Additionally, the privileged `error` namespace (emitted by `next(err)` and received by the client via `socket.on('error', err)`) is not available for use via the standard `socket.emit('error', err)` on the server. 

For these reasons, the server instead emits all errors to the client via `socket.emit('err', err)`. We use a `./CustomError` Class (inheriting from the native JS Error) to namespace our errors, and then rely on a switch to properly route errors on the client. (This also allows us to track and log outgoing errors via custom middleware on the server.)

So, in thinking thru this again, if you've already incorporated the ability to add a custom middleware to next(err), is it also possible to permit use of the socket.emit('error', err) namespace for manual use on the server?

I'm looking back at that code and all my event handlers look like this (if it's helpful to see):

    socket.on('quotes', async (params) => {
      try {
        await dispatchQuotes(socket, params);
      } catch (err) {
        socket.emit('err', err);
      }
    });

Oh! One more thing I'm remembering. Here's a work around I used to be able to monitor outgoing events for the purpose of logging/error-handling:

  ioServer.on('connection', (socket: Socket) => {
    const emitter = socket.emit;

    // We modify socket.io's native 'emit' method to monitor outgoing
    // events, since socket.use() (middleware) only tracks incoming events.
    socket.emit = (...args: any) => {
      emitter.apply(socket, args);
      outgoingErrorMiddleware(socket, args, app);
    };

etc.

Here's the way I used the outgoing middleware if it's helpful to see:

export function outgoingErrorMiddleware(socket: SocketIO$Socket, packet: Packet, app: App) {
  const [eventName, err] = packet;
  const { metrics } = app.locals;
  if (eventName === 'err') {
    logger.error(err);
    metrics.errors += 1;
  }
}

Thanks again for all your hard work! I'll try to respond more quickly next time.

  1. Additionally, since the 'error' namespace is blacklisted (socket.emit('error', err) won't be heard by the client), I can't emit an error from somewhere on the server that doesn't have easy access to next() (i.e. from inside of socket.on('event')). This makes it difficult to create a uniform error-handling solution.

Not sure what we could do. Do you have a suggestion?

@michaelegregious thanks for the examples you provided. (and no problem for the delay!)

Technically speaking, I'm not sure we can catch the errors thrown by an async listener:

socket.on("test", async () => {
  throw new Error("catch me?");
});

try {
  socket.emit("test");
} catch (e) {
  // won't catch the error
}

So I think it's better to let the user handle the errors by himself, either with the code you provided (socket.emit("err", err);), or with an acknowledgment function:

socket.on('quotes', async (params, cb) => {
  try {
    await dispatchQuotes(socket, params);
  } catch (err) {
    cb(err);
  }
});

What do you think?

What is currently implemented for v3:

  • "error" is renamed "connect_error", and in restricted to Namespace middleware error only:
// server
io.use((socket, next) => {
  next(new Error("unauthorized"));
});
// client
socket.on("connect_error", (err) => {
  // err instanceof Error === true
  // err.message === "unauthorized"
})

Which also means that "error" is not a reserved event name anymore.

  • socket.use() is removed and replaced by socket.onAny() (server and client)
// server
io.on("connect", (socket) => {
  socket.onAny((event, ...args) => {

 Β });
});

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

Does it sound good to you? Do you have any suggestion?

Hi everyone!

socket.[email protected] and socket.[email protected] are out. You can test them with npm i socket.io@beta socket.io-client@beta.

We plan to release 3.0.0 by next week. The migration guide has been created: https://socket.io/docs/migrating-from-2-x-to-3-0/ (some details are missing, like the reserved events on the client side)

As usual, feedback is welcome!

I have a doubt, i don't know if here is the right place for it, but it's possible to define the socket id on connection, for example, to use the client id come from db? Changing the "id" prop of the socket object on connection middleware makes the sincronization with the client?

@darrachequesne Thanks for the RC4 update.
With version 3.0.0.rc4 - 2 Questions:

  1. Whats the best way to get the number of or list of sockets (clients) in a room?
  2. Whats the best way to get a list of all the current rooms?

Going to be doing lots of testing over the next few days with rc4.

Thanks again for all your hard work.

@ferco0 it's not currently possible, the Socket#id is generated here. In your example (client ID from db), what happens if the same user opens a new tab? (since the Socket#id must be unique)

@szarkowicz

  1. Whats the best way to get the number of or list of sockets (clients) in a room?

io.allSockets() => get all socket ids (works with multiple servers) (well, once the Redis adapter is updated)
io.in("room1").allSockets() => get all socket ids in the room

(it was named io.clients() in Socket.IO v2)

  1. Whats the best way to get a list of all the current rooms?

There is currently no API for that, apart from digging into the adapter: io.of("/").adapter.rooms (here)

Besides, the Redis adapter has an allRooms method, but it was not reflected in the public API.

Okay really appreciate the info to get the clients and rooms for both with Redis and Non Redis. I will probably start testing with just socket.io v3 until the Redis adapter is updated.

Migrate to webpack 4 (or another bundler, if need be)

You could take a look at https://github.com/formium/tsdx as an alternative.
I wanted to push a rewrite of socket.io & engine.io in Typescript some months ago using tsdx, couldn't find the time to do it though :smile:

:rocket: Socket.IO v3.0.0 is out! :rocket: The release notes can be found here: https://socket.io/blog/socket-io-3-release/

Thanks to everyone involved in this thread for your ideas/feedback :heart:

@darrachequesne, that all makes good sense to me. I forgot about that issue re. async middlewares (in my current project we've created a wrap() function for every Express route to deal with that exact issue, so that no errors slip through the cracks).

Thanks for all the improvements!

Does it sound good to you? Do you have any suggestion?

async middlewares

@michaelegregious could you please open a feature request for that? Thanks!

I'll close this issue now. Discussion about the release: https://github.com/socketio/socket.io/discussions/3674

Was this page helpful?
0 / 5 - 0 ratings