|
|
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; |
|
|
|