Skip to content

Uncaught TypeError: Illegal invocation #5308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
apudiu opened this issue Feb 27, 2025 · 4 comments
Open

Uncaught TypeError: Illegal invocation #5308

apudiu opened this issue Feb 27, 2025 · 4 comments
Labels
to triage Waiting to be triaged by a member of the team

Comments

@apudiu
Copy link

apudiu commented Feb 27, 2025

Describe the bug
It produces Uncaught TypeError: Illegal invocation error when calling io()

context: I'm working in a react project where valtio is used for state manamement.

To Reproduce

Socket.IO server version: 4.8.1

Server

Using Nest.js for server. The server is working fine.

Socket.IO client version: 4.8.1

Client

// (next.js projec) the valtio store
'use client';
import {proxy, ref} from "valtio";
import {devtools, useProxy} from "valtio/utils";
import {IS_PRODUCTION_MODE, isServer} from "@/util/constants";
import {io, Socket} from "socket.io-client";
import {endpoints} from "@/util/urls";

type MsgModule<Actions = any> = {
  socket: Socket;
  connecting: boolean;
  actions: Actions
}

type Data = {
  message: MsgModule;
}
export const socketStore = proxy<Data>({
  message: {
    socket: io(endpoints.api.baseUrl + '/message', {
      auth: {
        token: ''
      },
      autoConnect: false
    }),
    connecting: false,
    actions: ref({
      abc: () => {}
    }),
  },
});

// socketStore.message.

export const useStoreSockMsg = () => useProxy(socketStore.message);

devtools(socketStore, {
  name: 'socket',
  enabled: !isServer && !IS_PRODUCTION_MODE,
});

export const socketActions = {
  connectMessageSocket(accessToken: string) {
    if (socketStore.message.socket.connected || socketStore.message.connecting) return;

    console.log('proceed to socket connection with token', accessToken)

    socketStore.message.socket = io(
      endpoints.api.baseUrl + '/message',
      {
        auth: {
          token: accessToken,
        },
        autoConnect: false,
      }
    );

    // Listen for connection events
    socketStore.message.socket.on('connect', () => {
      console.log('socket is connected')
    });

    socketStore.message.socket.on('disconnect', () => {
      console.log('socket is disconnected')
    });

    // Handle auth errors (e.g., expired token)
    socketStore.message.socket.on('connect_error', async (err) => {
      console.log('socket connection err: ', err.message)

      // if (err.message === 'Unauthorized') {
      //   retry connection with updated token ...
      // }
    });


    socketStore.message.socket.connect();
  },

  // Disconnect and cleanup
  disconnectSocket: () => {
    if (socketStore.message.socket) {
      socketStore.message.socket.disconnect();
    }
  },

};
// the component
function MessageSocket() {
  const userStore = useStoreUser();
  const sockStore = useStoreSockMsg();

  useEffect(() => {
    if (sockStore.socket.connected || !userStore.at) return; // at is access token
    socketActions.connectMessageSocket(userStore.at); // calling this producting the err: Uncaught TypeError: Illegal invocation

    return () => socketActions.disconnectSocket();
  }, [userStore.at, sockStore.socket.connected]);

  useEffect(() => {
    console.log('sock connected?', sockStore.socket.connected)
  }, [sockStore.socket.connected]);

  // ...
}

Expected behavior
Obviously expectation was to successfully connect, The official docs has a guide on how to use with react but that doesn't covers how to use with authentication when the token becomes available later.

Platform:

  • Device: A Desktop PC
  • OS: Ubuntu 24.04
@apudiu apudiu added the to triage Waiting to be triaged by a member of the team label Feb 27, 2025
@darrachequesne
Copy link
Member

Hi!

I don't think this is necessary to recreate a new client, you can simply update the auth object:

export const socketActions = {
  connectMessageSocket(accessToken: string) {
    if (socketStore.message.socket.connected || socketStore.message.connecting) return;

    console.log('proceed to socket connection with token', accessToken)

    socketStore.message.socket.auth.token = accessToken;

    // [...]

    socketStore.message.socket.connect();
  },

  // [...]
};

@apuatcfbd
Copy link

@darrachequesne Thank you for the suggestion. Unfortunately, that still produces Uncaught TypeError: Illegal invocation in my project.

@apudiu
Copy link
Author

apudiu commented Mar 10, 2025

Actually I tested that method before opening the issue here. Unfortunately that didn't worked. I'm really stuck on the middle of something due to this problem @darrachequesne.

@apudiu
Copy link
Author

apudiu commented Mar 10, 2025

In more investigation i've found that I can't store the socket (result of io()) to anywhere else otherwise this error is thrown. In my case I'm using Valtio

Following is what I'm doing.

// socketStore.ts (the store)
'use client';
import {proxy} from "valtio";
import {devtools, subscribeKey} from "valtio/utils";
import {Socket} from "socket.io-client";

type MsgModule = {
  socket: null | Socket;
  connecting: boolean;
}

type Data = {
  message: MsgModule;
}
export const socketStore = proxy<Data>({
  message: {
    socket: null,
    connecting: false,
  },
});


devtools(socketStore, {
  name: 'socket',
  enabled: true,
});

export const socketActions = {
  messageSocketSet(socket: Socket) { // just set the socket
    socketStore.message.socket = socket;
  },
};

subscribeKey(socketStore.message, 'socket', (sockConnected) => { // detect socket change
  console.log('---------> socket updated', sockConnected)
})

The Conponent

// ws.tsx
'use client'
import {io} from 'socket.io-client';
import {useEffect} from "react";
import {socketActions} from "@/store/socketStore";

const url = 'http://localhost:3000/message'
const ws = io(url, {
    auth: {
        token: ''
    },
    autoConnect: false
});

export default function WS() {

    const connectHandler = () => {
        console.log('connected')
        socketActions.messageSocketSet(ws); // when connected, store the socket to use somewhere else [but this is what causing the error]
    };
    const connectErrHandler = (reason: unknown) => {
        console.log('connect Err', reason);
    };
    const disconnectHandler = () => {
        console.log('connect Err')
    };

    useEffect(() => {
        ws.on('connect', connectHandler);
        ws.on('connect_error', connectErrHandler);
        ws.on('disconnect', disconnectHandler);

        ws.auth = {token: 'the token string, assuming the token gets available later so we need to connect when it is available'};

        ws.connect() // connect when we've the token 

        return () => {
            ws.off('connect', connectHandler);
            ws.off('connect_error', connectErrHandler);
            ws.off('disconnect', disconnectHandler);
        }

    }, []);

    return (
        <h1>WS Comp</h1>
    )
}

To figure it out I've setup a fresh Next.js 15 project.

Structure

./
├── eslint.config.mjs
├── next.config.ts
├── next-env.d.ts
├── package.json
├── package-lock.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── next.svg
│   ├── vercel.svg
│   └── window.svg
├── README.md
├── src
│   ├── app
│   │   ├── favicon.ico
│   │   ├── globals.css
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── components
│   │   └── ws.tsx  // imported in page.tsx
│   └── store
│       └── socketStore.ts
└── tsconfig.json

6 directories, 19 files

package.json

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "15.2.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "socket.io-client": "^4.8.1",
    "valtio": "^2.1.3"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.2.1",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
to triage Waiting to be triaged by a member of the team
Projects
None yet
Development

No branches or pull requests

3 participants