Merge pull request 'Stage 2 : Finish Alpha, Begin Beta' (#2) from alpha into stable
Reviewed-on: http://git.saqut.com/saqut/MWSE/pulls/2
This commit is contained in:
		
						commit
						30ea1fca8b
					
				| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
function Client()
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {string}
 | 
			
		||||
     */
 | 
			
		||||
    this.id = null;
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {import("websocket").connection}
 | 
			
		||||
     */
 | 
			
		||||
    this.socket = null;
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Date}
 | 
			
		||||
     */
 | 
			
		||||
    this.created_at = null;
 | 
			
		||||
 | 
			
		||||
    this.store = new Map();
 | 
			
		||||
    this.rooms = new Set();
 | 
			
		||||
    this.pairs = new Set();
 | 
			
		||||
    this.requiredPair = false;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @type {Map<string, Client>}
 | 
			
		||||
 */
 | 
			
		||||
Client.clients = new Map();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Client} client 
 | 
			
		||||
 */
 | 
			
		||||
Client.prototype.peerRequest = function(client){
 | 
			
		||||
    let info = {};
 | 
			
		||||
    this.store.forEach((value, name) => info[name] = value);
 | 
			
		||||
    this.pairs.add(client.id);
 | 
			
		||||
    client.send([{
 | 
			
		||||
        from: this.id,
 | 
			
		||||
        info
 | 
			
		||||
    },'request/pair']);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Client} client 
 | 
			
		||||
 */
 | 
			
		||||
 Client.prototype.acceptPeerRequest = function(client){
 | 
			
		||||
    this.pairs.add(client.id);
 | 
			
		||||
    client.send([{
 | 
			
		||||
        from: this.id
 | 
			
		||||
    },'accepted/pair']);
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Client} client 
 | 
			
		||||
 */
 | 
			
		||||
Client.prototype.rejectPeerRequest = function(client){
 | 
			
		||||
    this.pairs.delete(client.id);
 | 
			
		||||
    client.pairs.delete(this.id);
 | 
			
		||||
    client.send([{
 | 
			
		||||
        from: this.id
 | 
			
		||||
    },'rejected/pair']);
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Client|string} client 
 | 
			
		||||
 * @returns {Boolean}
 | 
			
		||||
 */
 | 
			
		||||
Client.prototype.isPaired = function(client){
 | 
			
		||||
    if(typeof client == "string")
 | 
			
		||||
    {
 | 
			
		||||
        return Client.clients.get(client)?.pairs.has(this.id) && this.pairs.has(client)
 | 
			
		||||
    }
 | 
			
		||||
    return client.pairs.has(this.id) && this.pairs.has(client.id);
 | 
			
		||||
};
 | 
			
		||||
Client.prototype.pairList = function(){
 | 
			
		||||
    return [...this.pairs.values()].filter(e => this.isPaired(e));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Client.prototype.send = function(obj){
 | 
			
		||||
    this.socket.sendUTF(JSON.stringify(obj));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.Client = Client;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
let http = require("http");
 | 
			
		||||
let express = require("express");
 | 
			
		||||
let compression = require("compression");
 | 
			
		||||
let server = http.createServer();
 | 
			
		||||
let app = express();
 | 
			
		||||
server.addListener("request", app);
 | 
			
		||||
app.use(compression({
 | 
			
		||||
    level: 9
 | 
			
		||||
}));
 | 
			
		||||
server.listen(7707,'0.0.0.0',() => {
 | 
			
		||||
    console.log("HTTP Service Running...");
 | 
			
		||||
});
 | 
			
		||||
exports.http = server;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
let {resolve} = require("path");
 | 
			
		||||
app.get("/script",(request, response)=>{
 | 
			
		||||
    response.sendFile(resolve("./script/wsjs.js"))
 | 
			
		||||
});
 | 
			
		||||
app.get("*",(request, response)=>{
 | 
			
		||||
    response.sendFile(resolve("./script/status.xml"))
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,151 @@
 | 
			
		|||
const { Client } = require("../Client.js");
 | 
			
		||||
let {addService, addListener} = require("../WebSocket.js");
 | 
			
		||||
 | 
			
		||||
addListener('disconnect',(global, xClient)=>{
 | 
			
		||||
    for (const pair of xClient.pairs) {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
addService(({
 | 
			
		||||
    client,
 | 
			
		||||
    message,
 | 
			
		||||
    end,
 | 
			
		||||
    next
 | 
			
		||||
})=>{
 | 
			
		||||
    let {type,username,password,to} = message;
 | 
			
		||||
    switch(type)
 | 
			
		||||
    {
 | 
			
		||||
        case 'auth/public':{
 | 
			
		||||
            client.requiredPair = false;
 | 
			
		||||
            return end({
 | 
			
		||||
                value: 'success',
 | 
			
		||||
                mode: 'public'
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        case 'auth/private':{
 | 
			
		||||
            client.requiredPair = true;
 | 
			
		||||
            return end({
 | 
			
		||||
                value: 'success',
 | 
			
		||||
                mode: 'private'
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        case 'request/pair':{
 | 
			
		||||
            if(Client.clients.has(to)){
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail',
 | 
			
		||||
                    message: 'CLIENT-NOT-FOUND'
 | 
			
		||||
                })
 | 
			
		||||
            };
 | 
			
		||||
            let pairclient = Client.clients.get(to);
 | 
			
		||||
            if(pairclient.pairs.has(client.id))
 | 
			
		||||
            {
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'success',
 | 
			
		||||
                    message: 'ALREADY-PAIRED'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            if(client.pairs.add(to))
 | 
			
		||||
            {
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail',
 | 
			
		||||
                    message: 'ALREADY-REQUESTED'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            client.peerRequest(pairclient);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case 'accept/pair':{
 | 
			
		||||
            if(Client.clients.has(to)){
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail',
 | 
			
		||||
                    message: 'CLIENT-NOT-FOUND'
 | 
			
		||||
                })
 | 
			
		||||
            };
 | 
			
		||||
            let pairclient = Client.clients.get(to);
 | 
			
		||||
            if(pairclient.pairs.has(client.id))
 | 
			
		||||
            {
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'success',
 | 
			
		||||
                    message: 'ALREADY-PAIRED'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            if(!client.pairs.has(to))
 | 
			
		||||
            {
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail',
 | 
			
		||||
                    message: 'NOT-REQUESTED-PAIR'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            client.acceptPeerRequest(pairclient);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case 'reject/pair':{
 | 
			
		||||
            if(Client.clients.has(to)){
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail',
 | 
			
		||||
                    message: 'CLIENT-NOT-FOUND'
 | 
			
		||||
                })
 | 
			
		||||
            };
 | 
			
		||||
            let pairclient = Client.clients.get(to);
 | 
			
		||||
            if(pairclient.pairs.has(client.id))
 | 
			
		||||
            {
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'success',
 | 
			
		||||
                    message: 'ALREADY-PAIRED'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            if(!client.pairs.has(to))
 | 
			
		||||
            {
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail',
 | 
			
		||||
                    message: 'NOT-REQUESTED-PAIR'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            client.rejectPeerRequest(pairclient);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case 'pair/list':{
 | 
			
		||||
            end({
 | 
			
		||||
                type:'pair/list',
 | 
			
		||||
                value: pairList
 | 
			
		||||
            })
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case 'auth/check':{
 | 
			
		||||
            let auth = client.store.has('user');
 | 
			
		||||
            return end({
 | 
			
		||||
                value: auth
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        case 'auth/login':{
 | 
			
		||||
            if(username == '*' && password == '*')
 | 
			
		||||
            {
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'success'
 | 
			
		||||
                })
 | 
			
		||||
            }else{
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        case 'auth/logout':{
 | 
			
		||||
            let auth = client.store.has('user');
 | 
			
		||||
            if(auth)
 | 
			
		||||
            {
 | 
			
		||||
                client.store.delete('user');
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'success'
 | 
			
		||||
                })
 | 
			
		||||
            }else{
 | 
			
		||||
                return end({
 | 
			
		||||
                    status: 'fail'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        default:{
 | 
			
		||||
            next();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
const { Client } = require("../Client.js");
 | 
			
		||||
let {randomUUID} = require("crypto");
 | 
			
		||||
let {addService,addListener} = require("../WebSocket.js");
 | 
			
		||||
const { Room } = require("./Room.js");
 | 
			
		||||
let term = require("terminal-kit").terminal;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Peer to peer veri aktarımı
 | 
			
		||||
 | 
			
		||||
- Kişiden kişiye direkt veri aktarımı
 | 
			
		||||
- Kişiler arası tünelleme / Kişiler arası özel protokol
 | 
			
		||||
- Oda katılımcıları içerisinde veri aktarımı
 | 
			
		||||
- Oda katılımcıları arasında belli kişilere veri aktarımı
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
addService(({
 | 
			
		||||
    client,
 | 
			
		||||
    end,
 | 
			
		||||
    global,
 | 
			
		||||
    message,
 | 
			
		||||
    next,
 | 
			
		||||
    response
 | 
			
		||||
}) => {
 | 
			
		||||
    let {type} = message;
 | 
			
		||||
    switch(type)
 | 
			
		||||
    {
 | 
			
		||||
        case "pack/to":{
 | 
			
		||||
            let {to,pack,handshake} = message;
 | 
			
		||||
            if(Client.clients.has(to))
 | 
			
		||||
            {
 | 
			
		||||
                let otherPeer = Client.clients.get(to);
 | 
			
		||||
                otherPeer.send([{
 | 
			
		||||
                    from: client.id,
 | 
			
		||||
                    pack: pack
 | 
			
		||||
                }, 'pack']);
 | 
			
		||||
                handshake && end({
 | 
			
		||||
                    type: 'success'
 | 
			
		||||
                })
 | 
			
		||||
            }else{
 | 
			
		||||
                handshake && end({
 | 
			
		||||
                    type: 'fail'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case "pack/room":{
 | 
			
		||||
            let {to,pack, handshake} = message;
 | 
			
		||||
            if(Room.rooms.has(to))
 | 
			
		||||
            {
 | 
			
		||||
                if(!client.rooms.has(to))
 | 
			
		||||
                {
 | 
			
		||||
                    return handshake && end({
 | 
			
		||||
                        type: 'fail'
 | 
			
		||||
                    })
 | 
			
		||||
                };
 | 
			
		||||
                Room.rooms.get(to).send([{
 | 
			
		||||
                    from: client.id,
 | 
			
		||||
                    pack: pack
 | 
			
		||||
                }, 'pack']);
 | 
			
		||||
                handshake && end({
 | 
			
		||||
                    type: 'success'
 | 
			
		||||
                })
 | 
			
		||||
            }else{
 | 
			
		||||
                handshake && end({
 | 
			
		||||
                    type: 'fail'
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default:{
 | 
			
		||||
            next();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,506 @@
 | 
			
		|||
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){
 | 
			
		||||
    for (const client of this.clients.values()) {
 | 
			
		||||
        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()
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
let {addListener} = require("../WebSocket.js");
 | 
			
		||||
 | 
			
		||||
addListener('connect',(global, client)=>{
 | 
			
		||||
    client.send([{
 | 
			
		||||
        type: 'id',
 | 
			
		||||
        value: client.id
 | 
			
		||||
    },'id'])
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,124 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
 | 
			
		||||
let websocket = require("websocket");
 | 
			
		||||
let {http} = require("./HTTPServer");
 | 
			
		||||
let {randomUUID} = require("crypto");
 | 
			
		||||
const { Client } = require("./Client.js");
 | 
			
		||||
 | 
			
		||||
console.log("Web Socket Protocol is ready");
 | 
			
		||||
 | 
			
		||||
http.addListener("upgrade",() => {
 | 
			
		||||
    console.log("HTTP Upgrading to WebSocket");
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
let wsServer = new websocket.server({
 | 
			
		||||
    httpServer: http,
 | 
			
		||||
    autoAcceptConnections: true
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let global = new Map();
 | 
			
		||||
let clients = new Map();
 | 
			
		||||
wsServer.addListener("connect",(socket) => {
 | 
			
		||||
 | 
			
		||||
    let xClient = new Client();
 | 
			
		||||
    let id = randomUUID();
 | 
			
		||||
    socket.id = id;
 | 
			
		||||
    xClient.id = id;
 | 
			
		||||
    xClient.socket = socket;
 | 
			
		||||
    xClient.created_at = new Date();
 | 
			
		||||
    Client.clients.set(id, xClient);
 | 
			
		||||
    clients.set(id, xClient);
 | 
			
		||||
 | 
			
		||||
    emit("connect", global, xClient);
 | 
			
		||||
    socket.addListener("close",()=>{
 | 
			
		||||
        emit("disconnect",  global, xClient);
 | 
			
		||||
        Client.clients.set(id, xClient);
 | 
			
		||||
    });
 | 
			
		||||
    socket.addListener("message",({type,utf8Data}) => {
 | 
			
		||||
        if(type == "utf8")
 | 
			
		||||
        {
 | 
			
		||||
            let json;
 | 
			
		||||
            try{
 | 
			
		||||
                json = JSON.parse(utf8Data);
 | 
			
		||||
                emit('services', global, xClient, json);
 | 
			
		||||
                let [payload, id, action] = json;
 | 
			
		||||
                if(typeof id === "string")
 | 
			
		||||
                {
 | 
			
		||||
                    action = id;
 | 
			
		||||
                }
 | 
			
		||||
                emitService(global, xClient, id, payload, action);
 | 
			
		||||
            }catch{
 | 
			
		||||
                emit("messageError", global, xClient, utf8Data);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @type {Map<string, Function[]>}
 | 
			
		||||
 */
 | 
			
		||||
 let events = new Map();
 | 
			
		||||
 /**
 | 
			
		||||
  * @type {Map<string, Function[]>}
 | 
			
		||||
  */
 | 
			
		||||
 let services = []
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {string} event 
 | 
			
		||||
 * @param {(global:Map<string, any>, client:Client, data:any) => any} func 
 | 
			
		||||
 */
 | 
			
		||||
exports.addListener = (event, func) => {
 | 
			
		||||
    if(!events.has(event))
 | 
			
		||||
    {
 | 
			
		||||
        events.set(event,[]);
 | 
			
		||||
    };
 | 
			
		||||
    events.get(event).push(func);
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {string} event 
 | 
			
		||||
 * @param {(data:{global:Map<string, any>, client:Client, message:any,response:Function,end:Function,next:Function}) => any} func 
 | 
			
		||||
 */
 | 
			
		||||
exports.addService = (func) => {
 | 
			
		||||
    services.push(func);
 | 
			
		||||
};
 | 
			
		||||
function emit(event,...args)
 | 
			
		||||
{
 | 
			
		||||
    if(events.has(event))
 | 
			
		||||
    {
 | 
			
		||||
        for (const callback of events.get(event)) {
 | 
			
		||||
            callback(...args);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {Map} global 
 | 
			
		||||
 * @param {Client} local 
 | 
			
		||||
 * @param {number} id 
 | 
			
		||||
 * @param {{[key:string]:any}} payload 
 | 
			
		||||
 * @param {"R"|"S"} action [R]EQUEST flag or [S]TREAM flag
 | 
			
		||||
 */
 | 
			
		||||
async function emitService(global, client, id, payload, action)
 | 
			
		||||
{
 | 
			
		||||
    let willContinue = false;
 | 
			
		||||
    for (const callback of services) {
 | 
			
		||||
        await callback({
 | 
			
		||||
            message: payload,
 | 
			
		||||
            action,
 | 
			
		||||
            client,
 | 
			
		||||
            global,
 | 
			
		||||
            response:(obj)=>{
 | 
			
		||||
                client.send([obj, id, 'C']) // continue ([C]ONTINUE flag)
 | 
			
		||||
            },
 | 
			
		||||
            end:(obj)=>{
 | 
			
		||||
                client.send([obj, id, 'E']) // stopped data stream (this channel) ([E]ND flag)
 | 
			
		||||
            },
 | 
			
		||||
            next:function(){
 | 
			
		||||
                willContinue = true;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if(willContinue === false) break;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
exports.websocket = wsServer;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
require("./HTTPServer.js");
 | 
			
		||||
require("./WebSocket.js");
 | 
			
		||||
 | 
			
		||||
require("./Services/YourID.js");
 | 
			
		||||
require("./Services/Auth.js");
 | 
			
		||||
require("./Services/Room.js");
 | 
			
		||||
require("./Services/DataTransfer.js");
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "mwse",
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "description": "Mikro WebSocket Engine",
 | 
			
		||||
  "main": "Source/index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1"
 | 
			
		||||
  },
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "http://git.saqut.com/saqut/MWSE"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "WebSocket",
 | 
			
		||||
    "server",
 | 
			
		||||
    "microservice",
 | 
			
		||||
    "ws"
 | 
			
		||||
  ],
 | 
			
		||||
  "author": "Abdussamed ULUTAŞ <abdussamedulutas@yandex.com.tr>",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "compression": "^1.7.4",
 | 
			
		||||
    "express": "^4.18.2",
 | 
			
		||||
    "joi": "^17.7.0",
 | 
			
		||||
    "knex": "^2.3.0",
 | 
			
		||||
    "sqlite3": "^5.1.2",
 | 
			
		||||
    "terminal-kit": "^3.0.0",
 | 
			
		||||
    "websocket": "^1.0.34"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<Server mask="255.255.255.255" of="55.67.239.30" to="0.0.0.0">
 | 
			
		||||
    <unit name="sdp://74.50.50.1" cluster="0">
 | 
			
		||||
        <service md5="cbaaa7f6899508277f0a1579b2c869b9" checkout="true" status="verified">
 | 
			
		||||
            304e17d899c067afe3c439f92c5bd29869dfbde06ceca4f65e2128a93e7366c078bb0af1b14cb516008683df14c00107cadcce4d980f149da932bdba4a9892841b004ae877b2c7733e2d326d5586a4b2225384afbab6d00900852f5feb47ebd234ad95ec6957dbbbe585c86126f02e47cff3dc6d4ac9d321efbbd39a28655110fa850caca24a9b82984b60d0c517f3d8400594a17ba4a5fd0fcf19711c4b0ef503d698e8597c841eb099118d23e8cc8bc4db907d5093ca73e68f85c3dae629876b5d2fa49ed283591972559ae7da1b99489715c355f068c689c7cb4690ea4d4f3d1f364367b352bd74a0a57a541bfac90fe69d9b6d2fe941707c7316b067f5287c6aff01c86cbb23c2ac5e202cb497c203437c80159454b20dcadec7f2080b286c6b4e76826952b963f856e3c7e9ad89e50b76e2dd22f7bc1fbc8366a965cb76dc862d5c9b5b52802e431d2490079a7396db1bc279099a68a9b250135020e3906bc8c159b773a7dbb3d5beb54a30746fc776a07aeeb4f1f170d688b15742f530901959e4a4cd8ed869249e95a58c3cc45bc323aee66aec9090f4c938411c348b72d6ae4c3169c0049d84e88f4c26318136353db77519d7c060df07f7da78df71f13ed7be5916b2973fa712b4d826527911a04923dc763630306d04e09229584370e670c24d6af48bee2ead5525f55b8bd66083314b11d1dede385bfbdcaacd0b577c1d37f7bbcbb8d24f461723713570537abbcdfd8b96c9dcfc0f8c0afd7e6e43620dbfb88442cacd15a6cb28ee2d318e56d023af40a832010e8e2cb1d2d5223fd465616094fca6fecd92ff66e398f3ac8ac091beb3635dfacea5147848514c94ecdc011180cda61f977f9c59265682c77dc7af662e1c50386ac50835c6802adb86f941ec3d05316e505f7183bf8dc048685af48f6514726d993e789cfe13e28bd0ce7ea0e0e97f19364ec8567e63f28afcdf07de02fa8c79a850ffc4e9da3786ecec269aff4995ed5e3029a2abcba65ba62990e0842714688ca48162510449ebaf80878405c7373534aa6e688f4c56569f15e349272db874f3846eb3cea77f8de261628c285e93ff7c38aba3d65df19bf8dd8732068e817e90b92ddf7be29ec844f6b90af2547daf00093e4f2241c2d419e60c9b6d64e4278a2baa0a0396e98ccafebd131a6c5374baaf4a3e715c6c4d2c647de1cd0610cf9bcf3a9e857bef77a0ed51dc297ba914a5c41c5243cc72308b9de7908ec8955b52c9328b1d8efdd28d570c010f1329b7f0c40669004dc77cc87a34348310f444ba799fb0ed00687ae16681dc7eab7d1884213b59ff1e2e3cefa64676587fd37db308f4de795cb46b468948e38d340981e32ca2222cbff42bc3852f1657a690bfba8b918cfa05bd3f6a7bbc284e15ba39b114e8619b666748325e0b87514eff2b48a70ea906ca0ecf6f0f2c6438ffa8106239b94231f008339069fd420978e0f8103c94fcfb8a48018353443e8cbe131dc4261ef79945471cdaec9868954e420f4f43f738c098e3ee3378c5890409bc9e4f191e85f3318c5a125f48e8bc3a9eb67f413742c2b030847b3461bd9659e62af48aca3a456ae4f99beacd3df943608956fbadde816733edc9f2a107a8d92592b9690aeb69b48bfd9231ba345f1c71dcb6aae1c13cf1ab91942938db056a1569c6399cf57dd02032ef43f64f6e3ee3de56d11b7dc07f3bdb899714b912d6e8a45be8e2f6413ad51ef93aa75c5bd9c4a8c86231b31a5e68892bc8ec01de40db5d6ec87160c8d5d14b3725160407a9a7484bb794dee73e0a87cceeb874ed6d96941406f18b8634886c23d5be'
 | 
			
		||||
        </service>
 | 
			
		||||
        <service md5="cbaaa7f6899508277f0a1579b2c869b9" checkout="true" status="verified">
 | 
			
		||||
            304e17d899c067afe3c439f92c5bd29869dfbde06ceca4f65e2128a93e7366c078bb0af1b14cb516008683df14c00107cadcce4d980f149da932bdba4a9892841b004ae877b2c7733e2d326d5586a4b2225384afbab6d00900852f5feb47ebd234ad95ec6957dbbbe585c86126f02e47cff3dc6d4ac9d321efbbd39a28655110fa850caca24a9b82984b60d0c517f3d8400594a17ba4a5fd0fcf19711c4b0ef503d698e8597c841eb099118d23e8cc8bc4db907d5093ca73e68f85c3dae629876b5d2fa49ed283591972559ae7da1b99489715c355f068c689c7cb4690ea4d4f3d1f364367b352bd74a0a57a541bfac90fe69d9b6d2fe941707c7316b067f5287c6aff01c86cbb23c2ac5e202cb497c203437c80159454b20dcadec7f2080b286c6b4e76826952b963f856e3c7e9ad89e50b76e2dd22f7bc1fbc8366a965cb76dc862d5c9b5b52802e431d2490079a7396db1bc279099a68a9b250135020e3906bc8c159b773a7dbb3d5beb54a30746fc776a07aeeb4f1f170d688b15742f530901959e4a4cd8ed869249e95a58c3cc45bc323aee66aec9090f4c938411c348b72d6ae4c3169c0049d84e88f4c26318136353db77519d7c060df07f7da78df71f13ed7be5916b2973fa712b4d826527911a04923dc763630306d04e09229584370e670c24d6af48bee2ead5525f55b8bd66083314b11d1dede385bfbdcaacd0b577c1d37f7bbcbb8d24f461723713570537abbcdfd8b96c9dcfc0f8c0afd7e6e43620dbfb88442cacd15a6cb28ee2d318e56d023af40a832010e8e2cb1d2d5223fd465616094fca6fecd92ff66e398f3ac8ac091beb3635dfacea5147848514c94ecdc011180cda61f977f9c59265682c77dc7af662e1c50386ac50835c6802adb86f941ec3d05316e505f7183bf8dc048685af48f6514726d993e789cfe13e28bd0ce7ea0e0e97f19364ec8567e63f28afcdf07de02fa8c79a850ffc4e9da3786ecec269aff4995ed5e3029a2abcba65ba62990e0842714688ca48162510449ebaf80878405c7373534aa6e688f4c56569f15e349272db874f3846eb3cea77f8de261628c285e93ff7c38aba3d65df19bf8dd8732068e817e90b92ddf7be29ec844f6b90af2547daf00093e4f2241c2d419e60c9b6d64e4278a2baa0a0396e98ccafebd131a6c5374baaf4a3e715c6c4d2c647de1cd0610cf9bcf3a9e857bef77a0ed51dc297ba914a5c41c5243cc72308b9de7908ec8955b52c9328b1d8efdd28d570c010f1329b7f0c40669004dc77cc87a34348310f444ba799fb0ed00687ae16681dc7eab7d1884213b59ff1e2e3cefa64676587fd37db308f4de795cb46b468948e38d340981e32ca2222cbff42bc3852f1657a690bfba8b918cfa05bd3f6a7bbc284e15ba39b114e8619b666748325e0b87514eff2b48a70ea906ca0ecf6f0f2c6438ffa8106239b94231f008339069fd420978e0f8103c94fcfb8a48018353443e8cbe131dc4261ef79945471cdaec9868954e420f4f43f738c098e3ee3378c5890409bc9e4f191e85f3318c5a125f48e8bc3a9eb67f413742c2b030847b3461bd9659e62af48aca3a456ae4f99beacd3df943608956fbadde816733edc9f2a107a8d92592b9690aeb69b48bfd9231ba345f1c71dcb6aae1c13cf1ab91942938db056a1569c6399cf57dd02032ef43f64f6e3ee3de56d11b7dc07f3bdb899714b912d6e8a45be8e2f6413ad51ef93aa75c5bd9c4a8c86231b31a5e68892bc8ec01de40db5d6ec87160c8d5d14b3725160407a9a7484bb794dee73e0a87cceeb874ed6d96941406f18b8634886c23d5be'
 | 
			
		||||
        </service>
 | 
			
		||||
        <service md5="2836d60b7e3a9a41f75d662d63c65197" checkout="true" status="verified">
 | 
			
		||||
            35b9ea4d98445d6fda595e42c921269e0699ed78a2655b169fcaffef66521e649c1818cb71fb5a2ef4c0f453786de08c9cf81cdc4f6bcb8a7af68f23d9a4b3dc4a84950a13261e929ef4aa41a53819b8fc98582250533b476e46d6d0894a0003f315a6d8612d2d46eebe367330ae4587635daa4d4b929a33de91dfdb30b1aa3b3bded0ab06741786b19564b74e3b0d86a3d056bf9662fa004d2609a62b862caea7f6448a5257689a0de69798ac8eaf6186799c1e5de7ba124493365a76ded957d0f133190608b49c940cd7db6bdcb6b98739dfe6dc44b4736ebb10a3563ac7a4c6c63177cb292f32be6be3041cd2c27fef4c9f3ec2005041f80df142941185c57114d90500c744d9442f4507e43c91bbacc4205ee8dc4dbbc0656a6bf948309c446ffc67844f8e49171672ebbf380368979e20dcd114f889936f5a6ae0fdc393d4292659ee8f00070c0b6f01781e9c9d64a1ef7dc43912de5666d2f3495d9e4f3b478621e0e7e2ade6728e2de162aaf06b000ef5c514ba8004ed28cdbb9893a2b9e024a3ffedf98b920ff94130f31775cbc6a4f7a0dd361c40a8ca0956bf7fa4047bb00ab51c8aae729cce29a5427b8f80e28fcd5163a6f070c654ef05b85671fd1c9a2a2e52e8737b3cc04d6e95e0690b5e7d711b23c45a065f66193ab6d070a23efa56654f9427ec05e4e32a79e43869db3f3b61ca5c4fed04f2926e4bdb99367b15068868d0fb232359ea3c9e9c64150b4058a592b59943a7e23b69813bf08e637cca4ea1e5e996613a1db2684386a5e5f0dcbbea04b312f69512bab31db7e9ef94b6905a79690193f7289851821d135974af3696a2e0439585a960b3d8a31568bca8de1271a14cd30ed1a7b85adf23f9d76f5b62149242296677fea61288a4b58d2e38889b2bbd9926672df36b46fd6463bf0c0f7b1db1a7312071ef8e39a5a7deea4e47b6cda985de2cc5133337633b3db6d0cb54d56795a8a76fef2edf93debc3cbea6d0b902a0c710e73fa443c4842ec1646b4934514d2700eaa48b1320f0477f992ac0849bfd1fa628fd2b85a8997c5c2128bcb8135718b62180ede1ee8d4bf375c053a5419173b6adf54ab6f5943a377f7b2b4b81807fba3460b6acd28ef6d4be2bb015d9d5ea9b3c2d22389d7c688dd871e0a9fe295389711bf19804eeb68a86204fed691fe7493db9e0d2bce3f6ae4df5debe4cd4b4c668111146dc82b638080da9fa26d47d00f37538ee2a4a2930d39d1f734537a79d0d34f6cfab7893e2ebe71ec110e82f63a7d5c246737acadf1fc75c777faaa150164fc204c7ad3261fc9563a1b5bac908905d53fb7f37060dc1c5750634b200b31fbfaf4de1fefbc687b4c808968065e9cb567f003e1bd06b77e6442a7e5d3f54ff98d12d03d3f8705fdeb9614764acd153fc15a8763e5dc95e2c19ce478b8da93744680f2bfca75e50bebabc0cbb609575bb1000141dc35da3f4b5f5141899ff9ef856b7904baf5a828d2e4f576a58e505c5c9f3e5bc0e1ecfaf4ef36a7dee842aaf155db65912cd8dfccd11b188f5da5ea10035d47f17e0996791b0aacefa38799708198e4780846c7b1cf60aa9172a3a5935338bdb71e33a20b96181e487dd5caeeeff0446bc945ac2a83c3ca0ded82939d314073711c2afce8cc2a94fc6d46e28ea5bbafcd6adb517dedc9fa99446be727718bc41122214b6b788641e785ba0bee6530c61f9a9a58c14c6a5c477fd6f2117c67b1a2d8ca20e3aaef5c2d4e1f2b525fc8516ac90654880e72b4d87916e76592691d154a89f4125db6d9635e553955797f77ec00797f130f43b6245bf97cb88c93a641c78cbc352cf4816b075c81133cff364f4120399e3beb8cb6aae362a33de38c0d14d919a1939c00d46cd7ec2368620378fa490dd7d746361df3ee49b19a848035f619b08a66271fd97cb805ec834d85355495f003e39b6284b1ff52d5fbfeca7f7676f2a780c0b649abe6574da5867b2949af7d01e33fb9ffdee
 | 
			
		||||
        </service>
 | 
			
		||||
        <service md5="ae9965260e1098a43543a9a79fa1dac4" checkout="true" status="verified">
 | 
			
		||||
            2fe9827b8286e6e5329cc9af2bb910f78b57ec5d073e6ef061360d7103f1c38245e04f804f431da3b51887a6e16cfee2a69534f4ab8ded57b74029beba7ccd2347b962d8f0d2d8bc93771a52ef4688fdf9759d33be9ba18b306a86fd3848b8ca8fe61abdd9ea2b9018c31554122172488166517e67276384b23239f02f4b5c4d8290dc542f0af54a66874a34bb8cd6114d7d5bce7404294f2c41ec20b85b942e5dca6e7b94fdea2e3dcd98777d0d7b631246c2222b69bd0547dc56d4c7990e0f803c32593cd218f4d655998ddd90e488a5d12aa7ef868f7641dc6a6df0bb0abfc0e76335ab682d909d117d863c90f6e21ebdb3ff8a7126bde4b14c524afe31fad20d1b000356cd25ab2276a2adf82638a5bf20304e2ad1a2f28cd7490e818fbec3566514cdeb412ce63d421738b038e40f70073d50352307de058b40d6215f6af1ce09cf6b954be69c7ef0f8030449a20faa5c2dd9b8d0f1adf911371156dc65b470fb02a092975af2f9080f689dd56cc3cd0f4c0434537e32b80cc22bd19743b1145d713422fed681b3bb9a09c5e5ec8f739631a8ffe2a49cebb2f13634b9414da48845948473616f18814447f63e018015b3d385cc592cc80c1f5a54d04029766572be94991d3e96b952961813e35fdc3f86a2d5fbfcd48a12d531be0f68ffcf48f1eb0e14623a6cfbdea026dd766fab9bbbc442468e6559d934959f0643c963c0799fcc3f98ccfa38b84cfb962a77cf5008037c81934425f0d7f4d1b7093adfc109918a7a130f1b3d5073a1517ac1c5b6dfe962917c8704e0d94d68795355f4ac341f84124bddd270c1da1a180ca101fb5f0bd4204e11461dce54d96827dcaa3e3a3889a621734268cf27ac18fe8c0df23108f3ef949fc68fd54122f3ea5fd5b2131b1ab7264006d730a7c4b90493b6858d78f5dfcf2cddbe8f1aa2b5da1de0580862ae16e01c5bce21a537ce0a8dfec8379c5152c0a3af41e09b7d35ed8920ad0f01abf1495b6e3e565cc8734ecf22a839212626f8f46cee5b90a742d43c2de2a280a2b1ffbe7e8285d8632bbf1137c28590f0420b3e75809c71f8b0e9bd8ed8995ba81003386d9c206ff5d3db014353d9565d1ac859f9335146cd3d2c8a04b4c3a1e7fa2e37a067ea2cb8fd4826010ecc5915114301fd7b2c7c72a222618c424797fe690cc9af7bc0a609c17a1dc8de4603df8ecbcc08d09bdd382b440fb29d11c822319b8572727592915fdcc1d9f7f3a025bec58834d5c55505c8013a5daf7f6e312b509500c2adc8e664c56031666124ab3f997185ae4f9286e7e6760ca8a106484fc68e50da961819fce231fca20330729b80be767c75006b1f8b796d60da0636d8fec8bd4c69671a8681f11f21f2b104ab4961c50b50f6b2b5fc59f78b674de891652d91f32ba60deaabf554158a38989438b67fb1713a14b4a2b58843a8b2df832d0969309f23ce4e07504a6be8aba05d8d79af93f41cf70fe0ef7b20ad603e4bd4c9261b0c3a007b46663c7b16ad77e8b8029d4d76fc4941205134d4cc9b7b7f9cc961ce5651afe8b308fa982cfdd57e9c21e1c65b2e906001f98f63cea267c44032c28348655541242b2092bfe8cb521bc491d376d989baa75c8f3c588089956f0736a71245c7acf0654dbaaad04803f8d8096d9a2c786a4c96ab99e4bc38119171839b718cffba4f8503d624cdfc2209597505da520878e2a10a9a7b0cd1e54ca09cf8c1ec9898735ea38e0886c940d8390d4581d205e4d9eb03d280071c6428bcee962122094af10e3784150651c5c4a583d8814c9e14810e021c659bf8cb66f82141d9a87248b20cba5cc212b2c0cd6dd085e690e549b31376babf1f3b19581956e26b1107f198975502
 | 
			
		||||
        </service>
 | 
			
		||||
        <service md5="7c72644fd27fad50a9a932107bbf9321" checkout="true" status="verified">
 | 
			
		||||
            74f4e96bbc0f84de1f5f481c7e3257243edfcec173663557469a90ef23c067b8606774b867568db7dfe5209aada24cc96fcf93faa7a95180ffe2a20154898e82b27e27d01358c42c1857f9bdcb4a83ee1709226e2fc117c3a93cd3cb139777fc9295b4602c5d7c76f5a47d2f574009959a274f49d36e7435ed61e755768551859dfc34277df224c2dc9365112b9459e26235e7ce8619d0d1457954de93f604a200b0e9368b53aa7a3313b4f020d28625c646b6ca7bc6feadc7297b5a51f7eeb77adb016f346ca3bd2f4878a92dcef4abad57b0e5c8a10087f717e813892f55ed4e248cc6bdf0ea1edcaa1d42adbec7d2832c184d2185fdb66f0ee0043ce0a6035a151b22d81eafcf4cf9a8ce962e6ce1f6571b6df0a86dc221b770974f5bbfe960efbfb8c148f590584fbefae0b5cb95545bf69e4e5c9a39b431155649468ef093127aba424654ee0bb46d194ad17e61b022c43671dfef0eae98ec6ba6cd2924279fb592ac1d3babb61c0befddf6521efc38d4d75007499864cced0c8ee14cb5fd37b79626f636753b585ccfcf583ee92784850f0cacd3389c3546827bf3e7ef2719b25819f0208dcf7c3493edfc048a6fa3d35511e6f9c2a367cb87ad74c0f41bf853b6ba35f51fadfd5632012bde0180585dff6a7f114a2a958ce7ecbe484304dba2f138a86c4b0aad337c0047b1cb9dcd358300155d09b72d6c0ed4c53778e86fbdc7674d37a1441c1d38bf8f4c9976540090575863c41a8211c9b9450f28edd514ed8d51e3565de2fd30d6f0bf02fcfb79319ff04e7c4ad452be372fd9fe64021c8af6e566076b78f9c6d553848f9e1bf2ad3955c94470cfb8b6ba4aa81c53acc82395870ef9677b634515c4e4bd8de971292c3aa0c150516c546f8b172613dd1e314d4caf46ebd1770bb0d73b6ea2d53969
 | 
			
		||||
        </service>
 | 
			
		||||
        <service md5="5ca1eafd97774b352b06fe6a5f2d537a" checkout="true" status="verified">
 | 
			
		||||
            8305f8dd0b412a4b95ef71a44a2cb069890895668054436640b9dffd2b0db4d84e21788b2cf8d609eb95773b4ef3c926335bc93bdc4df971a95f4c4c68ee627402c8935aa619ad276a6994004babf0bb41a8ca9fd5d22e76ec68f662eeb449ad9cdbd37f78b52fb88101e86a54323be7cd4777fdab26ada1cbba1ff0b08b6f451aa45daadcf9ef35a0a24039f2b1d5ca88ad64057afdbfe7c2bba6033380208d8d7c6c5f822a5f8bf19b5ee4f16efe068bd914d2e6ca831af24d53fa330c5d8150eb52fdd29518c14ecbc9af7e68a6e5f7a866150ecccf2b598fd6ba78521d5fe815797a6430ea09d6b394abe630df9510046bc983d9c41e3213868aeaa9dd531220959dd6305a1c582bde1a3e01d9b4be5d09150c1f4a84aa35c995103e6c6e3b6bb5345759b2219a31d05626373653d8151130ba1abef2aa78494f736792c0a462f54ebd86d2a110ae27ad1ad413baca5c7c4a302252da1423e44647cd57399e92cc805a60ce43bbda7a74797be1ad0275c0b270be2f117bd9ccc07bee42e247d178b15ba233535bffbb1c641cdd9ca3d387985b7980e8aed9752dbe30313f8a4b911f0ac7d8fd9352e3be80cdfaa3fd20ac5771c9f918d456b88cd3155de13a3f71bbcc627e766a16a86cac17f83fde98a2e7742bee4144ad235e23c76459522a96812819585155b6fcb6a08dafb19ccf93de37cb6738c803f73b0876e583d47ea3c59d6aba7b168c8de113a8f04cb813847a79682574ee71f6d9a5987b5e3be7169bf3bd82fbda4f5899e31b55b8c257f989516bbc879e60a49a4c5370230b4f728dd9389a059b481ea61046230e1382046955dae636005db580ec4a0540ee382fbc74b78b00ec821a19778fc119b673496df2bc2f11639922141d4cbb8fe22833f6fb210801419ab1b43f77a2baf892055a41c57e529026d4784aad469a4d2b530e81780f2aaea6b37b44243fa0f5e60a99391c6cf2e8a0f263a38566bfa56f761684b754bb8fa95aed80511332bff4673d600b6a09852cba34faca550f0652241a9eb04ca9652c4a19c20d37d6772604c3bbd34c82fa913a1a95e33b811f33952f4618d3600165574e29c44f7f748c05f90105791906e5920b356c4a8ce133dc2a809f0e47aa0ca2537a09688d2b92d49fa6b1f25165b6d2905ea99d1d6d56b2c39b08167df0cf18ce0f593228abbdbba718f05de4700ffa54067cb01fa65d9e378c0bc88d5f4d7bc153db964920e3c0dab30c02e87d957c369c6492bba7b240c27541d80bf0d39cbe62c077bdf6b1fb252d470cc39f9e43a13a95c08788dd5fe4e62fa3b07be01496bc977c6e1fe62977a2a8b72197fb3ad812369e96e3dd2ad329f134aaa4baee81c2ebd1b2c39e55fd9afcc554633ffb04a827ac75f7b26e49bcc341ea062ba13f672cd0a1dfd79a093273a5187e3adb6ece0e460555dc872c49b95e5054ffb772a4301feecdb072d6087345814b4af54de55d413eee562be25741a041bbdcb2850281
 | 
			
		||||
        </service>
 | 
			
		||||
        <service md5="a35aeef398a27556cf9e08aa6e25650c" checkout="true" status="verified">
 | 
			
		||||
            8305f8dd0b412a4b95ef71a44a2cb069890895668054436640b9dffd2b0db4d84e21788b2cf8d609eb95773b4ef3c926335bc93bdc4df971a95f4c4c68ee627402c8935aa619ad276a6994004babf0bb41a8ca9fd5d22e76ec68f662eeb449ad9cdbd37f78b52fb88101e86a54323be7cd4777fdab26ada1cbba1ff0b08b6f451aa45daadcf9ef35a0a24039f2b1d5ca88ad64057afdbfe7c2bba6033380208d8d7c6c5f822a5f8bf19b5ee4f16efe068bd914d2e6ca831af24d53fa330c5d8150eb52fdd29518c14ecbc9af7e68a6e5f7a866150ecccf2b598fd6ba78521d5fe815797a6430ea09d6b394abe630df9510046bc983d9c41e3213868aeaa9dd531220959dd6305a1c582bde1a3e01d9b4be5d09150c1f4a84aa35c995103e6c6e3b6bb5345759b2219a31d05626373653d8151130ba1abef2aa78494f736792c0a462f54ebd86d2a110ae27ad1ad413baca5c7c4a302252da1423e44647cd57399e92cc805a60ce43bbda7a74797be1ad0275c0b270be2f117bd9ccc07bee42e247d178b15ba233535bffbb1c641cdd9ca3d387985b7980e8aed9752dbe30313f8a4b911f0ac7d8fd9352e3be80cdfaa3fd20ac5771c9f918d456b88cd3155de13a3f71bbcc627e766a16a86cac17f83fde98a2e7742bee4144ad235e23c76459522a96812819585155b6fcb6a08dafb19ccf93de37cb6738c803f73b0876e583d47ea3c59d6aba7b168c8de113a8f04cb813847a79682574ee71f6d9a5987b5e3be7169bf3bd82fbda4f5899e31b55b8c257f989516bbc879e60a49a4c5370230b4f728dd9389a059b481ea61046230e1382046955dae636005db580ec4a0540ee382fbc74b78b00ec821a19778fc119b673496df2bc2f11639922141d4cbb8fe22833f6fb210801419ab1b43f77a2baf892055a41c57e529026d4784aad469a4d2b530e81780f2aaea6b37b44243fa0f5e60a99391c6cf2e8a0f263a38566bfa56f761684b754bb8fa95aed80511332bff4673d600b6a09852cba34faca550f0652241a9eb04ca9652c4a19c20d37d6772604c3bbd34c82fa913a1a95e33b811f33952f4618d3600165574e29c44f7f748c05f90105791906e5920b356c4a8ce133dc2a809f0e47aa0ca2537a09688d2b92d49fa6b1f25165b6d2905ea99d1d6d56b2c39b08167df0cf18ce0f593228abbdbba718f05de4700ffa54067cb01fa65d9e378c0bc88d5f4d7bc153db964920e3c0dab30c02e87d957c369c6492bba7b240c27541d80bf0d39cbe62c077bdf6b1fb252d470cc39f9e43a13a95c08788dd5fe4e62fa3b07be01496bc977c6e1fe62977a2a8b72197fb3ad812369e96e3dd2ad329f134aaa4baee81c2ebd1b2c39e55fd9afcc554633ffb04a827ac75f7b26e49bcc341ea062ba13f672cd0a1dfd79a093273a5187e3adb6ece0e460555dc872c49b95e5054ffb772a4301feecdb072d6087345814b4af54de55d413eee562be25741a041bbdcb2850281
 | 
			
		||||
        </service>
 | 
			
		||||
        <service md5="af214a23bf109f45d423f0fdcad927f4" checkout="true" status="verified">
 | 
			
		||||
            fb7d736ac8ee7a3a74f952551e8856a34c05d564ee0408ca0eea47a86e833b5b30a732b9d3ca954e56231908beb9022aec02c756c3e075bd9086f9ade0381b953185b4d3ed332d5c4b454abe5d2ccbeb48b473f956d543bde4f48c1978f129da7bf481b629a8da09941e5ea66d069617f6c82ac3a685b9e6cf75d8d4faf0b2ca0b52df201372c6e2d08dbcb2b724da94c733590165282e6faefd7bb89bb5be76d452f84b50a557d4b0f45192fc562f3682aba1098818ed3ddfaf72181d66f1f7b59be2b31759b66c
 | 
			
		||||
        </service>
 | 
			
		||||
    </unit>
 | 
			
		||||
</Server>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,194 @@
 | 
			
		|||
function WSJS()
 | 
			
		||||
{
 | 
			
		||||
    this.isActive = false;
 | 
			
		||||
    this.ws = WSJS.activeWS || null;
 | 
			
		||||
    this.endpoint = null;
 | 
			
		||||
};
 | 
			
		||||
WSJS.activeWS = null;
 | 
			
		||||
WSJS.prototype.connect = function(url){
 | 
			
		||||
    this.ws = new WebSocket(url);
 | 
			
		||||
    this.addListeners();
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.addListeners = function(){
 | 
			
		||||
    this.ws.addEventListener("close", this.closedEvent.bind(this));
 | 
			
		||||
    this.ws.addEventListener("message", this.messageEvent.bind(this));
 | 
			
		||||
    this.ws.addEventListener("open", this.openMessage.bind(this));
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.closedEvent = function(){
 | 
			
		||||
    WSJS.activeWS = null;
 | 
			
		||||
    this.isActive = false;
 | 
			
		||||
    this.ws = null;
 | 
			
		||||
    this.events.dispatchEvent(new Event("close"));
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.openMessage = function(){
 | 
			
		||||
    WSJS.activeWS = this.ws;
 | 
			
		||||
    this.isActive = true;
 | 
			
		||||
    this.events.dispatchEvent(new Event("open"));
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.messageEvent = function({data}){
 | 
			
		||||
    let [payload, id, action] = JSON.parse(data);
 | 
			
		||||
    if(typeof id === 'number')
 | 
			
		||||
    {
 | 
			
		||||
        if(this.requests.has(id))
 | 
			
		||||
        {
 | 
			
		||||
            this.requests.get(id)(payload, action);
 | 
			
		||||
            switch(action)
 | 
			
		||||
            {
 | 
			
		||||
                case 'E':{  // [E]ND flag
 | 
			
		||||
                    this.requests.delete(id);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case 'S':  // [S]TREAM flag
 | 
			
		||||
                default:{
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }else console.warn("Missing event sended from server");
 | 
			
		||||
    }else{
 | 
			
		||||
        if(this.signals.has(id))
 | 
			
		||||
        {
 | 
			
		||||
            for (const callback of this.signals.get(id)) {
 | 
			
		||||
                callback(payload);
 | 
			
		||||
            }
 | 
			
		||||
        }else console.warn("Missing event sended from server");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.events = new EventTarget();
 | 
			
		||||
WSJS.prototype.scope = function(func){
 | 
			
		||||
    if(this.isActive)
 | 
			
		||||
    {
 | 
			
		||||
        func();
 | 
			
		||||
    }else this.events.addEventListener("open", func);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
WSJS.prototype.sendOnly = function(obj){
 | 
			
		||||
    if(this.isActive)
 | 
			
		||||
    {
 | 
			
		||||
        this.sendRaw([obj,'R']);
 | 
			
		||||
    }else throw new Error(`socket could be a active`);
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.requests = new Map();
 | 
			
		||||
WSJS.prototype.requestCount = 0;
 | 
			
		||||
WSJS.prototype.request = async function(obj){
 | 
			
		||||
    if(this.isActive)
 | 
			
		||||
    {
 | 
			
		||||
        return await new Promise(ok => {
 | 
			
		||||
            let id = ++this.requestCount;
 | 
			
		||||
            this.sendRaw([obj,id,'R']);
 | 
			
		||||
            this.requests.set(id,data => {
 | 
			
		||||
                ok(data);
 | 
			
		||||
            });
 | 
			
		||||
        })
 | 
			
		||||
    }else throw new Error(`socket could be a active`);
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.stream = async function(obj,callback){
 | 
			
		||||
    if(this.isActive)
 | 
			
		||||
    {
 | 
			
		||||
        let id = ++this.requestCount;
 | 
			
		||||
        this.sendRaw([obj, id, 'S']);
 | 
			
		||||
        this.requests.set(id,data => {
 | 
			
		||||
            callback(data);
 | 
			
		||||
        });
 | 
			
		||||
    }else throw new Error(`socket could be a active`);
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.signals = new Map();
 | 
			
		||||
WSJS.prototype.signal = async function(name, callback){
 | 
			
		||||
    if(!this.signals.has(name))
 | 
			
		||||
    {
 | 
			
		||||
        this.signals.set(name, [callback]);
 | 
			
		||||
    }else{
 | 
			
		||||
        this.signals.get(name).push(callback);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.slot = async function(name, obj){
 | 
			
		||||
    if(this.isActive)
 | 
			
		||||
    {
 | 
			
		||||
        if(typeof name == "string")
 | 
			
		||||
        {
 | 
			
		||||
            this.sendOnly([obj,name]);
 | 
			
		||||
        }else{
 | 
			
		||||
            throw new Error(`name could be a string, gived ${typeof name}`);
 | 
			
		||||
        }
 | 
			
		||||
    }else throw new Error(`socket could be a active`);
 | 
			
		||||
}
 | 
			
		||||
WSJS.prototype.sendRaw = function(obj){
 | 
			
		||||
    if(this.isActive)
 | 
			
		||||
    {
 | 
			
		||||
        this.ws.send(JSON.stringify(obj))
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
WSJS.prototype.authWith = async function(username, password){
 | 
			
		||||
    await this.request({
 | 
			
		||||
        type: 'auth/login',
 | 
			
		||||
        username,
 | 
			
		||||
        password
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.fetchMyRoomInfo = async function(){
 | 
			
		||||
    return await this.request({
 | 
			
		||||
        type: 'myroom-info'
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.createRoom = async function(options){
 | 
			
		||||
    let result =  await this.request({
 | 
			
		||||
        type: 'create-room',
 | 
			
		||||
        accessType: options.accessType || "private",
 | 
			
		||||
        notifyActionInvite: options.notifyActionInvite === undefined ? true : options.notifyActionInvite,
 | 
			
		||||
        notifyActionJoined: options.notifyActionJoined === undefined ? true : options.notifyActionJoined,
 | 
			
		||||
        notifyActionEjected: options.notifyActionEjected === undefined ? true : options.notifyActionEjected,
 | 
			
		||||
        joinType: options.joinType || "free",
 | 
			
		||||
        description: options.description || "No Description",
 | 
			
		||||
        name: options.name || "No",
 | 
			
		||||
        credential: options.credential || undefined
 | 
			
		||||
    });
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.roomInfo = async function(name){
 | 
			
		||||
    let result = await this.request({
 | 
			
		||||
        type: 'room-info',
 | 
			
		||||
        name
 | 
			
		||||
    });
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.joinRoom = async function(options){
 | 
			
		||||
    let result =  await this.request({
 | 
			
		||||
        ...options,
 | 
			
		||||
        type: 'joinroom'
 | 
			
		||||
    });
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.joinedRooms = new Map();
 | 
			
		||||
WSJS.prototype.getJoinedRooms = async function(){
 | 
			
		||||
    return await this.request({
 | 
			
		||||
        type: 'joinedrooms'
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.getRoomPeers = async function(id){
 | 
			
		||||
    return await this.request({
 | 
			
		||||
        type: 'room-peers',
 | 
			
		||||
        roomId: id
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.sendPackToPeer = async function(roomId, pack){
 | 
			
		||||
    return await this.sendOnly({
 | 
			
		||||
        type: 'pack/to',
 | 
			
		||||
        to: roomId,
 | 
			
		||||
        pack,
 | 
			
		||||
        handshake: false
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
WSJS.prototype.sendPackToRoom = async function(roomId, pack){
 | 
			
		||||
    return await this.sendOnly({
 | 
			
		||||
        type: 'pack/room',
 | 
			
		||||
        to: roomId,
 | 
			
		||||
        pack,
 | 
			
		||||
        handshake: false
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,129 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Document</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <script src="./wsjs.js"></script>
 | 
			
		||||
    <script>
 | 
			
		||||
       /* let wsjs = new WSJS({
 | 
			
		||||
            endpoint: "ws://localhost:8282"
 | 
			
		||||
        });
 | 
			
		||||
        let room = wsjs.room({
 | 
			
		||||
            name: "MY-ROOM",
 | 
			
		||||
            description: "Gizli Odam",
 | 
			
		||||
            joinType: "password",
 | 
			
		||||
            credential: "123456Kc",
 | 
			
		||||
            ifexistsJoin: true
 | 
			
		||||
        });
 | 
			
		||||
        room.on('joinpeer',(id)=>{
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
        room.on('invitepeer',(id)=>{
 | 
			
		||||
            
 | 
			
		||||
        });
 | 
			
		||||
        room.on('acceptedinvite',(id)=>{
 | 
			
		||||
            
 | 
			
		||||
        });
 | 
			
		||||
        room.on('rejectedinvite',(id)=>{
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
        peer.on('message',(pack)=>{
 | 
			
		||||
            
 | 
			
		||||
        })
 | 
			
		||||
        room.info();
 | 
			
		||||
        room.send({
 | 
			
		||||
            message: "HI!"
 | 
			
		||||
        });
 | 
			
		||||
        room.closeRoom();
 | 
			
		||||
        room.ejectRoom();
 | 
			
		||||
 | 
			
		||||
        room.fetchPeerList().then(e => console.log("e id listesi"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        let peer = wsjs.peer('07781835-0e96-4fc3-b7e1-8e3c67f9c5b7');
 | 
			
		||||
 | 
			
		||||
        peer.pairRequest();
 | 
			
		||||
        
 | 
			
		||||
        peer.on('accept',()=>{
 | 
			
		||||
            peer.send({
 | 
			
		||||
                message: "HI!"
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        peer.on('reject',()=>{
 | 
			
		||||
            
 | 
			
		||||
        });
 | 
			
		||||
        peer.on('message',(data)=>{
 | 
			
		||||
            
 | 
			
		||||
        });
 | 
			
		||||
        peer.on('close',(data)=>{
 | 
			
		||||
            
 | 
			
		||||
        });
 | 
			
		||||
        peer.close();
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let ws = new WSJS();
 | 
			
		||||
        ws.connect('ws://localhost:8282');
 | 
			
		||||
        ws.scope(async ()=>{
 | 
			
		||||
            let secretRoom;
 | 
			
		||||
            console.log("Connected ws")
 | 
			
		||||
            let roomInfo = await ws.roomInfo("MY-ROOM");
 | 
			
		||||
            console.log("Room  Info", roomInfo)
 | 
			
		||||
 | 
			
		||||
            let type = "";
 | 
			
		||||
 | 
			
		||||
            if(roomInfo.status == 'fail' && roomInfo.message == "NOT-FOUND-ROOM")
 | 
			
		||||
            {
 | 
			
		||||
                type = "owner";
 | 
			
		||||
                roomInfo = await ws.createRoom({
 | 
			
		||||
                    name: "MY-ROOM",
 | 
			
		||||
                    description: "Gizli Odam",
 | 
			
		||||
                    joinType: "password",
 | 
			
		||||
                    credential: "123456Kc"
 | 
			
		||||
                });
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
                setInterval(()=>{
 | 
			
		||||
                    ws.sendPackToRoom( roomInfo.room.id,"Merhaba");
 | 
			
		||||
                }, 5000);
 | 
			
		||||
 | 
			
		||||
            }else{
 | 
			
		||||
                roomInfo = await ws.joinRoom({name:"MY-ROOM",credential:"123456Kc"});
 | 
			
		||||
 | 
			
		||||
                setInterval(()=>{
 | 
			
		||||
                    ws.sendPackToPeer( roomInfo.room.owner,"Merhaba Yönetici");
 | 
			
		||||
                }, 5000);
 | 
			
		||||
                
 | 
			
		||||
                type = "member";
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            console.log("Oda bilgisi: ",roomInfo);
 | 
			
		||||
            let Peers = await ws.getRoomPeers( roomInfo.room.id);
 | 
			
		||||
 | 
			
		||||
            console.log("Odadaki eşlerin bilgisi: ",Peers)
 | 
			
		||||
 | 
			
		||||
            console.log(await ws.getJoinedRooms());
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
        ws.signal('id',(data)=>{
 | 
			
		||||
            console.log("Your id is ", data.value)
 | 
			
		||||
        });
 | 
			
		||||
        ws.signal('pack',(pack)=>{
 | 
			
		||||
            console.log("Recaived Package :", pack)
 | 
			
		||||
        });
 | 
			
		||||
        ws.signal("room/joined",(joinStatus)=>{
 | 
			
		||||
            console.log("Room joined", joinStatus)
 | 
			
		||||
        });
 | 
			
		||||
        ws.signal("room/ejected",(ejectStatus)=>{
 | 
			
		||||
            console.log("Room ejected", ejectStatus)
 | 
			
		||||
        })
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
		Loading…
	
		Reference in New Issue