From 1dbabaff2739dc397f2977f740b10f2bcf01f6c8 Mon Sep 17 00:00:00 2001 From: knoxiboy Date: Fri, 12 Jun 2026 22:56:17 +0530 Subject: [PATCH 1/2] feat: Peer-to-Peer Voice and Video Calling via WebRTC (#574) From 1204baab04423fb80d2632253b46af3209478f59 Mon Sep 17 00:00:00 2001 From: knoxiboy Date: Fri, 12 Jun 2026 23:32:29 +0530 Subject: [PATCH 2/2] feat(webrtc): establish robust WebRTC voice and video connections with ICE failure auto-end closes #574 --- backend/src/lib/socket.js | 35 +++++++++++++---------------- frontend/components/CallHandler.jsx | 7 ++++++ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/backend/src/lib/socket.js b/backend/src/lib/socket.js index 92f5f85..6b6e8ea 100644 --- a/backend/src/lib/socket.js +++ b/backend/src/lib/socket.js @@ -39,6 +39,21 @@ io.use((socket, next) => { const userSocketMap = {}; +// Cache to throttle user lastSeen database updates (max once per minute per user) +const lastDbUpdateCache = new Map(); + +const throttledUpdateLastSeen = async (userId) => { + const now = Date.now(); + const lastUpdate = lastDbUpdateCache.get(userId); + if (lastUpdate && now - lastUpdate < 60000) return; + lastDbUpdateCache.set(userId, now); + try { + await User.findByIdAndUpdate(userId, { lastSeen: new Date() }); + } catch (err) { + console.error("Failed to update user lastSeen:", err); + } +}; + export const getReceiverSocketIds = (userId) => userSocketMap[userId] ? [...userSocketMap[userId]] : []; @@ -48,8 +63,6 @@ export const broadcastStatusMoodUpdate = ({ userId, statusMood }) => { /** * 🛠️ Security Helper: Validates if two users can communicate. - * Adjust the database query inside based on whether you track relationships via - * a Friends/Block schema or directly within the User model. */ const canCommunicate = async (senderId, receiverId) => { if (!senderId || !receiverId || senderId === receiverId) return false; @@ -58,7 +71,6 @@ const canCommunicate = async (senderId, receiverId) => { const receiver = await User.findById(receiverId); if (!receiver) return false; - // Example: If your User schema has a 'blockedUsers' array if (receiver.blockedUsers && receiver.blockedUsers.includes(senderId)) { return false; } @@ -70,21 +82,6 @@ const canCommunicate = async (senderId, receiverId) => { } }; -io.on("connection", (socket) => { -const getActiveContacts = async (userId) => { - try { - const [senders, receivers] = await Promise.all([ - Message.distinct("senderId", { receiverId: userId }), - Message.distinct("receiverId", { senderId: userId }) - ]); - const merged = [...senders, ...receivers].map(id => id.toString()); - return [...new Set(merged)]; - } catch (err) { - console.error("Error fetching active contacts:", err); - return []; - } -} - io.on("connection", (socket) => { const userId = socket.userId; @@ -187,6 +184,6 @@ io.on("connection", (socket) => { io.emit("getOnlineUsers", Object.keys(userSocketMap)); }); -}); +}); export { io, app, server }; diff --git a/frontend/components/CallHandler.jsx b/frontend/components/CallHandler.jsx index d86d02e..ba84977 100644 --- a/frontend/components/CallHandler.jsx +++ b/frontend/components/CallHandler.jsx @@ -209,6 +209,13 @@ export default function CallHandler() { } }; + pc.onconnectionstatechange = () => { + if (pc.connectionState === "failed" || pc.connectionState === "disconnected") { + toast.error("Call connection lost"); + endCall(); + } + }; + setPeer(pc); return pc; };