Compare commits

..

No commits in common. "stable" and "perfectnogation" have entirely different histories.

43 changed files with 5742 additions and 2194 deletions

View File

@ -1,59 +1,23 @@
# MWSE — Micro WebSocket Engine
# MWSE Nedir?
> **Durum: Go yeniden yazımı (0.1.0) başlıyor.** Mevcut Node.js engine `ws.saqut.com`'da canlı; çekirdek, concurrency'i doğru çözmek için Go'ya taşınıyor. Frontend SDK'nın giriş/çıkış sözleşmesi **değişmiyor**.
MWSE yani Micro Web Socket Engine, kendisine bağlanan eşleri birbirleriyle ile eşleştirerek, eşler arası veri tünelleri oluşturan geniş ölçekli bir mikroservistir.
MWSE, kendisine bağlanan eşleri (peer) birbirleriyle eşleştirip aralarında **düşük gecikmeli veri tünelleri** kuran bir gerçek-zamanlı iletişim motorudur. Sunucu cihazları sanallaştırır; eşler birbirlerinin IP'sini veya cihaz türünü bilmeden, çift yönlü serbestçe haberleşir. Üstünde oda yönetimi, peer pairing, sanal adresleme, dosya transferi ve WebRTC sinyalleşmesi gelir.
Servis, bağlantı sağlayan cihazların verilerini kendi aralarında senkron etmek için kullanılabilir, cihazları gruplayabilir, odalar oluşturabilir, sohbet ve görüntülü görüşme yazılımları için alt yapı olarak kullanılabilir
## Neden var?
Bağlantı TCP tabanlı yüksek hızlı WebSocket protokolüne dayanır ve sunucunun cihazları sanallaştırması sayesinde diğer kişilerin IP adreslerini veya cihaz türü gibi bilgilere ihtiyaç duymadan düşük gecikmeli çift taraflı serbest iletişim kurmalarını sağlar.
Gerçek-zamanlı uygulama altyapısı bugün ya sadece transport (Socket.io) ya da pahalı SaaS (Twilio/LiveKit). MWSE'nin hedefi: **birkaç satırda** oda + mesaj + sesli/görüntülü görüşme kurabileceğin, kendi sunucunda çalışan açık bir motor.
[Geliştirici Dökümanı](https://git.saqut.com/saqut/MWSE/wiki/Entegrasyon)
```js
const mwse = new MWSE({ endpoint: "wss://ws.saqut.com/" });
# Güvenlik !
mwse.scope(async () => {
const room = mwse.room({ name: "lobi", joinType: "free", ifexistsJoin: true });
await room.join();
room.on("message", (peer, data) => console.log(peer.socketId, data));
room.broadcast("herkese merhaba");
});
```
Framework, bağlı tüm cihazlar arasında mesajları doğru hedefe, verinin bozulmadığını garanti ederek iletmekden sorumludur.
- **~20 satır** → grup mesajlaşma
- **~100 satır** → sesli görüşme
- **~500 satır** → görüntülü görüşme
Bunların dışında hassas verilerin soket üzerinden iletilmesi şimdilik önerilmez, clientlerin ileteceği mesajlar **SOKETE İLETİLMEDEN ÖNCE** kullanıcılar tarafından manipüle edilebilir veya taklit edilebilir ve MWSE bunun doğrulamasını **YAPMAZ**
Tam örnekler için → [Wiki](https://git.saqut.com/saqut/MWSE/wiki/Home).
## WebSocket topolojisi
## Mimari (özet)
![image](https://www.hitechmv.com/wp-content/uploads/2014/05/startopology.jpg)
```
[Client SDK] ⇄ WSTS protokolü ⇄ [MWSE Engine]
(frontend/) (paket kimliklendirme, (Source/: WebSocket, MessageRouter,
request/response/stream) Services/{Auth, Room, Session,
IPPressure, DataTransfer, YourID})
```
## Proje tarafından uygulanan load balance teknolojisi
- **WSTS protokolü:** WebSocket üzerine kurulu, giden/gelen paketleri kimliklendiren ve "kimden/kime" yönlendiren request-response-stream katmanı.
- **Sanal adresleme (IPPressure):** uzun socket hash'leri yerine sanal IP / numara / kısa kod (ör. `15.214.11.74`, `884`, `ZQT`) atayıp eşleri kolayca çağırma.
- **Oda sistemi:** `free` / `invite` / `password` / `lock` katılım türleri, per-connection okuma/yazma/bildirim ayarları.
## Yol haritası
| Sürüm | İçerik |
|------|--------|
| **0.1.0** | Go engine çekirdeği + concurrency (goroutine/channel/mutex) + `go test -race` süreç testleri |
| **1.0.0** | Go + Frontend + WebRTC tam parite (oda, pairing, sanal adres, tünel, signaling, demolar) |
| **2.0.0** | WebRTC Studio: tam WebRTC API, çoklu track/kamera, canvas compositing, SRS yayını, sanal IP çakışma + alt-network |
| **2.5.0** | JSON → binary protokol |
| **3.0.0** | Platform: Notify (offline/suit), aktif/pasif sync + datastore, 3. parti sunucu köprüsü |
Detaylı görevler **Issues** ve milestone'larda. Tasarım bağlamı: `todo.md`.
## Güvenlik
MWSE mesajları doğru hedefe ve bozulmadan iletmekten sorumludur. Ancak istemcilerin gönderdiği mesajlar **sokete iletilmeden önce** kullanıcı tarafında manipüle/taklit edilebilir; MWSE bunu **doğrulamaz**. Hassas verileri uygulama katmanında doğrula/şifrele.
## Lisans
Bkz. `LICENSE`.
![image](https://git.saqut.com/saqut/storage/raw/branch/master/Diagram1.png)

View File

@ -1,3 +1,5 @@
const { CLIENT_SEND_MESSAGE, CLIENT_UPDATE_PROP } = require("./IPC");
const stats = require("./stats");
function Client()
{
/**
@ -34,6 +36,16 @@ function Client()
this.APNumber = 0;
this.APShortCode = 0;
this.APIPAddress = 0;
this.isProxy = false;
this.proxyProcess = null;
this.sync = function(...args){
process.nextTick(()=>{
for (const name of args) {
CLIENT_UPDATE_PROP(this.id, name, this[name]);
}
})
};
};
/**
* @type {Map<string, Client>}
@ -47,6 +59,7 @@ Client.prototype.peerRequest = function(client){
let info = {};
this.info.forEach((value, name) => info[name] = value);
this.pairs.add(client.id);
this.sync('pairs');
client.send([
{ from: this.id },
'request/pair'
@ -152,6 +165,7 @@ Client.prototype.getSucureClients = function()
*/
Client.prototype.acceptPeerRequest = function(client){
this.pairs.add(client.id);
this.sync('pairs');
client.send([{
from: this.id
},'accepted/pair']);
@ -162,6 +176,7 @@ Client.prototype.getSucureClients = function()
Client.prototype.rejectPeerRequest = function(client){
this.pairs.delete(client.id);
client.pairs.delete(this.id);
this.sync('pairs');
client.send([{
from: this.id
},'end/pair']);
@ -185,6 +200,11 @@ Client.prototype.pairList = function(){
};
Client.prototype.send = function(obj){
if(this.isProxy)
{
CLIENT_SEND_MESSAGE(this.id, obj, this.proxyProcess)
}else{
stats.ws_sended_packs++;
if(this.socket.connected){
this.socket.sendUTF(JSON.stringify(obj),err => {
if(err && this.socket)
@ -195,6 +215,7 @@ Client.prototype.send = function(obj){
}else{
console.error("Bağlantısı kopmuş yazma işlemi")
}
}
};
Client.prototype.packWriteable = function(){

View File

@ -1,62 +0,0 @@
"use strict";
const events = new Map();
function on(event, callback) {
if (!events.has(event)) {
events.set(event, []);
}
events.get(event).push(callback);
}
function emit(event, ...args) {
if (events.has(event)) {
for (const callback of events.get(event)) {
try {
callback(...args);
} catch (error) {
console.error(`Event error [${event}]:`, error);
}
}
}
}
function once(event, callback) {
const wrapper = (...args) => {
off(event, wrapper);
callback(...args);
};
on(event, wrapper);
}
function off(event, callback) {
if (events.has(event)) {
const callbacks = events.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
function removeAllListeners(event) {
if (event) {
events.delete(event);
} else {
events.clear();
}
}
function listenerCount(event) {
return events.has(event) ? events.get(event).length : 0;
}
module.exports = {
on,
emit,
once,
off,
removeAllListeners,
listenerCount,
events
};

View File

@ -4,40 +4,52 @@ let http = require("http");
let express = require("express");
let compression = require("compression");
let {resolve} = require("path");
let auth = require("express-basic-auth");
const { termoutput } = require("./config");
let server = http.createServer();
const stats = require("./stats");
let app = express();
app.use(compression({ level: 9 }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
let server = http.createServer(app);
server.addListener("request", app);
app.use(compression({
level: 9
}));
server.listen(7707,'0.0.0.0',() => {
termoutput && console.log("HTTP Service Running...");
});
server.on("error", (err) => {
console.error(err);
});
server.addListener("error",(err)=> {
console.err(err)
})
exports.http = server;
const apiRouter = require("./api");
app.use("/api", apiRouter);
app.get("/script", (req, res) => {
res.sendFile(resolve("./script/index.js"));
app.get("/script",(request, response)=>{
response.sendFile(resolve("./script/index.js"))
});
app.use(express.static(resolve("./public")));
app.use("/script", express.static(resolve("./script")));
app.get("/", (req, res) => {
res.sendFile(resolve("./script/index.js"));
app.get("/test",(request, response)=>{
response.sendFile(resolve("./script/test.html"))
});
app.get("*", (req, res) => {
res.sendFile(resolve("./script/status.xml"));
app.get("/index.js.map",(request, response)=>{
response.sendFile(resolve("./script/index.js.map"))
});
app.get("/stream",(request, response)=>{
response.sendFile(resolve("./public/index.html"))
});
app.get("/",(request, response)=>{
response.sendFile(resolve("./script/index.html"))
});
app.post("/stats",(request, response)=>{
response.json(stats.others);
});
app.get("/console",(request, response)=>{
response.sendFile(resolve("./console/index.html"))
});
app.get("/console/lib.js",(request, response)=>{
response.sendFile(resolve("./console/lib.js"))
});
app.use("/stream",express.static(resolve("./public")));
app.use("/console/",express.static(resolve("./console")));
app.get("*",(request, response)=>{
response.sendFile(resolve("./script/status.xml"))
});

246
Source/IPC.js Normal file
View File

@ -0,0 +1,246 @@
process.on('message',data => {
const { Client } = require("./Client.js");
const { Room } = require("./Services/Room");
switch(data.type)
{
case "CLIENT_CREATED":{
slog("CLIENT_CREATED");
let client = new Client();
client.isProxy = true;
client.proxyProcess = data.pid;
client.id = data.uuid;
if(Client.clients.has(client.id))
{
console.error("IPC: Zaten var olan kullanıcı oluşturuluyor")
}else{
Client.clients.set(client.id, client);
}
break;
}
case "CLIENT_UPDATE_PROP":{
data.value = transformDeserialization(data.value, data.typing);
slog("CLIENT_UPDATE_PROP");
let client = Client.clients.get(data.uuid);
client[data.name] = data.value;
break;
}
case "CLIENT_SEND_MESSAGE":{
//slog("CLIENT_SEND_MESSAGE");
let client = Client.clients.get(data.uuid);
if(client)
{
if(client.isProxy != true)
{
client.send(data.message)
}else{
console.error("IPC: Proxy olmayan bir client için IPC mesajı alındı")
}
}else{
console.error("IPC: Olmayan bir kullanıcı için mesaj gönderiliyor")
}
break;
}
case "CLIENT_DESTROY":{
slog("CLIENT_DESTROY");
if(Client.clients.has(data.uuid))
{
Client.clients.delete(data.uuid);
}else{
console.error("IPC: Olmayan bir kullanıcı için silme gerçekleştiriliyor")
}
break;
}
case "ROOM_CREATED":{
slog("ROOM_CREATED");
let room = Room.fromJSON(data.value);
Room.rooms.set(room.id, room);
break;
}
case "ROOM_UPDATE_PROP":{
data.value = transformDeserialization(data.value, data.typing);
slog("ROOM_UPDATE_PROP");
let room = Room.rooms.get(data.uuid);
room[data.name] = data.value;
break;
}
case "ROOM_JOIN_CLIENT":{
slog("ROOM_JOIN_CLIENT");
let room = Room.rooms.get(data.uuid);
let client = Client.clients.get(data.client);
if(room && client)
{
client.rooms.add(room.id);
room.clients.set(client.id, client);
}
break;
}
case "ROOM_EJECT_CLIENT":{
slog("ROOM_EJECT_CLIENT");
let room = Room.rooms.get(data.uuid);
let client = Client.clients.get(data.client);
if(room && client)
{
client.rooms.delete(room.id);
room.clients.delete(client.id, client);
}
break;
}
case "ROOM_DESTROY":{
slog("ROOM_DESTROY");
Room.rooms.delete(data.value);
break;
}
}
});
function CLIENT_CREATED(uuid)
{
mlog("CLIENT_CREATED");
process.send({
type:'CLIENT_CREATED',
uuid: uuid
})
};
function CLIENT_UPDATE_PROP(uuid, name, value)
{
mlog("CLIENT_UPDATE_PROP");
let typing = value.__proto__.constructor.name;
value = transformSerialization(value);
process.send({
type:'CLIENT_UPDATE_PROP',
uuid: uuid,
name,
value,
typing
})
};
function CLIENT_SEND_MESSAGE(uuid, message, clusterPid)
{
mlog("CLIENT_SEND_MESSAGE");
process.send({
type:'CLIENT_SEND_MESSAGE',
uuid: uuid,
message,
process:clusterPid
})
};
function CLIENT_DESTROY(uuid)
{
mlog("CLIENT_DESTROY");
process.send({
type:'CLIENT_DESTROY',
uuid: uuid
})
};
function ROOM_CREATED(room)
{
mlog("ROOM_CREATED");
let raw = room.toJSON(true);
process.send({
type:'ROOM_CREATED',
value: raw
})
};
function ROOM_UPDATE_PROP(uuid, name, value)
{
mlog("ROOM_UPDATE_PROP");
let typing = value.__proto__.constructor.name;
value = transformSerialization(value);
process.send({
type:'ROOM_UPDATE_PROP',
uuid: uuid,
name,
value,
typing
})
};
function ROOM_JOIN_CLIENT(uuid, client)
{
mlog("ROOM_JOIN_CLIENT");
process.send({
type:'ROOM_JOIN_CLIENT',
uuid: uuid,
client
})
};
function ROOM_EJECT_CLIENT(uuid, client)
{
mlog("ROOM_EJECT_CLIENT");
process.send({
type:'ROOM_EJECT_CLIENT',
uuid: uuid,
client
})
};
function ROOM_DESTROY(room)
{
mlog("ROOM_DESTROY");
process.send({
type:'ROOM_DESTROY',
value: room.id
})
};
function mlog(command)
{
return;
console.log("M",process.pid, command)
}
function slog(command)
{
return;
console.log("S",process.pid, command)
}
function transformSerialization(value)
{
switch(value.__proto__.constructor.name)
{
case "Map":{
return [...value];
}
case "Set":{
return [...value];
}
}
}
function transformDeserialization(value,type)
{
switch(type)
{
case "Map":{
return new Map(value);
}
case "Set":{
return new Set(value)
}
}
}
exports.CLIENT_CREATED = CLIENT_CREATED;
exports.CLIENT_UPDATE_PROP = CLIENT_UPDATE_PROP;
exports.CLIENT_DESTROY = CLIENT_DESTROY;
exports.CLIENT_SEND_MESSAGE = CLIENT_SEND_MESSAGE;
exports.ROOM_CREATED = ROOM_CREATED;
exports.ROOM_UPDATE_PROP = ROOM_UPDATE_PROP;
exports.ROOM_JOIN_CLIENT = ROOM_JOIN_CLIENT;
exports.ROOM_EJECT_CLIENT = ROOM_EJECT_CLIENT;
exports.ROOM_DESTROY = ROOM_DESTROY;
exports.mlog = mlog;
exports.slog = slog;

View File

@ -1,54 +0,0 @@
"use strict";
const handlers = new Map();
function register(type, handler) {
handlers.set(type, handler);
}
function handle(client, message) {
const { type } = message;
if (!type) {
return { status: 'fail', message: 'MISSING_TYPE' };
}
const handler = handlers.get(type);
if (!handler) {
return { status: 'fail', message: 'UNKNOWN_TYPE' };
}
try {
const result = handler(client, message);
return result;
} catch (error) {
console.error(`Handler error [${type}]:`, error);
return { status: 'fail', message: 'HANDLER_ERROR', error: error.message };
}
}
function unregister(type) {
handlers.delete(type);
}
function clear() {
handlers.clear();
}
function hasHandler(type) {
return handlers.has(type);
}
function listHandlers() {
return [...handlers.keys()];
}
module.exports = {
register,
handle,
unregister,
clear,
hasHandler,
listHandlers
};

View File

@ -1,175 +1,252 @@
"use strict";
const { Client } = require("../Client.js");
const { on, emit, register } = require("../WebSocket");
let {addService, addListener} = require("../WebSocket.js");
on('disconnect', (xclient) => {
addListener('disconnect',(global, xclient)=>{
const {intersection, pairs} = xclient.getSucureClients();
for (const [clientid, client] of intersection) {
client?.send([{ id: xclient.id }, "peer/disconnect"]);
for (const [clientid, client] of intersection)
{
client?.send([
{
id: xclient.id
},
"peer/disconnect"
])
}
for (const [id, peer] of pairs) {
for (const [id, peer] of pairs)
{
peer?.pairs.delete(xclient.id);
xclient.pairs.delete(id);
}
});
register('auth/pair-system', (client, msg) => {
if (msg.value == 'everybody') {
addService(({
client,
message,
end,
next
})=>{
let {type,to,value,name} = message;
switch(type)
{
case "auth/pair-system":{
if(value == 'everybody')
{
client.requiredPair = true;
return { status: 'success' };
end({status:"success"});
client.sync('requiredPair');
}
if (msg.value == 'disable') {
if(value == 'disable')
{
client.requiredPair = false;
return { status: 'success' };
end({status:"success"});
client.sync('requiredPair');
//CLIENT_UPDATE_PROP(client.id, 'requiredPair', client.requiredPair);
}
return { status: 'fail', message: 'INVALID_VALUE' };
});
register('my/socketid', (client, msg) => {
return client.id;
});
register('auth/public', (client, msg) => {
break;
}
case "my/socketid":{
end(client.id);
break;
}
case 'auth/public':{
client.requiredPair = false;
return { value: 'success', mode: 'public' };
});
register('auth/private', (client, msg) => {
client.sync('requiredPair');
return end({
value: 'success',
mode: 'public'
})
}
case 'auth/private':{
client.requiredPair = true;
return { value: 'success', mode: 'private' };
});
register('request/pair', (client, msg) => {
const { to } = msg;
if (!Client.clients.has(to)) {
return { status: 'fail', message: 'CLIENT_NOT_FOUND' };
client.sync('requiredPair');
return end({
value: 'success',
mode: 'private'
})
}
const pairclient = Client.clients.get(to);
if (pairclient.pairs.has(client.id)) {
return { status: 'success', message: 'ALREADY-PAIRED' };
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.has(to)) {
return { status: 'fail', message: 'ALREADY-REQUESTED' };
}
if(client.pairs.has(to))
{
return end({
status: 'fail',
message: 'ALREADY-REQUESTED'
})
};
end({
status: 'success',
message: 'REQUESTED'
})
client.peerRequest(pairclient);
return { status: 'success', message: 'REQUESTED' };
});
register('accept/pair', (client, msg) => {
const { to } = msg;
if (!Client.clients.has(to)) {
return { status: 'fail', message: 'CLIENT_NOT_FOUND' };
break;
}
const pairclient = Client.clients.get(to);
if (pairclient.pairs.has(client.id)) {
return { status: 'success', message: 'ALREADY-PAIRED' };
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 { status: 'fail', message: 'NOT_REQUESTED_PAIR' };
if(!client.pairs.has(to))
{
return end({
status: 'fail',
message: 'NOT-REQUESTED-PAIR'
})
}
client.acceptPeerRequest(pairclient);
return { status: 'success' };
});
register('reject/pair', (client, msg) => {
const { to } = msg;
if (!Client.clients.has(to)) {
return { status: 'fail', message: 'CLIENT_NOT_FOUND' };
break;
}
const pairclient = Client.clients.get(to);
if (pairclient.pairs.has(client.id)) {
return { status: 'success', message: 'ALREADY-PAIRED' };
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 { status: 'fail', message: 'NOT_REQUESTED_PAIR' };
if(!client.pairs.has(to))
{
return end({
status: 'fail',
message: 'NOT-REQUESTED-PAIR'
})
}
client.rejectPeerRequest(pairclient);
return { status: 'success' };
});
register('end/pair', (client, msg) => {
const { to } = msg;
if (!Client.clients.has(to)) {
return { status: 'fail', message: 'CLIENT_NOT_FOUND' };
break;
}
const pairclient = Client.clients.get(to);
if (!pairclient.pairs.has(client.id)) {
return { status: 'success', message: 'NOT_PAIRED' };
case 'end/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: 'NOT-PAIRED'
})
}
client.rejectPeerRequest(pairclient);
return { status: 'success' };
});
register('pair/list', (client, msg) => {
return { type: 'pair/list', value: client.pairList() };
});
register('is/reachable', (client, msg) => {
const { to } = msg;
if (!Client.clients.has(to)) {
return false;
break;
}
const otherPeer = Client.clients.get(to);
if (otherPeer.requiredPair && !otherPeer.pairs.has(to)) {
return false;
case 'pair/list':{
end({
type:'pair/list',
value: client.pairList()
})
break;
}
return true;
});
register('auth/info', (client, msg) => {
const { name, value } = msg;
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/info':{
client.info.set(name, value);
const clients = client.getSucureClients();
for (const [, spair] of clients.pairs) {
spair.send([{ from: client.id, name, value }, "pair/info"]);
let clients = client.getSucureClients();
for (const [,spair] of clients.pairs)
{
spair.send([{
from: client.id,
name,
value
},"pair/info"]);
};
for (const [,spair] of clients.roompairs)
{
spair.send([{
from: client.id,
name,
value
},"pair/info"]);
};
process.send({
type: 'AUTH/INFO',
uuid: client.id,
name,
value
});
return end({
status: 'success'
});
}
case 'peer/info':{
if(client.isSecure(message.peer))
{
let info = {};
let peer = Client.clients.get(message.peer);
peer.info.forEach((value, name) => info[name] = value);
return end({
status: "success",
info
});
}else{
return end({
status: "fail",
message: "unaccessible user"
});
}
}
default:{
next();
}
for (const [, spair] of clients.roompairs) {
spair.send([{ from: client.id, name, value }, "pair/info"]);
}
return { status: 'success' };
});
register('peer/info', (client, msg) => {
const { peer } = msg;
if (!client.isSecure(peer)) {
return { status: "fail", message: "unaccessible user" };
process.on('message',({type, uuid, value, name}) => {
switch(type)
{
case "AUTH/INFO":{
let client = Client.clients.get(uuid);
if(client)
{
client.info.set(name, value);
}
const peerClient = Client.clients.get(peer);
const info = {};
peerClient.info.forEach((value, name) => { info[name] = value; });
return { status: "success", info };
});
break;
}
}
})

View File

@ -1,101 +1,168 @@
"use strict";
const { Client } = require("../Client.js");
const { register } = require("../WebSocket");
const { Room } = require("./Room");
let {randomUUID} = require("crypto");
let {addService,addListener} = require("../WebSocket.js");
const { Room } = require("./Room.js");
register('pack/to', (client, msg) => {
const { to, pack, handshake } = msg;
/*
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,
messageId
}) => {
let {type} = message;
switch(type)
{
case "pack/to":{
let {to,pack,handshake} = message;
if(!client.packReadable()){
return handshake ? { type: 'fail' } : undefined;
console.error("Okunabilir olmayan client bir mesaj iletiyor")
handshake && end({
type: 'fail'
})
}
if (!Client.clients.has(to)) {
return handshake ? { type: 'fail' } : undefined;
}
const otherPeer = Client.clients.get(to);
if (otherPeer.requiredPair) {
if (!otherPeer.pairs.has(to)) {
return handshake ? { type: 'fail' } : undefined;
if(Client.clients.has(to))
{
let otherPeer = Client.clients.get(to);
if(otherPeer.requiredPair)
{
if(!otherPeer.pairs.has(to))
{
console.error("Client, güvenilir olmayan bir cliente mesaj iletiyor")
return handshake && end({
type: 'fail'
})
}
}else{
if (!otherPeer.pairs.has(to)) {
if(!otherPeer.pairs.has(to))
{
otherPeer.pairs.add(client.id);
client.pairs.add(otherPeer.id);
}
}
if(!otherPeer.packWriteable()){
return handshake ? { type: 'fail' } : undefined;
console.error("Client, yazılabilir olmayan bir cliente mesaj iletiyor")
handshake && end({
type: 'fail'
})
}
otherPeer.send([{ from: client.id, pack }, 'pack']);
return handshake ? { type: 'success' } : undefined;
});
register('request/to', (client, msg) => {
const { to, pack } = msg;
if (!Client.clients.has(to)) {
return;
otherPeer.send([{
from: client.id,
pack: pack
}, 'pack']);
handshake && end({
type: 'success'
})
}else{
console.error("Client, olmayan bir cliente mesaj iletiyor")
handshake && end({
type: 'fail'
})
}
const otherPeer = Client.clients.get(to);
if (otherPeer.requiredPair) {
if (!otherPeer.pairs.has(to)) {
return;
break;
}
case "request/to":{
let {to,pack} = message;
if(Client.clients.has(to))
{
let otherPeer = Client.clients.get(to);
if(otherPeer.requiredPair)
{
if(!otherPeer.pairs.has(to))
{
console.error("Client, güvenilir olmayan bir clientden veri istiyor")
return handshake && end({
type: 'fail'
})
}
}else{
otherPeer.pairs.add(client.id);
client.pairs.add(otherPeer.id);
}
otherPeer.send([{ from: client.id, pack }, 'request']);
});
register('response/to', (client, msg) => {
const { to, pack, id } = msg;
if (!Client.clients.has(to)) {
otherPeer.send([{
from: client.id,
pack: pack,
id:messageId
}, 'request']);
}else console.error("request/to error not finded Peer");
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))
{
console.error("response istenen peer güvenli değil")
return;
}
const otherPeer = Client.clients.get(to);
if (otherPeer.requiredPair && !otherPeer.pairs.has(to)) {
return;
otherPeer.send([{
from: client.id,
pack: pack
}, id]);
}else console.error("response/to error not finded Peer");
break;
}
case "pack/room":{
let {to,pack, handshake,wom} = message;
otherPeer.send([{ from: client.id, pack }, id]);
});
register('pack/room', (client, msg) => {
const { to, pack, handshake, wom } = msg;
if(!client.packReadable()){
return handshake ? { type: 'fail' } : undefined;
console.error("Client paketi okumak için müsait değil")
handshake && end({
type: 'fail'
})
}
if (!Room.rooms.has(to)) {
return handshake ? { type: 'fail' } : undefined;
}
if (!client.rooms.has(to)) {
return handshake ? { type: 'fail' } : undefined;
}
const room = Room.rooms.get(to);
room.send(
[{ from: to, pack, sender: client.id }, 'pack/room'],
wom ? client.id : undefined,
c => c.packWriteable()
if(Room.rooms.has(to))
{
if(!client.rooms.has(to))
{
console.error("Client katılmadığı bir odaya mesaj iletiyor")
return handshake && end({
type: 'fail'
})
};
Room.rooms.get(to).send(
[
{
from: to,
pack: pack,
sender: client.id
}, 'pack/room'
],
wom ? client.id : void 0,
client => client.packWriteable()
);
return handshake ? { type: 'success' } : undefined;
handshake && end({
type: 'success'
})
}else{
console.error("Olmayan oda için veri gönderme isteniyor")
handshake && end({
type: 'fail'
})
}
break;
}
default:{
next();
}
};
});

View File

@ -1,70 +1,88 @@
"use strict";
const { Client } = require("../Client");
const { on, register } = require("../WebSocket");
let {addService, addListener} = require("../WebSocket.js");
class APNumber{
/**
* @type {Map<Number, Client>}
*/
static busyNumbers = new Map();
static lock(client) {
/**
* @type {number}
* @param {Client} client
*/
static lock(client)
{
let c = 24;
while(true){
if (!APNumber.busyNumbers.has(c)) {
if(!APNumber.busyNumbers.has(c))
{
APNumber.busyNumbers.set(c,client);
process.send({
type: 'AP_NUMBER/LOCK',
uuid: client.id,
value: c
});
})
return c;
}
c++;
}
}
static release(num) {
/**
* @param {number} num
*/
static release(num)
{
process.send({
type: 'AP_NUMBER/RELEASE',
uuid: APNumber.busyNumbers.get(num).id,
value: num
});
})
APNumber.busyNumbers.delete(num);
}
static whois(num){
return APNumber.busyNumbers.get(num)?.id;
}
}
class APShortCode{
/**
* @type {Map<string, Client>}
*/
static busyCodes = new Map();
/**
*
* @param {Client} client
* @returns
*/
static lock(client){
let firstLetter = new ShortCodeLetter();
let secondLetter = new ShortCodeLetter();
let thirdLetter = new ShortCodeLetter();
while (1) {
while(1)
{
let code = [firstLetter.code,secondLetter.code,thirdLetter.code].join('');
if (!APShortCode.busyCodes.has(code)) {
if(APShortCode.busyCodes.has(code) == false)
{
APShortCode.busyCodes.set(code, client);
process.send({
type: 'AP_SHORTCODE/LOCK',
uuid: APShortCode.busyCodes.get(code).id,
value: code
});
})
return code;
}
if (!thirdLetter.end()) {
thirdLetter.next();
if(!thirdLetter.end())
{
thirdLetter.next()
}else{
thirdLetter.reset();
if (!secondLetter.end()) {
thirdLetter.reset()
if(!secondLetter.end())
{
secondLetter.next();
}else{
secondLetter.reset();
if (!firstLetter.end()) {
if(!firstLetter.end())
{
firstLetter.next();
}else{
break;
@ -73,197 +91,364 @@ class APShortCode {
}
}
}
static release(code){
if (APShortCode.busyCodes.has(code)) {
if(APShortCode.busyCodes.has(code))
{
process.send({
type: 'AP_SHORTCODE/RELEASE',
uuid: APShortCode.busyCodes.get(code).id,
value: code
});
})
APShortCode.busyCodes.delete(code);
}
}
static whois(num){
return APShortCode.busyCodes.get(num)?.id;
}
}
};
class ShortCodeLetter{
chars = 'ABCDEFGHIKLMNOPRSTVXYZ'.split('');
now = 0;
code = 'A';
next(){
this.now++;
this.now++
this.code = this.chars.at(this.now);
return this.code;
}
reset(){
this.now = 0;
this.code = 'A';
}
end(){
return !this.chars.at(this.now + 1);
return !this.chars.at(this.now + 1)
}
}
class APIPAddress{
/**
* @type {Map<[number,number,number,number], Client>}
*/
static busyIP = new Map();
/**
* @param {Client} client
*/
static lock(client){
let A = 10, B = 0, C = 0, D = 1;
while (1) {
let A = 10;
let B = 0;
let C = 0;
let D = 1;
while(1)
{
let code = [A,B,C,D].join('.');
if (!APIPAddress.busyIP.has(code)) {
if(APIPAddress.busyIP.has(code) == false)
{
APIPAddress.busyIP.set(code, client);
process.send({
type: 'AP_IPADDRESS/LOCK',
uuid: APIPAddress.busyIP.get(code).id,
value: code
});
})
return code;
}
if (D != 255) { D++; continue; }
if(D != 255)
{
D++;
continue;
}else{
D = 0;
if (C != 255) { C++; continue; }
}
if(C != 255)
{
C++;
continue;
}else{
C = 0;
if (B != 255) { B++; continue; }
}
if(B != 255)
{
B++;
continue;
}else{
B = 0;
if (A != 255) { A++; continue; }
}
if(A != 255)
{
A++;
continue;
}else{
A = 0;
}
return;
}
}
static release(code){
if (APIPAddress.busyIP.has(code)) {
if(APIPAddress.busyIP.has(code))
{
process.send({
type: 'AP_IPADDRESS/RELEASE',
uuid: APIPAddress.busyIP.get(code).id,
value: code
});
})
APIPAddress.busyIP.delete(code);
}
}
static whois(num){
return APIPAddress.busyIP.get(num)?.id;
}
}
};
exports.APNumber = APNumber;
exports.APShortCode = APShortCode;
exports.APIPAddress = APIPAddress;
register('alloc/APIPAddress', (client, msg) => {
addService(({
client,
message,
end,
next
})=>{
let {type,whois} = message;
switch(type)
{
case "alloc/APIPAddress":{
if(client.APIPAddress) {
return { status: 'success', ip: client.APIPAddress };
}
end({
status : "success",
ip : client.APIPAddress
})
};
let value = APIPAddress.lock(client);
client.APIPAddress = value;
return { status: 'success', ip: value };
});
register('alloc/APNumber', (client, msg) => {
if (client.APNumber) {
return { status: 'success', number: client.APNumber };
client.sync('APIPAddress');
end({
status : "success",
ip : value
})
break;
}
case "alloc/APNumber":{
if(client.APNumber) {
end({
status : "success",
number : client.APNumber
})
};
let value = APNumber.lock(client);
client.APNumber = value;
return { status: 'success', number: value };
});
register('alloc/APShortCode', (client, msg) => {
if (client.APShortCode) {
return { status: 'success', code: client.APShortCode };
client.sync('APNumber');
end({
status : "success",
number : value
})
break;
}
case "alloc/APShortCode":{
if(client.APShortCode) {
end({
status : "success",
code : client.APShortCode
})
};
let value = APShortCode.lock(client);
client.APShortCode = value;
return { status: 'success', code: value };
});
register('realloc/APIPAddress', (client, msg) => {
client.sync('APShortCode');
end({
status : "success",
code : value
})
break;
}
case "realloc/APIPAddress":{
if(client.APIPAddress == 0){
return { status: 'fail' };
return end({
status : "fail"
})
}
APIPAddress.release(client.APIPAddress);
let value = APIPAddress.lock(client);
return { status: 'success', ip: value };
});
register('realloc/APNumber', (client, msg) => {
client.sync('APIPAddress');
end({
status : "success",
ip : value
})
break;
}
case "realloc/APNumber":{
if(client.APNumber == 0){
return { status: 'fail' };
return end({
status : "fail"
})
}
APNumber.release(client.APNumber);
let value = APNumber.lock(client);
return { status: 'success', number: value };
});
register('realloc/APShortCode', (client, msg) => {
client.sync('APNumber');
end({
status : "success",
number : value
})
break;
}
case "realloc/APShortCode":{
if(client.APShortCode == 0){
return { status: 'fail' };
return end({
status : "fail"
})
}
APShortCode.release(client.APShortCode);
let value = APShortCode.lock(client);
return { status: 'success', code: value };
});
register('release/APIPAddress', (client, msg) => {
client.sync('APShortCode');
end({
status : "success",
code : value
})
break;
}
case "release/APIPAddress":{
APIPAddress.release(client.APIPAddress);
client.APIPAddress = undefined;
return { status: 'success' };
});
register('release/APNumber', (client, msg) => {
client.APIPAddress = void 0;
client.sync('APShortCode');
end({
status : "success"
})
break;
}
case "release/APNumber":{
APNumber.release(client.APNumber);
client.APNumber = undefined;
return { status: 'success' };
});
register('release/APShortCode', (client, msg) => {
client.APNumber = void 0;
client.sync('APIPAddress');
end({
status : "success"
})
break;
}
case "release/APShortCode":{
APShortCode.release(client.APShortCode);
client.APShortCode = undefined;
return { status: 'success' };
});
register('whois/APIPAddress', (client, msg) => {
let socketId = APIPAddress.whois(msg.whois);
if (socketId) {
return { status: 'success', socket: socketId };
client.APShortCode = void 0;
client.sync('APIPAddress');
end({
status : "success"
})
break;
}
return { status: 'fail' };
});
register('whois/APNumber', (client, msg) => {
let socketId = APNumber.whois(msg.whois);
if (socketId) {
return { status: 'success', socket: socketId };
case "whois/APIPAddress":{
let socketId = APIPAddress.whois(whois);
if(socketId)
{
end({
status : "success",
socket : socketId
})
}else{
end({
status : "fail"
})
}
return { status: 'fail' };
});
register('whois/APShortCode', (client, msg) => {
let socketId = APShortCode.whois(msg.whois);
if (socketId) {
return { status: 'success', socket: socketId };
break;
}
return { status: 'fail' };
});
case "whois/APNumber":{
let socketId = APNumber.whois(whois);
if(socketId)
{
end({
status : "success",
socket : socketId
})
}else{
end({
status : "fail"
})
}
break;
}
case "whois/APShortCode":{
let socketId = APShortCode.whois(whois);
if(socketId)
{
end({
status : "success",
socket : socketId
})
}else{
end({
status : "fail"
})
}
break;
}
}
})
on('disconnect', (client) => {
if (client.APIPAddress != 0) {
process.on('message',({type, uuid, value}) => {
switch(type)
{
case "AP_NUMBER/LOCK":{
let client = Client.clients.get(uuid);
APNumber.busyNumbers.set(value, client);
if(client)
{
client.APNumber = value;
}
break;
}
case "AP_NUMBER/RELEASE":{
APNumber.busyNumbers.delete(value);
let client = Client.clients.get(uuid);
if(client)
{
client.APNumber = void 0;
}
break;
}
case "AP_SHORTCODE/LOCK":{
let client = Client.clients.get(uuid);
APShortCode.busyCodes.set(value, client);
if(client)
{
client.APShortCode = value;
}
break;
}
case "AP_SHORTCODE/RELEASE":{
APShortCode.busyCodes.delete(value);
let client = Client.clients.get(uuid);
if(client)
{
client.APShortCode = void 0;
}
break;
}
case "AP_IPADDRESS/LOCK":{
let client = Client.clients.get(uuid);
APIPAddress.busyIP.set(value, client);
if(client)
{
client.APIPAddress = value;
}
break;
}
case "AP_IPADDRESS/RELEASE":{
APIPAddress.busyIP.delete(value);
let client = Client.clients.get(uuid);
if(client)
{
client.APIPAddress = void 0;
}
break;
}
}
})
addListener('disconnect',(global, client)=>{
if(client.APIPAddress != 0)
{
APIPAddress.release(client.APIPAddress);
}
if (client.APNumber != 0) {
if(client.APNumber != 0)
{
APNumber.release(client.APNumber);
}
if (client.APShortCode != 0) {
if(client.APShortCode != 0)
{
APShortCode.release(client.APShortCode);
}
});

View File

@ -1,11 +1,14 @@
const { Client } = require("../Client.js");
let {randomUUID,createHash} = require("crypto");
const joi = require("joi");
const { on, register } = require("../WebSocket");
let {addService,addListener} = require("../WebSocket.js");
const { termoutput } = require("../config.js");
const { ROOM_CREATED, ROOM_DESTROY, ROOM_UPDATE_PROP, ROOM_JOIN_CLIENT, ROOM_EJECT_CLIENT } = require("../IPC.js");
let term = require("terminal-kit").terminal;
const stats = require("../stats");
function Sha256(update) {
function Sha256(update)
{
return createHash("sha256").update(update).digest("hex");
};
@ -68,12 +71,22 @@ function Room()
* @type {Map<string,any>}
*/
this.info = new Map();
this.sync = function(...args){
process.nextTick(()=>{
for (const name of args) {
ROOM_UPDATE_PROP(this.id, name, this[name]);
}
})
};
}
/**
* @param {Room} room
*/
Room.prototype.publish = function(){
stats.mwse_rooms++;
Room.rooms.set(this.id, this);
ROOM_CREATED(this);
termoutput && term.green("Room Published ").white(this.name," in ").yellow(this.clients.size).white(" clients")('\n');
};
/**
@ -186,6 +199,7 @@ Room.prototype.join = function(client){
};
client.rooms.add(this.id);
this.clients.set(client.id, client);
ROOM_JOIN_CLIENT(this.id, client.id);
termoutput && term.green("Client Room joined ").white(this.name," in ").yellow(this.clients.size + "").white(" clients")('\n');
};
Room.prototype.down = function(){
@ -195,6 +209,8 @@ Room.prototype.down = function(){
ownerid: this.owner.id
},'room/closed']);
Room.rooms.delete(this.id);
ROOM_DESTROY(this)
stats.mwse_rooms--;
};
/**
* @param {Client} client
@ -217,6 +233,7 @@ Room.prototype.eject = function(client){
}
client.rooms.delete(this.id);
this.clients.delete(client.id);
ROOM_EJECT_CLIENT(this.id, client.id);
if(this.clients.size == 0)
{
@ -232,7 +249,7 @@ Room.prototype.eject = function(client){
*/
Room.rooms = new Map();
on('connect', (client) => {
addListener('connect',(global, client)=>{
let room = new Room();
room.accessType = "private";
room.joinType = "notify";
@ -244,12 +261,10 @@ on('connect', (client) => {
room.join(client);
});
on('disconnect', (client) => {
const room = Room.rooms.get(client.id);
if (room) room.eject(client);
addListener('disconnect',(global, client)=>{
Room.rooms.get(client.id).eject(client);
for (const roomId of client.rooms) {
const r = Room.rooms.get(roomId);
if (r) r.eject(client);
Room.rooms.get(roomId).eject(client);
}
});
@ -268,282 +283,466 @@ let CreateRoomVerify = joi.object({
autoFetchInfo: joi.boolean().optional(),
});
register('myroom-info', (client, msg) => {
addService(({
client,
end,
global,
message,
next,
response
})=>{
let {type} = message;
switch(type)
{
case 'myroom-info':{
let room = Room.rooms.get(client.id);
return { status: "success", room: room.toJSON() };
});
register('room-peers', (client, msg) => {
const { roomId, filter } = msg;
if (!Room.rooms.has(roomId)) {
return { status: 'fail' };
end({
status: "success",
room: room.toJSON()
})
break;
}
const filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {});
return { status: 'success', peers: filteredPeers.map(i => i.id) };
case 'room-peers':{
let {roomId,filter} = message;
if(Room.rooms.has(roomId))
{
let filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {});
end({
status: 'success',
peers: filteredPeers.map(i => i.id)
});
register('room/peer-count', (client, msg) => {
const { roomId, filter } = msg;
if (!Room.rooms.has(roomId)) {
return { status: 'fail' };
}else{
end({
status: 'fail'
})
}
const filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {});
return { status: 'success', count: filteredPeers.length };
break;
}
case 'room/peer-count':{
let {roomId,filter} = message;
if(Room.rooms.has(roomId))
{
let filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {});
end({
status: 'success',
count: filteredPeers.length
});
register('room-info', (client, msg) => {
const { name } = msg;
for (const [roomId, room] of Room.rooms) {
if (name == room.name) {
return { status: "success", room: room.toJSON() };
}else{
end({
status: 'fail'
})
}
break;
}
return { status: "fail", message: "NOT-FOUND-ROOM" };
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()
});
register('joinedrooms', (client, msg) => {
return [...client.rooms].map(e => Room.rooms.get(e).toJSON());
});
register('closeroom', (client, msg) => {
const { roomId } = msg;
if (!Room.rooms.has(roomId)) {
return { status: 'fail' };
end(data)
break;
}
const room = Room.rooms.get(roomId);
if (room.owner === client.id) {
case 'closeroom':{
let {roomId} = message;
if(Room.rooms.has(roomId))
{
let room = Room.rooms.get(roomId);
if(room.owner === client.id)
{
room.down();
return { status: 'success' };
}
return { status: 'fail' };
end({
status: 'success'
});
}else{
end({
status: 'fail'
});
register('create-room', (client, msg) => {
const { error } = CreateRoomValidate.validate(msg);
if (error) {
return { status: 'fail', messages: error.message };
}
const { name } = msg;
for (const [, room] of Room.rooms) {
if (name == room.name) {
return { status: "fail", message: "ALREADY-EXISTS" };
}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 = msg.accessType;
room.notifyActionInvite = msg.notifyActionInvite;
room.notifyActionJoined = msg.notifyActionJoined;
room.notifyActionEjected = msg.notifyActionEjected;
room.joinType = msg.joinType;
room.description = msg.description;
room.name = msg.name;
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 (msg.credential) {
room.credential = Sha256(msg.credential + "");
if(message.credential)
{
room.credential = Sha256(message.credential + "");
}
room.publish();
room.join(client);
return { status: "success", room: room.toJSON() };
end({
status: "success",
room: room.toJSON()
});
register('joinroom', (client, msg) => {
const { name, autoFetchInfo } = msg;
}
break;
}
case 'joinroom':{
let {name,autoFetchInfo} = message;
let roomId;
for (const [_roomId, room] of Room.rooms) {
if (name == room.name) {
roomId = _roomId;
for (const [_roomId,{name:RoomName}] of Room.rooms) {
if(name == RoomName)
{
roomId = _roomId
break;
}
}
if (!Room.rooms.has(roomId)) {
return { status: "fail", message: "NOT-FOUND-ROOM" };
}
const room = Room.rooms.get(roomId);
if (room.joinType == "lock") {
return { status: "fail", message: "LOCKED-ROOM" };
}
if (room.joinType == "password") {
if (room.credential == Sha256(msg.credential + "")) {
let isRoom = Room.rooms.has(roomId);
if(isRoom)
{
let room = Room.rooms.get(roomId);
if(room.joinType == "lock")
{
return end({
status : "fail",
message : "LOCKED-ROOM"
})
}else if(room.joinType == "password")
{
if(room.credential == Sha256(message.credential + ""))
{
let info = {};
if (autoFetchInfo) {
if(autoFetchInfo)
{
info.info = room.getInfo();
}
};
room.join(client);
return { status: "success", room: room.toJSON(), ...info };
}
return { status: "fail", message: "WRONG-PASSWORD", area: "credential" };
}
if (room.joinType == "free") {
return end({
status : "success",
room: room.toJSON()
})
}else return end({
status : "fail",
message : "WRONG-PASSWORD",
area: "credential"
})
}else if(room.joinType == "free"){
let info = {};
if (autoFetchInfo) {
if(autoFetchInfo)
{
info.info = room.getInfo();
}
};
room.join(client);
return { status: "success", room: room.toJSON(), ...info };
}
if (room.joinType == "invite") {
return end({
status : "success",
room: room.toJSON(),
...info
})
}else if(room.joinType == "invite"){
room.waitingInvited.add(client.id);
if (room.notifyActionInvite) {
room.send([{ id: client.id }, "room/invite"]);
if(room.notifyActionInvite)
{
room.send([{
id: client.id
},"room/invite"]);
}else{
room.owner.send([{ id: client.id }, "room/invite"]);
room.owner.send([{
id: client.id
},"room/invite"]);
}
};
}else{
return end({
status : "fail",
message : "NOT-FOUND-ROOM"
})
}
break;
}
case 'ejectroom':{
let {roomId} = message;
let isRoom = Room.rooms.has(roomId);
if(isRoom)
{
let room = Room.rooms.get(roomId);
if(room.clients.has(client.id))
{
room.eject(client)
return end({
status : "success"
})
}else{
return end({
status : "fail",
message : "ALREADY-ROOM-OUT"
})
}
}else{
return end({
status : "fail",
message : "NOT-FOUND-ROOM"
})
}
break;
}
case 'accept/invite-room':{
let {roomId, clientId} = message;
// Odanın varlığının kontrolü
if(!Room.rooms.has(roomId))
{
end({
status : "fail",
message : "NOT-FOUND-ROOM"
})
};
let room = Room.rooms.get(roomId);
// erişim kontrolü
if(!client.rooms.has(room.id))
{
return end({
status : "fail",
message : "FORBIDDEN-INVITE-ACTIONS"
})
}
return { status: "fail", message: "NOT-FOUND-ROOM" };
});
// 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"
})
};
register('ejectroom', (client, msg) => {
const { roomId } = msg;
// Odaya kişiyi kabul etme
let JoinClient = Client.clients.get(clientId)
if (!Room.rooms.has(roomId)) {
return { status: "fail", message: "NOT-FOUND-ROOM" };
}
const room = Room.rooms.get(roomId);
if (!room.clients.has(client.id)) {
return { status: "fail", message: "ALREADY-ROOM-OUT" };
}
room.eject(client);
return { status: "success" };
});
register('accept/invite-room', (client, msg) => {
const { roomId, clientId } = msg;
if (!Room.rooms.has(roomId)) {
return { status: "fail", message: "NOT-FOUND-ROOM" };
}
const room = Room.rooms.get(roomId);
if (!client.rooms.has(room.id)) {
return { status: "fail", message: "FORBIDDEN-INVITE-ACTIONS" };
}
if (room.joinType == 'invite') {
return { status: "fail", message: "INVALID-DATA" };
}
if (!room.waitingInvited.includes(clientId)) {
return { status: "fail", message: "NO-WAITING-INVITED" };
}
if (!Client.clients.has(clientId)) {
return { status: "fail", message: "NO-CLIENT" };
}
const JoinClient = Client.clients.get(clientId);
room.join(JoinClient);
JoinClient.send([{ status: "accepted" }, 'room/invite/status']);
return { status: "success" };
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);
register('reject/invite-room', (client, msg) => {
const { roomId, clientId } = msg;
if (!Room.rooms.has(roomId)) {
return { status: "fail", message: "NOT-FOUND-ROOM" };
// erişim kontrolü
if(!client.rooms.has(room.id))
{
return end({
status : "fail",
message : "FORBIDDEN-INVITE-ACTIONS"
})
}
const room = Room.rooms.get(roomId);
// 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"
})
};
if (!client.rooms.has(room.id)) {
return { status: "fail", message: "FORBIDDEN-INVITE-ACTIONS" };
}
// Odaya davet edilen kişiyi reddetme
let JoinClient = Client.clients.get(clientId)
if (room.joinType == 'invite') {
return { status: "fail", message: "INVALID-DATA" };
}
if (!room.waitingInvited.includes(clientId)) {
return { status: "fail", message: "NO-WAITING-INVITED" };
}
if (!Client.clients.has(clientId)) {
return { status: "fail", message: "NO-CLIENT" };
}
const 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 { status: "success" };
room.send([{id:clientId,roomId:room.id},'room/invite/status'])
JoinClient.send([{
status:"rejected"
},'room/invite/status']);
return end({
status : "success"
});
register('room/list', (client, msg) => {
const rooms = [];
for (const [id, room] of Room.rooms) {
if (room.accessType == "public") {
}
case 'room/list':{
let rooms = [];
for (const [id, {accessType,name,joinType,description}] of Room.rooms)
{
if(accessType == "public")
{
rooms.push({
name: room.name,
joinType: room.joinType,
description: room.description,
name,
joinType,
description,
id
})
}
}
end({
type:'public/rooms',
rooms
});
}
case 'room/info':{
let {roomId,name} = message;
// Odanın varlığının kontrolü
if(!Room.rooms.has(roomId))
{
end({
status : "fail",
message : "NOT-FOUND-ROOM"
})
};
let room = Room.rooms.get(roomId);
// erişim kontrolü
if(!client.rooms.has(room.id))
{
return end({
status : "fail",
message : "NO-jıoned-ROOM"
})
}
if(name)
{
return end({
status: "success",
value: room.info.get(name)
});
}else{
return end({
status: "success",
value: room.getInfo()
});
}
}
return { type: 'public/rooms', rooms };
});
case 'room/setinfo':{
let {roomId,name,value} = message;
register('room/info', (client, msg) => {
const { roomId, name } = msg;
// Odanın varlığının kontrolü
if(!Room.rooms.has(roomId))
{
return end({
status : "fail",
message : "NOT-FOUND-ROOM"
})
};
let room = Room.rooms.get(roomId);
if (!Room.rooms.has(roomId)) {
return { status: "fail", message: "NOT-FOUND-ROOM" };
// erişim kontrolü
if(!client.rooms.has(room.id))
{
return end({
status : "fail",
message : "NO-JOINED-ROOM"
})
}
const room = Room.rooms.get(roomId);
if (!client.rooms.has(room.id)) {
return { status: "fail", message: "NO-JOINED-ROOM" };
}
if (name) {
return { status: "success", value: room.info.get(name) };
}
return { status: "success", value: room.getInfo() };
});
register('room/setinfo', (client, msg) => {
const { roomId, name, value } = msg;
if (!Room.rooms.has(roomId)) {
return { status: "fail", message: "NOT-FOUND-ROOM" };
}
const room = Room.rooms.get(roomId);
if (!client.rooms.has(room.id)) {
return { status: "fail", message: "NO-JOINED-ROOM" };
}
room.info.set(name, value);
room.send(
[{ name, value, roomId: room.id }, "room/info"],
[
{
name,
value,
roomId:room.id
},
"room/info"
],
client.id,
c => c.roomInfoNotifiable()
client => {
return client.roomInfoNotifiable()
}
);
return { status: "success" };
return end({
status: "success"
});
}
default:{
next();
}
}
});
exports.Room = Room;

View File

@ -1,43 +1,69 @@
"use strict";
let {addService,addListener} = require("../WebSocket.js");
const { on, emit, register } = require("../WebSocket");
const Stdoutput = [
["notifyPairInfo", true],
["packrecaive", true],
["packsending", true],
["notifyRoomInfo", true]
];
const defaults = {
notifyPairInfo: true,
packrecaive: true,
packsending: true,
notifyRoomInfo: true
};
addListener('connect',(global, client)=>{
for (const [name, defaultValue] of Stdoutput)
{
client.store.set(name, defaultValue);
}
client.sync('store');
});
on('connect', (client) => {
for (const [name, value] of Object.entries(defaults)) {
client.store.set(name, value);
addService(({
client,
end,
global,
message,
next,
response
})=>{
let {type, value} = message;
switch(type)
{
// enable/disable pair/info messages
case 'connection/pairinfo':{
client.store.set("notifyPairInfo", !!value)
client.sync('store');
end(true);
break;
}
case 'connection/roominfo':{
client.store.set("notifyRoomInfo", !!value)
client.sync('store');
end(true);
break;
}
case 'connection/packrecaive':{
client.store.set("packrecaive", !!value)
client.sync('store');
end(true);
break;
}
case 'connection/packsending':{
client.store.set("packsending", !!value)
client.sync('store');
end(true);
break;
}
case 'connection/reset':{
for (const [name, defaultValue] of Stdoutput)
{
client.store.set(name, defaultValue);
}
client.sync('store');
end(true);
break;
}
}
});
register('connection/pairinfo', (client, msg) => {
client.store.set("notifyPairInfo", !!msg.value);
return { status: 'success' };
});
register('connection/roominfo', (client, msg) => {
client.store.set("notifyRoomInfo", !!msg.value);
return { status: 'success' };
});
register('connection/packrecaive', (client, msg) => {
client.store.set("packrecaive", !!msg.value);
return { status: 'success' };
});
register('connection/packsending', (client, msg) => {
client.store.set("packsending", !!msg.value);
return { status: 'success' };
});
register('connection/reset', (client, msg) => {
for (const [name, value] of Object.entries(defaults)) {
client.store.set(name, value);
}
return { status: 'success' };
});

View File

@ -1,7 +1,8 @@
"use strict";
let {addListener} = require("../WebSocket.js");
const { on } = require("../WebSocket");
on('connect', (client) => {
client.send([{ type: 'id', value: client.id }, 'id']);
addListener('connect',(global, client)=>{
client.send([{
type: 'id',
value: client.id
},'id'])
});

View File

@ -1,94 +1,170 @@
"use strict";
let websocket = require("websocket");
let http = null;
let wsServer = null;
let {http} = require("./HTTPServer");
let {randomUUID} = require("crypto");
const { Client } = require("./Client.js");
const { termoutput } = require("./config");
const EventEmitter = require("./EventEmitter");
const MessageRouter = require("./MessageRouter");
function init(server) {
http = server;
const { CLIENT_CREATED, CLIENT_DESTROY } = require("./IPC");
const stats = require("./stats");
termoutput && console.log("Web Socket Protocol is ready");
http.addListener("upgrade",() => {
termoutput && console.log("HTTP Upgrading to WebSocket");
});
})
wsServer = new websocket.server({
let wsServer = new websocket.server({
httpServer: http,
autoAcceptConnections: true
});
/*
process.send({
core: "writestat",
writeBytes:0,
readBytes:0,
totalBytes:0,
recaivedPacket:0,
sendedPacket:0,
totalPacket:0
})*/
let global = new Map();
let clients = new Map();
wsServer.addListener("connect",(socket) => {
let client = new Client();
let xClient = new Client();
let id = randomUUID();
socket.id = id;
client.id = id;
client.socket = socket;
client.created_at = new Date();
Client.clients.set(id, client);
xClient.id = id;
xClient.socket = socket;
xClient.created_at = new Date();
Client.clients.set(id, xClient);
clients.set(id, xClient);
EventEmitter.emit('connect', client);
stats.mwse_clients++;
CLIENT_CREATED(id);
emit("connect", global, xClient);
let oldw = 0, oldr = 0;
let timer = setInterval(()=>{
let writed = socket.socket.bytesRead - oldr;
let readed = socket.socket.bytesWritten - oldw;
stats.ws_total_bytes += (writed + readed);
stats.ws_readed_bytes += readed;
stats.ws_writed_bytes += writed;
oldr = socket.socket.bytesRead;
oldw = socket.socket.bytesWritten;
}, 1000)
socket.addListener("close",()=>{
stats.mwse_clients--;
emit("disconnect", global, xClient);
CLIENT_DESTROY(id);
Client.clients.delete(id);
clearInterval(timer);
clearInterval(pingTimer);
});
let pingTimer = setInterval(()=> socket.ping('saQut') , 10_000);
socket.addListener("pong", (validationText) => {
socket.addListener("pong",validationText => {
if(validationText.toString('utf8') != "saQut"){
socket.close();
}
});
})
socket.addListener("message",({type,utf8Data}) => {
if (type == "utf8") {
stats.ws_recaived_packs++;
stats.ws_total_packs++;
if(type == "utf8")
{
let json;
try{
const json = JSON.parse(utf8Data);
const [message, id, action] = json;
let response;
if (typeof id === 'number' || typeof id === 'string') {
response = MessageRouter.handle(client, message);
if (action === 'R') {
client.send([response, id, 'E']);
} else if (action === 'S') {
client.send([response, id, 'C']);
}
} else {
const result = MessageRouter.handle(client, message);
if (result && result.broadcast) {
EventEmitter.emit('broadcast', result.broadcast, client);
}
}
} catch (error) {
EventEmitter.emit('messageError', client, utf8Data);
json = JSON.parse(utf8Data);
emit('services', global, xClient, json);
let [payload, id, action] = json;
if(typeof id === "string")
{
action = id;
id = void 0;
};
emitService(global, xClient, id, payload, action);
}catch{
emit("messageError", global, xClient, utf8Data);
}
}
});
});
socket.addListener("close", () => {
EventEmitter.emit('disconnect', client);
Client.clients.delete(id);
clearInterval(pingTimer);
});
});
/**
* @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);
}
const on = EventEmitter.on;
const emit = EventEmitter.emit;
const off = EventEmitter.off;
const register = MessageRouter.register;
const handle = MessageRouter.handle;
exports.init = init;
exports.on = on;
exports.emit = emit;
exports.off = off;
exports.register = register;
exports.handle = handle;
};
};
/**
*
* @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,
messageId: id,
response:(obj)=>{
id != undefined && client.send([obj, id, 'C']) // continue ([C]ONTINUE flag)
},
end:(obj)=>{
id != undefined && client.send([obj, id, 'E']) // stopped data stream (this channel) ([E]ND flag)
},
next:function(){
willContinue = true;
}
});
if(willContinue === false) break;
}
};
exports.websocket = wsServer;

View File

@ -1,235 +0,0 @@
"use strict";
const express = require("express");
const router = express.Router();
const { Client } = require("./Client");
const { Room } = require("./Services/Room");
const apiKeys = new Map();
const webhooks = new Map();
function auth(req, res, next) {
const key = req.headers['x-api-key'];
if (!key) {
return res.status(401).json({ status: 'fail', message: 'API_KEY_REQUIRED' });
}
if (!apiKeys.has(key)) {
return res.status(401).json({ status: 'fail', message: 'INVALID_API_KEY' });
}
req.server = apiKeys.get(key);
next();
}
router.post('/auth/key', (req, res) => {
const { domain } = req.body;
if (!domain) {
return res.json({ status: 'fail', message: 'DOMAIN_REQUIRED' });
}
const key = require("crypto").randomUUID();
apiKeys.set(key, { domain, key });
res.json({ status: 'success', key });
});
router.post('/client/:id/send', auth, (req, res) => {
const { id } = req.params;
const { pack } = req.body;
const client = Client.clients.get(id);
if (!client) {
return res.json({ status: 'fail', message: 'CLIENT_NOT_FOUND' });
}
if (!pack) {
return res.json({ status: 'fail', message: 'PACK_REQUIRED' });
}
const fromServer = req.server;
client.send([{ from: 'server', fromServer, pack }, 'server/pack']);
res.json({ status: 'success' });
});
router.post('/room/:id/send', auth, (req, res) => {
const { id } = req.params;
const { pack, wom } = req.body;
const room = Room.rooms.get(id);
if (!room) {
return res.json({ status: 'fail', message: 'ROOM_NOT_FOUND' });
}
if (!pack) {
return res.json({ status: 'fail', message: 'PACK_REQUIRED' });
}
const fromServer = req.server;
room.send(
[{ from: 'server', fromServer, pack, roomId: id }, 'server/pack/room'],
wom ? undefined : 'server',
() => true
);
res.json({ status: 'success' });
});
router.post('/room/create', auth, (req, res) => {
const { name, accessType, joinType, description, credential } = req.body;
if (!name) {
return res.json({ status: 'fail', message: 'NAME_REQUIRED' });
}
for (const [, room] of Room.rooms) {
if (room.name === name) {
return res.json({ status: 'fail', message: 'ROOM_ALREADY_EXISTS' });
}
}
const room = new Room();
room.name = name;
room.accessType = accessType || 'public';
room.joinType = joinType || 'free';
room.description = description || '';
room.owner = { id: 'server', isServer: true };
if (credential) {
const { createHash } = require("crypto");
room.credential = createHash("sha256").update(credential).digest("hex");
}
room.publish();
res.json({ status: 'success', room: room.toJSON() });
});
router.post('/room/:id/join', auth, (req, res) => {
const { id } = req.params;
const { credential } = req.body;
const room = Room.rooms.get(id);
if (!room) {
return res.json({ status: 'fail', message: 'ROOM_NOT_FOUND' });
}
if (room.joinType === 'lock') {
return res.json({ status: 'fail', message: 'ROOM_LOCKED' });
}
if (room.joinType === 'password') {
const { createHash } = require("crypto");
const hash = createHash(credential || '').digest("hex");
if (room.credential !== hash) {
return res.json({ status: 'fail', message: 'WRONG_PASSWORD' });
}
}
const fakeClient = {
id: 'server-joined',
isServer: true,
rooms: new Set(),
send: (msg) => {
const fromServer = req.server;
room.send([{ from: 'server', fromServer, ...msg[0] }, msg[1]], 'server');
}
};
room.join(fakeClient);
res.json({ status: 'success', room: room.toJSON() });
});
router.delete('/room/:id/leave', auth, (req, res) => {
const { id } = req.params;
const room = Room.rooms.get(id);
if (!room) {
return res.json({ status: 'fail', message: 'ROOM_NOT_FOUND' });
}
const fakeClient = { id: 'server-joined', isServer: true };
room.eject(fakeClient);
res.json({ status: 'success' });
});
router.get('/room/:id', (req, res) => {
const { id } = req.params;
const room = Room.rooms.get(id);
if (!room) {
return res.json({ status: 'fail', message: 'ROOM_NOT_FOUND' });
}
res.json({ status: 'success', room: room.toJSON() });
});
router.get('/rooms', (req, res) => {
const rooms = [];
for (const [id, room] of Room.rooms) {
rooms.push({
id,
name: room.name,
accessType: room.accessType,
joinType: room.joinType,
description: room.description,
clientCount: room.clients.size
});
}
res.json({ status: 'success', rooms });
});
router.get('/clients', (req, res) => {
const clients = [];
for (const [id, client] of Client.clients) {
clients.push({
id,
rooms: [...client.rooms],
pairs: [...client.pairs]
});
}
res.json({ status: 'success', clients });
});
router.post('/webhook', auth, (req, res) => {
const { url, events } = req.body;
if (!url) {
return res.json({ status: 'fail', message: 'URL_REQUIRED' });
}
const server = req.server;
webhooks.set(server.domain, { url, events: events || ['server/pack', 'server/pack/room'] });
res.json({ status: 'success' });
});
function triggerWebhook(event, data) {
for (const [, webhook] of webhooks) {
if (webhook.events.includes(event)) {
fetch(webhook.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ event, data })
}).catch(console.error);
}
}
}
module.exports = router;
module.exports.triggerWebhook = triggerWebhook;

View File

@ -1,9 +1,5 @@
require("./HTTPServer.js");
const { http } = require("./HTTPServer");
const WebSocket = require("./WebSocket");
WebSocket.init(http);
require("./WebSocket.js");
require("./Services/YourID.js");
require("./Services/Auth.js");

61
Source/stats.js Normal file
View File

@ -0,0 +1,61 @@
const { mlog } = require("./IPC");
exports.ws_writed_bytes = 0;
exports.ws_readed_bytes = 0;
exports.ws_total_bytes = 0;
exports.ws_sended_packs = 0;
exports.ws_recaived_packs = 0;
exports.ws_total_packs = 0;
exports.mwse_rooms = 0;
exports.mwse_clients = 0;
process.send({
core: "writestat",
ws_writed_bytes: exports.ws_writed_bytes,
ws_readed_bytes: exports.ws_readed_bytes,
ws_total_bytes: exports.ws_total_bytes,
ws_sended_packs: exports.ws_sended_packs,
ws_recaived_packs: exports.ws_recaived_packs,
ws_total_packs: exports.ws_total_packs,
mwse_rooms: exports.mwse_rooms,
mwse_clients: exports.mwse_clients
});
setInterval(()=>{
process.send({
core: "writestat",
ws_writed_bytes: exports.ws_writed_bytes,
ws_readed_bytes: exports.ws_readed_bytes,
ws_total_bytes: exports.ws_total_bytes,
ws_sended_packs: exports.ws_sended_packs,
ws_recaived_packs: exports.ws_recaived_packs,
ws_total_packs: exports.ws_total_packs,
mwse_rooms: exports.mwse_rooms,
mwse_clients: exports.mwse_clients
})
mlog(`writed ${exports.ws_writed_bytes} bytes, readed ${exports.ws_readed_bytes} bytes`);
exports.ws_writed_bytes = 0;
exports.ws_readed_bytes = 0;
exports.ws_total_bytes = 0;
exports.ws_sended_packs = 0;
exports.ws_recaived_packs = 0;
exports.ws_total_packs = 0;
process.send({
core: "readstat",
ws_writed_bytes: exports.ws_writed_bytes,
ws_readed_bytes: exports.ws_readed_bytes,
ws_total_bytes: exports.ws_total_bytes,
ws_sended_packs: exports.ws_sended_packs,
ws_recaived_packs: exports.ws_recaived_packs,
ws_total_packs: exports.ws_total_packs,
mwse_rooms: exports.mwse_rooms,
mwse_clients: exports.mwse_clients
})
}, 3000)
process.on('message', stat => {
if(stat.type == ':stats:')
{
exports.others = stat.data;
}
})

508
console/Hemex.js Normal file
View File

@ -0,0 +1,508 @@
function Hemex()
{
};
Hemex.EOL = "\n";
/**
* Hemex variable white space chars
* @type {Number[]}
*/
Hemex.WhiteSpace = [
9,10,11,12,13,32,133
];
/**
* Current cursor position
* @type {Number}
*/
Hemex.prototype.offset = 0;
/**
* Mapping offset points
* @type {Number[]}
*/
Hemex.prototype.offsetMap = [];
Hemex.prototype.beginPosition = function(){
this.offsetMap.push(
this.getLastPosition()
)
}
/**
* Adding current position to offset map
*/
Hemex.prototype.acceptPosition = function(){
let t = this.offsetMap.pop();
this.setLastPosition(t)
}
/**
* Get text range current and parent offsets
* @returns {[Number,Number]}
*/
Hemex.prototype.positionRange = function(){
let len = this.offsetMap.length;
if(len == 0)
{
return [0,this.offset]
}else if(len == 1){
return [this.offset,this.offsetMap[len - 1]]
}else{
return [this.offsetMap[len - 2],this.offsetMap[len - 1]]
}
}
/**
* Get text range between current offset and parent offset
* @returns {String}
*/
Hemex.prototype.getPositionRange = function(){
let u = this.positionRange();
return this.text.slice(u[0],u[1])
}
/**
* Cancel current position and return to parent offset
*/
Hemex.prototype.rejectPosition = function(){
this.offsetMap.pop()
}
/**
* Get current layer of position from last offset of map
* @returns {Number}
*/
Hemex.prototype.getLastPosition = function(){
return this.offsetMap.length == 0 ? this.offset : this.offsetMap.slice(-1)[0]
}
/**
* Set last position offset from offset map last layer
* @param {Number} n
*/
Hemex.prototype.setLastPosition = function(n){
if(this.offsetMap.length == 0)
this.offset = n
else this.offsetMap[this.offsetMap.length - 1] = n
}
/**
* Get current layer of position from last offset of map
* Some as getLastPosition()
* @returns {Number}
*/
Hemex.prototype.getOffset = function(){
return this.getLastPosition()
}
/**
* Set last position offset from offset map last layer and return it value
* @param {Number} n
* @returns {Number}
*/
Hemex.prototype.setOffset = function(n){
this.setLastPosition(n);
return this.getLastPosition()
}
/**
* Get text length
* @type {Number}
*/
Hemex.prototype.length = 0;
/**
* Hemex lexing data
* @type {String}
*/
Hemex.prototype.text = "";
/**
* set lexing data
* @param {String} text
* @returns {void}
*/
Hemex.prototype.setText = function(text){
this.offset = 0;
this.length = text.length;
this.offsetMap = [];
this.text = text;
}
/**
* get lexing all data
* @returns {String}
*/
Hemex.prototype.getText = function(){
return this.text;
}
/**
* Get one character from cursor
* @param {Number} n
* @returns {String}
*/
Hemex.prototype.getChar = function(n){
return this.text.charAt(n?this.getOffset()+n:this.getOffset())
}
/**
* Boolean
* @param {Number} n
* @returns {String}
*/
Hemex.prototype.isChar = function(b){
return this.getChar() == b
}
/**
* Dump all data from cursor position to end of char
* @param {Number} n
*/
Hemex.prototype.dump = function(n){
return this.text.slice(this.getOffset(),this.getOffset()+n)
}
/**
* Control coming end of line
* @returns {Bollean}
*/
Hemex.prototype.isEnd = function(){
return this.length > this.getOffset()
}
/**
* Forward one char
*/
Hemex.prototype.nextChar = function(){
this.setOffset(this.getOffset() + 1);
}
/**
* Forward n char
*/
Hemex.prototype.toChar = function(n){
this.setOffset(this.getOffset() + n);
}
/**
* Reading while end of line
* @returns {String}
*/
Hemex.prototype.getLine = function(){
return this.of(function(){
switch(this.getChar())
{
case Hemex.EOL: return false;
default: return true;
}
}.bind(this))
}
/**
* Read all data until the function returns false
* @param {Boolean} p
* @param {(char:String)=>Boolean} e
* @returns {String}
*/
Hemex.prototype.of = function(e,p){
let k = [],count=0;
while(this.isEnd()){
if(e(p,count)) k.push(this.getChar());
else return k.join('');
count++;
this.nextChar();
};
return k.length == 0 ? false : k.join('')
}
Hemex.prototype.each = function(e,p){
let k = [];
while(this.isEnd())
if(!e(p)) return;
else this.nextChar();
}
Hemex.prototype.while = function(e,p){
let k = [];
while(this.isEnd())
if(!e(p)) return;
}
/**
* Controlling for current char type
* @param {Boolean} reverse
* @returns {Boolean}
*/
Hemex.prototype.isNumber = function(reverse){
let c = this.getChar().charCodeAt(0);
let result = c >= 48 && c <= 57;
return reverse ? !result : result;
}
/**
* Read all data until char type is not number
* @param {Boolean} reverse
* @returns {String}
*/
Hemex.prototype.readNumbers = function(reverse){
return this.of(this.isNumber.bind(this),reverse)
}
/**
* Controlling for current char type
* @param {Boolean} reverse
* @returns {Boolean}
*/
Hemex.prototype.isBigLetter = function(reverse){
let c = this.getChar().charCodeAt(0);
let result = c >= 97 && c <= 122;
return reverse ? !result : result;
}
/**
* Controlling for current char type
* @param {Boolean} reverse
* @returns {Boolean}
*/
Hemex.prototype.isSmallLetter = function(reverse){
let c = this.getChar().charCodeAt(0);
let result = c >= 65 && c <= 90;
return reverse ? !result : result;
}
/**
* Controlling for current char type
* @param {Boolean} reverse
* @returns {Boolean}
*/
Hemex.prototype.isLetter = function(reverse){
let result = this.isSmallLetter() || this.isBigLetter()
return reverse ? !result : result;
}
/**
* Read all data until char type is not letter
* @param {Boolean} reverse
* @returns {String}
*/
Hemex.prototype.readLetters = function(reverse){
return this.of(this.isLetter.bind(this),reverse)
}
/**
* Controlling for current char type
* @param {Boolean} reverse
* @returns {Boolean}
*/
Hemex.prototype.isWhiteSpace = function(reverse){
let c = this.getChar(),ct = c.charCodeAt(0);
let result = (
c == '\n' ||
c == '\r' ||
c == '\t' ||
c == ' ' ||
Hemex.WhiteSpace.includes(ct)
)
return reverse ? !result : result;
}
/**
* Read all data until char type is not white space
* @param {Boolean} reverse
* @returns {String}
*/
Hemex.prototype.readWhiteSpace = function(reverse){
return this.of(this.isWhiteSpace.bind(this),reverse)
}
/**
* Controlling data
* @param {Boolean} reverse
* @returns {String}
*/
Hemex.prototype.include = function(words,next){
this.beginPosition();
for(let i = 0; i<words.length; i++)
{
if(words[i] != this.getChar())
{
this.rejectPosition();
return false;
};
this.nextChar();
};
if(next) this.acceptPosition();
else this.rejectPosition();
return true;
}
/**
* Controlling data
* @param {Boolean} reverse
* @returns {String|boolean}
*/
Hemex.prototype.includes = function(arrays,accept){
this.beginPosition();
let flags = Array.from(arrays).fill(true);
let index = 0;
this.each(function(){
let stopLoop = true;
for(let T in arrays)
{
if(!flags[T] || arrays[T].length <= index) continue;
stopLoop = false;
flags[T] &= arrays[T][index] == this.getChar()
};
index++;
return !stopLoop && flags.filter(function(val){return val}).length != 0;
}.bind(this));
let result = arrays.filter(function(_,index){return flags[index]});
if(accept) this.acceptPosition();
else this.rejectPosition();
return result.length == 0 ? false : result
}
/**
* Parsing number formats like; 12 75.1 0xE7 0b10 +3.46
* @returns {[String,Number]}
*/
Hemex.prototype.readNumber = function(){
let data = [];
let base = 10;
let nextDot = false;
let c = this.getChar();
if(this.isChar('0'))
{
this.nextChar();
switch(this.getChar())
{
case 'x':{
base = 16;
this.nextChar();
data.push('0x')
break;
}
case 'b':{
base = 2;
this.nextChar();
data.push('0b')
break;
}
default:{
base = 8;
this.nextChar();
data.push('0')
}
}
}else base = 10;
this.each(()=>{
switch(c = this.getChar())
{
case '0':
case '1':{
data.push(c);
break;
}
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':{
if(base >= 8){
data.push(c);
break;
}else return false;
}
case '8':
case '9':{
if(base >= 10){
data.push(c);
break;
}else return false;
}
case 'A':
case 'a':
case 'B':
case 'b':
case 'C':
case 'c':
case 'D':
case 'd':
/* case 'E': case 'e': */
case 'F':
case 'f':{
if(base >= 16){
data.push(c);
break;
}else return false;
}
case '.':{
if(!nextDot){
if(data.length == 0){
data.push("0");
}else data.push(".");
nextDot = true;
isFloat = true;
}else{
throw new Error("Float number in Double dot");
};
break;
}
case 'E':
case 'e':{
if(this.getChar(1)!='+'){
if(base == 16){
data.push(c);
break;
}else return false;
};
if(data.length == 0){
this.rejectPosition();
return false;
};
data.push('e');
this.nextChar();
if(this.getChar()=='+' || this.getChar()=='-'){
data.push(char());
this.nextChar();
};
let result = null;
this.each(()=>{
switch(this.getChar()){
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':{
data.push(this.getChar());
this.nextChar();
break;
}
default:{
return false;
}
}
})
}
default:{
return false;
}
};
return true
});
return data.length == 0 ? false : [data.join(''),base]
}
Hemex.prototype.syntaxs = new Map();
/**
*
* @param {string} name
* @param {(hmx:Hemex, result: (result:any) => any,...args:any[]) => any} callback
*/
Hemex.prototype.syntax = function(name, callback){
this.syntaxs.set(name, callback)
}
Hemex.prototype.give = function(name, ...args){
let sandbox = this.syntaxs.get(name);
if(sandbox)
{
let res = undefined;
hmx.beginPosition();
if(sandbox(
this,
arg => {
res = arg
},
...args
))
{
hmx.acceptPosition();
return res;
}else{
hmx.rejectPosition();
return res;
}
}
}
/**
* @param {Error} message
*/
Hemex.prototype.throw = function(message){
throw new Error(message)
};

21
console/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<base href="/console/">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Terminal</title>
<style>
body{
background-color: black;
}
</style>
</head>
<body>
<div id="terminal"></div>
<script src="xterm.js"></script>
<link rel="stylesheet" href="xterm.min.css">
<script src="./Hemex.js"></script>
<script src="lib.js"></script>
</body>
</html>

920
console/lib.js Normal file
View File

@ -0,0 +1,920 @@
var term = new Terminal({
cursorStyle: "underline",
cols: 180,
rows: 50
});
term.open(document.querySelector('#terminal'));
function newLine(t = true, prompt = true) {
t && term.write('\r\n');
cursorPosition = 0;
if(prompt)
{
term.write(DEFAULT_PROMPT);
cursorPosition += CLEAR_STYLING(DEFAULT_PROMPT).length;
}
resetUserSpace()
}
let waitms = ms => new Promise(ok => setTimeout(()=>ok(), ms));
let cursorPosition = 0;
let _buffer = [];
let COLOR_RESET = () => "\x1B[0m";
let COLOR_TEXT = (r,g,b) => `\x1B[38;2;${r};${g};${b}m`;
let COLOR_BACKGROUND = (r,g,b) => `\x1B[48;2;${r};${g};${b}m`;
let CURSOR_MOVE = (row, col) => `\x1B[${row};${col}H`;
let CURSOR_MOVE_UP = () => `\x1B[A`;
let CURSOR_MOVE_DOWN = () => `\x1B[B`;
let CURSOR_MOVE_RIGHT = () => `\x1B[C`;
let CURSOR_MOVE_LEFT = () => `\x1B[D`;
let CLEAR_SCREEN = () => `\x1B[2J`;
let CLEAR_LINE = () => `\x1B[2K`;
term.write(CURSOR_MOVE(2,1));
let CLEAR_STYLING = (text) => text.toString().replace(/\x1B(.*?)m/ig,'');
function writeHi()
{
term.write(
"Merhaba, "
+ COLOR_TEXT(0,255,0)
+ COLOR_BACKGROUND(32,32,32)
+ ' saQut RC Shell '
+ COLOR_RESET()
+ "\n\n\r"
);
newLine();
}
let userSpace = {
buffer: [],
paddingChars: 0,
size: 0
};
function resetUserSpace()
{
userSpace = {
buffer: [],
paddingChars: 0,
size: 0
}
}
function getCurrentLine()
{
return term
.buffer
.active
.getLine(
term
.buffer
.active
.cursorY
)
.translateToString()
}
let pausedInput = false;
let activeProgram = false;
term.onData(async chars => {
if(pausedInput)
{
return
}
if(chars.length > 1)
{
return await DefaultProcess(chars);
}
if(chars.length == 1)
{
if(activeProgram)
{
if(activeProgram._sys.idleChar)
{
return activeProgram._sys.idleChar.triggerChar(chars);
}
}
return await DefaultProcess(chars);
}else if(activeProgram){
return
}
let tokens = [];
chars.toString().replace(/(\x1B.+?m)|(\x1B\[.+)|(.+)/igm, (_,a,b,c,d,e,f,g) => {
let charx = a || b || c;
if(charx)
{
if(/^(\x1B.*?m)$|^(\x1B\[.*)$/i.test(charx))
{
tokens.push([charx,])
}else{
tokens.push([,charx])
}
}
});
for (const [code, chars] of tokens)
{
pausedInput = true;
if(code)
{
await DefaultProcess(code);
}else if(chars)
{
for (const char of chars.split(''))
{
await DefaultProcess(char);
}
}
pausedInput = false;
}
})
async function DefaultProcess(char)
{
let contextchanged = false;
switch(char)
{
// Backspace
case '\x7f':
if(userSpace.paddingChars > 0)
{
term.write('\b \b');
cursorPosition--;
let after = userSpace.buffer.slice(0, userSpace.paddingChars - 1);
let before = userSpace.buffer.slice(userSpace.paddingChars);
userSpace.buffer = [
...after,
...before
];
userSpace.paddingChars--;
userSpace.size--;
contextchanged = true;
term.write(before.join('')+" \b");
term.write(before.map(() => '\b').join(''));
}
break;
// Enter
case '\r':
let command = userSpace.buffer.join('');
if(activeProgram && activeProgram._sys.idleReadline)
{
newLine(true, false);
return activeProgram._sys.triggerReadline(command);
}
let parsedCommand = ParseCommand(command);
if(parsedCommand != false)
{
let program = parsedCommand.find(e => e.type == 'commandname').data;
pausedInput = true;
newLine(true, false);
await ExecuteCommand(
program,
parsedCommand
);
pausedInput = false;
newLine(false);
}else{
newLine();
}
break;
case CURSOR_MOVE_LEFT():
if(userSpace.paddingChars > 0)
{
userSpace.paddingChars--;
term.write(CURSOR_MOVE_LEFT());
}
break;
case CURSOR_MOVE_UP():
case CURSOR_MOVE_DOWN():
break;
// Delete
case "\x1B[3~":
let after = userSpace.buffer.slice(0, userSpace.paddingChars);
let before = userSpace.buffer.slice(userSpace.paddingChars + 1);
term.write(before.join('')+" \b");
term.write(before.map(() => '\b').join(''));
userSpace.buffer = [
...after,
...before
];
userSpace.size--;
contextchanged = true;
break;
case CURSOR_MOVE_RIGHT():
if(userSpace.paddingChars < userSpace.size)
{
term.write(CURSOR_MOVE_RIGHT());
userSpace.paddingChars++;
}
break;
default:
let printable = char.charCodeAt(0);
if(printable < 32)
{
return
}
// insert or append ?
if(userSpace.paddingChars == userSpace.size)
{
term.write(char);
userSpace.buffer = [
...userSpace.buffer.slice(0, userSpace.paddingChars),
char,
...userSpace.buffer.slice(userSpace.paddingChars),
];
userSpace.paddingChars++;
userSpace.size++;
cursorPosition++;
contextchanged = true;
}else{
let after = userSpace.buffer.slice(0, userSpace.paddingChars);
let before = userSpace.buffer.slice(userSpace.paddingChars);
userSpace.buffer = [
...after,
char,
...before
];
term.write(char);
term.write(before.join(''));
term.write(before.map(() => '\b').join(''));
contextchanged = true;
userSpace.paddingChars++;
userSpace.size++;
cursorPosition++;
}
}
if(!activeProgram)
{
contextchanged && COLORIZE_LINE();
}
};
async function COLORIZE_LINE()
{
let before = userSpace.buffer.slice(0, userSpace.paddingChars);
let after = userSpace.buffer.slice(userSpace.paddingChars);
let coloredCommand = CommandColorize(before.concat(after).join(''));
if(coloredCommand)
{
// Cursoru en başa al
term.write(CURSOR_MOVE_LEFT().repeat(before.length));
term.write(coloredCommand);
term.write(CURSOR_MOVE_LEFT().repeat(after.length));
}
}
let USER_SPACE = "virtualhost";
let USER_NAME = [
147 + Math.random() * 25 | 0,
200 + Math.random() * 56 | 0,
0 + Math.random() * 256 | 0,
0 + Math.random() * 256 | 0
].join('.');
let DEFAULT_PROMPT = (
COLOR_RESET()
+ '['
+ COLOR_TEXT(0,255,0)
+ USER_SPACE
+ ' '
+ COLOR_TEXT(0,128,255)
+ USER_NAME
+ COLOR_RESET()
+ '] $ '
);
writeHi();
let _color_command = [255,255,255];
let _color_option = [128,128,255];
let _color_stringdata = [128,255,64];
function CommandColorize(command)
{
let hmx = new Hemex();
hmx.setText(command);
let output = [];
let commandBefore = hmx.of(() => !hmx.isLetter());
if(commandBefore.length != 0)
{
output.push(
COLOR_TEXT(255,255,255),
commandBefore,
COLOR_RESET()
)
}
if(!hmx.isLetter())
{
return false
}
let commandName = hmx.of(() => hmx.isLetter() || hmx.isNumber() || hmx.includes(['-','_',':','.','$']));
if(hmx.isEnd() && !hmx.isWhiteSpace())
{
return false
}
output.push(
COLOR_TEXT(..._color_command),
commandName,
COLOR_RESET()
);
let all = [], stringstarted = false;
hmx.each(() => {
let char = hmx.getChar();
switch(char)
{
case '\'':
case '"':
case '`':{
if(stringstarted == false)
{
stringstarted = char;
all.push(COLOR_TEXT(..._color_stringdata), char)
}else if(stringstarted == char){
stringstarted = false;
all.push(char, COLOR_TEXT(..._color_option))
}else{
all.push(char)
}
return true;
}
default:{
if(all.length == 0)
{
all.push(COLOR_TEXT(..._color_option), char);
}else{
all.push(char)
}
return true;
}
}
});
output.push(all.join(''))
output.push(COLOR_RESET())
return output.join('')
}
function ParseCommand(command)
{
let hmx = new Hemex();
hmx.setText(command);
hmx.of(() => !hmx.isLetter());
if(!hmx.isLetter())
{
return false
}
let onlyArgs = false;
let commandName = hmx.of(() => hmx.isLetter() || hmx.isNumber() || hmx.includes(['-','_',':','.','$']));
if(hmx.isEnd() && !hmx.isWhiteSpace())
{
return false
}
hmx.readWhiteSpace();
let args = [];
let xargs = [], operation = 'option';
hmx.beginPosition();
hmx.while(()=>{
if(hmx.include('--') && operation == 'option')
{
xargs.length && (
args.push({
type: 'data',
data: xargs.join('')
}),
xargs = []
);
hmx.beginPosition();
hmx.include('--', true);
let argumentkey = hmx.of(() => hmx.isLetter() || hmx.isNumber() || hmx.includes(['-','_',':','.','$']));
if(argumentkey == false)
{
hmx.rejectPosition();
return true;
}
args.push({
type: "argument",
data: argumentkey
});
hmx.acceptPosition();
hmx.readWhiteSpace();
return true;
}
if(hmx.include('-') && operation == 'option')
{
xargs.length && (
args.push({
type: 'data',
data: xargs.join('').trim()
}),
xargs = []
);
hmx.beginPosition();
hmx.nextChar();
let argumentkey = hmx.of(() => hmx.isLetter() || hmx.isNumber());
if(argumentkey == false)
{
hmx.rejectPosition();
return true;
}
argumentkey.split('').forEach(e => {
args.push({
type: "flag",
data: e
})
})
hmx.acceptPosition();
hmx.readWhiteSpace();
return true;
}
let char;
if((char = hmx.includes(['\'','"'])) && operation == 'option')
{
xargs.length && (
args.push({
type: 'data',
data: xargs.join('')
}),
xargs = []
);
let escapeChar = char[0];
hmx.nextChar();
let data = [];
hmx.while(() => {
let char = hmx.getChar();
switch(hmx.getChar())
{
case escapeChar:{
hmx.nextChar();
return false;
}
default:{
data.push(char);
hmx.nextChar();
return true;
}
}
});
args.push({
type: "string",
data: data.join('')
})
hmx.readWhiteSpace();
return true;
}
xargs.push(hmx.getChar());
if(hmx.isWhiteSpace())
{
operation = 'option';
}else{
operation = 'data';
}
hmx.nextChar();
if(hmx.isEnd())
{
return true;
}else{
args.push({
type: "data",
data: xargs.join('')
})
return false;
}
});
onlyArgs = hmx.getPositionRange();
hmx.acceptPosition();
return [{
type: "cmdline",
data: command
},{
type: "commandname",
data: commandName
},{
type: "arguments",
data: onlyArgs
}, ...args]
}
function generatepipe()
{
pausedInput = true;
let events = {};
let gen = {
stdin: {
setPaused(boolean){
pausedInput = Boolean(boolean);
},
async readline(){
gen.stdin.setPaused(false);
gen._sys.idleReadline = true;
return await new Promise(ok => {
events['readline'] = (result) => {
gen.stdin.setPaused(true);
gen._sys.idleReadline = false;
ok(result)
events['readline'] = null;
};
})
},
async getchar(){
gen.stdin.setPaused(false);
gen._sys.idleChar = true;
return await new Promise(ok => {
events['read'] = (result) => {
gen.stdin.setPaused(true);
gen._sys.idleChar = false;
events['read'] = null;
ok(result)
};
})
}
},
stdout: {
async write(...args){
term.write(args.map(e => e.toString()).join(' '))
},
async writeln(...args){
term.writeln(args.map(e => e.toString()).join(' '))
},
COLOR_RESET,
COLOR_TEXT,
COLOR_BACKGROUND,
CURSOR_MOVE,
CURSOR_MOVE_UP,
CURSOR_MOVE_DOWN,
CURSOR_MOVE_RIGHT,
CURSOR_MOVE_LEFT,
CLEAR_SCREEN,
CLEAR_LINE,
CLEAR_STYLING
},
stderr: {
async write(...args){
term.write(COLOR_TEXT(255,0,0));
term.write(args.map(e => e.toString()).join(' '))
term.write(COLOR_RESET());
},
async writeln(...args){
term.write(COLOR_TEXT(255,0,0));
term.writeln(args.map(e => e.toString()).join(' '))
term.write(COLOR_RESET());
}
},
_sys:{
idleReadline: false,
idleChar: false,
triggerReadline(text){
if(events['readline']) events['readline'](text)
},
triggerChar(text){
if(events['read']) events['read'](text)
},
destroy(){
gen.stdin.setPaused(false);
events = void 0;
gen._sys.idleReadline = false;
gen._sys.idleChar = false;
gen._sys = void 0;
gen.stderr = void 0;
gen.stdin = void 0;
gen.stdout = void 0;
activeProgram = false;
}
}
};
activeProgram = gen;
return gen;
}
async function ExecuteCommand(command, cmdline)
{
let Namespace = await CommandNamespace(command);
if(Namespace == null)
{
return
}
try
{
await new Promise(async (ok,reject) => {
resetUserSpace();
let program = new Namespace(generatepipe());
program.exit = () => ok();
try{
let t = program.main(cmdline);
if(t?.constructor?.name == 'AsyncFunction')
{
await t;
}
}catch(e){
reject(e)
}
})
}
catch(exception)
{
term.writeln('');
term.write(COLOR_TEXT(255,0,0));
term.writeln('Yazılım hatası:');
term.writeln('=========================');
term.write(COLOR_RESET());
term.writeln(exception.message);
term.writeln(exception.stack.replace(/\n/g,'\r\n'));
term.write(COLOR_TEXT(255,0,0));
term.writeln('=========================');
term.write(COLOR_RESET());
}
finally
{
pausedInput = false;
activeProgram = void 0;
}
}
async function InspectRepo()
{
if(CommandNamespace.repos.find(e => e.downloaded == false))
{
term.writeln(COLOR_RESET()+"\r\nUpdating repos....")
await wait(100);
}else{
term.writeln('');
}
for(const repo of CommandNamespace.repos)
{
if(repo.downloaded == false)
{
term.writeln(`Downloading:${COLOR_TEXT(0,255,0)} ${repo.name} ${COLOR_RESET()} ${repo.endpoint}`);
await wait(100);
let file = await DownloadRepoMetapack(repo.endpoint);
repo.packages = file;
term.writeln(`Downloaded:${COLOR_TEXT(0,255,0)} ${repo.name} ${COLOR_RESET()} ${repo.endpoint}`);
await wait(100);
repo.downloaded = true;
}
}
return
}
async function CommandNamespace(command)
{
let package = false;
if(!CommandNamespace.namespaces.has(command))
{
await InspectRepo();
for(const repo of CommandNamespace.repos)
{
for (const repopackage in repo.packages)
{
let packageName = repopackage;
let meta = repo.packages[repopackage];
let _command = meta.commands.find(_command => command == _command);
if(_command)
{
package = [packageName,meta];
}
}
}
if(package == false)
{
term.writeln(COLOR_TEXT(255,0,0) + "'" + command + "' not found !" + COLOR_RESET());
}else{
term.writeln([
COLOR_TEXT(0,255,0),
package[0],
COLOR_TEXT(200,200,200),
" paketine ait olan ",
COLOR_TEXT(0,255,0),
command,
COLOR_TEXT(200,200,200),
" komutunu kullanabilmek için aşağıdaki komutunu çalıştırın"
].join('') +
"\r\n\n" +
CommandColorize(`load '${package[0]}' from 'official'`)+
"\n"
);
}
}else{
return CommandNamespace.namespaces.get(command);
}
}
function wait(ms)
{
return new Promise(ok => setTimeout(() => ok(),ms));
}
async function DownloadRepoMetapack(endpoint)
{
let request = await fetch(endpoint,{
method: "get",
cache: "force-cache",
priority: "high",
redirect: "follow",
referrerPolicy: "no-referrer"
});
await wait(100);
try{
let response = await request.json();
return response;
}catch
{
return null
}
}
CommandNamespace.namespaces = new Map();
CommandNamespace.repos = [{
name: "official",
endpoint: new URL('/console/official/packages.json',window.location),
downloaded: false,
packages: {}
}];
class SystemLoad{
stdin = null;
stdout = null;
constructor(pipe){
this.stdin = pipe.stdin;
this.stdout = pipe.stdout;
}
printhelp()
{
this.stdout.writeln('Kullanım şekli: ');
this.stdout.writeln('ExampleProgram paketini kurmak için aşağıdaki komutu yazmalısınız\r\n');
this.stdout.writeln(
CommandColorize(`load 'ExampleProgram'`)+"\r\n"
);
}
async main(args)
{
let m = false, nargs = args.filter(({type}) => {
if(type == "arguments") return m = true, false;
return m;
});
let packname;
if(nargs[0].type == "string")
{
packname = nargs[0].data;
}else{
this.printhelp();
this.exit();
return;
}
if(CommandNamespace.repos.find(e => e.downloaded == false))
{
this.stdout.writeln(COLOR_RESET()+"\r\nUpdating repos....")
}else{
this.stdout.writeln('');
}
for(const repo of CommandNamespace.repos)
{
if(repo.downloaded == false)
{
this.stdout.writeln(`Downloading:${COLOR_TEXT(0,255,0)} ${repo.name} ${COLOR_RESET()} ${repo.endpoint}`);
await wait(100);
let file = await DownloadRepoMetapack(repo.endpoint.href);
if(file == null){
continue
}
repo.packages = file;
this.stdout.writeln(`Downloaded:${COLOR_TEXT(0,255,0)} ${repo.name} ${COLOR_RESET()} ${repo.endpoint}`);
await wait(100);
repo.downloaded = true;
}
}
let endpoints = CommandNamespace.repos.map(e => e.packages[packname] && e.packages[packname].endpoints.map(endpoint => new URL(endpoint,e.endpoint))).filter(e => !!e)
if(endpoints.length == 0)
{
term.writeln(COLOR_TEXT(255,0,0) + "'" + packname + "' repo not found !" + COLOR_RESET());
await wait(100);
this.exit();
return;
}
for (const endpoint of endpoints[0])
{
let href = endpoint.href;
this.stdout.writeln(`Packet downloading :${COLOR_TEXT(0,255,0)} ${packname} ${COLOR_RESET()} ${endpoint.href}`);
await wait(100);
let request = await fetch(href,{
method: "get",
cache: "force-cache",
priority: "high",
redirect: "follow",
referrerPolicy: "no-referrer"
});
this.stdout.writeln(`Packet unzip to :${COLOR_TEXT(0,255,0)} /mount/xpack/${packname}/ ${COLOR_RESET()}`);
await wait(100);
let script = await request.text();
try{
this.stdout.writeln(`Analyzing..`);
await new Promise(ok => setTimeout(() => ok(),1000));
(new Function(script))();
this.stdout.writeln(`Process success`);
await wait(100);
}catch(error){
debugger;
this.stderr.writeln(`Process error`);
await wait(100);
}
}
this.exit();
}
};
CommandNamespace.namespaces.set('load', SystemLoad);

View File

@ -0,0 +1,51 @@
class CommandJS{
stdin = null;
stdout = null;
stderr = null;
constructor(pipe){
this.stdin = pipe.stdin;
this.stdout = pipe.stdout;
this.stderr = pipe.stderr;
}
async main(args)
{
let commandline = args.find(e => e.type == "arguments");
if(commandline)
{
try{
let t = eval(commandline.data);
if(typeof t != "undefined")
{
this.stdout.writeln(JSON.stringify(t,null, ' ').replace(/\n/g,'\r\n'));
}
}catch(e){
this.stderr.writeln(e.message);
}
}
this.exit();
}
};
class CommandJSRaw{
stdin = null;
stdout = null;
stderr = null;
constructor(pipe){
this.stdin = pipe.stdin;
this.stdout = pipe.stdout;
this.stderr = pipe.stderr;
}
async main(args)
{
let commandline = args.find(e => e.type == "arguments");
if(commandline)
{
let t = eval(commandline.data);
this.stdout.writeln(t);
}
this.exit();
}
};
CommandNamespace.namespaces.set('js', CommandJS);
CommandNamespace.namespaces.set('jsecho', CommandJSRaw);

View File

@ -0,0 +1,18 @@
class CommandSet{
stdin = null;
stdout = null;
stderr = null;
constructor(pipe){
this.stdin = pipe.stdin;
this.stdout = pipe.stdout;
this.stderr = pipe.stderr;
}
async main(args)
{
this.stdout.writeln(JSON.stringify(args,null, ' ').replace(/\n/g,'\r\n'));
this.exit();
}
};
CommandNamespace.namespaces.set('set', CommandSet);

View File

@ -0,0 +1,23 @@
{
"native": {
"endpoints": [
"./native/js.js",
"./native/set.js"
],
"commands": [
"js",
"jsecho",
"set"
]
},
"rtc": {
"endpoints": [
"./rtc/mwse-socket.js",
"./rtc/mwse-close.js"
],
"commands": [
"mwse-socket",
"mwse-close"
]
}
}

18
console/official/plan.txt Normal file
View File

@ -0,0 +1,18 @@
Oluşturulmuş cihazlar | device list
Yeni AudioContext | device create audio /dev/audio0
Yeni HTMLVideoElement | device create video /dev/video0
getUserMedia isteği | webcam 480x360 --audio /dev/audio0 --video /dev/video0
AudioContext sesi dışarı verme | device /dev/audio0 --output speaker
AudioContext sesi açma | device /dev/audio0 --value 100
Videoyu oynatma | lightplayer --source /dev/video0 --target popup
Tüm bağlantıları görme | rtc connections
RTC Oluşturma | rtc create "peer-0"
RTC offer verme | rtc "peer-0" create offer
RTC offer alma | rtc "peer-0" emit offer "offer message"
RTC answer verme | rtc "peer-0" create answer
RTC answer alma | rtc "peer-0" emit answer "answer message"
RTC answer verme | rtc "peer-0" create answer "answer message"
RTC candidate alma | rtc "peer-0" getcandidate --all
RTC candidate verme | rtc "peer-0" setcandidate

View File

@ -0,0 +1,44 @@
class MWSECloser{
stdin = null;
stdout = null;
stderr = null;
endpoint = null;
static mwse = null;
constructor(pipe){
this.stdin = pipe.stdin;
this.stdout = pipe.stdout;
this.stderr = pipe.stderr;
}
greenText(text){
this.stdout.writeln(`${this.stdout.COLOR_TEXT(0,255,0)}${text}${this.stdout.COLOR_RESET()}`);
}
redText(text){
this.stdout.writeln(`${this.stdout.COLOR_TEXT(255,0,0)}${text}${this.stdout.COLOR_RESET()}`);
}
async main(args)
{
let mwseConnector = CommandNamespace.namespaces.get('mwse-socket');
if(mwseConnector == null){
this.stderr.writeln(`mwse-socket yok`);
return this.exit();
}
if(mwseConnector.mwse == null){
this.stderr.writeln(`ık bir soket bulunamadı`);
return this.exit();
}
if(mwseConnector.mwse.server?.disconnect == null){
this.stderr.writeln(`MWSE bağlayıcısında hata oluştu`);
return this.exit();
}
mwseConnector.mwse.server.disconnect();
this.greenText("MWSE socketi başarılı bir şekilde kapatıldı");
this.redText(`\n\n${mwseConnector.mwse.server.endpoint.host} sunucusuyla MWSE bağlantısı kesildi\n\n`);
return this.exit();
}
};
CommandNamespace.namespaces.set('mwse-close', MWSECloser);

View File

@ -0,0 +1,93 @@
class MWSECloser{
stdin = null;
stdout = null;
stderr = null;
endpoint = null;
static mwse = null;
constructor(pipe){
this.stdin = pipe.stdin;
this.stdout = pipe.stdout;
this.stderr = pipe.stderr;
}
greenText(text){
this.stdout.writeln(`${this.stdout.COLOR_TEXT(0,255,0)}${text}${this.stdout.COLOR_RESET()}`);
}
async main(args)
{
this.stdout.write("Endpoint: ");
let answer = await this.stdin.readline();
let url;
try{
url = new URL(answer)
}catch{
this.stderr.writeln('Bilinmeyen path yapısı');
return this.exit();
};
this.greenText('Path doğrulandı');
if(url.protocol.toLowerCase() != 'wss:' && url.protocol.toLowerCase() != 'ws:'){
this.stderr.writeln('Bilinmeyen protokol');
return this.exit();
}
this.greenText('Şema doğrulandı');
this.endpoint = url;
try{
await this.scriptImport();
}catch{
this.stderr.writeln('MWSE bağlayıcısı indirilemedi');
return this.exit();
};
this.greenText(`${url.host} üzerinden bağlanılıyor : ${url.href}`);
let mwse;
try{
mwse = new MWSE({
endpoint: url.href
});
await new Promise(ok => {
mwse.scope(async ()=>{
this.greenText(`\n\nMWSE üzerinden ${url.host} makinesine bağlısınız\n\n`);
ok();
})
});
MWSECloser.mwse = mwse;
}catch{
this.stderr.writeln(`${url.host} makinesine bağlanırken bir sorun oluştu`);
return this.exit();
}finally{
this.exit();
}
}
async scriptImport()
{
let t = new URL("script",this.endpoint);
if(t.protocol == 'wss:'){
t.protocol = 'https:';
}else{
t.protocol = 'http:';
}
let script = document.createElement("script");
await new Promise((ok,rej) => {
script.onload = () => ok();
script.onerror = () => rej();
script.setAttribute("src",t.href);
document.head.appendChild(script);
});
if(window.MWSE === void 0){
this.stderr.writeln('MWSE bağlayıcısı indirilemedi');
return this.exit();
}
}
};
CommandNamespace.namespaces.set('mwse-socket', MWSECloser);

2
console/xterm.js Normal file

File diff suppressed because one or more lines are too long

1
console/xterm.min.css vendored Normal file
View File

@ -0,0 +1 @@
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}

2
console/xterm.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,8 @@
import WebRTC from "./WebRTC";
import Peer from "./Peer";
/**
* Deneyseldir kullanılması önerilmez
*/
export default class P2PFileSender
{
public rtc : RTCPeerConnection;

View File

@ -219,12 +219,7 @@ export default class Peer extends EventTarget
to: this.socketId
});
}else{
if(pack.type != ':rtcpack:')
{
this.rtc?.sendMessage(pack)
}else{
return console.warn("Socket is not writable");
}
}
}
async forget(){

View File

@ -31,7 +31,7 @@ export default class WebRTC
rtcpMuxPolicy:"require",
};
private isPolite() : boolean
public isPolite() : boolean
{
let myId = this.peer?.mwse.peer('me').socketId as string;
let peerId = this.peer?.socketId as string;
@ -201,7 +201,6 @@ export default class WebRTC
};
sendingStream.senders = senders;
}
this.emit('stream:accepted', sendingStream);
break;
}
case "message":{
@ -238,10 +237,6 @@ export default class WebRTC
}
public sendMessage(data: any)
{
if(data.type == ':rtcpack:')
{
throw "WebRTC Kanalında Sızma";
}
this.send({
type: 'message',
payload: data
@ -296,7 +291,6 @@ export default class WebRTC
this.channel = undefined;
WebRTC.channels.delete(this.id);
WebRTC.requireGC = true;
this.active = false;
})
}else{
this.emit('datachannel', event.channel);

View File

@ -184,13 +184,13 @@ export default class MWSE extends EventTarget {
let peer = this.peer(from, true);
peer.info.info = info;
peer.emit("accepted/pair", peer);
this.peer('me').emit('accepted/pair', peer);
this.peer('me').emit('accepted/pairr', peer);
})
this.EventPooling.signal("end/pair", (payload : {from : string,info: any}) => {
let {from, info} = payload;
let peer = this.peer(from, true);
peer.emit("end/pair", info);
this.peer('me').emit('end/pair', from, info);
peer.emit("endPair", info);
this.peer('me').emit('endPair', from, info);
})
}
public room(options: IRoomOptions | string) : Room

5
incoming-features.md Normal file
View File

@ -0,0 +1,5 @@
/ Odaların kendi verilerinin bulunması
> HTTP ile kişilere mesaj (request) mesaj iletimi
> HTTP ile odalar ve kişiler hakkında veri alınabilmesi
> Websoket ile http proxy uygulama (WSAuth)
> Session WSAuth

137
index.js
View File

@ -1,14 +1,127 @@
require("./Source/index");
/** @type {import('node:cluster').Cluster} */
const cluster = require("cluster");
const os = require("os");
let {randomUUID} = require("crypto");
process.on('unhandledRejection',(reason, promise)=>{
console.log("Process unhandledRejection",{reason, promise})
});
process.on('rejectionHandled',(promise)=>{
console.log("Process rejectionHandled",{promise})
});
process.on('multipleResolves',(type, promise, value)=>{
console.log("Process multipleResolves",{type, promise, value})
});
process.on('warning',(err)=>{
console.log("Process warning", err)
/**
* Use Round Robin algorithm for cluster process load balancer
*/
// cluster.schedulingPolicy = cluster.SCHED_RR;
async function main()
{
if(cluster.isPrimary == false)
{
console.log("Slave Process PID:", process.pid);
// This process is a worker / slave
// Compile source code and run
require("./Source/index");
// stay here
return;
};
// This process is a primary / master
console.log("Master Process PID:", process.pid);
// Worker process list
const master = new Map();
const coreCount = 1 //os.cpus().length;
for(let index = 0; index < coreCount; index++)
{
// Open slave process
let worker = await generateFlow();
// Save process with id
master.set(worker.id, worker);
// Listen process for commands
worker.message(
// This process want to send payload to sibling process with IPC
(workerId, payload) =>{
// Check Target worker
if(payload.core)
{
switch(payload.core)
{
case "writestat":{
master.get(workerId).setStats(payload)
break;
}
case "readstat":{
master.get(workerId).send({
type: ':stats:',
data: [
...master.entries()
].map((
[, master]
) => {
let e = master.getStats();
return {
core: master.uuid,
ws_writed_bytes:e.ws_writed_bytes,
ws_readed_bytes:e.ws_readed_bytes,
ws_total_bytes:e.ws_total_bytes,
ws_sended_packs:e.ws_sended_packs,
ws_recaived_packs:e.ws_recaived_packs,
ws_total_packs:e.ws_total_packs,
mwse_rooms: e.mwse_rooms,
mwse_clients: e.mwse_clients
}
})
})
break;
}
}
}else if(payload.process)
{
master.get(payload.process).send({
...payload,
pid: worker.id
})
}else for (const [siblingWorkerId,{send}] of master) {
// No sending to itself
if(workerId !== siblingWorkerId)
{
// Send command to sibling with IPC
send({
...payload,
pid: worker.id
})
}
}
}
)
}
}
async function generateFlow()
{
// Mirror this process with for (low-level os multitasking)
const worker = cluster.fork();
// Wait process is online
await new Promise(ok => {
worker.addListener("online",()=> {
ok()
})
});
// Get process pid on the os
let id = worker.process.pid;
let stats = {};
// Simplification wrapping send and get events with IPC's event functions
return {
id,
uuid: randomUUID(),
send: message => worker.send(message),
message: (callback) => worker.addListener("message", e => callback(id,e)),
getStats: () => stats,
setStats: e => Object.assign(stats, e)
}
}
// Run immediately
process.nextTick(main);

100
public/index.css Normal file
View File

@ -0,0 +1,100 @@
html,body{
margin: 0;
height: 100%;
}
body{
background-color: #141414;
overflow: hidden;
}
*{
box-sizing: border-box;
}
.root{
gap: 10px;
padding: 10px;
height: 100%;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
box-sizing: border-box;
overflow: hidden;
}
.videolist{
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(40vh, 1fr));
grid-template-rows: repeat(auto-fit, minmax(23vh, 1fr));
gap: 10px;
justify-content: center;
align-items: center;
overflow: hidden;
}
.videolist .frame{
border: solid 2px white;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
align-items: center;
border-radius: 20px;
box-sizing: border-box;
padding: 1px;
overflow: hidden;
position: relative;
}
.videolist .frame > video{
width: 100%;
height: 100%;
object-fit: cover;
}
.videolist .frame.active{
border: solid 3px green;
box-shadow: 0px 0px 20px -10px green;
}
video{
image-rendering: pixelated;
}
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-thumb {
background: white;
border-radius: 10px;
}
::-webkit-scrollbar-track {
background-color: #ffffff1f;
}
.tool-container{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
}
.tool-container > .tools{
margin: auto;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 10px;
overflow: hidden;
}
.tool-container > .tools > button{
border: none;
color: white;
vertical-align: middle;
cursor: pointer;
padding: 0 10px;
}
.tool-container > .tools > button:not(:hover){
background-color: transparent;
}
.tool-container > .tools > button:hover{
background-color: rgba(255,255,255,0.2);
}
.tool-container > .tools > button i{
vertical-align: middle;
line-height: 2;
}

20
public/index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<base href="/stream/">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>saQüt Video Streaming</title>
<script src="https://ws.saqut.com/script"></script>
<link rel="stylesheet" href="./index.css?v=42">
</head>
<body>
<div class="root">
<div class="videolist">
</div>
</div>
<script src="./index.js?v=42"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</body>
</html>

278
public/index.js Normal file
View File

@ -0,0 +1,278 @@
/**
* @type {import("./MWSE/index").default}
*/
let mwse;
/**
* @type {string}
*/
let mySocketId;
/**
* @type {string}
*/
let roomid;
/**
* @type {import("./MWSE/Room").default}
*/
let room;
/**
* @type {MediaStream}
*/
let outgoingStream;
/**
* @type {HTMLDivElement}
*/
let videoContainer = document.querySelector(".videolist");
let maxbitrate;
let resulation;
let activePeers = {}
let ofscreencanvas = document.createElement("canvas");
function connect()
{
mwse = new MWSE({
endpoint: "wss://ws.saqut.com"
});
MWSE.rtc.defaultICEServers = [{
urls: "turn:20.166.82.187:3478",
username: "turnserver",
credential: "turnserver"
},{
urls: "stun:stun.l.google.com:19302"
}];
mwse.scope(beginEngine);
}
let interact = false;
setInterval(()=>{
document.querySelectorAll(".soundon").forEach(e => (e.muted = 0,e.play()));
},1000)
document.addEventListener("click",()=>{
interact = true;
document.querySelectorAll(".soundon").forEach(e => (e.muted = 0,e.play()));
})
/**
* @type {HTMLVideoElement}
*/
let activeVideo;
function templateVideo(name, stream,infinitedMute)
{
let t = new DOMParser().parseFromString(`
<div class="frame">
<video autoplay playsinline muted data-name="${name}">
</video>
<!--div class="tool-container">
<div class="tools">
<button>
<i class="material-icons">home</i>
</button>
<button>
<i class="material-icons">close</i>
</button>
</div>
</div--->
</div>
`,"text/html");
let i = t.querySelector("video");
if(infinitedMute == true)
{
i.muted = 1;
}else if(interact == false)
{
i.muted = 1;
i.classList.add("soundon");
}
if(stream) i.srcObject = stream;
return t.querySelector("div");
}
function addVideoList(name, stream, peer, infinitedMute)
{
if(!videoContainer.querySelector(`[name="${name}"]`))
{
let video = templateVideo(name, stream, infinitedMute);
video.dataset.user = peer.socketId;
videoContainer.appendChild(video);
}
}
function removeVideoList(name)
{
if(videoContainer.querySelector(`[data-user="${name}"]`))
{
let k = videoContainer.querySelector(`[data-user="${name}"]`);
if(k.dataset.user == activeVideo?.dataset.user || !activeVideo)
{
activePeer = null;
}
k.remove();
}
}
async function beginEngine()
{
let me = mwse.peer("me");
me.disablePairAuth();
mySocketId = me.socketId;
let url = new URL(window.location);
roomid = url.searchParams.get("room");
if(!!roomid == 0)
{
let hash = window.crypto.randomUUID();
url.searchParams.set("room", hash);
window.location = url.href;
};
connectRoom(roomid);
if(url.searchParams.get("maxbitrate"))
{
let n = Number(url.searchParams.get("maxbitrate"));
if(Number.isFinite(n) && !Number.isNaN(n))
{
maxbitrate = n;
}else maxbitrate = 2500_000;
}else maxbitrate = 2500_000;
if(url.searchParams.get("resulation"))
{
let n = Number(url.searchParams.get("resulation"));
if(Number.isFinite(n) && !Number.isNaN(n))
{
resulation = n;
}else resulation = 1.2;
}else resulation = 1.2;
};
window.addEventListener("load", () => {
connect()
});
async function startOutgoingWebcam()
{
let mediaStream = await navigator.mediaDevices.getUserMedia({
video: /*true*/{
advanced: [
{ width: { exact: 1920 } },
{ width: { exact: 1600 } },
{ width: { exact: 1366 } },
{ width: { exact: 1280 } },
{ width: { exact: 1024 } },
{ width: { exact: 900 } },
{ width: { exact: 800 } },
{ width: { exact: 640 } },
{ width: { exact: 320 } },
{ width: { exact: 240 } }
],
facingMode: "user"
}
});
outgoingStream = mediaStream;
}
async function connectRoom()
{
await startOutgoingWebcam();
room = mwse.room({
name: roomid,
joinType: "free",
accessType: "private",
description: "Private free joined room",
ifexistsJoin: true,
notifyActionEjected: true,
notifyActionInvite: false,
notifyActionJoined: true
});
await room.createRoom();
room.on("join", peer => IncomingPeer(peer,true));
room.on("eject", peer => OutgoingPeer(peer));
for (const peer of await room.fetchPeers()) {
if(peer.socketId != mwse.peer('me').socketId){
IncomingPeer(peer,false)
}
}
addVideoList("My Webcam",outgoingStream, mwse.peer("me"), true)
};
/**
* @param {import("./MWSE/Peer").default} peer
*/
function IncomingPeer(peer,activeConnect)
{
if(activeConnect)
{
peer.rtc.connect();
}
peer.rtc.rtc.turboBitrate = 0;
peer.rtc.on('connected',() => {
console.log("Connected");
if(!activeConnect)
{
peer.rtc.sendStream(outgoingStream, "Webcam", {});
activePeers[peer.socketId] = peer.rtc.rtc;
}
});
peer.rtc.on('disconnected',() => {
console.log("Disconnected");
removeVideoList(peer.streamY, peer);
delete activePeers[peer.socketId];
});
peer.rtc.on("stream:added", ({stream,name}) => {
peer.streamY = peer.socketId + " | " + name + " - " + stream.id;
addVideoList(peer.socketId + " | " + name + " - " + stream.id,stream, peer);
if(activeConnect)
{
peer.rtc.sendStream(outgoingStream, "Webcam", {});
}
})
}
/**
* @param {import("./MWSE/Peer").default} peer
*/
function OutgoingPeer(peer)
{
removeVideoList(peer.socketId, peer);
}
let relative;
setInterval(() => {
for(const [,peerRtc] of Object.entries(activePeers))
{
if(peerRtc?.turboBitrate !== 1)
{
const senders = peerRtc.getSenders();
const videoSender = senders.find(sender => sender.track?.kind === 'video');
if(videoSender){
const parameters = videoSender.getParameters();
parameters.encodings[0].maxBitrate = maxbitrate;
parameters.encodings[0].scaleResolutionDownBy = resulation;
videoSender.setParameters(parameters).then(() => {
peerRtc.turboBitrate = 1;
});
}
}
}
},1000);

208
public/m.h.2.8.8.js Normal file
View File

@ -0,0 +1,208 @@
function HostListItem()
{
let item = $(`
<tr>
<td class="text-nowrap">
<button class="btn btn-outline-primary btn-sm action-download">
İndir
</button>
<button class="btn btn-outline-danger btn-sm action-remove">
Kaldır
</button>
</td>
<td class="text-nowrap">
<button class="btn btn-outline-secondary btn-sm action-sha1">
Hesapla
</button>
</td>
<td class="text-nowrap">
<span class="action-filename">DCIM_AMG_25TEM2025.png</span>
<br>
<span class="text-muted">
(Sizin cihazda)
</span>
</td>
<td class="text-nowrap">
<div class="progress" style="height:30px">
<div class="progress-bar" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</td>
</tr>
`);
return {
download: item.find(".action-download"),
hide: item.find(".action-hide"),
remove: item.find(".action-remove"),
sha1: item.find(".action-sha1"),
filename: item.find(".action-filename"),
progress: item.find(".progress"),
container: item
}
}
function RemoteListItem()
{
let item = $(`
<tr>
<td class="text-nowrap">
<button class="btn btn-outline-primary btn-sm action-download">
İndir
</button>
<button class="btn btn-outline-secondary btn-sm action-hide">
Gizle
</button>
</td>
<td class="text-nowrap">
<button class="btn btn-outline-secondary btn-sm action-sha1">
Hesapla
</button>
</td>
<td class="text-nowrap">
<span class="action-filename">DCIM.mp4</span>
<br>
<span class="text-muted">
(Karşı cihazda)
</span>
</td>
<td class="text-nowrap">
<div class="progress" style="height:30px">
<div class="progress-bar" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</td>
</tr>
`);
return {
download: item.find(".action-download"),
hide: item.find(".action-hide"),
remove: item.find(".action-remove"),
sha1: item.find(".action-sha1"),
filename: item.find(".action-filename"),
progress: item.find(".progress"),
container: item
}
}
function EmptyListItem()
{
return $(`
<tr>
<td class="text-center text-muted" colspan="4">
Gönderilecek veya alınacak dosya bulunmuyor
</td>
</tr>
`)
}
function formatBytes(a,b=2){if(!+a)return"0 Bytes";const c=0>b?0:b,d=Math.floor(Math.log(a)/Math.log(1024));return`${parseFloat((a/Math.pow(1024,d)).toFixed(c))} ${["Bytes","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"][d]}`}
const endpoint = `wss://ws.saqut.com/socket/webserver/${("000000"+(Math.random() * 512 | 0)).slice(-6)}.sock`;
let mwse = new MWSE({
endpoint,
autoReconnect: true,
autoReconnectTimeout: 60_000
});
mwse.scope(Connect);
let room;
let ip;
async function Connect()
{
await genRoom();
$(".network-id").text(ip);
}
async function genRoom()
{
do{
let vip = ip = [
(Math.random() * 16 | 0) + 10,
(Math.random() * 8 | 0),
(Math.random() * 4 | 0),
(Math.random() * 254 | 0) + 1
].join('.');
room = mwse.room({
name: `WEB3-Smash [${vip}] RO CONNECT:2`,
description: "Web3 Smash saQut Server Application",
joinType: "free",
notifyActionJoined: true,
notifyActionEjected: true,
ifexistsJoin: false
});
try{
await room.createRoom();
break;
}catch{
continue;
}
}while(1);
}
class FileDescriptor {
static hostfiles = new Map();
static remotefiles = new Map();
static hostindex = 0;
static remoteindex = 0;
file = null;
listItem = null;
constructor(isHost, item,file){
this.file = file;
this.listItem = item;
$("#thelist").append(item.container);
if(isHost)
{
FileDescriptor.hostfiles.set(
++FileDescriptor.hostindex,
this
);
}else{
FileDescriptor.remotefiles.set(
++FileDescriptor.remoteindex,
this
);
}
this.init();
}
init(){
this.listItem.filename.html(
this.file.name + " <span class='text-muted'>" + formatBytes(this.file.size) + "</span>"
);
}
};
$("#files").on('change',function(){
for (const file of this.files) {
$("#thelist .empty").remove();
new FileDescriptor(
true,
HostListItem(),
file
)
};
$("#files").val('');
});
$(".joinroom").on("chnage",function(){
let ip = $("#remoteadress").val();
if(!/^\d+\.\d+\.\d+$/.test(ip)){
return alert(" ")
}
$("#thelist .empty").remove();
room = mwse.room({
name: `WEB3-Smash [${vip}] RO CONNECT:2`,
description: "Web3 Smash saQut Server Application",
joinType: "free",
notifyActionJoined: true,
notifyActionEjected: true,
ifexistsJoin: false
});
})

32
public/test.js Normal file
View File

@ -0,0 +1,32 @@
let mwse = new MWSE({
endpoint: "ws://localhost:7707"
});
mwse.scope(beginEngine);
async function beginEngine()
{
room = mwse.room({
name: "Naber",
joinType: "free",
accessType: "private",
description: "Private free joined room",
ifexistsJoin: false,
notifyActionEjected: true,
notifyActionInvite: false,
notifyActionJoined: true
});
try{
await room.createRoom();
let time = 0;
setInterval(()=>{
room.info.set("set time",time);
time++;
},1000);
}catch{
await room.join();
}
room.on('updateinfo',(name, value) => {
console.log("read",name,value)
})
}

64
public/transfer.html Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dosya Transfer Sistem</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container" style="max-width: 1200px">
<h2 class="text-center mb-2 mt-5">Dosya Transfer Sistemi</h2>
<h1 class="text-center mb-5 mt-3 text-bold fw-bold">
<span>Ağ Adresiniz</span>
<span class="text-danger network-id">
#.#.#.#
</span>
</h1>
<div class="mx-auto d-flex flex-row mb-5" style="max-width: 800px;gap:20px">
<div class="flex-fill">
<input
type="file"
id="files"
multiple
class="form-control"
>
</div>
<div class="flex-fill d-flex" style="gap:5px">
<input
type="text"
class="form-control"
placeholder="Farklı bir ağa katıl"
id="remoteadress"
>
<button class="btn btn-outline-success joinroom">
Katıl
</button>
</div>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th class="text-nowrap" width="1%">İşlem</th>
<th class="text-nowrap" width="1%">SHA-1</th>
<th class="text-nowrap" width="49%">Dosya İsmi</th>
<th class="text-nowrap" width="50%">İşlem</th>
</tr>
</thead>
<tbody id="thelist">
<tr class="empty">
<td class="text-center text-muted" colspan="4">
Gönderilecek veya alınacak dosya bulunmuyor
</td>
</tr>
</tbody>
</table>
</div>
<script src="https://ws.saqut.com/script"></script>
<script src="m.h.2.8.8.js"></script>
</body>
</html>

714
script/index.html Normal file
View File

@ -0,0 +1,714 @@
<!DOCTYPE html>
<html lang="tr">
<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>Network meter</title>
</head>
<body style="background-color: #333333;">
<div id="container">
<div class="speed-container">
</div>
</div>
<style>
html,body,#container{
height: 100%;
margin: 0;
}
#container{
display: flex;
flex-direction: column;
}
.speed-container{
display: flex;
flex-direction: row;
max-width: 1200px;
flex-wrap: wrap;
margin: auto;
}
.speed-container > div{
flex: 1 1 25%;
max-width: 100%;
overflow: auto;
margin-bottom: 50px;
}
.speed-container > div canvas{
max-width: 100%;
overflow: auto;
}
.speed-container > div .text{
display: flex;
color: white;
vertical-align: middle;
}
.speed-container > div .mwse{
display: flex;
justify-content: space-evenly;
color: white;
text-align: center;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.speed-container > div .text > .text1{
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 2em;
text-align: center;
vertical-align: middle;
flex: 1;
display: flex;
justify-content: center;
}
.speed-container > div .text > .text2{
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 2em;
text-align: center;
vertical-align: middle;
flex: 1;
display: flex;
justify-content: center;
}
.speed-container > div .text1::after{
content:' FLOW';
font-size: 0.4em;
margin: auto 0 auto 5px;
}
.speed-container > div .text2::after{
content:' PRESSURE';
font-size: 0.4em;
margin: auto 0 auto 5px;
}
</style>
<script>
function CreateMeter()
{
var iCurrentSpeed = 0,
iTargetSpeed = 0,
bDecrement = null,
job = null;
function degToRad(angle) {
// Degrees to radians
return ((angle * Math.PI) / 180);
}
function radToDeg(angle) {
// Radians to degree
return ((angle * 180) / Math.PI);
}
function drawLine(options, line) {
// Draw a line using the line object passed in
options.ctx.beginPath();
// Set attributes of open
options.ctx.globalAlpha = line.alpha;
options.ctx.lineWidth = line.lineWidth;
options.ctx.fillStyle = line.fillStyle;
options.ctx.strokeStyle = line.fillStyle;
options.ctx.moveTo(line.from.X,
line.from.Y);
// Plot the line
options.ctx.lineTo(
line.to.X,
line.to.Y
);
options.ctx.stroke();
}
function createLine(fromX, fromY, toX, toY, fillStyle, lineWidth, alpha) {
// Create a line object using Javascript object notation
return {
from: {
X: fromX,
Y: fromY
},
to: {
X: toX,
Y: toY
},
fillStyle: fillStyle,
lineWidth: lineWidth,
alpha: alpha
};
}
function drawOuterMetallicArc(options) {
/* Draw the metallic border of the speedometer
* Outer grey area
*/
options.ctx.beginPath();
// Nice shade of grey
options.ctx.fillStyle = "rgb(127,127,127)";
// Draw the outer circle
options.ctx.arc(options.center.X,
options.center.Y,
options.radius,
0,
Math.PI,
true);
// Fill the last object
options.ctx.fill();
/* */
}
function drawInnerMetallicArc(options) {
/* Draw the metallic border of the speedometer
* Inner white area
*/
options.ctx.beginPath();
// White
options.ctx.fillStyle = "rgb(255,255,255)";
// Outer circle (subtle edge in the grey)
options.ctx.arc(options.center.X,
options.center.Y,
(options.radius / 100) * 90,
0,
Math.PI,
true);
options.ctx.fill();
/* */
}
function drawMetallicArc(options) {
/* Draw the metallic border of the speedometer
* by drawing two semi-circles, one over lapping
* the other with a bot of alpha transparency
*/
drawOuterMetallicArc(options);
drawInnerMetallicArc(options);
}
function drawBackground(options) {
/* Black background with alphs transparency to
* blend the edges of the metallic edge and
* black background
*/
var i = 0;
options.ctx.globalAlpha = 0.2;
options.ctx.fillStyle = "rgb(0,0,0)";
// Draw semi-transparent circles
for (i = 170; i < 180; i++) {
options.ctx.beginPath();
options.ctx.arc(options.center.X,
options.center.Y,
i,
0,
Math.PI,
true);
options.ctx.fill();
}
}
function applyDefaultContextSettings(options) {
/* Helper function to revert to gauges
* default settings
*/
options.ctx.lineWidth = 2;
options.ctx.globalAlpha = 0.5;
options.ctx.strokeStyle = "rgb(255, 255, 255)";
options.ctx.fillStyle = 'rgb(255,255,255)';
}
function drawSmallTickMarks(options) {
/* The small tick marks against the coloured
* arc drawn every 5 mph from 10 degrees to
* 170 degrees.
*/
var tickvalue = options.levelRadius - 8,
iTick = 0,
gaugeOptions = options.gaugeOptions,
iTickRad = 0,
onArchX,
onArchY,
innerTickX,
innerTickY,
fromX,
fromY,
line,
toX,
toY;
applyDefaultContextSettings(options);
// Tick every 20 degrees (small ticks)
for (iTick = 10; iTick < 180; iTick += 10) {
iTickRad = degToRad(iTick);
/* Calculate the X and Y of both ends of the
* line I need to draw at angle represented at Tick.
* The aim is to draw the a line starting on the
* coloured arc and continueing towards the outer edge
* in the direction from the center of the gauge.
*/
onArchX = gaugeOptions.radius - (Math.cos(iTickRad) * tickvalue);
onArchY = gaugeOptions.radius - (Math.sin(iTickRad) * tickvalue);
innerTickX = gaugeOptions.radius - (Math.cos(iTickRad) * gaugeOptions.radius);
innerTickY = gaugeOptions.radius - (Math.sin(iTickRad) * gaugeOptions.radius);
fromX = (options.center.X - gaugeOptions.radius) + onArchX;
fromY = (gaugeOptions.center.Y - gaugeOptions.radius) + onArchY;
toX = (options.center.X - gaugeOptions.radius) + innerTickX;
toY = (gaugeOptions.center.Y - gaugeOptions.radius) + innerTickY;
// Create a line expressed in JSON
line = createLine(fromX, fromY, toX, toY, "rgb(127,127,127)", 3, 0.6);
// Draw the line
drawLine(options, line);
}
}
function drawLargeTickMarks(options) {
/* The large tick marks against the coloured
* arc drawn every 10 mph from 10 degrees to
* 170 degrees.
*/
var tickvalue = options.levelRadius - 8,
iTick = 0,
gaugeOptions = options.gaugeOptions,
iTickRad = 0,
innerTickY,
innerTickX,
onArchX,
onArchY,
fromX,
fromY,
toX,
toY,
line;
applyDefaultContextSettings(options);
tickvalue = options.levelRadius - 2;
// 10 units (major ticks)
for (iTick = 20; iTick < 180; iTick += 20) {
iTickRad = degToRad(iTick);
/* Calculate the X and Y of both ends of the
* line I need to draw at angle represented at Tick.
* The aim is to draw the a line starting on the
* coloured arc and continueing towards the outer edge
* in the direction from the center of the gauge.
*/
onArchX = gaugeOptions.radius - (Math.cos(iTickRad) * tickvalue);
onArchY = gaugeOptions.radius - (Math.sin(iTickRad) * tickvalue);
innerTickX = gaugeOptions.radius - (Math.cos(iTickRad) * gaugeOptions.radius);
innerTickY = gaugeOptions.radius - (Math.sin(iTickRad) * gaugeOptions.radius);
fromX = (options.center.X - gaugeOptions.radius) + onArchX;
fromY = (gaugeOptions.center.Y - gaugeOptions.radius) + onArchY;
toX = (options.center.X - gaugeOptions.radius) + innerTickX;
toY = (gaugeOptions.center.Y - gaugeOptions.radius) + innerTickY;
// Create a line expressed in JSON
line = createLine(fromX, fromY, toX, toY, "rgb(127,127,127)", 3, 0.6);
// Draw the line
drawLine(options, line);
}
}
function drawTicks(options) {
/* Two tick in the coloured arc!
* Small ticks every 5
* Large ticks every 10
*/
drawSmallTickMarks(options);
drawLargeTickMarks(options);
}
function drawTextMarkers(options) {
/* The text labels marks above the coloured
* arc drawn every 10 mph from 10 degrees to
* 170 degrees.
*/
var innerTickX = 0,
innerTickY = 0,
iTick = 0,
gaugeOptions = options.gaugeOptions,
iTickToPrint = 00;
applyDefaultContextSettings(options);
// Font styling
options.ctx.font = 'italic 10px sans-serif';
options.ctx.textBaseline = 'top';
options.ctx.beginPath();
// Tick every 20 (small ticks)
for (iTick = 10; iTick < 160; iTick += 20) {
innerTickX = gaugeOptions.radius - (Math.cos(degToRad(iTick)) * gaugeOptions.radius);
innerTickY = gaugeOptions.radius - (Math.sin(degToRad(iTick)) * gaugeOptions.radius);
// Some cludging to center the values (TODO: Improve)
if (iTick <= 10) {
options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX,
(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY + 5);
} else if (iTick < 50) {
options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX - 5,
(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY + 5);
} else if (iTick < 90) {
options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX,
(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY);
} else if (iTick === 90) {
options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX + 4,
(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY);
} else if (iTick < 145) {
options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX + 10,
(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY);
} else {
options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX + 15,
(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY + 5);
}
// MPH increase by 10 every 20 degrees
//iTickToPrint += Math.round(2160 / 9);
iTickToPrint += 30;
}
options.ctx.stroke();
}
function drawSpeedometerPart(options, alphaValue, strokeStyle, startPos) {
/* Draw part of the arc that represents
* the colour speedometer arc
*/
options.ctx.beginPath();
options.ctx.globalAlpha = alphaValue;
options.ctx.lineWidth = 5;
options.ctx.strokeStyle = strokeStyle;
options.ctx.arc(options.center.X,
options.center.Y,
options.levelRadius,
Math.PI + (Math.PI / 360 * startPos),
0 - (Math.PI / 360 * 10),
false);
options.ctx.stroke();
}
function drawSpeedometerColourArc(options) {
/* Draws the colour arc. Three different colours
* used here; thus, same arc drawn 3 times with
* different colours.
* TODO: Gradient possible?
*/
drawSpeedometerPart(options, 1.0, "rgb(255,255,255)", 10);
drawSpeedometerPart(options, 0.9, "rgb(0,255,0)", 80);
drawSpeedometerPart(options, 0.9, "rgb(255,128,0)", 250);
drawSpeedometerPart(options, 0.9, "rgb(255,0,0)", 300);
}
function drawNeedleDial(options, alphaValue, strokeStyle, fillStyle) {
/* Draws the metallic dial that covers the base of the
* needle.
*/
var i = 0;
options.ctx.globalAlpha = alphaValue;
options.ctx.lineWidth = 3;
options.ctx.strokeStyle = strokeStyle;
options.ctx.fillStyle = fillStyle;
// Draw several transparent circles with alpha
for (i = 0; i < 30; i++) {
options.ctx.beginPath();
options.ctx.arc(options.center.X,
options.center.Y,
i,
0,
Math.PI,
true);
options.ctx.fill();
options.ctx.stroke();
}
}
function drawNeedle(options) {
/* Draw the needle in a nice read colour at the
* angle that represents the options.speed value.
*/
var iSpeedAsAngle = convertSpeedToAngle(options),
iSpeedAsAngleRad = degToRad(iSpeedAsAngle),
gaugeOptions = options.gaugeOptions,
innerTickX = gaugeOptions.radius - (Math.cos(iSpeedAsAngleRad) * 20),
innerTickY = gaugeOptions.radius - (Math.sin(iSpeedAsAngleRad) * 20),
fromX = (options.center.X - gaugeOptions.radius) + innerTickX,
fromY = (gaugeOptions.center.Y - gaugeOptions.radius) + innerTickY,
endNeedleX = gaugeOptions.radius - (Math.cos(iSpeedAsAngleRad) * gaugeOptions.radius),
endNeedleY = gaugeOptions.radius - (Math.sin(iSpeedAsAngleRad) * gaugeOptions.radius),
toX = (options.center.X - gaugeOptions.radius) + endNeedleX,
toY = (gaugeOptions.center.Y - gaugeOptions.radius) + endNeedleY,
line = createLine(fromX, fromY, toX, toY, "rgb(255, 0, 0)", 5, 0.6);
drawLine(options, line);
// Two circle to draw the dial at the base (give its a nice effect?)
drawNeedleDial(options, 0.6, "rgb(127, 127, 127)", "rgb(255,255,255)");
drawNeedleDial(options, 0.2, "rgb(127, 127, 127)", "rgb(127,127,127)");
}
function buildOptionsAsJSON(canvas, iSpeed) {
/* Setting for the speedometer
* Alter these to modify its look and feel
*/
var centerX = 210,
centerY = 210,
radius = 150,
outerRadius = 200;
// Create a speedometer object using Javascript object notation
return {
ctx: canvas.getContext('2d'),
speed: iSpeed,
center: {
X: centerX,
Y: centerY
},
levelRadius: radius - 10,
gaugeOptions: {
center: {
X: centerX,
Y: centerY
},
radius: radius
},
radius: outerRadius
};
}
function clearCanvas(options) {
options.ctx.clearRect(0, 0, 800, 600);
applyDefaultContextSettings(options);
}
function draw() {
/* Main entry point for drawing the speedometer
* If canvas is not support alert the user.
*/
var canvas = createCanvas.value,
options = null;
// Canvas good?
if (canvas !== null && canvas.getContext) {
options = buildOptionsAsJSON(canvas, iCurrentSpeed);
// Clear canvas
clearCanvas(options);
// Draw the metallic styled edge
drawMetallicArc(options);
// Draw thw background
drawBackground(options);
// Draw tick marks
drawTicks(options);
// Draw labels on markers
drawTextMarkers(options);
// Draw speeometer colour arc
drawSpeedometerColourArc(options);
// Draw the needle and base
drawNeedle(options);
} else {
alert("Canvas not supported by your browser!");
};
if(iTargetSpeed == iCurrentSpeed) {
cancelAnimationFrame(job);
return;
} else if(iTargetSpeed < iCurrentSpeed) {
bDecrement = true;
} else if(iTargetSpeed > iCurrentSpeed) {
bDecrement = false;
}
console.log(iTargetSpeed,bDecrement)
if(bDecrement) {
iCurrentSpeed = iCurrentSpeed - 1;
} else {
iCurrentSpeed = iCurrentSpeed + 1;
}
job = requestAnimationFrame(draw);
};
function createCanvas()
{
let container = document.createElement("div");
container.classList.add("boxmodel")
let canvas = document.createElement("canvas");
let text = document.createElement("div");
let text1 = document.createElement("div");
let text2 = document.createElement("div");
text.classList.add("text")
text1.classList.add("text1")
text2.classList.add("text2")
let mwse = document.createElement("div");
let text3 = document.createElement("span");
let text4 = document.createElement("span");
mwse.classList.add("mwse")
text3.classList.add("text3")
text4.classList.add("text4")
text.append(text1);
text.append(text2);
mwse.append(text3);
mwse.append(text4);
canvas.setAttribute("width", "440px");
canvas.setAttribute("height", "220px");
container.append(canvas);
container.append(text);
container.append(mwse);
createCanvas.value = canvas;
createCanvas.container = container;
createCanvas.ps = text1;
createCanvas.pw = text2;
createCanvas.rooms = text3;
createCanvas.clients = text4;
return container;
};
function update(value, pressure, rooms, clients)
{
iTargetSpeed = value;
createCanvas.ps.innerText = value;
createCanvas.pw.innerText = pressure + '%';
createCanvas.rooms.innerText = rooms;
createCanvas.clients.innerText = clients;
draw()
}
function convertSpeedToAngle(options) {
/* Helper function to convert a speed to the
* equivelant angle.
*/
let m = 150;
let value = (m/10) + Math.min(
Math.max(
options.speed,
0
),
240
);
iSpeedAsAngle = (
(100 / m) * value
);
// Ensure the angle is within range
if (iSpeedAsAngle > 180) {
iSpeedAsAngle = iSpeedAsAngle - 180;
} else if (iSpeedAsAngle < 0) {
iSpeedAsAngle = iSpeedAsAngle + 180;
}
return iSpeedAsAngle;
}
return {
createCanvas,
update
}
}
/*
let elem = document.querySelector(".speed-container");
for(let e = 0; e < 8; e++)
{
let meter = CreateMeter();
let container = meter.createCanvas();
elem.appendChild(container);
meter.update(54,25)
}
*/
let elem = document.querySelector(".speed-container");
let meters = new Map();
async function reloadData()
{
while(1)
{
await fetchData();
await new Promise(ok => {
setTimeout(() => ok(), 3000)
})
}
};
let isFirst = true;
async function fetchData()
{
let response = await fetch("/stats",{
method: "post",
credentials: "same-origin",
cache: "no-cache",
mode:"no-cors"
}).then(e => e.json());
for (const {
ws_total_packs,
core,
mwse_rooms,
mwse_clients
} of response) {
if(!meters.has(core))
{
let meter = CreateMeter();
let container = meter.createCanvas();
elem.appendChild(container);
meters.set(core, meter)
};
let _meter = meters.get(core);
_meter.update(
ws_total_packs,
ws_total_packs < 30 ? 0 : ws_total_packs < 60 ? 1 : ws_total_packs < 90 ? 2 : 3,
"RM: "+mwse_rooms,
"CL: "+mwse_clients
);
}
}
reloadData();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long