const { Client } = require("../Client.js"); let {randomUUID,createHash} = require("crypto"); const joi = require("joi"); let {addService,addListener} = require("../WebSocket.js"); const { termoutput } = require("../config.js"); const { ROOM_CREATED, ROOM_DESTROY, ROOM_UPDATE_PROP, ROOM_JOIN_CLIENT, ROOM_EJECT_CLIENT } = require("../IPC.js"); let term = require("terminal-kit").terminal; const stats = require("../stats"); function Sha256(update) { return createHash("sha256").update(update).digest("hex"); }; function Room() { /** * @type {string} */ this.id = randomUUID(); /** * @type {string} */ this.name = ""; /** * @type {string} */ this.description = ""; /** * @type {Client} */ this.owner = null; /** * @type {Date} */ this.createdAt = new Date(); /** * @type {Map} */ this.clients = new Map(); /** * @type {"public"|"private"} */ this.accessType = ""; /** * @type {"free"|"invite"|"password"|"lock"} */ this.joinType = "invite"; /** * @type {boolean} */ this.notifyActionInvite = false; /** * @type {boolean} */ this.notifyActionJoined = true; /** * @type {boolean} */ this.notifyActionEjected = true; /** * @type {string} */ this.credential = null; /** * @type {string[]} */ this.waitingInvited = new Set(); /** * @type {Map} */ this.info = new Map(); this.sync = function(...args){ process.nextTick(()=>{ for (const name of args) { ROOM_UPDATE_PROP(this.id, name, this[name]); } }) }; } /** * @param {Room} room */ Room.prototype.publish = function(){ stats.mwse_rooms++; Room.rooms.set(this.id, this); ROOM_CREATED(this); termoutput && term.green("Room Published ").white(this.name," in ").yellow(this.clients.size).white(" clients")('\n'); }; /** * @return {Client[]} */ Room.prototype.filterPeers = function(optiJson){ let peers = []; this.clients.forEach(client => { if(client.match(optiJson)) { peers.push(client); } }); return peers; }; Room.prototype.toJSON = function(detailed){ let obj = {}; obj.id = this.id; obj.accessType = this.accessType; obj.createdAt = this.createdAt; obj.description = this.description; obj.joinType = this.joinType; obj.name = this.name; obj.owner = this.owner.id; obj.waitingInvited = [...this.waitingInvited]; if(detailed) { obj.credential = this.credential; obj.notifyActionInvite = this.notifyActionInvite; obj.notifyActionJoined = this.notifyActionJoined; obj.notifyActionEjected = this.notifyActionEjected; obj.clients = [...this.clients.keys()]; } return obj; }; Room.prototype.getInfo = function(){ let obj = {}; for (const [name, value] of this.info) { obj[name] = value; } return obj; }; /** * @param {Object} data * @param {Room} room */ Room.fromJSON = function(data, room){ room = room || new Room(); let obj = {}; room.id = data.id; room.accessType = data.accessType; room.createdAt = data.createdAt; room.description = data.description; room.joinType = data.joinType; room.name = data.name; if(data.owner && Client.clients.has(data.owner)) { room.owner = Client.clients.get(data.owner); } room.waitingInvited = new Set(data.waitingInvited); obj.credential = data.credential; obj.notifyActionInvite = data.notifyActionInvite; obj.notifyActionJoined = data.notifyActionJoined; obj.notifyActionEjected = data.notifyActionEjected; obj.clients = new Map( data.clients.map(e => ([ e, // map key Client.clients.get(e) // map value ]) ) ) return room; }; Room.prototype.send = function(obj, withOut){ for (const client of this.clients.values()) { if(client.id != withOut) { client.send(obj); } } termoutput && term.green("Room bulk message ").white(this.name," in ").yellow(this.clients.size + "").white(" clients")('\n'); }; /** * @param {Client} client */ Room.prototype.join = function(client){ if(this.notifyActionJoined) { this.send([{ id: client.id, roomid: this.id, ownerid: this.owner.id },'room/joined']); }; client.rooms.add(this.id); this.clients.set(client.id, client); ROOM_JOIN_CLIENT(this.id, client.id); termoutput && term.green("Client Room joined ").white(this.name," in ").yellow(this.clients.size + "").white(" clients")('\n'); }; Room.prototype.down = function(){ termoutput && term.red("Room is downed ").red(this.name," in ").yellow(this.clients.size + "").red(" clients")('\n'); this.send([{ roomid: this.id, ownerid: this.owner.id },'room/closed']); Room.rooms.delete(this.id); ROOM_DESTROY(this) stats.mwse_rooms--; }; /** * @param {Client} client */ Room.prototype.eject = function(client){ if(this.notifyActionEjected) { this.send([{ id: client.id, roomid: this.id, ownerid: this.owner.id },'room/ejected']); } client.rooms.delete(this.id); this.clients.delete(client.id); ROOM_EJECT_CLIENT(this.id, client.id); if(this.clients.size == 0) { this.down(); termoutput && term.red("Client Room closed ").red(this.name," at 0 clients")('\n'); } termoutput && term.red("Client Room ejected ").red(this.name," in ").yellow(this.clients.size + "").red(" clients")('\n'); }; /** * @type {Map} */ Room.rooms = new Map(); addListener('connect',(global, client)=>{ let room = new Room(); room.accessType = "private"; room.joinType = "notify"; room.description = 'Private room'; room.id = client.id; room.name = "Your Room | " + client.id; room.owner = client; room.publish(); room.join(client); }); addListener('disconnect',(global, client)=>{ Room.rooms.get(client.id).eject(client); for (const roomId of client.rooms) { Room.rooms.get(roomId).eject(client); } }); let CreateRoomVerify = joi.object({ type: joi.any().required(), accessType: joi.string().pattern(/^public$|private$/).required(), notifyActionInvite: joi.boolean().required(), notifyActionJoined: joi.boolean().required(), notifyActionEjected: joi.boolean().required(), joinType: joi.string().pattern(/^free$|^invite$|^password$|^lock$/).required(), description: joi.string().required(), name: joi.string().required(), credential: joi.string().optional(), ifexistsJoin: joi.boolean().optional(), autoFetchInfo: joi.boolean().optional(), }); addService(({ client, end, global, message, next, response })=>{ let {type} = message; switch(type) { case 'myroom-info':{ let room = Room.rooms.get(client.id); end({ status: "success", room: room.toJSON() }) break; } case 'room-peers':{ let {roomId,filter} = message; if(Room.rooms.has(roomId)) { let filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {}); end({ status: 'success', peers: filteredPeers.map(i => i.id) }); }else{ end({ status: 'fail' }) } break; } case 'room/peer-count':{ let {roomId,filter} = message; if(Room.rooms.has(roomId)) { let filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {}); end({ status: 'success', count: filteredPeers.length }); }else{ end({ status: 'fail' }) } break; } case 'room-info':{ let {name} = message; for (const [roomId,{name:RoomName}] of Room.rooms) { if(name == RoomName) { return end({ status : "success", room : Room.rooms.get(roomId).toJSON() }) } }; end({ status : "fail", message : "NOT-FOUND-ROOM" }) break; } case 'joinedrooms':{ let data = [ ...client.rooms ].map(e => { return Room.rooms.get(e).toJSON() }); end(data) break; } case 'closeroom':{ let {roomId} = message; if(Room.rooms.has(roomId)) { let room = Room.rooms.get(roomId); if(room.owner === client.id) { room.down(); end({ status: 'success' }); }else{ end({ status: 'fail' }); } }else{ end({ status: 'fail' }) } break; } case 'create-room':{ let {error} = CreateRoomVerify.validate(message); if(error) { return end({ status: 'fail', messages: error.message }) }else{ let {name} = message; for (const [,{name:RoomName}] of Room.rooms) { if(name == RoomName) { return end({ status : "fail", message : "ALREADY-EXISTS" }) } } let room = new Room(); room.accessType = message.accessType; room.notifyActionInvite = message.notifyActionInvite; room.notifyActionJoined = message.notifyActionJoined; room.notifyActionEjected = message.notifyActionEjected; room.joinType = message.joinType; room.description = message.description; room.name = message.name; room.owner = client; if(message.credential) { room.credential = Sha256(message.credential + ""); } room.publish(); room.join(client); end({ status: "success", room: room.toJSON() }); } break; } case 'joinroom':{ let {name,autoFetchInfo} = message; let roomId; for (const [_roomId,{name:RoomName}] of Room.rooms) { if(name == RoomName) { roomId = _roomId break; } } let isRoom = Room.rooms.has(roomId); if(isRoom) { let room = Room.rooms.get(roomId); if(room.joinType == "lock") { return end({ status : "fail", message : "LOCKED-ROOM" }) }else if(room.joinType == "password") { if(room.credential == Sha256(message.credential + "")) { let info = {}; if(autoFetchInfo) { info.info = room.getInfo(); }; room.join(client); return end({ status : "success", room: room.toJSON() }) }else return end({ status : "fail", message : "WRONG-PASSWORD", area: "credential" }) }else if(room.joinType == "free"){ let info = {}; if(autoFetchInfo) { info.info = room.getInfo(); }; room.join(client); return end({ status : "success", room: room.toJSON(), ...info }) }else if(room.joinType == "invite"){ room.waitingInvited.add(client.id); if(room.notifyActionInvite) { room.send([{ id: client.id },"room/invite"]); }else{ room.owner.send([{ id: client.id },"room/invite"]); } }; }else{ return end({ status : "fail", message : "NOT-FOUND-ROOM" }) } break; } case 'ejectroom':{ let {roomId} = message; let isRoom = Room.rooms.has(roomId); if(isRoom) { let room = Room.rooms.get(roomId); if(room.clients.has(client.id)) { room.eject(client) return end({ status : "success" }) }else{ return end({ status : "fail", message : "ALREADY-ROOM-OUT" }) } }else{ return end({ status : "fail", message : "NOT-FOUND-ROOM" }) } break; } case 'accept/invite-room':{ let {roomId, clientId} = message; // Odanın varlığının kontrolü if(!Room.rooms.has(roomId)) { end({ status : "fail", message : "NOT-FOUND-ROOM" }) }; let room = Room.rooms.get(roomId); // erişim kontrolü if(!client.rooms.has(room.id)) { return end({ status : "fail", message : "FORBIDDEN-INVITE-ACTIONS" }) } // Odaya katılma şeklinin doğruluğu if(room.joinType == 'invite') { return end({ status : "fail", message : "INVALID-DATA" }) }; // Odaya katılma talebinin doğruluğu if(!room.waitingInvited.includes(clientId)) { return end({ status : "fail", message : "NO-WAITING-INVITED" }) }; // Odaya katılan kişinin varlığı if(!Client.clients.has(clientId)) { return end({ status : "fail", message : "NO-CLIENT" }) }; // Odaya kişiyi kabul etme let JoinClient = Client.clients.get(clientId) room.join(JoinClient); JoinClient.send([{ status:"accepted" },'room/invite/status']); return end({ status : "success" }); } case 'reject/invite-room':{ let {roomId, clientId} = message; // Odanın varlığının kontrolü if(!Room.rooms.has(roomId)) { end({ status : "fail", message : "NOT-FOUND-ROOM" }) }; let room = Room.rooms.get(roomId); // erişim kontrolü if(!client.rooms.has(room.id)) { return end({ status : "fail", message : "FORBIDDEN-INVITE-ACTIONS" }) } // Odaya katılma şeklinin doğruluğu if(room.joinType == 'invite') { return end({ status : "fail", message : "INVALID-DATA" }) }; // Odaya katılma talebinin doğruluğu if(!room.waitingInvited.includes(clientId)) { return end({ status : "fail", message : "NO-WAITING-INVITED" }) }; // Odaya katılan kişinin varlığı if(!Client.clients.has(clientId)) { return end({ status : "fail", message : "NO-CLIENT" }) }; // Odaya davet edilen kişiyi reddetme let JoinClient = Client.clients.get(clientId) room.waitingInvited = room.waitingInvited.filter(e => e != clientId); room.send([{id:clientId,roomId:room.id},'room/invite/status']) JoinClient.send([{ status:"rejected" },'room/invite/status']); return end({ status : "success" }); } case 'room/list':{ let rooms = []; for (const [id, {accessType,name,joinType,description}] of Room.rooms) { if(accessType == "public") { rooms.push({ name, joinType, description, id }) } } end({ type:'public/rooms', rooms }); } case 'room/info':{ let {roomId,name} = message; // Odanın varlığının kontrolü if(!Room.rooms.has(roomId)) { end({ status : "fail", message : "NOT-FOUND-ROOM" }) }; let room = Room.rooms.get(roomId); // erişim kontrolü if(!client.rooms.has(room.id)) { return end({ status : "fail", message : "NO-jıoned-ROOM" }) } if(name) { return end({ status: "success", value: room.info.get(name) }); }else{ return end({ status: "success", value: room.getInfo() }); } } case 'room/setinfo':{ let {roomId,name,value} = message; // Odanın varlığının kontrolü if(!Room.rooms.has(roomId)) { return end({ status : "fail", message : "NOT-FOUND-ROOM" }) }; let room = Room.rooms.get(roomId); // erişim kontrolü if(!client.rooms.has(room.id)) { return end({ status : "fail", message : "NO-JOINED-ROOM" }) } room.info.set(name, value); room.send([ { name, value, roomId:room.id }, "room/info" ], client.id); return end({ status: "success" }); } default:{ next(); } } }); exports.Room = Room;