RIZWAN ALI
RIZWAN ALI

Reputation: 175

Node.js Video Call Issue: Users Creating New Room Instead of Joining Existing One via Link

Hi there I hope you guys are doing well.

I am learning nodeJs and trying to create video call where user can join by link. I am also using the mongoDB for rooms creation and data handling.

The issue i am facing is when I create a video call and share the link with someone instead of joining the same room new video call starts as the user still remain one.

Here is my app.js code:

const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const crypto = require('crypto');
const secret = crypto.randomBytes(32).toString('hex');
const MONGODB_URI = 'mongodb+srv://**********:*******@******.majjl.mongodb.net/?retryWrites=true&w=majority&appName=******';
const http = require('http');
const csrf = require('csurf')
const {ExpressPeerServer} = require('peer')



// 1-Define controllers imports and Models
const errorController = require('./controllers/error');
const User = require ('./models/user')

// 2- Copre import like express Store
const app = express();
const store = new MongoDBStore({
  uri: MONGODB_URI,
  collection: 'sessions'
});
const csrfProtection = csrf()  
app.set('view engine', 'ejs');
app.set('views', 'views');

// 3- Routes Imports in same order
const authRoutes = require('./routes/auth');
const homeRoutes = require('./routes/home');
const flash = require('connect-flash/lib/flash');
app.use(bodyParser.urlencoded({ extended: false }));
// app.use(multer({storage:fileStorage, fileFilter:fileFilter}).single('imageUrl'));
app.use(express.static(path.join(__dirname, 'public')));
// app.use('/images',express.static(path.join(__dirname, 'images')));


app.use(session({
  secret: secret,
  resave: false,
  saveUninitialized: false,
  store: store,
  cookie : {
    maxAge: 1000 * 60 * 60 * 24 * 1
  }
}));
app.use(csrfProtection)
app.use(flash())
app.use((req,res,next)=>{
  res.locals.isAuthenticated=req.session.isLoggedIn;
  res.locals.csrfToken=req.csrfToken();
  next();
})
app.use((req,res,next)=>{
  if(!req.session.user)
  {
  return next();
  }
  User.findById(req.session.user)
  .then(user=>{
      if(!user)
      {
          return next();
      }
    req.user=user;
    next()
  })
  .catch(err=>{
    next(new Error(err))
  })
})



// Use authentication routes
app.use(authRoutes);
app.use(homeRoutes);
app.use(errorController.get404);
app.use(errorController.get500);
app.use((err, req, res, next) => {
    res.status(err.httpStatusCode || 500);
    res.render('500', {
        statusCode: err.httpStatusCode || 500,
        message: err.message || 'Something went wrong!'
    });
});



// Create HTTP server
const server = http.createServer(app);
const io = require('socket.io')(server)
// Integrate Peer server with the existing HTTP server
const peerServer = ExpressPeerServer(server, {
  debug: true
});

app.use('/peerjs', peerServer);

io.on('connection',(socket)=>{
  socket.on('join-room',(roomId,userId)=>{
    socket.join(roomId);
    setTimeout(()=>{
      socket.io(roomId).broadcast.emit('user-connected',userId)

    },1000)
    socket.on('disconnect',()=>{
      console.log("User Disconnected");
      io.emit("user-disconnected",userId)
    })
  })
 
})

mongoose
  .connect(MONGODB_URI)
  .then(result => {
    server.listen(3000, () => {
      console.log('Server is running on port 3000');
    });
  })
  .catch(err => console.log(err));

Here is my rooms model:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;


const roomSchema = new Schema({
roomId: {
    type:String,
    required:true,
},
adminName: {
    type:String,
    required:true,
},
adminEmail: {
    type:String,
    required:true,
},
isEnded:{
    type:Boolean,
    required:true,
},
inCall:{
    type:Boolean,
    required:true,
},
roomUsers:{
    users : [
        {
            userId:{
                type: Schema.Types.ObjectId,
                required: true,
                ref: 'User'
              },
              userName: {
                type: Schema.Types.String,
                required: true,
                ref: 'User'
              }
        }
    ]
},

});

module.exports=mongoose.model('Room',roomSchema);

Here is my rooms controller:

const User = require('../models/user')
const Room = require('../models/room')
const bcrypt  = require('bcryptjs')
const crypto = require('crypto');
const { validationResult } = require('express-validator')

exports.startRoomCall = async (req, res, next)=> {
    const uuid = crypto.randomUUID();
    try{
        const room =await Room.findOne({adminEmail:req.session.user.email});
        console.log(room)
        if(room)
        {
            const error = new Error("You cannot create more then one call at same time");
            error.httpStatusCode=500;
            return next(error); 
        }else{
            const room = new Room({
                roomId : uuid,
                adminName : req.session.user.fullName,
                adminEmail : req.session.user.email,
                isEnded: false,
                inCall : true,
                roomUsers : {
                    users : []
                }
            })

           await room.save();
           return res.render('room/room', {
                path: '/room',
                pageTitle: 'Call Room',
                roomId: uuid
            })
        }
    }
    catch(err){
        const error = new Error(err);
        error.httpStatusCode=500;
        return next(error); 
    
    }
}

exports.joinRoom = async (req, res, next) => {
    const id = req.params.roomId;
    try {
      const room = await Room.findOne({ roomId: id });
      if (!room) {
        const error = new Error("Call does not exist.");
        error.httpStatusCode = 404;
        return next(error);
      }
      // Check if the user is the admin of the room trying to join
      if (room.adminEmail === req.session.user.email) {
        const error = new Error("You cannot join your own call.");
        error.httpStatusCode = 400;
        return next(error);
      }
      // Check if the room has ended
      if (room.isEnded) {
        const error = new Error("This call has already ended.");
        error.httpStatusCode = 400;
        return next(error);
      }
      // Update room with the new user
      const existingUser = room.roomUsers.users.find(
        (user) => user.userId.toString() === req.session.user._id.toString()
      );
      if (!existingUser) {
        room.roomUsers.users.push({
          userId: req.session.user._id,
          userName: req.session.user.fullName,
        });
        await room.save();
      }
      // Render the room view with updated room data
      res.render('room/room', {
        path: '/room',
        pageTitle: 'Call Room',
        roomId: id,
      });
    } catch (err) {
      const error = new Error(err);
      error.httpStatusCode = 500;
      return next(error);
    }
  };

Here are my room routes:

const express = require('express');
const router = express.Router();
const homeController = require('../controllers/home')
const isAuth = require('../middleware/is-auth')
const { check, body } = require('express-validator')




router.get('/',isAuth, homeController.getHome),

router.post('/logout',isAuth,homeController.postLogout),

router.get('/update-password',isAuth,homeController.getUpdatePassword),

 router.post('/update-password',isAuth,[ body('newPassword')
    .isStrongPassword()
    .withMessage('Password should be minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1.').trim(),
    body('confirmPassword')
    .custom((value, { req }) => {
    if (value !== req.body.newPassword) {
        throw new Error('Confirm password did not match');
    }
    return true;
    }),],homeController.postUpdatePassword)


router.post('/room',isAuth, homeController.startRoomCall),

router.get('/room/:roomId',isAuth, homeController.joinRoom),



module.exports = router;

Here is my script.js:

const socket = io("/")
let myVideoStream;
const videoGrid = document.getElementById('video-grid');
const myVideo = document.createElement('video')

var peer = new Peer(undefined,{
    path: "/peerjs",
    host: "/",
    port: "3000"
})

myVideo.muted=true;
navigator.mediaDevices.getUserMedia({
    audio:true,
    video: true,
}).then(stream=>{
    myVideoStream=stream;
    addVideoStream(myVideo,stream)
    peer.on("call",(call)=>{
        call.answer(stream)
        const video = document.createElement("video");
        call.on("stream",(userVideoStream)=>{
            addVideoStream(video,userVideoStream)
        })
    })
    socket.on("user-connected",(userId)=>{
        connectToNewUser(userId,stream)
    })
});

const connectToNewUser = (userId,stream)=>{
    const call = peer.call(userId,stream);
    const video = document.createElement("video")
    call.on("stream",(userVideoStream)=>{
        addVideoStream(video,userVideoStream)
    })
}

peer.on("open",(id)=>{
    socket.emit("join-room",ROOM_ID,id)
})

const addVideoStream = (video,stream)=>{
    video.srcObject=stream;
    video.addEventListener('loadedmetadata',()=>{
        video.play(),
        videoGrid.append(video)
    })
}


const inviteButton = document.querySelector("#inviteButton")
const muteButton = document.querySelector("#muteButton")
const stopVideo = document.querySelector("#stopVideo")
const disconnectBtn = document.querySelector("#disconnect");

muteButton.addEventListener("click",() => {
    const enabled = myVideoStream.getAudioTracks()[0].enabled;
    if(enabled){
      myVideoStream.getAudioTracks()[0].enabled = false;
      html = `<i class="fas fa-microphone-slash"></i>`;
      muteButton.classList.toggle("background_red");
      muteButton.innerHTML = html;
    }
    else{
      myVideoStream.getAudioTracks()[0].enabled = true;
      html = `<i class="fas fa-microphone"></i>`;
      muteButton.classList.toggle("background_red");
      muteButton.innerHTML = html;
    }
  })
  
  stopVideo.addEventListener("click",() => {
    const enabled = myVideoStream.getVideoTracks()[0].enabled;
    if(enabled){
      myVideoStream.getVideoTracks()[0].enabled = false;
      html = `<i class="fas fa-video-slash"></i>`;
      stopVideo.classList.toggle("background_red");
      stopVideo.innerHTML = html;
    }
    else{
      myVideoStream.getVideoTracks()[0].enabled = true;
      html = `<i class="fas fa-video"></i>`;
      stopVideo.classList.toggle("background_red");
      stopVideo.innerHTML = html;
    }
  })
 
  
  disconnectBtn.addEventListener("click",() => {
    peer.destroy();
    const myVideoElement = document.querySelector("video");
    if(myVideoElement){
      myVideoElement.remove();
    }
    socket.emit("disconnect");
    window.location.href = "https://localhost:3000/";
  })

  inviteButton.addEventListener("click", async() => {
    prompt("Copy this link and send it to people you want to have video call with",
    window.location.href+"/"+ROOM_ID
    );
  })

Here is room.ejs for view:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>videoChatApp</title>
    <link rel="stylesheet" href="/css/room.css" />
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://kit.fontawesome.com/c939d0e917.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/peerjs.min.js"></script>
    <script>
        const ROOM_ID = "<%= roomId %>";
    </script>
  </head>
  <body>
    <div class="header">
      <div class="logo">
        <div class="header_back">
          <i class="fas fa-angle-left"></i>
        </div>
        <h3>Video Chat</h2>
      </div>
    </div>  
    <div class="main">  
    <div class="main_left">
      <div class="videos_group">
        <div id="video-grid">

        </div>
      </div>
      <div class="options">
        <div class="options_left">
          <div id="stopVideo" class="options_button">
            <i class="fa fa-video-camera"></i>
          </div>
          <div id="muteButton" class="options_button">
            <i class="fa fa-microphone"></i>
          </div>
          <div id="disconnect" class="options_button background_red">
            <i class="fa fa-phone"></i>
          </div>
        </div>
        <div class="options_right">
          <div id="inviteButton" class="options_button">
            <i class="fas fa-user-plus"></i>
          </div>
          
        </div>
      </div>
    </div>
  </div>
  </body>
  <script src="/js/script.js"></script>
</html>

I KNOW ITS BEEN A LONG CODE POST BUT I TRIED TO PROVIDE ALL THE RELEVANT DETAILS WHICH CAN HELP TO FIGURE OUT THE ISSUE.

Any help will be appreciated please.

Upvotes: 0

Views: 34

Answers (0)

Related Questions