'Socket.io server TransportError

I'm facing to an issue with a Socket.io server running throw an Express.js server with Next.js.

The server send error without any client connected. But clients can connect from browser without any issue...

Here is the error on console where the server is running :

Socket error TransportError: xhr poll error
    at Polling.onError (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transport.js:46:37)
    at Request.<anonymous> (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:255:18)
    at Request.Emitter.emit (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/@socket.io/component-emitter/index.js:143:20)
    at Request.onError (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:356:14)
    at Timeout._onTimeout (/Users/cedricbapst/Projects/gynemanager-frontend-next/node_modules/engine.io-client/build/cjs/transports/polling.js:329:30)
    at listOnTimeout (node:internal/timers:559:17)
    at processTimers (node:internal/timers:502:7) {
  description: 0,
  context: XMLHttpRequest {
    UNSENT: 0,
    OPENED: 1,
    HEADERS_RECEIVED: 2,
    LOADING: 3,
    DONE: 4,
    readyState: 4,
    onreadystatechange: [Function (anonymous)],
    responseText: 'Error: getaddrinfo ENOTFOUND undefined\n' +
      '    at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26)',
    responseXML: '',
    status: 0,
    statusText: Error: getaddrinfo ENOTFOUND undefined
        at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:71:26) {
      errno: -3008,
      code: 'ENOTFOUND',
      syscall: 'getaddrinfo',
      hostname: 'undefined'
    },
    open: [Function (anonymous)],
    setDisableHeaderCheck: [Function (anonymous)],
    setRequestHeader: [Function (anonymous)],
    getResponseHeader: [Function (anonymous)],
    getAllResponseHeaders: [Function (anonymous)],
    getRequestHeader: [Function (anonymous)],
    send: [Function (anonymous)],
    handleError: [Function (anonymous)],
    abort: [Function (anonymous)],
    addEventListener: [Function (anonymous)],
    removeEventListener: [Function (anonymous)],
    dispatchEvent: [Function (anonymous)]
  },
  type: 'TransportError'
}
  socket.io-client:manager reconnect attempt error +1ms
  socket.io-client:manager will wait 5000ms before reconnect attempt +0ms

Just to be clear, this error is not on client side but on the server, clients from browser have a working connection even with this error from the server. The client does not get any error and works fine.

Here is my express.js server code :

import express, {Express} from 'express';
import * as http from 'http';
import next, {NextApiHandler} from 'next';
import passport from 'passport';
import session, {SessionOptions} from 'express-session';
import {Options} from "session-file-store";
const FileStore = require('session-file-store')(session);
import uid from 'uid-safe';
import bodyParser from 'body-parser';
import routes from './routes';
import SocketServer from "../socketio/SocketServer";
import socketMiddleware from "../socketio/SocketMiddleware";

const port: number = parseInt(process.env.PORT || '3000', 10);
const dev: boolean = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const nextHandler: NextApiHandler = nextApp.getRequestHandler();

nextApp.prepare().then(() => {
    const app: Express = express();
    const server: http.Server = http.createServer(app);

    // Socket io server
    const socketServer = new SocketServer(server);

    // Session
    const fileStoreOptions: Options = {
        path: './.next/session' // register session folder in .next folder
    };

    const sessionConfig: SessionOptions = {
        //secret: uid.sync(18),
        genid: (req) => {
            return 'app_' + uid.sync(18) // use UUIDs for session IDs
        },
        secret: 'SECRET',
        cookie: {
            maxAge: 43200 * 1000 // 12h
        },
        resave: false,
        saveUninitialized: true,
        store: new FileStore(fileStoreOptions)
    };

    app.use(bodyParser.json());
    app.use(session(sessionConfig));

    passport.serializeUser((user: any, done: (a: any, b: string) => void) => done(null, user));
    passport.deserializeUser((user: string, done: (a: any, b: any) => void) => done(null, user));

    app.use(passport.initialize());
    app.use(passport.session());

    // Add socketio to request to have available in express routes
    app.use(socketMiddleware(socketServer));

    app.use('/api/auth', routes.security); // anonymous
    app.use('/api/users', routes.users); // authenticated
    app.use('/api/patients', routes.patients); // authenticated
    app.use('/api/appointments', routes.appointments); // authenticated
    app.use('/api/consultations', routes.consultations); // authenticated
    app.use('/api/tasks', routes.tasks); // authenticated
    app.use('/api/documents', routes.documents); // authenticated
    app.use('/', routes.nextjs); // anonymous and authenticated

    // Handle all nextjs route
    app.all('*', (req: any, res: any) => nextHandler(req, res));

    server.listen(port, () => {
        console.log(`> Ready on http://localhost:${port}`);
    });
});

And then my SocketServer.ts

import {Server, Socket} from "socket.io";
import * as http from 'http';
import {ServerEvents, ClientEvents} from "./SocketEvents";

class SocketServer {
    private io: Server;

    public constructor(server: http.Server) {
        this.io = new Server(server);
        this.initServer();
    }

    private initServer = (): void => {
        this.io.on(ServerEvents.CONNECTION, (socket: Socket) => {
            this.initClient(socket);
        });
    }

    private initClient = (socket: Socket): void => {
        console.log('New client connected with id', socket.id);
        // Emit status message for new connected client
        socket.emit(ServerEvents.STATUS, 'Connected on Gynemanager SocketIO server');

        socket.on(ClientEvents.USER_CONNECTED, async (socketId, username) => {
            const sockets = await this.io.fetchSockets(); // Get all connected clients

            // Add username to socket data
            sockets.forEach(socket => {
                if (socket.id === socketId) socket.data.username = username; // add username to new connected client
            })
        })

        socket.on(ServerEvents.DISCONNECT, (reason: string) => {
            console.log('Client disconnected', reason);
        })
    }
}

export default SocketServer

And then the SocketMiddleware.ts

import {Response, NextFunction} from 'express';
import SocketServer from "./SocketServer";
import {CustomRequest} from "../types/express";

const socketMiddleware = (socketServer: SocketServer) => {
    return (req: CustomRequest, res: Response, next: NextFunction) => {
        req.socketServer = socketServer;
        next();
    }
}

export default socketMiddleware;

Any help will be appreciate.



Solution 1:[1]

Finally I figure out what was my problem.

I initialised the socket.io connection from _app.tsx for clients because this is the place where I use React context provider to bind my SocketClient.ts to the whole application.

To fix the issue I have to move the connection initialisation from _app.tsx to another page (for my case from the page redirected from login page), and check if connection is already init on the other page where socket is also needed to avoid multi connection and then everything works as expected.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Gulivert