diff --git a/Source/Client.js b/Source/Client.js index 3ac70ef..3e2a75d 100644 --- a/Source/Client.js +++ b/Source/Client.js @@ -20,4 +20,6 @@ Client.prototype.send = function(obj){ this.socket.sendUTF(JSON.stringify(obj)); }; +Client.prototype.rooms = new Set(); + exports.Client = Client; \ No newline at end of file diff --git a/Source/Services/Auth.js b/Source/Services/Auth.js index ed94686..da3adf4 100644 --- a/Source/Services/Auth.js +++ b/Source/Services/Auth.js @@ -1,12 +1,10 @@ let {addService} = require("../WebSocket.js"); addService(({ - global, client, message, end, - next, - response + next })=>{ let {type,username,password} = message; if(type === 'auth/check') @@ -44,4 +42,5 @@ addService(({ }) } }; + next(); }); \ No newline at end of file diff --git a/Source/Services/Room.js b/Source/Services/Room.js new file mode 100644 index 0000000..74262cd --- /dev/null +++ b/Source/Services/Room.js @@ -0,0 +1,240 @@ +const { Client } = require("../Client.js"); +let {randomUUID} = require("crypto"); +const joi = require("joi"); +let {addService,addListener} = require("../WebSocket.js"); + +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.password = null; + /** + * @type {string[]} + */ + this.waitingInvited = new Set(); +} +/** + * @param {Room} room + */ +Room.prototype.publish = function(room){ + Room.rooms.set(room.id, room); +}; +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){ + for (const client of this.clients.values()) { + client.send(obj); + } +}; +/** + * @param {Client} client + */ +Room.prototype.join = function(client){ + 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.prototype.active = function(){ + Room.rooms.set(this.id, this); +}; +Room.prototype.deactive = function(){ + Room.rooms.delete(this.id); +}; +/** + * @param {Client} client + */ +Room.prototype.eject = function(client){ + 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); +}; + +/** + * @type {Map} + */ +Room.rooms = new Map(); + +addListener('connect',(global, client)=>{ + let room = new Room(); + room.accessType = "private"; + room.joinType = "notify"; + room.clients.set(client.id, client); + room.description = 'Yourself private room, you can invite friends'; + room.id = client.id; + room.name = "Your Room"; + room.owner = client; + room.join(client); + room.active(); +}); + +addListener('disconnect',(global, client)=>{ + for (const roomId of client.rooms) { + Room.rooms.get(roomId).eject(client); + Room.rooms.get(roomId).deactive(); + } +}); + + +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() +}); + +addService(({ + client, + end, + global, + message, + next, + response +})=>{ + let {type} = message; + switch(type) + { + case 'myroom-info':{ + let room = Room.rooms.get(client.id); + end(room.toJSON()) + break; + } + case 'create-room':{ + let {error} = CreateRoomVerify.validate(message); + if(error) + { + return end({ + status: 'fail', + messages: error.message + }) + }else{ + 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; + room.join(client); + room.active(); + end(room.toJSON()) + } + break; + } + case 'join':{ + let isRoom = Room.rooms.has(client.id); + if(isRoom) + { + let room = Room.rooms.get(client.id); + if(room.joinType == "lock") + { + return end({ + status : "fail", + message : "LOCKED-ROOM" + }) + }else if(room.joinType == "password") + { + if(room.password == message.credential) + { + room.join(client); + return end({status : "success"}) + }else return end({ + status : "fail", + message : "WRONG-PASSWORD" + }) + }else if(room.joinType == "free"){ + room.join(client); + }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; + } + } +}); + + +exports.Room = Room; \ No newline at end of file diff --git a/Source/index.js b/Source/index.js index b9c6f6f..da92496 100644 --- a/Source/index.js +++ b/Source/index.js @@ -2,4 +2,5 @@ require("./HTTPServer.js"); require("./WebSocket.js"); require("./Services/YourID.js"); -require("./Services/Auth.js"); \ No newline at end of file +require("./Services/Auth.js"); +require("./Services/Room.js"); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 47069ca..e211967 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { + "joi": "^17.7.0", "knex": "^2.3.0", "sqlite3": "^5.1.2", "websocket": "^1.0.34" @@ -20,6 +21,19 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "optional": true }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", @@ -63,6 +77,24 @@ "node": ">=10" } }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -616,6 +648,18 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "optional": true }, + "node_modules/joi": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/knex": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/knex/-/knex-2.3.0.tgz", @@ -1470,6 +1514,19 @@ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "optional": true }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@mapbox/node-pre-gyp": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", @@ -1506,6 +1563,24 @@ "rimraf": "^3.0.2" } }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1955,6 +2030,18 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "optional": true }, + "joi": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "knex": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/knex/-/knex-2.3.0.tgz", diff --git a/package.json b/package.json index 1637386..49583c1 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "author": "Abdussamed ULUTAŞ ", "license": "MIT", "dependencies": { + "joi": "^17.7.0", "knex": "^2.3.0", "sqlite3": "^5.1.2", "websocket": "^1.0.34" diff --git a/test.html b/test.html index c719251..da095ce 100644 --- a/test.html +++ b/test.html @@ -11,13 +11,23 @@ \ No newline at end of file diff --git a/wsjs.js b/wsjs.js index 9867d19..d2cfa8e 100644 --- a/wsjs.js +++ b/wsjs.js @@ -123,11 +123,35 @@ WSJS.prototype.checkAuth = async function(username, password){ WSJS.prototype.authWith = async function(username, password){ if(!await this.checkAuth()) { - let requets = await this.request({ + await this.request({ type: 'auth/login', username, password }); + } +}; +WSJS.prototype.fetchMyRoomInfo = async function(){ + if(!await this.checkAuth()) + { + return await this.request({ + type: 'myroom-info' + }); + } +}; +WSJS.prototype.createRoom = async function(options){ + if(!await this.checkAuth()) + { + let result = await this.request({ + type: 'create-room', + accessType: options.accessType || "public", + notifyActionInvite:options.notifyActionInvite || true, + notifyActionJoined: options.notifyActionJoined || true, + notifyActionEjected: options.notifyActionEjected || true, + joinType: options.joinType || "free", + description: options.description || "No Description", + name: options.name || "No" + + }); debugger; } }; \ No newline at end of file