MWSE/Source/Services/Room.js

510 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { Client } = require("../Client.js");
let {randomUUID,createHash} = require("crypto");
const joi = require("joi");
let {addService,addListener} = require("../WebSocket.js");
let term = require("terminal-kit").terminal;
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<string, Client>}
*/
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();
}
/**
* @param {Room} room
*/
Room.prototype.publish = function(room){
Room.rooms.set(this.id, this);
term.green("Room Published ").white(this.name," in ").yellow(this.clients.size).white(" clients")('\n');
};
Room.prototype.toJSON = function(){
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];
return obj;
};
Room.prototype.send = function(obj, withOut){
for (const client of this.clients.values()) {
if(client.id != withOut)
{
client.send(obj);
}
}
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);
term.green("Client Room joined ").white(this.name," in ").yellow(this.clients.size + "").white(" clients")('\n');
};
Room.prototype.down = function(){
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);
};
/**
* @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);
if(this.clients.size == 0)
{
this.down();
term.red("Client Room closed ").red(this.name," at 0 clients")('\n');
}
term.red("Client Room ejected ").red(this.name," in ").yellow(this.clients.size + "").red(" clients")('\n');
};
/**
* @type {Map<string, Room>}
*/
Room.rooms = new Map();
addListener('connect',(global, client)=>{
let room = new Room();
room.accessType = "private";
room.joinType = "notify";
room.description = 'Yourself private room, you can invite friends';
room.id = client.id;
room.name = "Your Room | " + client.id;
room.owner = client;
room.join(client);
room.publish();
});
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()
});
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} = message;
if(Room.rooms.has(roomId))
{
end({
status: 'success',
peers: [
...Room.rooms.get(roomId).clients.values()
].map(i => i.id)
});
}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.join(client);
room.publish();
end({
status: "success",
room: room.toJSON()
});
}
break;
}
case 'joinroom':{
let {name} = 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 + ""))
{
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"){
room.join(client);
return end({
status : "success",
room: room.toJSON()
})
}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 '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
});
}
default:{
next();
}
}
});
exports.Room = Room;