Giter VIP home page Giter VIP logo

socket.io-adapter's Introduction

socket.io-adapter

Default socket.io in-memory adapter class.

Compatibility table:

Adapter version Socket.IO server version
1.x.x 1.x.x / 2.x.x
2.x.x 3.x.x

How to use

This module is not intended for end-user usage, but can be used as an interface to inherit from other adapters you might want to build.

As an example of an adapter that builds on top of this, please take a look at socket.io-redis.

License

MIT

socket.io-adapter's People

Contributors

billouboq avatar cha0s avatar dantehemerson avatar darrachequesne avatar dependabot[bot] avatar joway avatar kant avatar kapouer avatar keskival avatar kevin-roark avatar marcooliveira avatar mikaturunen avatar nevir avatar nkzawa avatar poldridge avatar rase- avatar rauchg avatar sam-github avatar sebamarynissen avatar sharat87 avatar tatellos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

socket.io-adapter's Issues

Add option to fetchSockets that returns results even if some nodes didn't respond

Hey there,

the current implementation of fetchSockets in ClusterAdapterWithHeartbeat only resolves if a response was received from all cluster nodes. In some situations this isn't needed and an optimistic response would suffice (i.e. return all responses of nodes that are alive).
Two options I can think of to do this would be to add an optional optimistic flag to the fetchSockets method or add it as parameter to the cluster config. The value would default to false for backwards compatibility. I can open a PR and implement it if you don't see any issues with this.

Adapter does not respect value of rooms

socket.io-adapter/index.js

Lines 220 to 223 in ae23c7e

Adapter.prototype.clientRooms = function(id, fn){
var rooms = this.sids[id];
if (fn) process.nextTick(fn.bind(null, null, rooms ? Object.keys(rooms) : null));
};

I comprehend that this project is a base to build from but the current structure of rooms and sids, to me, is cumbersome and prone to issues such as this. I'm not sure any performance gains warrant the current structure. If Maps are not an option, perhaps a single multi-dimensional array would suffice?

The fact that sids and rooms are basically replica datasets could lead to really hard to track down issues, awkward code, and inconsistencies. I also don't know whether sids and rooms are treated as readonly outside of the adapter. Is there anywhere that may do something like adapter.sids['some-id'] = true ?

The reason I ask is that I've condensed down rooms and sids into a single map with getters for sids and rooms to conform to the expected data structure.

export class RabbitSocketIOAdapter extends EventEmitter implements Adapter {
  public namespace: Namespace;
  private _rooms: Map<string, string[]>;

  get sids() {
    const result: SocketIdsDictionary = {};
    for (const [roomName, participants] of this._rooms.entries()) {
      participants.reduce((accumulator, sid) => {
        const current = accumulator[sid] || {};
        accumulator[sid] = { ...current, [roomName]: true };
        return accumulator;
      }, result);
    }
    return result;
  }

  get nsp(): Namespace {
    return this.namespace;
  }
  set nsp(value: Namespace) {
    this.namespace = value;
  }

  get rooms(): Rooms {
    const result: Rooms = {};
    for (const [roomName, participants] of this._rooms.entries()) {
      result[roomName] = participants.reduce(
        (accumulator, participant) => {
          accumulator.sockets[participant] = true;
          return accumulator;
        },
        { sockets: {}, length: participants.length } as Room
      );
    }
    return result;
  }

  constructor(namespace: Namespace) {
    super();
    this.namespace = namespace;

    this._rooms = new Map();
  }
//......
}

For what it's worth, I don't mean to sound critical. I greatly appreciate the project and the effort you all have put into it.

There seems to be a problem with the computeExceptSids method called in the apply method.

BroadcastOptions.except is of type Set<SocketId> but computeExceptSids requires Set<Room>:

const except = this.computeExceptSids(opts.except);

BroadcastOptions.except:

except?: Set<SocketId>;

computeExceptSids:

private computeExceptSids(exceptRooms?: Set<Room>) {

Allow excluding all sockets in a room

I have a use case where I need to send a message to all sockets except a certain user. In my application I let my sockets join a room equal to their user id, which looks like this:

io.on('connection', () => {
  let user = authenticate(socket);
  socket.join(user.id);
});

I noticed that it was possible to implement this behavior by extending the socket.io adapter as:

const BaseAdapter = require('socket.io-adapter');

class Adapter extends BaseAdapter {
  broadcast(packet, opts) {
    let {
      rooms,
      except = new Set(),
      ...rest
    } = opts;
    if (rooms.size === 0 && except.size > 0) {
      for (let id of [...except]) {
        if (!this.rooms.has(id)) continue;
        for (let sid of this.rooms.get(id)) {
          if (sid !== id) {
            except.add(sid);
          }
        }
      }
    }
    return super.broadcast(packet, {
      rooms,
      except,
      ...rest,
    });
  }
}

which works nicely. (Note that I have implemented some custom logic that allows me to use io.except('user').emit() by calling the namespace's adapter directly.

However, I'm in a position now where I need this functionality to work with socket.io-redis as well. This is a problem because I can't have socket.io-redis extend from my custom adapter. I filed a PR for this but it looks like this is not possible due to TypeScript limitations. Hence I thought it would be a nice functionality to have in the adapter itself. I will file a PR for this.

Types

Hello, I'm facing issues when trying to extend this base adapter for local memory use in a TS project.

Declaring a module,

declare module 'socket.io-adapter' {
  import * as Events from 'events';
  import * as SocketIO from 'socket.io';

  export default class Adapter extends Events.EventEmitter {
    constructor(nsp: SocketIO.Namespace);

    nsp: SocketIO.Namespace;

    rooms: SocketIO.Rooms

    sids: { [id: string]: { [room: string]: boolean } };

    encoder: any;

    public add(id: string, room: string, fn?: (err?: any) => void): void;
    public addAll(id: string, rooms: string[], fn?: (err?: any) => void): void;
    public del(id: string, room: string, fn?: (err?: any) => void): void;
    public delAll(id: string, rooms: string[], fn?: () => void): void;
    public broadcast(packet: any, opts: any): void;
    public clients(rooms: string[], fn: (this: this, error: null, sids: string[]) => void): void;
    public clientRooms(id: string, fn: (this: this, error: null, rooms: string[] | null) => void): void;
  }

  class Room {
    sockets: { [key: string]: boolean };
    length: number;
    add(id: string): void;
    del(id: string): void;
  }
}

extending it

import Adapter from 'socket.io-adapter';

export default class LocalAdapter extends Adapter {}

and trying to use it

import SocketIO from 'socket.io';
import LocalAdapter from './LocalAdapter';
...
io = SocketIO(server, { adapter: LocalAdapter });

results on

(alias) SocketIO(): SocketIO.Server (+3 overloads)
import SocketIO
Default Server constructor
No overload matches this call.
  Overload 1 of 4, '(srv: any, opts?: ServerOptions): Server', gave the following error.
    Type 'typeof LocalAdapter' is missing the following properties from type 'Adapter': nsp, rooms, sids, add, and 18 more.
  Overload 2 of 4, '(port: string | number, opts?: ServerOptions): Server', gave the following error.
    Argument of type 'Server' is not assignable to parameter of type 'string | number'.
      Type 'Server' is not assignable to type 'number'.ts(2769)
index.d.ts(314, 3): The expected type comes from property 'adapter' which is declared here on type 'ServerOptions'

Seems like class has no properties...

Adapter doesn't call middleware function

I am using socket.io-redis adapter for REDIS sub/pub feature and socket.io-emitter which broadcast data out of of socket.io process.
On connection new socket it register middleware like this:

socket.use((packet: Packet, next: (err?: any) => void) => {
                console.log('SOCKET MIDDLEWARE INVOKED');
                next();
            });

but on broadcasting data this middleware is not called...
I investigate that socket.io-redis lib is calling socket.io-adapter to broadcast data but this broadcast doesn't invoke middlewares like I expected. I appreciate any help :)

error TS1005: ',' expected V 2.5.3

Hi, good evening.
I'm using socket.io with an old version of TSC 4.3.5 and a new version of socket.io-adapter V 2.5.3. With the new version i had a problem when i run TSC command. Close this issue if you think TSC is too old. Thanks

`[Container] 2024/02/21 17:59:00.205028 Running command npm run build

[email protected] build
npx tsc

node_modules/socket.io-adapter/dist/cluster-adapter.d.ts(1,24): error TS1005: ',' expected.
node_modules/socket.io-adapter/dist/cluster-adapter.d.ts(1,45): error TS1005: ',' expected.
node_modules/socket.io-adapter/dist/cluster-adapter.d.ts(1,68): error TS1005: ',' expected.

[Container] 2024/02/21 17:59:21.459878 Command did not exit successfully npm run build exit status 2`

[Question] Could i use socket.io-adapter not for broadcast events between instances of the server?

I'm developing a gateway on Nodejs for getting data from different microservices and send to the frontend. I get some data from RabbitMQ and I need to send this data via socket.io. I don't need to broadcast and emit events to and from instances of the server. I just need to get data from RabbitMQ and send it via socket. Should I use socket.io-adapter for this or write some custom solution?
If I understood correctly, socket.io-amqp broadcast data between instances and have an old version of socket.io dependencies (i use the latest version of socket.io). So, this solution does not suit me.

Another question: could i use several adapters (redis-adapter and custom rabbitmq adapter)?

toobusy support for emit ?

when there are lots of connections, i see high CPU and "ping timeout" when doing broadcast.

should we support toobusy here ?

Autopruning of Empty Rooms No Longer Works

I believe this was introduced by commit 7793a33, which should be reverted.

!arr.length works for an array as the author expected, but !obj.length for an object always evaluates as true as shown by the snippet below.

this.rooms[room], i.e., io.sockets.adapter.rooms[room], is an object (see commit ce93e2a).

Thus a room was being autopruned the first time someone in that room disconnected. The next client to disconnect triggered an attempt to delete a room that had already been deleted, thereby crashing the app.

The error message was TypeError: Cannot convert null to object.

The error occurred on Line 85 of index.js.

// JavaScript Snippet

// Arrays
var arr = [ "a", "b", "c" ];
var arrResult = ( !arr.length ) ? true : false;
console.log( "Array: " + arrResult ); // Array: false

// Objects
var obj = {
a: "one",
b: "two",
c: "three"
};
var objResult = ( !obj.length ) ? true : false;
console.log( "Object: " + objResult ); // Object: true

socket.io-adapter block emit calls

In my application, I have used a socket.io and also an adapter for it. Now when I try to send a response from the server to the frontend with socket.to(id).emit("message", message), it throws this error

unknown

It starts working again after I comment on the code that configures the adapter.

io.adapter(require("socket.io-redis")({host: "localhost", port: 6379}))

But it's not a good solution, because I need to use the adapter for my application. How can I fix this problem in another way?

Promisify socketRooms

Let's make socketRooms return promise.

Sometimes it's usefull to know what rooms the specific socket is in. In single node, the Adapter.socketRooms(socketId) function works as intended.

However, with multiple socket.io servers connected through socket.io-redis adapter, it is impossible to know what rooms the socket is in, if the socket is from different node.

Making Adapter.socketRooms function to return promise will allow other adapters to implement logic of getting socket rooms even if the socket is not stored in current node.

Adapter.prototype.broadcast performance

Hello.

Adapter.prototype.broadcast always encodes the packet even if there are no rooms or connected clients.

In practice this causes namespaces with no connected clients but with high traffic to affect the latency for namespaces that have connected clients because the CPU is crammed, encoding data that will never reach a client.

I can help looking at this and submitting a merge request but first I want to hear from you if you see value in optimising this behaviour before I start.

What do you think?

serverSideEmit should be allowed

I think the default adapter should either loopback or ignore (if the event is not intended to be received locally) the emit, instead of throwing. This makes the default adapter incompatible with other real cluster mode adapters and cannot be used as a drop in replacement in tests.

public serverSideEmit(packet: any[]): void {
throw new Error(
"this adapter does not support the serverSideEmit() functionality"
);
}

Method to list socket ids in a room

I was writing something and came across a problem. I need a way to get the id of every socket in a room.

According to stackoverflow there used to be a way to do this. It should really be standard.

A list(room) method on the adapter would do nicely since that's the place where you normally work with rooms. I might submit a pull request before anyone comments on this, but it can always be updated later.

Why nextTick is used when add/dell to room?

I've seen this multiple times and I can't figure this out. Why when socket is added/deleted from room the callback is called with process.nextTick?

As there is no option to return an error, nor there is a real synchronous function - is there any reason why this is done? Sometimes it's pretty handy if synchronous.

How to get the list of sockets connected to a room?

I'm a bit confused about what the right way is to obtain a list of sockets connected to a room. In particular, are there any scenarios in which Object.values(namespace.connected) would be different from the list of client ID's returned by namespace.clients? It seems that the first method returns a map of client ID to socket objects, and the second accepts a callback function which is called with the list of client ID's. If I wanted to obtain the list of sockets objects connected to a room or simply the number of clients connected to the room, what is the best way to obtain this?

v. 2.5.4 timeout reached while waiting for fetchSockets response

Hi, i have @socket.io/redis-adapter at version ^8.2.1. Yesterday, after a pnpm install the peer dependencies socket.io-adapter was updated from 2.5.2 to 2.5.4:

image

after this update, this, line of coce:

io.of(`/${namespace}`).in(room).fetchSockets()

throw error

timeout reached while waiting for fetchSockets response

rooms and sids are protected

Refers to socketio/socket.io#3697

Is there any particular reason you made those protected? If it's to not allow modifications from the outside, shouldn't we provide getRooms and getSids to act as getters? I can work on a PR if this is the approved solution.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.