Files
relay/server/socket/socket.js

362 lines
10 KiB
JavaScript

const {
insertMessage,
getConversationsForUser,
deleteContact,
deleteMessage,
removeUserFromGroupById,
isConversationMember,
isAdmin,
addAdministrator,
removeAdministrator,
updateContactStatus,
} = require("../db/db");
const { isValidUsername } = require("../utils/filter");
const { verifyJwtToken } = require("../auth/jwt");
function initializeSocket(io) {
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
console.log("(socket) Not logged in");
return next(new Error("Not logged in"));
}
try {
const { username, user_id } = verifyJwtToken(token);
if (!username || !user_id) {
console.log("(socket) Invalid token payload");
return next(new Error("(socket) Invalid token payload"));
}
socket.username = username;
if (!isValidUsername(username)) {
console.log("(socket) Invalid username");
return;
}
socket.user_id = user_id;
console.log(
`(socket) socket id: ${socket.id}, username: ${username}, user_id: ${user_id}`,
);
next();
} catch (error) {
console.error("(socket) Token verification failed:", error.message);
next(new Error("(socket) Invalid token"));
}
});
io.on("connection", async (socket) => {
const username = socket.username;
if (!username) {
socket.on("disconnect", () => {
console.log(
"(socket)",
socket.id,
" disconnected due to: invalid username/token",
);
});
return;
}
if (!isValidUsername(username)) {
socket.on("disconnect", () => {
console.log(
"(socket)",
socket.id,
" disconnected due to: invalid username/token",
);
});
return;
}
let conversations = [];
try {
conversations = await getConversationsForUser(socket.user_id);
conversations.push(socket.user_id);
socket.join(conversations);
console.log(`User: ${socket.username} joined to: ${conversations}`);
} catch (e) {
console.error("(socket) Failed to get user conversations");
}
socket.on("join room", async (conversation_id, callback) => {
const isMember = await isConversationMember(
socket.user_id,
conversation_id,
);
if (isMember) {
console.log("Join room for: ", username, "room: ", conversation_id);
socket.join(conversation_id);
return callback({
status: "ok",
message: "Successfully joined to conversation",
});
} else {
return callback({
status: "error",
message: "You are not member of this group",
});
}
});
socket.on("chat message", async (msg, callback) => {
const { message, recipient, recipient_id, attachment_urls } = msg;
const sender = socket.username;
if (!message && !attachment_urls) {
callback({
status: "error",
message: "No message or attachment provided",
});
return;
}
if (!recipient) {
callback({
status: "error",
message: "No recipient provided",
});
return;
}
try {
const insertedMessage = await insertMessage(
socket.user_id,
recipient,
message,
attachment_urls,
);
if (!insertedMessage) {
callback({
status: "error",
message: "Failed to insert message",
});
return;
} else if (insertedMessage.length === 0) {
callback({
status: "error",
message: "You are not an member of this conversation",
});
}
const { message_id, content, sent_at, sender_id, conversation_id } =
insertedMessage;
console.log("(socket) received from chat message", msg);
io.to(recipient).to(recipient_id).emit("chat message", {
sender,
message: content,
attachment_urls,
recipient,
message_id,
sender_id,
sent_at,
conversation_id,
});
console.log(
`(socket) sent on 'chat message' socket to: recipient: ${recipient}, recipient_id: ${recipient_id} `,
{
sender,
message,
attachment_urls,
recipient,
sent_at,
message_id,
sender_id,
},
);
callback({
message_id,
status: "ok",
message: "Received message",
});
} catch (e) {
console.error("(socket) Failed to insert message ", e);
callback({
status: "error",
message: "Internal server error",
});
}
});
socket.on("delete message", async (msg, callback) => {
const { conversation_id, message_id } = msg;
if (!message_id) {
return callback({ status: "error", message: "No message id provided" });
}
if (!conversation_id) {
return callback({
status: "error",
message: "No conversation id provided",
});
}
const result = await deleteMessage(
socket.user_id,
conversation_id,
message_id,
);
if (result?.message !== undefined) {
return callback({ status: "error", message: result.message });
} else {
io.to(conversation_id).emit("delete message", {
conversation_id,
message_id,
});
return callback({
status: "ok",
message: "Successfully deleted message",
});
}
});
socket.on("delete contact", async (conversation_id, callback) => {
console.log(
"(socket) delete contact, conversation_id: ",
conversation_id,
);
const user_id = socket.user_id;
if (!conversation_id) {
callback({ status: "error", message: "No conversation id provided" });
}
const result = await deleteContact(user_id, conversation_id);
if (result?.message) {
callback({ status: "error", message: result.message });
}
callback({ status: "ok", message: "Successfully deleted contact" });
io.to(conversation_id).emit("left group", { conversation_id, user_id });
});
socket.on("remove user from group", async (msg, callback) => {
const { group_id, user_id } = msg;
if (!group_id) {
return callback({ status: "error", message: "No group id provided" });
}
if (!user_id) {
return callback({ status: "error", message: "No user id provided" });
}
try {
const result = await removeUserFromGroupById(group_id, user_id);
if (result?.message) {
return callback({ status: "error", message: result.message });
}
// Get all sockets in the room
const socketsInRoom = await io.in(group_id).fetchSockets();
// Remove user from room
for (const socketInstance of socketsInRoom) {
if (socketInstance.user_id === user_id) {
socketInstance.leave(group_id);
}
}
io.to(group_id).to(user_id).emit("left group", {
group_id,
user_id,
});
console.log(
`Successfully removed user: ${user_id}, from group: ${group_id}`,
);
return callback({
status: "ok",
message: "Successfully removed user from group",
});
} catch (error) {
console.error("Failed to remove user from group:", error);
return callback({
status: "error",
message: "Internal server error",
});
}
});
socket.on("added administrator", async (msg, callback) => {
const { group_id, user_id } = msg;
if (!group_id) {
return callback({
status: "error",
message: "No conversation id provided",
});
}
if (!user_id) {
return callback({ status: "error", message: "No user id provided" });
}
const isUserAdmin = await isAdmin(socket.user_id, group_id);
if (!isUserAdmin) {
return callback({
status: "error",
message: "You is not an administrator",
});
}
const result = await addAdministrator(group_id, user_id, socket.user_id);
if (result?.message) {
return callback({ status: "error", message: result.message });
}
console.log(
`Successfully added user: ${user_id} to administrators to group: ${group_id}`,
);
io.to(group_id).emit("added administrator", {
group_id,
user_id,
});
return callback({
status: "ok",
message: "Successfully added administrator",
});
});
socket.on("removed administrator", async (msg, callback) => {
const { group_id, user_id } = msg;
if (!group_id) {
return callback({
status: "error",
message: "No conversation id provided",
});
}
if (!user_id) {
return callback({ status: "error", message: "No user id provided" });
}
const result = await removeAdministrator(
group_id,
user_id,
socket.user_id,
);
if (result?.message) {
return callback({ status: "error", message: result.message });
}
console.log(
`Successfully removed user: ${user_id} from administrators in group: ${group_id}`,
);
io.to(group_id).emit("removed administrator", {
group_id,
user_id,
});
return callback({
status: "ok",
message: "Successfully removed administrator",
});
});
socket.on("message read", async (msg) => {
const { conversation_id, message_id } = msg;
if (!conversation_id || !message_id) {
return;
}
console.error("MESSGE READ: ", conversation_id, message_id);
await updateContactStatus(socket.user_id, conversation_id, message_id);
});
socket.on("disconnect", (reason) => {
console.log("(socket)", socket.id, " disconnected due to: ", reason);
});
});
}
module.exports = { initializeSocket };