socketio / socket.io-admin-ui

Admin UI for Socket.IO
https://admin.socket.io
MIT License
346 stars 94 forks source link

Using Admin UI with Acknowledgements breaks with Could not encode error #54

Closed mzalewski closed 1 year ago

mzalewski commented 1 year ago

Libraries used: Socket IO socket.io-admin-ui socket.io-redis

When receiving an event with an acknowledgment callback, I'm seeing a 'Could not encode' error being thrown from within socket.io-redis.

It appears to be caused by the additional timestamp being added here:

  socket.onAny((...args) => {
      adminNamespace.emit("event_received", nsp.name, socket.id, args, new Date());
  });

This is then passed through the Socket IO Broadcast Adapter, and then to the Redis Adapter which serialises all the arguments. When it tries to serialise the callback function, it throws an error since the encode library used doesn't support functions.

Without Admin UI installed, this issue is avoided because the Broadcast Adapter correctly identifies the acknowledgement callback. It does this using this with the following code: const withAck = typeof data[data.length - 1] === "function";

When Admin UI is installed and enabled, the line above doesn't detect the acknowledgement as it is no longer the last argument.

mzalewski commented 1 year ago

Example test which demonstrates issue:

    describe(`RedisStore with Redis Client`, () => {
      let port: number, io: Server, redisClient: RedisClient;

      beforeEach((done) => {
        const httpServer = createServer();
        io = new Server(httpServer);

        const redisClient = createClient();
        const subClient = redisClient.duplicate();
        io.adapter(
          createAdapter({ pubClient: redisClient, subClient: subClient })
        );
        httpServer.listen(() => {
          port = (httpServer.address() as AddressInfo).port;
          done();
        });
      });

      afterEach(() => {
        io.close();
      });

      it("emits event when event received", async () => {
        instrument(io, {
          auth: false,
        });

        const adminSocket = ioc(`http://localhost:${port}/admin`);

        await waitFor(adminSocket, "connect");

        const clientSocket = ioc(`http://localhost:${port}`, {
          forceNew: true,
          transports: ["polling"],
        });

        io.use((socket, next) => {
          socket.data = socket.data || {};
          socket.data.count = 1;
          socket.data.array = [1];
          next();
        });

        const [socket] = await waitFor(adminSocket, "socket_connected");
        expect(socket.data).to.eql({ count: 1, array: [1] });
        clientSocket.emit("event-name", "test-arg", function () {});

        adminSocket.disconnect();
        clientSocket.disconnect();
      });
    });
darrachequesne commented 1 year ago

This should be fixed by https://github.com/socketio/socket.io-admin-ui/commit/6d58a755b4d692970d3f2066903a4d503f334f0a, included in release 0.5.1. Thanks for the detailed report :+1: