import buildFunctionHandler from "@utils/buildFunctionHandler" import { ChatMessage } from "@db_models" export default class Room { constructor(io, roomID, options) { if (!io) { throw new OperationError(500, "io is required") } this.io = io if (!roomID) { throw new OperationError(500, "roomID is required") } this.roomID = `room:${roomID}` this.roomSocket = this.io.to(`room:${this.roomID}`) this.options = { owner_user_id: null, ...options, } } connections = new Set() limitations = { maxMessageLength: 540, } roomEvents = { "room:change:owner": async (client, payload) => { throw new OperationError(500, "Not implemented") }, "room:send:message": async (client, payload) => { console.log( `[${this.roomID}] [@${client.userData.username}] sent message >`, payload, ) let { message } = payload if (!message || typeof message !== "string") { throw new Error("Invalid message") } if (message.length > this.limitations.maxMessageLength) { message = message.substring( 0, this.limitations.maxMessageLength, ) } const created_at = new Date().getTime() const id = `msg:${client.userData._id}:${created_at}` this.handlers.broadcastToMembers("room:message", { _id: id, timestamp: payload.timestamp ?? Date.now(), content: String(message), user: { user_id: client.userData._id, username: client.userData.username, fullName: client.userData.fullName, avatar: client.userData.avatar, }, }) if (payload.route) { const routeValues = payload.route.split(":") console.log(routeValues) if (routeValues.length > 0) { const [type, to_id] = routeValues switch (type) { case "user": { const doc = await ChatMessage.create({ type: type, from_user_id: client.userData._id, to_user_id: to_id, content: message, created_at: created_at, }) console.log(doc) } default: break } } } }, } handlers = { join: (client) => { if (client.connectedRoomID) { console.warn( `[${client.id}][@${client.userData.username}] already connected to room ${client.connectedRoomID}`, ) client.leave(client.connectedRoomID) } console.log( `[${client.id}][@${client.userData.username}] joined room ${this.roomID}`, ) client.connectedRoomID = this.roomID this.connections.add(client) for (let [event, handler] of Object.entries(this.roomEvents)) { handler = buildFunctionHandler(handler, client) if (!Array.isArray(client.handlers)) { client.handlers = [] } client.handlers.push([event, handler]) client.on(event, handler) } // emit to self client.emit("room:joined", { roomID: this.roomID, limitations: this.limitations, connectedUsers: this.getConnectedUsers(), }) // emit to others this.roomSocket.emit("room:user:joined", { user: { user_id: client.userData._id, username: client.userData.username, fullName: client.userData.fullName, avatar: client.userData.avatar, }, }) }, leave: (client) => { if (!client.connectedRoomID) { console.warn( `[${client.id}][@${client.userData.username}] not connected to any room`, ) return } if (client.connectedRoomID !== this.roomID) { console.warn( `[${client.id}][@${client.userData.username}] not connected to room ${this.roomID}, cannot leave`, ) return false } this.connections.delete(client) client.emit("room:left", { room: this.roomID, }) this.roomSocket.emit("room:user:left", { user: { user_id: client.userData._id, username: client.userData.username, fullName: client.userData.fullName, avatar: client.userData.avatar, }, }) for (const [event, handler] of client.handlers) { client.off(event, handler) } console.log( `[${client.id}][@${client.userData.username}] left room ${this.roomID}`, ) }, broadcastToMembers: (event, payload) => { for (const client of this.connections) { client.emit(event, payload) } }, } getConnectedUsers = () => { let users = {} for (const client of this.connections) { users[client.userData._id] = client.userData } return users } }