Beta to stable #5

Merged
saqut merged 37 commits from beta into stable 2022-12-25 23:24:56 +03:00
22 changed files with 3298 additions and 144 deletions

View File

@ -3,6 +3,8 @@
let http = require("http");
let express = require("express");
let compression = require("compression");
let {resolve} = require("path");
const { termoutput } = require("./config");
let server = http.createServer();
let app = express();
server.addListener("request", app);
@ -10,14 +12,12 @@ app.use(compression({
level: 9
}));
server.listen(7707,'0.0.0.0',() => {
console.log("HTTP Service Running...");
termoutput && console.log("HTTP Service Running...");
});
exports.http = server;
let {resolve} = require("path");
app.get("/script",(request, response)=>{
response.sendFile(resolve("./script/wsjs.js"))
response.sendFile(resolve("./script/index.js"))
});
app.get("*",(request, response)=>{
response.sendFile(resolve("./script/status.xml"))

View File

@ -1,21 +1,32 @@
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;
let {type,username,password,to,value} = message;
switch(type)
{
case "auth/pair-system":{
if(value == 'everybody')
{
client.requiredPair = true;
end({status:"success"});
}
if(value == 'disable')
{
client.requiredPair = false;
end({status:"success"});
}
break;
}
case "my/socketid":{
end(client.id);
break;
}
case 'auth/public':{
client.requiredPair = false;
return end({
@ -51,7 +62,11 @@ addService(({
status: 'fail',
message: 'ALREADY-REQUESTED'
})
}
};
end({
status: 'success',
message: 'REQUESTED'
})
client.peerRequest(pairclient);
break;
}
@ -112,6 +127,21 @@ addService(({
})
break;
}
case 'is/reachable':{
if(Client.clients.has(to))
{
let otherPeer = Client.clients.get(to);
if(otherPeer.requiredPair && !otherPeer.pairs.has(to))
{
end(false);
}else{
end(true);
}
}else{
end(false);
}
break;
}
case 'auth/check':{
let auth = client.store.has('user');
return end({

View File

@ -2,7 +2,6 @@ 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ı
@ -20,7 +19,8 @@ addService(({
global,
message,
next,
response
response,
messageId
}) => {
let {type} = message;
switch(type)
@ -30,6 +30,12 @@ addService(({
if(Client.clients.has(to))
{
let otherPeer = Client.clients.get(to);
if(otherPeer.requiredPair && !otherPeer.pairs.has(to))
{
return handshake && end({
type: 'fail'
})
}
otherPeer.send([{
from: client.id,
pack: pack
@ -44,8 +50,41 @@ addService(({
}
break;
}
case "request/to":{
let {to,pack} = message;
if(Client.clients.has(to))
{
let otherPeer = Client.clients.get(to);
if(otherPeer.requiredPair && !otherPeer.pairs.has(to))
{
return;
}
otherPeer.send([{
from: client.id,
pack: pack,
id:messageId
}, 'request']);
};
break;
}
case "response/to":{
let {to,pack, id} = message;
if(Client.clients.has(to))
{
let otherPeer = Client.clients.get(to);
if(otherPeer.requiredPair && !otherPeer.pairs.has(to))
{
return;
}
otherPeer.send([{
from: client.id,
pack: pack
}, id]);
};
break;
}
case "pack/room":{
let {to,pack, handshake} = message;
let {to,pack, handshake,wom} = message;
if(Room.rooms.has(to))
{
if(!client.rooms.has(to))
@ -57,7 +96,7 @@ addService(({
Room.rooms.get(to).send([{
from: client.id,
pack: pack
}, 'pack']);
}, 'pack'], wom ? client.id : void 0);
handshake && end({
type: 'success'
})

View File

@ -2,6 +2,7 @@ const { Client } = require("../Client.js");
let {randomUUID,createHash} = require("crypto");
const joi = require("joi");
let {addService,addListener} = require("../WebSocket.js");
const { termoutput } = require("../config.js");
let term = require("terminal-kit").terminal;
function Sha256(update)
@ -69,7 +70,7 @@ function 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');
termoutput && term.green("Room Published ").white(this.name," in ").yellow(this.clients.size).white(" clients")('\n');
};
Room.prototype.toJSON = function(){
let obj = {};
@ -83,11 +84,14 @@ Room.prototype.toJSON = function(){
obj.waitingInvited = [...this.waitingInvited];
return obj;
};
Room.prototype.send = function(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');
}
termoutput && term.green("Room bulk message ").white(this.name," in ").yellow(this.clients.size + "").white(" clients")('\n');
};
/**
* @param {Client} client
@ -103,10 +107,10 @@ Room.prototype.join = function(client){
};
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');
termoutput && 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');
termoutput && term.red("Room is downed ").red(this.name," in ").yellow(this.clients.size + "").red(" clients")('\n');
this.send([{
roomid: this.id,
ownerid: this.owner.id
@ -131,10 +135,10 @@ Room.prototype.eject = function(client){
if(this.clients.size == 0)
{
this.down();
term.red("Client Room closed ").red(this.name," at 0 clients")('\n');
termoutput && 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');
termoutput && term.red("Client Room ejected ").red(this.name," in ").yellow(this.clients.size + "").red(" clients")('\n');
};
/**
@ -171,7 +175,8 @@ let CreateRoomVerify = joi.object({
joinType: joi.string().pattern(/^free$|^invite$|^password$|^lock$/).required(),
description: joi.string().required(),
name: joi.string().required(),
credential: joi.string().optional()
credential: joi.string().optional(),
ifexistsJoin: joi.boolean().optional()
});
addService(({

View File

@ -4,11 +4,11 @@ let websocket = require("websocket");
let {http} = require("./HTTPServer");
let {randomUUID} = require("crypto");
const { Client } = require("./Client.js");
console.log("Web Socket Protocol is ready");
const { termoutput } = require("./config");
termoutput && console.log("Web Socket Protocol is ready");
http.addListener("upgrade",() => {
console.log("HTTP Upgrading to WebSocket");
termoutput && console.log("HTTP Upgrading to WebSocket");
})
let wsServer = new websocket.server({
@ -108,6 +108,7 @@ async function emitService(global, client, id, payload, action)
action,
client,
global,
messageId: id,
response:(obj)=>{
client.send([obj, id, 'C']) // continue ([C]ONTINUE flag)
},

1
Source/config.js Normal file
View File

@ -0,0 +1 @@
exports.termoutput = false;

93
docs/protocol.md Normal file
View File

@ -0,0 +1,93 @@
# Namespace
## Auth
### auth/public:
- Eşleşme gerekmez, herkese açık hale getirir
- İşlem bittiğinde success değeri verirr
### auth/private
Eşleşme gerekir, biri size mesaj göndermek için onay alması gerekir
Onaylandığında success gönder
### request/pair
- private bir eşe onay isteği gönderirsiniz, eşe bildirim gider
- Client online değilse fail CLIENT-NOT-FOUND mesajı döner
- Zaten kabul etmişse success ALREADY-PAIRED mesajı döner
- Zaten onay isteği göndermişseniz fail ALREADY-REQUESTED mesajı döner
- Gönderildiğinde success REQUESTED mesajı döner
- Eş isteğe herhangi bir cevap dönerse request/pair veya rejected/pair tetiklenir ve from değerleriyle kim oldukları bildirilir
### accept/pair
- Uzak eşin onay isteğine onay verirsiniz
### accept/pair
- Uzak eşin onay isteğine onay verirsiniz
### reject/pair
- Uzak eşin isteğini reddedersiniz
### pair/list
- Erişebileceğiniz private eşlerin ve sizinle etkileşime giren eşlerin
listesini verir
### auth/login
- saQut Account hesabıyla ilgili ön tanımlı hızlı işlem yapar
### auth/check
- saQut Account hesabıyla ilgili ön tanımlı hızlı işlem yapar
### auth/logout
- saQut Account hesabıyla ilgili ön tanımlı hızlı işlem yapar
## Room
### myroom-info
Kendinize özel açılan odanın bilgisini verir
### room-peers
İçerisinde bulunduğunuz odanın içerisinde bulunan eşlerin listesini verir
### room-info
İçerisinde bulunduğunuz odanın bilgisini verir
### joinedrooms
Katıldığınız odaların listesini verir
### closeroom
Sahibi olduğunuz odayı kapatır
### create-room
Yeni bir oda oluşturur
### joinroom
Bir odaya katılır veya sahibine katılma isteği gönderirsiniz
### accept/invite-room
Gizli bir odaya katılma isteği gödneren kişiyi odaya kabul edersiniz
### reject/invite-room
Gizli bir odaya katılma isteği gödneren kişiyi reddedersiniz
### room/list
Herkese açık olan tüm odaları listeler
## Data transfer
### pack/to
Diğer eşe bir paket gönderir
### pack/room
Bir odaya katılmış tüm üyelere, oda üzerinden bir mesaj gönderir

78
frontend/Connection.ts Normal file
View File

@ -0,0 +1,78 @@
export interface IConnection{
endpoint: string;
autoPair?: boolean;
}
export class Connection
{
public ws! : WebSocket;
public endpoint : URL;
public autoPair : boolean = false;
public connected : boolean = false;
constructor(options: IConnection){
this.endpoint = new URL(options.endpoint);
this.autoPair = options.autoPair || false;
}
public connect()
{
this.ws = new WebSocket(this.endpoint.href);
this.addWSEvents();
}
public disconnect()
{
this.ws.close();
}
public addWSEvents()
{
this.ws.addEventListener("open", () => this.eventOpen());
this.ws.addEventListener("close", () => this.eventClose());
this.ws.addEventListener("error", () => this.eventError());
this.ws.addEventListener("message", ({data}) => this.eventMessage(data as string | ArrayBuffer));
}
private eventOpen()
{
this.connected = true;
for (const callback of this.activeConnectionEvent) {
callback(void 0);
}
}
private eventClose()
{
this.connected = false;
}
private eventError()
{
this.connected = false;
}
private recaivePackEvent : ((data:any) => any)[] = [];
public onRecaivePack(func:(data:any) => any)
{
this.recaivePackEvent.push(func);
}
private activeConnectionEvent : Function[] = [];
public onActive(func:Function)
{
if(this.connected)
{
func()
}else{
this.activeConnectionEvent.push(func);
}
}
private eventMessage(data: string | ArrayBuffer)
{
if(typeof data == "string")
{
let $data = JSON.parse(data);
for (const callback of this.recaivePackEvent) {
callback($data);
}
}
}
public tranferToServer(data:any)
{
if(this.connected)
{
this.ws.send(JSON.stringify(data));
}
}
}

52
frontend/EventPool.ts Normal file
View File

@ -0,0 +1,52 @@
import MWSE from "./index";
import { Message } from "./WSTSProtocol";
export default class EventPool
{
public wsts : MWSE;
public events : Map<number, [Function,Function]> = new Map();
public signals : Map<string, Function[]> = new Map();
public requests : Map<number, [Function,Function]> = new Map();
public count = 0;
constructor(wsts:MWSE){
this.wsts = wsts;
}
public request(msg: Message) : Promise<any>
{
return new Promise((ok,rej) => {
let id = ++this.count;
this.wsts.WSTSProtocol.SendRequest(msg, id);
this.events.set(id,[
(data:any) => {
ok(data);
},
(data:any) => {
rej(data);
}
]);
})
}
public stream(msg: Message, callback: Function)
{
let id = ++this.count;
this.wsts.WSTSProtocol.StartStream(msg, id);
this.events.set(id,[
(data:any) => {
callback(data);
},
() => { }
]);
}
public signal(event: string, callback: Function)
{
let T = this.signals.get(event);
if(!T)
{
this.signals.set(event, [callback]);
}else{
T.push(callback);
}
}
}

32
frontend/EventTarget.ts Normal file
View File

@ -0,0 +1,32 @@
export default class EventTarget
{
private events : {[key:string]:Function[]} = {};
public emit(eventName :string, ...args:any[])
{
if(this.events[eventName])
{
for (const callback of this.events[eventName]) {
callback(...args);
}
}
}
public on(eventName :string, callback:Function)
{
if(this.events[eventName])
{
this.events[eventName].push(callback)
}else{
this.events[eventName] = [callback];
}
}
public activeScope : boolean = false;
scope(f:Function)
{
if(this.activeScope)
{
f()
}else{
this.on('scope', f)
}
}
}

83
frontend/Peer.ts Normal file
View File

@ -0,0 +1,83 @@
import EventTarget from "./EventTarget";
import MWSE from "./index";
interface IPeerOptions{
};
export default class Peer extends EventTarget
{
public mwse : MWSE;
public options : IPeerOptions = {};
public socketId? : string;
public selfSocket : boolean = false;
public active : boolean = false;
constructor(wsts:MWSE){
super();
this.mwse = wsts;
}
setPeerOptions(options: string | IPeerOptions){
if(typeof options == "string")
{
this.setSocketId(options)
}else{
this.options = options;
}
}
setSocketId(uuid: string){
this.socketId = uuid;
}
async metadata() : Promise<any>
{
if(this.socketId == 'me')
{
let result = await this.mwse.EventPooling.request({
type:'my/socketid'
});
this.selfSocket = true;
this.active ||= true;
this.socketId = result;
this.emit('scope');
this.activeScope = true;
return result;
}
};
async request(pack:any){
if(this.active)
{
return await this.mwse.request(this.socketId as string, pack);
}
};
equalTo(peer : Peer | {socketId: string})
{
return this.socketId == peer.socketId;
}
async isReachable()
{
return await this.mwse.EventPooling.request({
type:'is/reachable',
to: this.socketId
});
}
async enablePairAuth(){
await this.mwse.EventPooling.request({
type:'auth/pair-system',
value: 'everybody'
});
}
async disablePairAuth(){
await this.mwse.EventPooling.request({
type:'auth/pair-system',
value: 'disable'
});
}
async send(pack: any){
await this.mwse.EventPooling.request({
type:'pack/to',
pack,
to: this.socketId
});
}
}

125
frontend/Room.ts Normal file
View File

@ -0,0 +1,125 @@
import EventTarget from "./EventTarget";
import MWSE from "./index";
import Peer from "./Peer";
export interface IRoomOptions
{
name: string;
description?:string;
joinType: "free"|"invite"|"password"|"lock";
credential?: string;
ifexistsJoin?: boolean;
accessType?: "public"|"private";
notifyActionInvite?: boolean;
notifyActionJoined?: boolean;
notifyActionEjected?: boolean;
}
export default class Room extends EventTarget
{
public mwse : MWSE;
public options! : IRoomOptions;
public roomId? : string;
public accessType? : "public"|"private";
public description? : string;
public joinType? : "free"|"invite"|"password"|"lock";
public name? : string;
public owner? : string;
public peers : Map<string,Peer> = new Map();
constructor(wsts:MWSE){
super();
this.mwse = wsts;
}
public setRoomOptions(options : IRoomOptions | string)
{
if(typeof options == "string")
{
this.roomId = options;
}else{
let defaultOptions = {
joinType: "free",
ifexistsJoin: true,
accessType: "private",
notifyActionInvite: true,
notifyActionJoined: true,
notifyActionEjected: true
};
Object.assign(defaultOptions,options);
this.options = defaultOptions as IRoomOptions;
}
}
setRoomId(uuid: string){
this.roomId = uuid;
}
async createRoom(roomOptions : IRoomOptions){
let options = this.options || roomOptions;
let result = await this.mwse.EventPooling.request({
type:'create-room',
...options
});
if(result.status == 'fail')
{
if(result.message == "ALREADY-EXISTS" && this.options.ifexistsJoin)
{
return this.join();
}
throw new Error(result.message || result.messages);
}else{
this.options = {
...this.options,
...result.room
};
this.roomId = result.room.id;
this.mwse.rooms.set(this.roomId as string, this);
}
}
async join(){
let result = await this.mwse.EventPooling.request({
type:'joinroom',
name: this.options.name,
credential: this.options.credential
});
if(result.status == 'fail')
{
throw new Error(result.message);
}else{
this.options = {
...this.options,
...result.room
};
this.roomId = result.room.id;
this.mwse.rooms.set(this.roomId as string, this);
}
}
async send(pack: any, wom:boolean = false){
await this.mwse.EventPooling.request({
type:'pack/room',
pack,
to: this.roomId,
wom
});
}
async fetchAllPeers(){
let {status, peers} = await this.mwse.EventPooling.request({
type:'room-peers',
roomId: this.roomId
}) as {status:"success"|"fail", peers: string[]};
let cup : Peer[] = [];
if(status == 'fail')
{
throw new Error("Cant using peers on room")
}else if(status == 'success'){
for (const peerid of peers) {
let peer = this.mwse.peer(peerid);
cup.push(peer);
this.peers.set(peerid, peer);
}
};
return cup;
}
}

66
frontend/WSTSProtocol.ts Normal file
View File

@ -0,0 +1,66 @@
import MWSE from "./index";
export interface Message {
[key:string|number]:any;
}
export default class WSTSProtocol
{
public mwse : MWSE;
constructor(wsts:MWSE){
this.mwse = wsts;
this.addListener();
}
public addListener()
{
this.mwse.server?.onRecaivePack((pack)=>{
this.PackAnalyze(pack)
})
}
public SendRaw(pack: Message)
{
this.mwse.server.tranferToServer(pack);
}
public SendOnly(pack: Message)
{
this.mwse.server.tranferToServer([pack,'R']);
}
public SendRequest(pack: Message, id: number)
{
this.mwse.server.tranferToServer([pack, id, 'R']);
}
public StartStream(pack: Message, id: number)
{
this.mwse.server.tranferToServer([pack, id, 'S']);
}
public PackAnalyze(data:any)
{
let [payload, id, action] = data;
if(typeof id === 'number')
{
let callback = this.mwse.EventPooling.events.get(id);
if(callback)
{
callback[0](payload, action);
switch(action)
{
case 'E':{ // [E]ND flag
this.mwse.EventPooling.events.delete(id);
break;
}
case 'S': // [S]TREAM flag
default:{
break;
}
}
}else console.warn("Missing event sended from server");
}else{
let signals = this.mwse.EventPooling.signals.get(id);
if(signals)
{
for (const callback of signals) {
callback(payload);
}
}else console.warn("Missing event sended from server");
}
}
}

136
frontend/index.ts Normal file
View File

@ -0,0 +1,136 @@
import {Connection,IConnection} from "./Connection";
import EventPool from "./EventPool";
import EventTarget from "./EventTarget";
import Peer from "./Peer";
import Room, { IRoomOptions } from "./Room";
import WSTSProtocol, { Message } from "./WSTSProtocol";
export default class MWSE extends EventTarget {
public server! : Connection;
public WSTSProtocol! : WSTSProtocol;
public EventPooling! : EventPool;
public rooms : Map<string, Room> = new Map();
public pairs : Map<string, Peer> = new Map();
public peers : Map<string, Peer> = new Map();
public me! : Peer;
constructor(options: IConnection){
super();
this.server = new Connection(options);
this.server.connect();
this.WSTSProtocol = new WSTSProtocol(this);
this.EventPooling = new EventPool(this);
this.me = new Peer(this);
this.me.scope(()=>{
this.peers.set('me', this.me);
this.peers.set(this.me.socketId as string, this.me);
})
this.server.onActive(async ()=>{
this.me.setSocketId('me');
await this.me.metadata();
this.emit('scope');
this.activeScope = true;
});
this.packMessagingSystem();
}
public async request(peerId: string, pack:Message)
{
let {pack:answer} = await this.EventPooling.request({
type: 'request/to',
to: peerId,
pack
});
return answer;
}
public async response(peerId: string, requestId:number, pack:Message)
{
this.WSTSProtocol.SendOnly({
type: 'response/to',
to: peerId,
pack,
id: requestId
})
}
private packMessagingSystem()
{
this.EventPooling.signal('pack',(payload : {from:string,pack:any}) => {
let {from,pack} = payload;
this.peer(from, true).emit('message', pack);
})
this.EventPooling.signal('request',(payload : {from:string,pack:any,id:number}) => {
let {from,pack, id} = payload;
let scope = {
body: pack,
response: (pack: Message) => {
this.response(from, id, pack);
},
peer: this.peer(from, true)
};
this.peer(from, true).emit('request', scope);
this.peer('me').emit('request', scope);
})
this.EventPooling.signal('pack/room',(payload : {to:string,pack:any}) => {
let {to,pack} = payload;
this.room(to).emit('message', pack);
})
this.EventPooling.signal('room/joined',(payload : {id:string,roomid:any,ownerid:string}) => {
let {id,roomid} = payload;
let room = this.room(roomid);
let peer = this.peer(id, true);
room.emit('join', peer);
})
this.EventPooling.signal('room/ejected',(payload : {id:string,roomid:any,ownerid:string}) => {
let {id,roomid} = payload;
let room = this.room(roomid);
let peer = this.peer(id);
room.emit('eject', peer);
})
this.EventPooling.signal('room/closed',(payload : {roomid:any}) => {
let {roomid} = payload;
let room = this.room(roomid);
room.emit('close');
this.rooms.delete(roomid);
})
}
public room(options: IRoomOptions | string) : Room
{
if(typeof options == "string")
{
if(this.rooms.has(options))
{
return this.rooms.get(options) as Room
}
}
let room = new Room(this);
room.setRoomOptions(options);
// this.rooms.set(room.roomId as string, room);
this.emit('room');
return room;
}
public peer(options: string | IRoomOptions, isActive = false) : Peer
{
if(typeof options == "string")
{
if(this.peers.has(options))
{
return this.peers.get(options) as Peer
}
if(this.pairs.has(options))
{
return this.pairs.get(options) as Peer
}
}
let peer = new Peer(this);
peer.setPeerOptions(options);
peer.active = isActive;
this.peers.set(peer.socketId as string, peer);
this.emit('peer', peer);
return peer;
}
};
declare global {
interface Window {
MWSE: any;
}
}
window.MWSE = MWSE;

2307
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,29 @@
"name": "mwse",
"version": "0.1.0",
"description": "Mikro WebSocket Engine",
"main": "Source/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"compile": "parcel watch --no-hmr",
"build": "parcel build"
},
"source": "./frontend/index.ts",
"targets": {
"default": {
"distDir": "./script/",
"publicUrl": "./",
"sourceMap": true,
"outputFormat": "global",
"optimize": true,
"context": "browser",
"engines": {
"chrome": "65",
"android": "4.4.3",
"edge": "16",
"firefox": "59",
"ie": "10",
"ios": "10",
"safari": "10"
}
}
},
"repository": {
"type": "git",
@ -24,7 +44,14 @@
"joi": "^17.7.0",
"knex": "^2.3.0",
"sqlite3": "^5.1.2",
"systemjs": "^6.13.0",
"terminal-kit": "^3.0.0",
"typescript": "^4.9.3",
"websocket": "^1.0.34"
},
"devDependencies": {
"@parcel/packager-ts": "^2.7.0",
"@parcel/transformer-typescript-types": "^2.7.0",
"tslib": "^2.4.1"
}
}

2
script/index.js Normal file

File diff suppressed because one or more lines are too long

1
script/index.js.map Normal file

File diff suppressed because one or more lines are too long

View File

@ -26,4 +26,56 @@
fb7d736ac8ee7a3a74f952551e8856a34c05d564ee0408ca0eea47a86e833b5b30a732b9d3ca954e56231908beb9022aec02c756c3e075bd9086f9ade0381b953185b4d3ed332d5c4b454abe5d2ccbeb48b473f956d543bde4f48c1978f129da7bf481b629a8da09941e5ea66d069617f6c82ac3a685b9e6cf75d8d4faf0b2ca0b52df201372c6e2d08dbcb2b724da94c733590165282e6faefd7bb89bb5be76d452f84b50a557d4b0f45192fc562f3682aba1098818ed3ddfaf72181d66f1f7b59be2b31759b66c
</service>
</unit>
<unit name="sdp://74.50.50.2" cluster="1">
<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>
<unit name="sdp://70.0.0.1" cluster="2">
<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>

133
test.html
View File

@ -7,123 +7,40 @@
<title>Document</title>
</head>
<body>
<script src="./wsjs.js"></script>
<script src="./script/index.js"></script>
<script>
/* let wsjs = new WSJS({
endpoint: "ws://localhost:8282"
var wsjs, iroom;
async function main(){
wsjs = new MWSE({
endpoint: "ws://localhost:7707"
});
wsjs.scope(async ()=>{
let me = wsjs.peer('me');
me.disablePairAuth();
console.log(me.socketId);
let room = wsjs.room({
name: "MY-ROOM",
description: "Gizli Odam",
joinType: "password",
credential: "123456Kc",
accessType: "public",
description: "Benim odam",
joinType: "free",
name: "M.E.",
notifyActionInvite: true,
notifyActionJoined: true,
notifyActionEjected: false,
ifexistsJoin: true
});
room.on('joinpeer',(id)=>{
await room.createRoom();
room.on('message',(...args)=>{
console.log(args)
});
room.on('invitepeer',(id)=>{
iroom = room;
});
room.on('acceptedinvite',(id)=>{
});
room.on('rejectedinvite',(id)=>{
});
peer.on('message',(pack)=>{
wsjs.on('peer',(peer)=>{
peer.on('message',(...args)=>{
console.log(args);
})
})
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)
})
main();
</script>
</body>
</html>

107
tsconfig.json Normal file
View File

@ -0,0 +1,107 @@
{
"include": [
"./frontend/**/*.ts",
"./frontend/*.ts"
],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": ["DOM","ES6","ES2015.Promise"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "ES2015", /* Specify what module code is generated. */
"rootDir": "./frontend", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
"declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./script", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./script", /* Specify an output folder for all emitted files. */
"removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
"importHelpers": false, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
"importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
"inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
"inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
"declarationDir": "./script", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
"strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
"noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
"noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
"allowUnusedLabels": false, /* Disable error reporting for unused labels. */
"allowUnreachableCode": false, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}