"How to Implement User Join Functionality for User-created Rooms with a Defined User Limit using MediaSoup and WebRTC?"

"My application is designed to mimic functionality similar to Omegle, where it supports both peer-to-peer (P2P) connections and server-based connections using a Selective Forwarding Unit (SFU). I’m developing this application using a tech stack comprising JavaScript, Socket.io, jQuery, Mediasoup, WebRTC, Node.js, and Express.

Currently, I’m attempting to add a feature where users can create rooms, and other users can join these rooms. However, I’m facing difficulties in implementing this feature and need help. Specifically, I want to enforce a user limit per room, which can be defined by the user who creates the room. I would appreciate any advice on how to allow users to join these user-created rooms, respecting the set user limit."

This is my server.js require('dotenv').config();
const express = require("express");
const path = require("path");
const bodyparser = require("body-parser");
const http = require("http");
const cors = require('cors');
const dotenv = require("dotenv");
const connectDB = require("./Server/database/connection");
const { v4: uuidv4 } = require('uuid');

const app = express();
const rooms = new Map();
const maxUsersPerRoom = 10;
const socketToRoom = new Map();

dotenv.config({ path: "config.env" });
const PORT = process.env.PORT || 8080;

app.use(cors());
const corsOptions = {
  origin: 'https://dialoguedome.com',
  optionsSuccessStatus: 200
}
app.use(cors(corsOptions));

app.use(express.static('public'));
connectDB();
app.use(bodyparser.urlencoded({ extended: true }));
app.use(bodyparser.json());
app.set("view engine", "ejs");

app.use("/css", express.static(path.resolve(__dirname, "Assets/css")));
app.use("/img", express.static(path.resolve(__dirname, "Assets/img")));
app.use("/js", express.static(path.resolve(__dirname, "Assets/js")));

const routes = require("./Server/routes/router");
app.use("/", routes);

const server = http.createServer(app);
const io = require("socket.io")(server, {
  allowEIO3: true,
});

server.listen(PORT, '0.0.0.0', () => {
  console.log(`Server running on ${'0.0.0.0'}:${PORT}`);
});

const mediasoup = require('mediasoup');
const os = require('os');

const mediaCodecs = [
  {
    kind: 'audio',
    mimeType: 'audio/opus',
    clockRate: 48000,
    channels: 2
  },
  {
    kind: 'video',
    mimeType: 'video/VP8',
    clockRate: 90000,
    parameters:
    {
      'x-google-start-bitrate': 1000
    },
  },
];
let router = null

async function startServer() {
  const worker = await mediasoup.createWorker({
    logLevel: 'warn',
    rtcMinPort: 10000,
    rtcMaxPort: 10100,
  });

  router = await worker.createRouter({ mediaCodecs });
}

startServer().catch(console.error);

const ioSFU = io.of('/sfu');



  // Your 'connectTransport', 'produce' and 'consume' events here
  ioSFU.on('connection', async (socket) => {
  let roomId
    
  socket.on('createRoom', async () => {
    // get an array of all rooms that have less than maxUsersPerRoom
    const availableRooms = [...rooms.entries()].filter(([roomId, room]) => room.peers.size < maxUsersPerRoom);

    if (availableRooms.length > 0) {
      // if there are available rooms, join the first one
      roomId = availableRooms[0][0];
      socketToRoom.set(socket.id, roomId);
      socket.emit('roomJoined', { roomId });
    } else {
      // if there are no available rooms, create a new one
      roomId = uuidv4();
      rooms.set(roomId, {
        router: router,
        peers: new Map(),
      });
      socketToRoom.set(socket.id, roomId);
      socket.emit('roomCreated', { roomId });
    }
  });


  // ... other events here ...

  socket.on('connectTransport', async ({ roomId, transportId, dtlsParameters }) => {
    const room = rooms.get(roomId);
    const peer = room.peers.get(socket.id);

    await peer.transport.connect({ dtlsParameters });

    socket.emit('transportConnected');
  });

  socket.on('produce', async ({ roomId, kind, rtpParameters }) => {
    const room = rooms.get(roomId);
    const peer = room.peers.get(socket.id);
    const transport = peer.transport;
    const producer = await transport.produce({ kind, rtpParameters });

    peer.producers.set(producer.id, producer);

    producer.on('transportclose', () => {
      peer.producers.delete(producer.id);
    });

    socket.emit('produceResponse', { producerId: producer.id });
  });

  socket.on('consume', async ({ roomId, producerId, rtpCapabilities }) => {
    const room = rooms.get(roomId);
    const peer = room.peers.get(socket.id);
    const transport = peer.transport;

    if (!room.router.canConsume({ producerId, rtpCapabilities })) {
      socket.emit('error', 'cannot consume');
      return;
    }

    const consumer = await transport.consume({ producerId, rtpCapabilities });

    peer.consumers.set(consumer.id, consumer);

    consumer.on('transportclose', () => {
      peer.consumers.delete(consumer.id);
    });

    socket.emit('consumeResponse', {
      producerId,
      id: consumer.id,
      kind: consumer.kind,
      rtpParameters: consumer.rtpParameters
    });
  });

socket.on('disconnect', () => {
  // Get the room ID for this socket
  const roomId = socketToRoom.get(socket.id);

  if (roomId) {
    const room = rooms.get(roomId);
    if (room) {
      room.peers.delete(socket.id);
      if (room.peers.size === 0) {
        rooms.delete(roomId);
      }
      socket.broadcast.to(roomId).emit('user-disconnected', socket.id);
    }

    // Notify other users in the room that this user has left
    socket.broadcast.to(roomId).emit('user-disconnected', socket.id);
  
    // Remove the socket ID -> room ID mapping
    socketToRoom.delete(socket.id);
  }

  // This loop is the same as before
  for (const [roomId, room] of rooms.entries()) {
    if (room.peers.size === 0) {
      console.log(`Room ${roomId} deleted.`);
      rooms.delete(roomId);
    }
  }
});





  


//.. below is P2p part web rtc 
//.. below is P2p part web rtc 
//.. below is P2p part web rtc 
//.. below is P2p part web rtc 
//.. below is P2p part web rtc 
const ioP2P = io.of('/p2p');

var userConnection = [];

ioP2P.on("connection", (socket) => {
  console.log("Socket id is: ", socket.id);

  socket.on("userconnect", (data) => {
    console.log("Logged in username", data.displayName);
    userConnection.push({
      connectionId: socket.id,
      user_id: data.displayName,
    });

    var userCount = userConnection.length;
    console.log("UserCount", userCount);
  });

  socket.on("offerSentToRemote", (data) => {
    var offerReceiver = userConnection.find(
      (o) => o.user_id === data.remoteUser
    );
    if (offerReceiver) {
      console.log("OfferReceiver user is: ", offerReceiver.connectionId);
      socket.to(offerReceiver.connectionId).emit("ReceiveOffer", data);
    }
  });

  socket.on("answerSentToUser1", (data) => {
    var answerReceiver = userConnection.find(
      (o) => o.user_id === data.receiver
    );
    if (answerReceiver) {
      console.log("answerReceiver user is: ", answerReceiver.connectionId);
      socket.to(answerReceiver.connectionId).emit("ReceiveAnswer", data);
    }
  });

  socket.on("candidateSentToUser", (data) => {
    var candidateReceiver = userConnection.find(
      (o) => o.user_id === data.remoteUser
    );
    if (candidateReceiver) {
      console.log(
        "candidateReceiver user is: ",
        candidateReceiver.connectionId
      );
      socket.to(candidateReceiver.connectionId).emit("candidateReceiver", data);
    }
  });

  socket.on("disconnect", () => {
    console.log("User disconnected");
    userConnection = userConnection.filter((p) => p.connectionId !== socket.id);
    console.log(
      "Rest users username are: ",
      userConnection.map(function (user) {
        return user.user_id;
      })
    );
  });

  socket.on("remoteUserClosed", (data) => {
    var closedUser = userConnection.find((o) => o.user_id === data.remoteUser);
    if (closedUser) {
      console.log("closedUser user is: ", closedUser.connectionId);
      socket.to(closedUser.connectionId).emit("closedRemoteUser", data);
    }
  });
});
  });

this is my client side code or index.js let localStream;
let username;
let remoteUser;
let url = new URL(window.location.href);
// // username = url.searchParams.get("username");
// remoteUser = url.searchParams.get("remoteuser");
let peerConnection;
let remoteStream;
let sendChannel;
let receiveChannel;
var msgInput = document.querySelector("#msg-input");
var msgSendBtn = document.querySelector(".msg-send-button");
var chatTextArea = document.querySelector(".chat-text-area");

let awsServerUrl = "https://dialoguedome.com";
var omeID = localStorage.getItem("omeID");
if (omeID) {
  username = omeID;
  $.ajax({
    url: awsServerUrl + "/new-user-update/" + omeID + "",
    type: "PUT",
    success: function (response) {
      console.log(response);
    },
  });
} else {
  var postData = "Demo Data";
  $.ajax({
    type: "POST",
    url: awsServerUrl + "/api/users",
    data: postData,
    success: function (response) {
      console.log(response);
      localStorage.setItem("omeID", response);
      username = response;
      omeID = response;
    },
    error: function (error) {
      console.log(error);
    },
  });
}

let init = async () => {
  try {
    localStream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: true,
    });
    document.getElementById("user-1").srcObject = localStream;
    $.post(awsServerUrl + "/get-remote-users", { omeID: omeID })
      .done(function (data) {
        console.log("Remoteuser id from Init() /get-remote-users: ", data[0]._id);
        if (data[0]) {
          if (data[0]._id == remoteUser || data[0]._id == username) {
          } else {
            remoteUser = data[0]._id;
            createOffer(data[0]._id);
          }
        }
      })
      .fail(function (xhr, textStatus, errorThrown) {
        console.log(xhr.responseText);
      });
  } catch (error) {
    console.error("Error initializing video stream", error);
  }    
};
init();



let socket = io.connect('/p2p');

socket.on("connect", () => {
  if (socket.connected) {
    socket.emit("userconnect", {
      displayName: username,
    });
  }
});
let servers = {
  iceServers: [
    {
      urls: ["stun:stun1.1.google.com:19302", "stun:stun2.1.google.com:19302"],
    },
  ],
};

let createPeerConnection = async () => {
  peerConnection = new RTCPeerConnection(servers);

  remoteStream = new MediaStream();

  document.getElementById("user-2").srcObject = remoteStream;

  localStream.getTracks().forEach((track) => {
    peerConnection.addTrack(track, localStream);
  });
  peerConnection.ontrack = async (event) => {
    event.streams[0].getTracks().forEach((track) => {
      remoteStream.addTrack(track);
    });
  };

  remoteStream.oninactive = () => {
    remoteStream.getTracks().forEach((track) => {
      track.enabled = !track.enabled;
    });
    peerConnection.close();
  };

  peerConnection.onicecandidate = async (event) => {
    if (event.candidate) {
      socket.emit("candidateSentToUser", {
        username: username,
        remoteUser: remoteUser,
        iceCandidateData: event.candidate,
      });
    }
  };

  sendChannel = peerConnection.createDataChannel("sendDataChannel");
  sendChannel.onopen = () => {
    console.log("Data channel is now open and ready to use");
    onSendChannelStateChange();
  };

  peerConnection.ondatachannel = receiveChannelCallback;

  // sendChannel.onmessage=onSendChannelMessageCallBack;
};
function sendData() {
  const msgData = msgInput.value;
  chatTextArea.innerHTML +=
    "<div style='margin-top:2px; margin-bottom:2px;'><b>Me: </b>" +
    msgData +
    "</div>";
  if (sendChannel) {
    onSendChannelStateChange();
    sendChannel.send(msgData);
    msgInput.value = "";
  } else {
    receiveChannel.send(msgData);
    msgInput.value = "";
  }
}
function receiveChannelCallback(event) {
  console.log("Receive Channel Callback");
  receiveChannel = event.channel;
  receiveChannel.onmessage = onReceiveChannelMessageCallback;
  receiveChannel.onopen = onReceiveChannelStateChange;
  receiveChannel.onclose = onReceiveChannelStateChange;
}
function onReceiveChannelMessageCallback(event) {
  console.log("Received Message");
  chatTextArea.innerHTML +=
    "<div style='margin-top:2px; margin-bottom:2px;'><b>Stranger: </b>" +
    event.data +
    "</div>";
}
function onReceiveChannelStateChange() {
  const readystate = receiveChannel.readystate;
  console.log("Receive channel state is: " + readystate);
  if (readystate === "open") {
    console.log(
      "Data channel ready state is open - onReceiveChannelStateChange"
    );
  } else {
    console.log(
      "Data channel ready state is NOT open - onReceiveChannelStateChange"
    );
  }
}
function onSendChannelStateChange() {
  const readystate = sendChannel.readystate;
  console.log("Send channel state is: " + readystate);
  if (readystate === "open") {
    console.log("Data channel ready state is open - onSendChannelStateChange");
  } else {
    console.log(
      "Data channel ready state is NOT open - onSendChannelStateChange"
    );
  }
}
function fetchNextUser(remoteUser) {
  $.post(
    awsServerUrl + "/get-next-user",
    { omeID: omeID, remoteUser: remoteUser },
    function (data) {
      console.log("Next user is: ", data);
      if (data[0]) {
        if (data[0]._id == remoteUser || data[0]._id == username) {
        } else {
          remoteUser = data[0]._id;
          createOffer(data[0]._id);
        }
      }
    }
  );
}
let createOffer = async (remoteU) => {
  createPeerConnection();
  let offer = await peerConnection.createOffer();
  await peerConnection.setLocalDescription(offer);
  socket.emit("offerSentToRemote", {
    username: username,
    remoteUser: remoteU,
    offer: peerConnection.localDescription,
  });
  console.log("from Offer");
};

let createAnswer = async (data) => {
  remoteUser = data.username;

  createPeerConnection();
  await peerConnection.setRemoteDescription(data.offer);
  let answer = await peerConnection.createAnswer();
  await peerConnection.setLocalDescription(answer);
  socket.emit("answerSentToUser1", {
    answer: answer,
    sender: data.remoteUser,
    receiver: data.username,
  });
  console.log("from answer");
  document.querySelector(".next-chat").style.pointerEvents = "auto";
  $.ajax({
    url: "/update-on-engagement/" + username + "",
    type: "PUT",
    success: function (response) {},
  });
};

socket.on("ReceiveOffer", function (data) {
  createAnswer(data);
});

let addAnswer = async (data) => {
  if (!peerConnection.currentRemoteDescription) {
    peerConnection.setRemoteDescription(data.answer);
  }
  document.querySelector(".next-chat").style.pointerEvents = "auto";
  $.ajax({
    url: "/update-on-engagement/" + username + "",
    type: "PUT",
    success: function (response) {},
  });
};

socket.on("ReceiveAnswer", function (data) {
  addAnswer(data);
});
socket.on("closedRemoteUser", function (data) {
  // .................Newly Added..........................
  const remoteStream = peerConnection.getRemoteStreams()[0];
  remoteStream.getTracks().forEach((track) => track.stop());

  peerConnection.close();
  const remoteVid = document.getElementById("user-2");

  if (remoteVid.srcObject) {
    remoteVid.srcObject.getTracks().forEach((track) => track.stop());
    remoteVid.srcObject = null;
  }
  // .................Newly Added..........................
  $.ajax({
    url: "/update-on-next/" + username + "",
    type: "PUT",
    success: function (response) {
      fetchNextUser(remoteUser);
    },
  });
});

socket.on("candidateReceiver", function (data) {
  peerConnection.addIceCandidate(data.iceCandidateData);
  console.log("from candidateReceiver");
});

msgSendBtn.addEventListener("click", function (event) {
  sendData();
});

window.addEventListener("unload", function (event) {
  if (navigator.userAgent.indexOf("Chrome") != -1) {
    $.ajax({
      url: "/leaving-user-update/" + username + "",
      type: "PUT",
      success: function (response) {
        console.log(response);
      },
    });
    console.log("Leaving local user is: ", username);
    // ..........................Newly Edited
    $.ajax({
      url: "/update-on-otherUser-closing/" + remoteUser + "",
      type: "PUT",
      success: function (response) {
        console.log(response);
      },
    });
    console.log("Leaving remote user is: ", remoteUser);
    // ..........................Newly Edited
    console.log("This is Chrome");
  } else if (navigator.userAgent.indexOf("Firefox") != -1) {
    // The browser is Firefox
    $.ajax({
      url: "/leaving-user-update/" + username + "",
      type: "PUT",
      async: false,
      success: function (response) {
        console.log(response);
      },
    });
    console.log("Leaving local user is: ", username);
    // ..........................Newly Edited
    $.ajax({
      url: "/update-on-otherUser-closing/" + remoteUser + "",
      type: "PUT",
      async: false,
      success: function (response) {
        console.log(response);
      },
    });
    console.log("Leaving remote user is: ", remoteUser);
    // ..........................Newly Edited

    console.log("This is Firefox");
  } else {
    // The browser is not Chrome or Firefox
    console.log("This is not Chrome or Firefox");
  }
});

async function closeConnection() {
  // .................Newly Added..........................
  const remoteStream = peerConnection.getRemoteStreams()[0];
  remoteStream.getTracks().forEach((track) => track.stop());
  await peerConnection.close();
  const remoteVid = document.getElementById("user-2");

  if (remoteVid.srcObject) {
    remoteVid.srcObject.getTracks().forEach((track) => track.stop());
    remoteVid.srcObject = null;
  }
  // .................Newly Added..........................
  socket.emit("remoteUserClosed", {
    username: username,
    remoteUser: remoteUser,
  });
  $.ajax({
    url: "/update-on-next/" + username + "",
    type: "PUT",
    success: function (response) {
      fetchNextUser(remoteUser);
    },
  });

  console.log("From closeConnection");
}
$(document).on("click", ".next-chat", function () {
  document.querySelector(".chat-text-area").innerHTML = "";
  // if (
  //   peerConnection.connectionState === "connected" ||
  //   peerConnection.iceCandidateState === "connected"
  // ) {
  closeConnection();
  peerConnection.oniceconnectionstatechange = (event) => {
    if (
      peerConnection.iceConnectionState === "disconnected" ||
      peerConnection.iceConnectionState === "closed"
    ) {
      // Peer connection is closed
      console.log("Peer connection closed.");
    }
  };
  //   console.log("User closed");
  // } else {
  //   fetchNextUser(remoteUser);
  //   console.log("Moving to next user");
  // }
});
//ABOVE IS P2P BELOW IS SFU
//ABOVE IS P2P BELOW IS SFU
//ABOVE IS P2P BELOW IS SFU
//ABOVE IS P2P BELOW IS SFU

const ioSFU = io('/sfu');

// Use the Socket.IO 'connection' event to get a reference to the socket
ioSFU.on('connect', async () => {
  
  console.log(`Connected to SFU with ID: ${ioSFU.id}`);
  const sfuRoomId = username
  // Send a 'createRoom' event to the server
  ioSFU.emit('createRoom', { roomId: sfuRoomId }); // assuming username is unique
});

// Add your socket event handlers here:
ioSFU.on('transportCreated', ({ transportId, iceParameters, iceCandidates, dtlsParameters, currentUsers }) => {
    // Handle transport creation...
    
    // Assign the existing users' streams to the video elements
    currentUsers.forEach((userId, i) => {
        // This assumes that you have a way of getting a user's video stream. You will need to implement this function.
        const stream = getVideoStreamForUser(userId);
        const videoElement = document.getElementById(`user-${i+1}`);
        videoElement.srcObject = stream;
    });
});

ioSFU.on('user-connected', userId => {
    // Get the next available video element
    const videoElement = getAvailableVideoElement();
    if (videoElement) {
        // Assign the new user's stream to the video element
        const stream = getVideoStreamForUser(userId); // You will need to implement this function
        videoElement.srcObject = stream;
    }
});

ioSFU.on('user-disconnected', userId => {
    // Find the video element for this user and clear it
    const videoElement = getVideoElementForUser(userId); // You will need to implement this function
    if (videoElement) {
        videoElement.srcObject = null;
    }
});

// Helper function to get the next available video element
function getAvailableVideoElement() {
    for(let i = 1; i <= 10; i++) {
        const videoElement = document.getElementById(`user-${i}`);
        if (!videoElement.srcObject) {
            return videoElement;
        }
    }
    return null;
}


ioSFU.on('error', (error) => {
    console.error(`Error: ${error}`);
});

ioSFU.on('roomCreated', (data) => {
   console.log(`Room created with ID: ${data.roomId}`);
    console.log(`Room created with ID: ${data.roomId}`);
});

ioSFU.on('transportCreated', async (data) => {
    // Create the WebRTC transport using the parameters provided by the server
    const transport = new WebRTCTransport(data.transportId, data.iceParameters, data.iceCandidates, data.dtlsParameters);

    // Connect the transport
    await transport.connect();
    ioSFU.emit('connectTransport', { roomId: 'roomId', transportId: data.transportId, dtlsParameters: transport.localParameters.dtlsParameters });
});

ioSFU.on('transportConnected', () => {
    console.log('Transport connected');
    // Here you can start sending/receiving media
});

// Handle the 'consume' event
ioSFU.on('consumeResponse', (data) => {
    const { producerId, id, kind, rtpParameters } = data;
    // Here you need to use the data to consume the producer
    // It depends on how you are handling the media streams on the client side
});
// Create a WebRTC connection and setup event handlers
class WebRTCTransport {
  constructor(id, iceParameters, iceCandidates, dtlsParameters) {
    this.id = id;
    this.peerConnection = new RTCPeerConnection({
      iceServers: [
        {
          urls: ["stun:stun.l.google.com:19302"]
        }
      ],
      iceParameters,
      iceCandidates,
      dtlsParameters
    });

    // Listen for 'icecandidate' events
    this.peerConnection.onicecandidate = event => {
      if (event.candidate) {
        ioSFU.emit('candidate', { id: this.id, candidate: event.candidate });
      }
    };
  }

  // Add tracks from local stream to the connection
  addTracks(stream) {
    stream.getTracks().forEach(track => {
      this.peerConnection.addTrack(track, stream);
    });
  }

  // Create an offer and set local description
  async createOffer() {
    let offer = await this.peerConnection.createOffer();
    await this.peerConnection.setLocalDescription(offer);
    return this.peerConnection.localDescription;
  }

  // Set remote description with answer from remote peer
  async setRemoteAnswer(answer) {
    await this.peerConnection.setRemoteDescription(answer);
  }

  // Add a remote track to the local stream
  addRemoteTrack(track, stream) {
    stream.addTrack(track);
  }

  // Close the connection
  close() {
    this.peerConnection.close();
  }
}

// The 'consumeResponse' event handler
ioSFU.on('consumeResponse', (data) => {
  const { producerId, id, kind, rtpParameters } = data;

  // Assuming you have a 'remoteStream' MediaStream object that will receive the remote video
  let transport = new WebRTCTransport(id, rtpParameters.iceParameters, rtpParameters.iceCandidates, rtpParameters.dtlsParameters);
  transport.addTracks(remoteStream);
  remoteVideoElement.srcObject = remoteStream; // Assuming 'remoteVideoElement' is a video element for displaying the remote video

  ioSFU.emit('consume', { id: transport.id, producerId: producerId });
});
// ... existing code ...

// Click event handler for the 'Join Room' button
$(document).on('click', '.join-room-button', function() {
    // Get the room ID from the input field
    let roomId = $('#name').val();
    // Get the user ID. Replace this with actual user id.
    let userId = 'username'; 

    // Send a request to the server to join the room
    fetch('https://dialoguedome.com/join-room/' + sfuRoomId, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ userId: userId }),
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            // Joined the room successfully
            console.log('Joined the room');
        } else {
            // Failed to join the room
            console.log('Failed to join the room:', data.error);
        }
    })
    .catch((error) => {
        console.error('Error:', error);
    });
});

// Click event handler for the 'Leave Room' button
$(document).on('click', '.leave-room-button', function() {
    // Get the room ID from the input field
    let roomId = $('#name').val();
    // Get the user ID. Replace this with actual user id.
    let userId = 'username'; 

    // Send a request to the server to leave the room
    fetch('https://dialoguedome.com/leave-room/' + sfuRoomId, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ userId: userId }),
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            // Left the room successfully
            console.log('Left the room');
        } else {
            // Failed to leave the room
            console.log('Failed to leave the room:', data.error);
        }
    })
    .catch((error) => {
        console.error('Error:', error);
    });


  // Get the room ID from the server-side injected roomId


// Use the Socket.IO 'connection' event to get a reference to the socket
ioSFU.on('connect', async () => {
    console.log(`Connected to SFU with ID: ${ioSFU.id}`);
  
    // Send a 'joinRoom' event to the server
    ioSFU.emit('joinRoom', { roomId: roomId });
});

// ... rest of the code ...

// Click event handler for the 'Join Room' button
$(document).on('click', '.join-room-button', function() {
    // Get the user ID. Replace this with actual user id.
    let userId = 'username'; 

    // Send a request to the server to join the room
    ioSFU.emit('joinRoom', { roomId: sfuRoomId, userId: userId });
});

// Click event handler for the 'Leave Room' button
$(document).on('click', '.leave-room-button', function() {
    // Get the user ID. Replace this with actual user id.
    let userId = 'username'; 

    // Send a request to the server to leave the room
    ioSFU.emit('leaveRoom', { roomId: sfuRoomId, userId: userId });
});

 

// Emit join room event using roomId 
socket.emit('joinRoom', roomId);

  socket.on('userJoined', userId => {
  socket.broadcast.to(roomId).emit('userJoined', userId);
});
  io.on('connection', socket => {

  socket.on('joinRoom', roomId => {
    socket.join(roomId);
  });

});

});

// ... existing code ...

below is my ejs files `

<!DOCTYPE html>
<html lang="en">
    <head> //this is for the rooms users can see
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Rooms</title>
    </head>
    <body>
        <% rooms.forEach((room) => { %>
            <div>
                <h2><%= room.name %></h2>
                <p><%= room.description %></p>
                <p>User limit: <%= room.userLimit %></p>
                <button class="join-room-button" data-room-id="<%= room._id %>">Join Room</button>
            </div>
        <% }) %>

        <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
        <script>
            $(document).on('click', '.join-room-button', function() {
                // Get the room ID from the button data
                let roomId = $(this).attr('data-room-id');

                if (roomId.startsWith("sfu-")) {
                    // Redirect to the SFU implementation
                    window.location.href = "/video_chat/sfu/" + roomId;
                } else {
                    // Redirect to the P2P implementation
                    window.location.href = "/video_chat/p2p/" + roomId;
                }
            });
        </script>
    </body>
</html>


below should change dynamically based on who what is put in the form```

Room ID: <%= roomId %>

<div id="video-container"></div>

<button class="msg-send-button">Send Message</button> <!-- Here is your button -->

<script src="/socket.io/socket.io.js"></script> <!-- Include Socket.io -->

<script>
    document.addEventListener("DOMContentLoaded", function() {
      // your code
      var msgSendBtn = document.querySelector(".msg-send-button");
      msgSendBtn.addEventListener("click", function (event) {
        sendData();
      });

      // Generate video elements for 10 users
      const videoContainer = document.getElementById('video-container');
      for(let i = 1; i <= 10; i++) {
        const videoElement = document.createElement('video');
        videoElement.id = `user-${i}`;
        videoElement.autoplay = true;
        videoContainer.appendChild(videoElement);
      }
    });

    const roomId = '<%= roomId %>'; // Assign server-side roomId to a client-side JavaScript variable
</script>

<script src="/js/index.js"></script> <!-- Include your JavaScript code for WebRTC -->
``` below is form.ejs `` Create Room Room Name:
        <label for="description">Description:</label>
        <input type="text" id="description" name="description">

        <label for="userLimit">User Limit:</label>
        <input type="number" id="userLimit" name="userLimit">

        <input type="submit" value="Create Room">
    </form>
</body>
```

This is all up to you how you design this thing. But I can give my 2 cents on this.

User 1 created the room lets say room_1, added some users to it. This room will be shown to all users who are part of it. Or you can have a join room option where anyone can mention the room id and join that room if that user is part of the meeting you will make him join the room.

About how the call joining process is, you may have already seen it in mediasoup demo, so it is same.