| | import { Server } from 'socket.io'; |
| | import matchingQueue from './MatchingQueue.js'; |
| | import { updateKarma } from './KarmaService.js'; |
| | import User from '../models/User.js'; |
| | import Message from '../models/Message.js'; |
| | import CallSession from '../models/CallSession.js'; |
| |
|
| | let io; |
| |
|
| | const ICEBREAKERS = [ |
| | "What's your most controversial food opinion?", |
| | "If you could teleport anywhere right now, where to?", |
| | "What's the last song you listened to on repeat?", |
| | "Zombie apocalypse team: You + 2 fictional characters. Who?", |
| | "Cats or Dogs? Defend your answer.", |
| | "What's a movie you can watch 100 times?", |
| | "Best Wi-Fi name you've ever seen?", |
| | "If you were a ghost, who would you haunt?", |
| | "What's the weirdest talent you have?", |
| | "Pineapple on pizza: Yes or Criminal?", |
| | "What's something you're oddly passionate about?", |
| | "If your life had a theme song right now, what would it be?", |
| | "What's the most random thing you've learned this semester?", |
| | "What's a hot take you're willing to defend?", |
| | "What's the best late-night food near your campus?", |
| | "What's your go-to comfort show or YouTube channel?", |
| | "What's a hobby you picked up and then immediately dropped?", |
| | "What's something small that instantly makes your day better?", |
| | "What's the most college thing that's happened to you this week?", |
| | "What's your current obsession?", |
| | "What's your major—and do you actually like it?", |
| | "Hardest class you've taken so far?", |
| | "Are you more of a library studier or last-minute grinder?", |
| | "Best campus spot nobody talks about?", |
| | "Most useless class requirement you've had?", |
| | "What's one class everyone should avoid?", |
| | "What's your ideal class schedule: mornings or afternoons?", |
| | "Biggest academic struggle right now?", |
| | "Group projects: blessing or curse?", |
| | "What's one thing you wish freshmen knew?", |
| | "What's a hill you're willing to die on?", |
| | "If sleep were a class, would you pass?", |
| | "What's your most controversial campus opinion?", |
| | "What's something you pretend to enjoy but actually don't?", |
| | "What's the worst fashion phase you went through?", |
| | "What's your most irrational fear?", |
| | "What's a weird habit you have?", |
| | "What's the worst app you still use daily?", |
| | "If procrastination were a sport, how good would you be?", |
| | "What's the dumbest thing you've done this month?", |
| | "If money didn't matter, what would you be doing right now?", |
| | "If you could restart college, what would you do differently?", |
| | "If you could instantly master one skill, what would it be?", |
| | "What would your dream elective be?", |
| | "If you could time-travel to one moment in your life, when?", |
| | "What's a random dream you haven't told many people?", |
| | "What would your perfect day look like?", |
| | "If you had to teach a class, what would it be about?", |
| | "What's something you want to get better at?", |
| | "If you disappeared for a year, what would you do?", |
| | "What's something you're proud of lately?", |
| | "What's been stressing you out recently?", |
| | "What's one thing you're looking forward to?", |
| | "Who's had the biggest influence on you?", |
| | "What's something you're trying to improve about yourself?", |
| | "What's the best advice you've gotten?", |
| | "What's something you're grateful for today?", |
| | "What's one goal you have this semester?", |
| | "What's something that motivates you?", |
| | "What's one thing that always cheers you up?", |
| | "Coffee or energy drinks?", |
| | "Night owl or early bird?", |
| | "Introvert, extrovert, or depends?", |
| | "Study music or silence?", |
| | "Netflix or YouTube?", |
| | "Gym, sports, or neither?", |
| | "Homebody or always out?", |
| | "Texting or calling?", |
| | "Winter or summer?", |
| | "Spontaneous or planned?" |
| | ]; |
| |
|
| | const onlineUsers = new Set(); |
| |
|
| | export const initSocket = (httpServer) => { |
| | io = new Server(httpServer, { |
| | cors: { |
| | origin: process.env.CLIENT_URL || "*", |
| | methods: ["GET", "POST"] |
| | } |
| | }); |
| |
|
| | io.on('connection', (socket) => { |
| | console.log(`🔌 Client connected: ${socket.id}`); |
| |
|
| | socket.on('register_online', (userId) => { |
| | socket.userId = userId.toString(); |
| | onlineUsers.add(socket.userId); |
| |
|
| | io.emit('online_count', { count: onlineUsers.size }); |
| | }); |
| |
|
| | |
| | socket.on('join_queue', (user) => { |
| | console.log(`🔍 User ${user.displayName} joined queue`); |
| | socket.userId = user._id.toString(); |
| | onlineUsers.add(socket.userId); |
| |
|
| | |
| | io.emit('online_count', { count: onlineUsers.size }); |
| |
|
| | const match = matchingQueue.findMatch(user); |
| |
|
| | if (match) { |
| | |
| | socket.partnerSocketId = match.socketId; |
| |
|
| | const partnerSocket = io.sockets.sockets.get(match.socketId); |
| | if (partnerSocket) { |
| | partnerSocket.partnerSocketId = socket.id; |
| | } |
| |
|
| | const createSession = async (userA_id, userB_id) => { |
| | try { |
| | const session = await new CallSession({ |
| | participants: [userA_id, userB_id] |
| | }).save(); |
| | return session._id; |
| | } catch (err) { |
| | console.error("Session log error", err); |
| | } |
| | }; |
| |
|
| | matchingQueue.remove(match.socketId); |
| | const roomId = `${socket.id}-${match.socketId}`; |
| | |
| | socket.join(roomId); |
| | io.to(match.socketId).socketsJoin(roomId); |
| |
|
| | |
| | const question = ICEBREAKERS[Math.floor(Math.random() * ICEBREAKERS.length)]; |
| |
|
| | |
| | io.to(socket.id).emit('match_found', { |
| | roomId, |
| | peerId: match.user._id.toString(), |
| | peerName: match.user.displayName, |
| | peerKarma: match.user.karma, |
| | peerProfile: match.user.profile, |
| | remoteSocketId: match.socketId, |
| | icebreaker: question |
| | }); |
| |
|
| | |
| | io.to(match.socketId).emit('match_found', { |
| | roomId, |
| | peerId: user._id.toString(), |
| | peerName: user.displayName, |
| | peerKarma: user.karma, |
| | peerProfile: user.profile, |
| | remoteSocketId: socket.id, |
| | icebreaker: question |
| | }); |
| | |
| | |
| | socket.emit('initiate_call', { remoteSocketId: match.socketId }); |
| |
|
| | } else { |
| | |
| | matchingQueue.add(socket.id, user); |
| | } |
| | }); |
| |
|
| | |
| | socket.on('offer', (payload) => { |
| | io.to(payload.target).emit('offer', payload); |
| | }); |
| |
|
| | socket.on('answer', (payload) => { |
| | io.to(payload.target).emit('answer', payload); |
| | }); |
| |
|
| | socket.on('ice-candidate', (payload) => { |
| | io.to(payload.target).emit('ice-candidate', payload); |
| | }); |
| |
|
| | |
| | socket.on('disconnect', async () => { |
| | console.log(`🔌 Client disconnected: ${socket.id}`); |
| |
|
| | if (socket.userId) { |
| | onlineUsers.delete(socket.userId); |
| | } |
| |
|
| | io.emit('online_count', { count: onlineUsers.size }); |
| |
|
| | if (socket.partnerSocketId) { |
| | io.to(socket.partnerSocketId).emit('call_ended'); |
| | } |
| | |
| | |
| | matchingQueue.remove(socket.id); |
| |
|
| | |
| | if (socket.activeSessionId) { |
| | await CallSession.findByIdAndUpdate(socket.activeSessionId, { |
| | endTime: Date.now(), |
| | status: 'dropped' |
| | }); |
| | |
| | |
| | |
| | |
| | if (socket.partnerSocketId) { |
| | io.to(socket.partnerSocketId).emit('peer_disconnected'); |
| | } |
| | } |
| | }); |
| | |
| | socket.on('report_violation', async ({ type }) => { |
| | if (!socket.userId) return; |
| |
|
| | console.log(`🚨 AI REPORT on ${socket.userId}: ${type}`); |
| | |
| | if (type === 'NSFW_AUTO') { |
| | |
| | await updateKarma(socket.userId, -20, 'NSFW AI Detection'); |
| | |
| | |
| | socket.disconnect(); |
| | } |
| | }); |
| |
|
| | socket.on('share_social', async ({ targetId, platform }) => { |
| | try { |
| | |
| | const sender = await User.findById(socket.userId); |
| | if (!sender) return; |
| |
|
| | const handle = sender.profile.socials?.[platform]; |
| | |
| | if (!handle) { |
| | |
| | socket.emit('error_toast', { message: `No ${platform} handle set in profile.` }); |
| | return; |
| | } |
| |
|
| | |
| | io.to(targetId).emit('social_received', { |
| | platform, |
| | handle, |
| | senderName: sender.displayName |
| | }); |
| |
|
| | |
| | socket.emit('social_shared_success', { platform }); |
| | |
| | } catch (err) { |
| | console.error("Share failed", err); |
| | } |
| | }); |
| |
|
| | socket.on('send_message', async ({ receiverId, content }) => { |
| | try { |
| | |
| | const newMsg = await new Message({ |
| | sender: socket.userId, |
| | receiver: receiverId, |
| | content |
| | }).save(); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | io.to(`user:${receiverId}`).emit('receive_message', newMsg); |
| | |
| | |
| | socket.emit('message_sent', newMsg); |
| | |
| | } catch (err) { |
| | console.error("Chat error", err); |
| | } |
| | }); |
| |
|
| | socket.on('login_flow', (userId) => { |
| | socket.join(`user:${userId}`); |
| | }); |
| |
|
| | |
| | socket.on('end_call', ({ roomId }) => { |
| | if (socket.partnerSocketId) { |
| | io.to(socket.partnerSocketId).emit('call_ended'); |
| | } |
| |
|
| | socket.leave(roomId); |
| | socket.partnerSocketId = null; |
| | }); |
| | }); |
| | }; |
| |
|
| | export const getIO = () => io; |
| |
|