Remote window in webrtc won’t display

I am trying to setup this web rtc video call function for a site i am building, the call will only be connected if the admin has in the function the same name as the client side’s name, in my server in the logs i get

Forwarding candidate for admin Received message: { type:
‘client_join’, name: ‘admin’ } Client trying to join: admin Client
successfully connected: admin Connection closed New connection
established

But the remoteVideo in each side won’t load, i dont know what more to try

Server.js:

const WebSocket = require("ws");

const wss = new WebSocket.Server({ port: 3000 });
let activeCalls = {};

wss.on("connection", (ws) => {
    console.log("New connection established");

    ws.on("message", (message) => {
        const data = JSON.parse(message);
        console.log("Received message:", data);

        if (data.type === "admin_join") {
            if (activeCalls[data.name]) {
                console.log(`Admin already in another call: ${data.name}`);
                ws.send(JSON.stringify({ type: "admin_busy" }));
                return;
            }
            activeCalls[data.name] = { admin: ws, client: null };
            console.log(`Admin joined the call: ${data.name}`);
        }

        if (data.type === "client_join") {
            console.log(`Client trying to join: ${data.name}`);

            if (!activeCalls[data.name]) {
                console.log(`No admin call for ${data.name}. Client rejected.`);
                ws.send(JSON.stringify({ type: "waiting", name: data.name }));
                return;
            }

            if (activeCalls[data.name].client) {
                console.log(`Another client is already in call with admin: ${data.name}`);
                ws.send(JSON.stringify({ type: "admin_busy" }));
                return;
            }

            activeCalls[data.name].client = ws;
            ws.send(JSON.stringify({ type: "client_joined", name: data.name }));
            activeCalls[data.name].admin.send(JSON.stringify({ type: "client_joined", name: data.name }));
            console.log(`Client successfully connected: ${data.name}`);
        }

        if (data.type === "offer" || data.type === "answer" || data.type === "candidate") {
            console.log(`Forwarding ${data.type} for ${data.name}`);

            if (activeCalls[data.name] && activeCalls[data.name].client && activeCalls[data.name].admin) {
                const target = data.type === "offer" ? activeCalls[data.name].client : activeCalls[data.name].admin;
                if (target && target.readyState === WebSocket.OPEN) {
                    target.send(JSON.stringify(data));
                }
            }
        }

        if (data.type === "leave") {
            console.log(`${data.name} left the call`);
            if (activeCalls[data.name]) {
                if (activeCalls[data.name].client) activeCalls[data.name].client.send(JSON.stringify({ type: "leave", name: data.name }));
                if (activeCalls[data.name].admin) activeCalls[data.name].admin.send(JSON.stringify({ type: "leave", name: data.name }));
                delete activeCalls[data.name];
            }
        }
    });

    ws.on("close", () => {
        console.log("Connection closed");
    });
});

console.log("WebSocket server running on ws://localhost:3000");

Client side:

  <section>
    <script>
        const name = "<?php echo $_SESSION['name']; ?>";
        console.log("Client is:", name);

        const videoCallSection = document.createElement("div");
        document.body.appendChild(videoCallSection);

        videoCallSection.innerHTML = `
            <h1>Client Video Call: ${name}</h1>
            <video id="localVideo" autoplay playsinline style="display: none;"></video>
            <video id="remoteVideo" autoplay playsinline style="display: none;"></video>
            <button id="joinCall" style="margin-top:10px; padding:10px; background:green; color:white; border:none; cursor:pointer;">Join Call</button>
            <button id="leaveCall" style="margin-top:10px; padding:10px; background:red; color:white; border:none; cursor:pointer; display:none;">Leave Call</button>
            <p id="statusMessage" style="color: red; font-weight: bold;"></p>
        `;

        const localVideo = document.getElementById("localVideo");
        const remoteVideo = document.getElementById("remoteVideo");
        const joinButton = document.getElementById("joinCall");
        const leaveButton = document.getElementById("leaveCall");
        const statusMessage = document.getElementById("statusMessage");

        const peerConnection = new RTCPeerConnection({
            iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
        });

        const socket = new WebSocket("ws://localhost:3000");
        let localStream;

        joinButton.addEventListener("click", async () => {
            joinButton.style.display = "none";
            leaveButton.style.display = "block";
            localVideo.style.display = "block";
            remoteVideo.style.display = "block";

            socket.send(JSON.stringify({ type: "client_join", name }));

            navigator.mediaDevices.getUserMedia({ video: true, audio: true })
                .then(stream => {
                    localStream = stream;
                    localVideo.srcObject = stream;
                    stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
                })
                .catch(error => console.error("Error accessing media devices.", error));
        });

        socket.onmessage = async (message) => {
            const data = JSON.parse(message.data);

            console.log("Received message:", data);

            if (data.name !== name) {
                statusMessage.innerText = "This call is not for you!";
                return;
            }

            if (data.type === "admin_busy") {
                statusMessage.innerText = "Admin is in another call.";
                return;
            }

            if (data.type === "waiting") {
                statusMessage.innerText = "Waiting for admin to start the call.";
                return;
            }

            if (data.type === "client_joined") {
                statusMessage.innerText = "Call connected!";
            }

            if (data.type === "offer") {
                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
                const answer = await peerConnection.createAnswer();
                await peerConnection.setLocalDescription(answer);
                socket.send(JSON.stringify({ type: "answer", answer, name }));
            } else if (data.type === "candidate") {
                await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
            } else if (data.type === "leave") {
                endCall();
            }
        };

        function endCall() {
            if (localStream) {
                localStream.getTracks().forEach(track => track.stop());
            }
            if (peerConnection) {
                peerConnection.close();
            }
            socket.send(JSON.stringify({ type: "leave", name }));
            socket.close();
            videoCallSection.innerHTML = "";
        }

        leaveButton.addEventListener("click", endCall);
    </script>
</section>

Admin side:

  function showvideo(name) {
    console.log("Admin wants to call:", name);

    const videoCallSection = document.getElementById("videocall");
    videoCallSection.style.display = "block";

    videoCallSection.innerHTML = `
        <h1>Admin Video Call: ${name}</h1>
        <video id="localVideo" autoplay playsinline></video>
        <video id="remoteVideo" autoplay playsinline></video>
        <button id="leaveCall" style="margin-top:10px; padding:10px; background:red; color:white; border:none; cursor:pointer;">Leave Call</button>
        <p id="statusMessage" style="color: red; font-weight: bold;"></p>
    `;

    const localVideo = document.getElementById("localVideo");
    const remoteVideo = document.getElementById("remoteVideo");
    const leaveButton = document.getElementById("leaveCall");
    const statusMessage = document.getElementById("statusMessage");

    const peerConnection = new RTCPeerConnection({
        iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
    });

    const socket = new WebSocket("ws://localhost:3000");
    let localStream;
    let inCall = false;
    let connectedClient = null;

    // Start local video only if user is valid
    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
        .then(stream => {
            localStream = stream;
            localVideo.srcObject = stream;
            stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
        })
        .catch(error => console.error("Error accessing media devices:", error));

    peerConnection.ontrack = event => {
        remoteVideo.srcObject = event.streams[0];
    };

    socket.onmessage = async (message) => {
        const data = JSON.parse(message.data);
        console.log("Received message:", data);

        if (data.type === "admin_busy") {
            statusMessage.innerText = "Admin is in another call.";
            return;
        }

        if (data.type === "waiting") {
            statusMessage.innerText = "Waiting for client to join...";
            return;
        }

        if (data.type === "client_joined") {
            if (data.name !== name) {
                statusMessage.innerText = "Client name mismatch!";
                return;
            }
            connectedClient = data.name;
            statusMessage.innerText = "Call connected!";
        }

        if (data.type === "offer") {
            if (data.name !== name) {
                statusMessage.innerText = "Wrong client trying to connect!";
                return;
            }
            await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
            const answer = await peerConnection.createAnswer();
            await peerConnection.setLocalDescription(answer);
            socket.send(JSON.stringify({ type: "answer", answer, name }));
        } else if (data.type === "answer") {
            await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
        } else if (data.type === "candidate") {
            if (data.name !== name) return;
            await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
        } else if (data.type === "leave") {
            endCall();
        }
    };

    peerConnection.onicecandidate = event => {
        if (event.candidate) {
            socket.send(JSON.stringify({ type: "candidate", candidate: event.candidate, name }));
        }
    };

    socket.onopen = () => {
        socket.send(JSON.stringify({ type: "admin_join", name }));
    };

    async function startCall() {
        if (inCall) {
            statusMessage.innerText = "Admin is already in another call.";
            return;
        }

        const offer = await peerConnection.createOffer();
        await peerConnection.setLocalDescription(offer);
        socket.send(JSON.stringify({ type: "offer", offer, name }));
        inCall = true;
    }

    function endCall() {
        if (localStream) localStream.getTracks().forEach(track => track.stop());
        if (peerConnection) peerConnection.close();
        socket.send(JSON.stringify({ type: "leave", name }));
        socket.close();
        videoCallSection.innerHTML = "";
        inCall = false;
    }

    leaveButton.addEventListener("click", endCall);
    setTimeout(startCall, 1000);
}