-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsocket.js
More file actions
173 lines (145 loc) · 5.13 KB
/
socket.js
File metadata and controls
173 lines (145 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
const jwt = require('jsonwebtoken');
const User = require('./models/user');
const Chat = require('./models/chat');
const Message = require('./models/message');
const mongoose = require('mongoose');
/**
* Socket.IO server implementation for real-time chat
* @param {SocketIO.Server} io - The Socket.IO server instance
*/
module.exports = (io) => {
// Track online users - Map of userId to socketId
const onlineUsers = new Map();
// Authentication middleware
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error('Authentication error: Token missing'));
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.userId);
if (!user) {
return next(new Error('Authentication error: User not found'));
}
// Attach user data to socket
socket.userId = decoded.userId;
socket.username = user.username;
next();
} catch (error) {
return next(new Error('Authentication error: Invalid token'));
}
});
io.on('connection', (socket) => {
console.log(`User connected: ${socket.userId}`);
// Add user to online users
onlineUsers.set(socket.userId, socket.id);
// Emit online users to all connected clients
io.emit('users:online', Array.from(onlineUsers.keys()));
// Join user to their chat rooms
socket.on('join:chats', async () => {
try {
// Find all chats where the user is a participant
const userChats = await Chat.find({
participants: socket.userId
});
// Join socket rooms for each chat
userChats.forEach(chat => {
socket.join(chat._id.toString());
});
socket.emit('chats:joined', userChats.map(chat => chat._id.toString()));
} catch (error) {
console.error('Error joining chat rooms:', error);
}
});
// Handle new message
socket.on('message:send', async (data) => {
try {
const { chatId, content } = data;
// Validate chat existence and user membership
const chat = await Chat.findById(chatId);
if (!chat) {
socket.emit('error', { message: 'Chat not found' });
return;
}
if (!chat.participants.includes(socket.userId)) {
socket.emit('error', { message: 'You are not a participant in this chat' });
return;
}
// Create and save the message
const newMessage = new Message({
chat: chatId,
sender: socket.userId,
text: content,
readBy: [socket.userId] // Mark as read by sender
});
await newMessage.save();
// Populate sender details for the frontend
const populatedMessage = await Message.findById(newMessage._id)
.populate('sender', 'username avatar')
.lean();
// Emit to all users in the chat room
io.to(chatId).emit('message:received', populatedMessage);
// Send notifications to other participants
chat.participants.forEach(participantId => {
const participantIdStr = participantId.toString();
// Skip sender
if (participantIdStr !== socket.userId) {
const participantSocketId = onlineUsers.get(participantIdStr);
// If participant is online, send notification
if (participantSocketId) {
io.to(participantSocketId).emit('notification:new_message', {
chatId,
message: populatedMessage
});
}
}
});
} catch (error) {
console.error('Error sending message:', error);
socket.emit('error', { message: error.message });
}
});
// Handle typing indicator
socket.on('typing:start', (chatId) => {
socket.to(chatId).emit('user:typing', {
chatId,
userId: socket.userId,
username: socket.username
});
});
socket.on('typing:stop', (chatId) => {
socket.to(chatId).emit('user:stopped_typing', {
chatId,
userId: socket.userId
});
});
// Handle read receipts
socket.on('message:read', async (data) => {
try {
const { chatId, messageId } = data;
// Update the readBy array for the message
await Message.findByIdAndUpdate(
messageId,
{ $addToSet: { readBy: socket.userId } }
);
// Notify other users in the chat
socket.to(chatId).emit('message:seen', {
chatId,
messageId,
userId: socket.userId
});
} catch (error) {
console.error('Error marking message as read:', error);
}
});
// Handle disconnection
socket.on('disconnect', () => {
console.log(`User disconnected: ${socket.userId}`);
// Remove user from online users
onlineUsers.delete(socket.userId);
// Emit updated online users list
io.emit('users:online', Array.from(onlineUsers.keys()));
});
});
};