Eski Node.js / TypeScript yapı dosyaları temizlendi
Silinen dizinler: - Source/ — Node.js engine (Go engine tarafından ikame edildi) - frontend/ — TypeScript SDK kaynağı (sdk/ ES modülleri ile değiştirildi) - script/ — Parcel bundle çıktısı (artık yok; /sdk/ route'u devrede) Silinen kök dosyalar: - index.js — Node.js giriş noktası (Source/index.js'e refer ediyordu) - package.json / package-lock.json — Parcel/TypeScript build araçları - tsconfig.json — TypeScript derleyici ayarları Taşınan: - script/status.xml → public/status.xml (httpserver fallback olarak kullanıyor) Güncelleme: - httpserver.go : /script/* route'u kaldırıldı; / artık /sdk/index.js'e yönlendiriyor - config.go : ScriptDir alanı ve MWSE_SCRIPT_DIR env değişkeni kaldırıldı - contract_test : ScriptDir → SDKDir - .gitignore : Node/Parcel artıkları için temizlendi ve kısaltıldı go test -race ./... yeşil Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
28abefaaa9
commit
06ca31eecb
|
|
@ -1,147 +1,31 @@
|
|||
# ---> Gitea CLI kimlik bilgisi (ASLA commit etme)
|
||||
# Gitea CLI credentials — never commit
|
||||
.gitea-auth.json
|
||||
|
||||
# ---> Go (engine rewrite)
|
||||
# Go build outputs
|
||||
/mwse
|
||||
/mwse-engine
|
||||
/mwse-loadtest
|
||||
loadtest/mwse-loadtest
|
||||
*.out
|
||||
*.test
|
||||
go.work
|
||||
go.work.sum
|
||||
loadtest/mwse-loadtest
|
||||
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
# node_modules stays on disk (gitignored) so npm tools still work if needed,
|
||||
# but nothing in the repo should require them anymore.
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
package-lock.json
|
||||
.parcel-cache/
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
# Environment / secrets
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
# Editor / OS
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
.well-known
|
||||
.well-known/*
|
||||
.well-known/
|
||||
|
|
|
|||
213
Source/Client.js
213
Source/Client.js
|
|
@ -1,213 +0,0 @@
|
|||
function Client()
|
||||
{
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = null;
|
||||
/**
|
||||
* @type {import("websocket").connection}
|
||||
*/
|
||||
this.socket = null;
|
||||
/**
|
||||
* @type {Date}
|
||||
*/
|
||||
this.created_at = null;
|
||||
|
||||
/**
|
||||
* @type {Map<string,any>}
|
||||
*/
|
||||
this.info = new Map();
|
||||
/**
|
||||
* @type {Map<string,any>}
|
||||
*/
|
||||
this.store = new Map();
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
this.rooms = new Set();
|
||||
/**
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
this.pairs = new Set();
|
||||
this.requiredPair = false;
|
||||
|
||||
this.APNumber = 0;
|
||||
this.APShortCode = 0;
|
||||
this.APIPAddress = 0;
|
||||
};
|
||||
/**
|
||||
* @type {Map<string, Client>}
|
||||
*/
|
||||
Client.clients = new Map();
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
*/
|
||||
Client.prototype.peerRequest = function(client){
|
||||
let info = {};
|
||||
this.info.forEach((value, name) => info[name] = value);
|
||||
this.pairs.add(client.id);
|
||||
client.send([
|
||||
{ from: this.id },
|
||||
'request/pair'
|
||||
]);
|
||||
};
|
||||
|
||||
Client.prototype.match = function(filterObject){
|
||||
let keys = Object.keys(filterObject);
|
||||
let size = keys.length;
|
||||
if(size > this.info.size)
|
||||
{
|
||||
return false
|
||||
}
|
||||
for (const key of keys)
|
||||
{
|
||||
if(this.info.has(key))
|
||||
{
|
||||
if(this.info.get(key) != filterObject[key])
|
||||
{
|
||||
return false
|
||||
}
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
};
|
||||
return true
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Client|string} client
|
||||
*/
|
||||
Client.prototype.isSecure = function(client)
|
||||
{
|
||||
const { Room } = require("./Services/Room");
|
||||
if(typeof client == "string")
|
||||
{
|
||||
if(Client.clients.has(client))
|
||||
{
|
||||
client = Client.clients.get(client);
|
||||
}else return false;
|
||||
}else if(!(client instanceof Client)){
|
||||
console.error("isSecure Client bir client veri tipinde değil")
|
||||
return false;
|
||||
};
|
||||
|
||||
// Eşleştirilmiş kullanıcı
|
||||
if(this.isPaired(client))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Aynı odada bulunan kullanıcı
|
||||
for (const id of this.rooms) {
|
||||
let room = Room.rooms.get(id);
|
||||
if(room)
|
||||
{
|
||||
if(room.clients.has(id))
|
||||
{
|
||||
return true
|
||||
}
|
||||
}
|
||||
};
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @returns {{pairs:Map<string, Client>,roompairs:Map<string, Client>,intersection:Map<string, Client>}}
|
||||
*/
|
||||
Client.prototype.getSucureClients = function()
|
||||
{
|
||||
const { Room } = require("./Services/Room");
|
||||
let pairs = new Map();
|
||||
let roompairs = new Map();
|
||||
|
||||
for (const id of this.pairs)
|
||||
{
|
||||
pairs.set(id, Client.clients.get(id))
|
||||
}
|
||||
|
||||
// Aynı odada bulunan kullanıcı
|
||||
for (const id of this.rooms) {
|
||||
let room = Room.rooms.get(id);
|
||||
if(room)
|
||||
{
|
||||
for (const [id, client] of room.clients)
|
||||
{
|
||||
if(id == this.id) continue;
|
||||
roompairs.set(id, client)
|
||||
};
|
||||
}
|
||||
};
|
||||
return {
|
||||
pairs,
|
||||
roompairs,
|
||||
intersection : new Map([
|
||||
...pairs,
|
||||
...roompairs
|
||||
])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
*/
|
||||
Client.prototype.acceptPeerRequest = function(client){
|
||||
this.pairs.add(client.id);
|
||||
client.send([{
|
||||
from: this.id
|
||||
},'accepted/pair']);
|
||||
};
|
||||
/**
|
||||
* @param {Client} client
|
||||
*/
|
||||
Client.prototype.rejectPeerRequest = function(client){
|
||||
this.pairs.delete(client.id);
|
||||
client.pairs.delete(this.id);
|
||||
client.send([{
|
||||
from: this.id
|
||||
},'end/pair']);
|
||||
};
|
||||
/**
|
||||
* @param {Client|string} client
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
Client.prototype.isPaired = function(client){
|
||||
if(typeof client == "string")
|
||||
{
|
||||
return Client.clients.get(client)?.pairs.has(this.id) && this.pairs.has(client)
|
||||
}
|
||||
return client.pairs.has(this.id) && this.pairs.has(client.id);
|
||||
};
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
Client.prototype.pairList = function(){
|
||||
return [...this.pairs.values()].filter(e => this.isPaired(e));
|
||||
};
|
||||
|
||||
Client.prototype.send = function(obj){
|
||||
if(this.socket.connected){
|
||||
this.socket.sendUTF(JSON.stringify(obj),err => {
|
||||
if(err && this.socket)
|
||||
{
|
||||
console.error("I/O: Hatalı yazma işlemi yapıldı",err.message)
|
||||
}
|
||||
});
|
||||
}else{
|
||||
console.error("Bağlantısı kopmuş yazma işlemi")
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.packWriteable = function(){
|
||||
return !!this.store.get("packrecaive")
|
||||
}
|
||||
Client.prototype.packReadable = function(){
|
||||
return !!this.store.get("packsending")
|
||||
}
|
||||
Client.prototype.peerInfoNotifiable = function(){
|
||||
return !!this.store.get("notifyPairInfo")
|
||||
}
|
||||
Client.prototype.roomInfoNotifiable = function(){
|
||||
return !!this.store.get("notifyRoomInfo")
|
||||
}
|
||||
|
||||
exports.Client = Client;
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
let http = require("http");
|
||||
let express = require("express");
|
||||
let compression = require("compression");
|
||||
let {resolve} = require("path");
|
||||
|
||||
const { termoutput } = require("./config");
|
||||
|
||||
let app = express();
|
||||
app.use(compression({ level: 9 }));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
let server = http.createServer(app);
|
||||
|
||||
server.listen(7707, '0.0.0.0', () => {
|
||||
termoutput && console.log("HTTP Service Running...");
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
exports.http = server;
|
||||
|
||||
const apiRouter = require("./api");
|
||||
app.use("/api", apiRouter);
|
||||
|
||||
app.get("/script", (req, res) => {
|
||||
res.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("*", (req, res) => {
|
||||
res.sendFile(resolve("./script/status.xml"));
|
||||
});
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { Client } = require("../Client.js");
|
||||
const { on, emit, register } = require("../WebSocket");
|
||||
|
||||
on('disconnect', (xclient) => {
|
||||
const { intersection, pairs } = xclient.getSucureClients();
|
||||
|
||||
for (const [clientid, client] of intersection) {
|
||||
client?.send([{ id: xclient.id }, "peer/disconnect"]);
|
||||
}
|
||||
|
||||
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') {
|
||||
client.requiredPair = true;
|
||||
return { status: 'success' };
|
||||
}
|
||||
if (msg.value == 'disable') {
|
||||
client.requiredPair = false;
|
||||
return { status: 'success' };
|
||||
}
|
||||
return { status: 'fail', message: 'INVALID_VALUE' };
|
||||
});
|
||||
|
||||
register('my/socketid', (client, msg) => {
|
||||
return client.id;
|
||||
});
|
||||
|
||||
register('auth/public', (client, msg) => {
|
||||
client.requiredPair = false;
|
||||
return { value: 'success', mode: 'public' };
|
||||
});
|
||||
|
||||
register('auth/private', (client, msg) => {
|
||||
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' };
|
||||
}
|
||||
|
||||
const pairclient = Client.clients.get(to);
|
||||
|
||||
if (pairclient.pairs.has(client.id)) {
|
||||
return { status: 'success', message: 'ALREADY-PAIRED' };
|
||||
}
|
||||
|
||||
if (client.pairs.has(to)) {
|
||||
return { status: 'fail', message: 'ALREADY-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' };
|
||||
}
|
||||
|
||||
const pairclient = Client.clients.get(to);
|
||||
|
||||
if (pairclient.pairs.has(client.id)) {
|
||||
return { status: 'success', message: 'ALREADY-PAIRED' };
|
||||
}
|
||||
|
||||
if (!client.pairs.has(to)) {
|
||||
return { 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' };
|
||||
}
|
||||
|
||||
const pairclient = Client.clients.get(to);
|
||||
|
||||
if (pairclient.pairs.has(client.id)) {
|
||||
return { status: 'success', message: 'ALREADY-PAIRED' };
|
||||
}
|
||||
|
||||
if (!client.pairs.has(to)) {
|
||||
return { 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' };
|
||||
}
|
||||
|
||||
const pairclient = Client.clients.get(to);
|
||||
|
||||
if (!pairclient.pairs.has(client.id)) {
|
||||
return { 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;
|
||||
}
|
||||
|
||||
const otherPeer = Client.clients.get(to);
|
||||
|
||||
if (otherPeer.requiredPair && !otherPeer.pairs.has(to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
register('auth/info', (client, msg) => {
|
||||
const { name, value } = msg;
|
||||
|
||||
client.info.set(name, value);
|
||||
|
||||
const 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"]);
|
||||
}
|
||||
|
||||
return { status: 'success' };
|
||||
});
|
||||
|
||||
register('peer/info', (client, msg) => {
|
||||
const { peer } = msg;
|
||||
|
||||
if (!client.isSecure(peer)) {
|
||||
return { status: "fail", message: "unaccessible user" };
|
||||
}
|
||||
|
||||
const peerClient = Client.clients.get(peer);
|
||||
const info = {};
|
||||
peerClient.info.forEach((value, name) => { info[name] = value; });
|
||||
|
||||
return { status: "success", info };
|
||||
});
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { Client } = require("../Client.js");
|
||||
const { register } = require("../WebSocket");
|
||||
const { Room } = require("./Room");
|
||||
|
||||
register('pack/to', (client, msg) => {
|
||||
const { to, pack, handshake } = msg;
|
||||
|
||||
if (!client.packReadable()) {
|
||||
return handshake ? { type: 'fail' } : undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
if (!otherPeer.pairs.has(to)) {
|
||||
otherPeer.pairs.add(client.id);
|
||||
client.pairs.add(otherPeer.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!otherPeer.packWriteable()) {
|
||||
return handshake ? { type: 'fail' } : undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const otherPeer = Client.clients.get(to);
|
||||
|
||||
if (otherPeer.requiredPair) {
|
||||
if (!otherPeer.pairs.has(to)) {
|
||||
return;
|
||||
}
|
||||
} 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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const otherPeer = Client.clients.get(to);
|
||||
|
||||
if (otherPeer.requiredPair && !otherPeer.pairs.has(to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
);
|
||||
|
||||
return handshake ? { type: 'success' } : undefined;
|
||||
});
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { Client } = require("../Client");
|
||||
const { on, register } = require("../WebSocket");
|
||||
|
||||
class APNumber {
|
||||
static busyNumbers = new Map();
|
||||
|
||||
static lock(client) {
|
||||
let c = 24;
|
||||
while (true) {
|
||||
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) {
|
||||
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 {
|
||||
static busyCodes = new Map();
|
||||
|
||||
static lock(client) {
|
||||
let firstLetter = new ShortCodeLetter();
|
||||
let secondLetter = new ShortCodeLetter();
|
||||
let thirdLetter = new ShortCodeLetter();
|
||||
|
||||
while (1) {
|
||||
let code = [firstLetter.code, secondLetter.code, thirdLetter.code].join('');
|
||||
if (!APShortCode.busyCodes.has(code)) {
|
||||
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();
|
||||
} else {
|
||||
thirdLetter.reset();
|
||||
if (!secondLetter.end()) {
|
||||
secondLetter.next();
|
||||
} else {
|
||||
secondLetter.reset();
|
||||
if (!firstLetter.end()) {
|
||||
firstLetter.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static release(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.code = this.chars.at(this.now);
|
||||
return this.code;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.now = 0;
|
||||
this.code = 'A';
|
||||
}
|
||||
|
||||
end() {
|
||||
return !this.chars.at(this.now + 1);
|
||||
}
|
||||
}
|
||||
|
||||
class APIPAddress {
|
||||
static busyIP = new Map();
|
||||
|
||||
static lock(client) {
|
||||
let A = 10, B = 0, C = 0, D = 1;
|
||||
|
||||
while (1) {
|
||||
let code = [A, B, C, D].join('.');
|
||||
if (!APIPAddress.busyIP.has(code)) {
|
||||
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; }
|
||||
D = 0;
|
||||
if (C != 255) { C++; continue; }
|
||||
C = 0;
|
||||
if (B != 255) { B++; continue; }
|
||||
B = 0;
|
||||
if (A != 255) { A++; continue; }
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static release(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) => {
|
||||
if (client.APIPAddress) {
|
||||
return { 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 };
|
||||
}
|
||||
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 };
|
||||
}
|
||||
let value = APShortCode.lock(client);
|
||||
client.APShortCode = value;
|
||||
return { status: 'success', code: value };
|
||||
});
|
||||
|
||||
register('realloc/APIPAddress', (client, msg) => {
|
||||
if (client.APIPAddress == 0) {
|
||||
return { status: 'fail' };
|
||||
}
|
||||
APIPAddress.release(client.APIPAddress);
|
||||
let value = APIPAddress.lock(client);
|
||||
return { status: 'success', ip: value };
|
||||
});
|
||||
|
||||
register('realloc/APNumber', (client, msg) => {
|
||||
if (client.APNumber == 0) {
|
||||
return { status: 'fail' };
|
||||
}
|
||||
APNumber.release(client.APNumber);
|
||||
let value = APNumber.lock(client);
|
||||
return { status: 'success', number: value };
|
||||
});
|
||||
|
||||
register('realloc/APShortCode', (client, msg) => {
|
||||
if (client.APShortCode == 0) {
|
||||
return { status: 'fail' };
|
||||
}
|
||||
APShortCode.release(client.APShortCode);
|
||||
let value = APShortCode.lock(client);
|
||||
return { status: 'success', code: value };
|
||||
});
|
||||
|
||||
register('release/APIPAddress', (client, msg) => {
|
||||
APIPAddress.release(client.APIPAddress);
|
||||
client.APIPAddress = undefined;
|
||||
return { status: 'success' };
|
||||
});
|
||||
|
||||
register('release/APNumber', (client, msg) => {
|
||||
APNumber.release(client.APNumber);
|
||||
client.APNumber = undefined;
|
||||
return { status: 'success' };
|
||||
});
|
||||
|
||||
register('release/APShortCode', (client, msg) => {
|
||||
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 };
|
||||
}
|
||||
return { status: 'fail' };
|
||||
});
|
||||
|
||||
register('whois/APNumber', (client, msg) => {
|
||||
let socketId = APNumber.whois(msg.whois);
|
||||
if (socketId) {
|
||||
return { status: 'success', socket: socketId };
|
||||
}
|
||||
return { status: 'fail' };
|
||||
});
|
||||
|
||||
register('whois/APShortCode', (client, msg) => {
|
||||
let socketId = APShortCode.whois(msg.whois);
|
||||
if (socketId) {
|
||||
return { status: 'success', socket: socketId };
|
||||
}
|
||||
return { status: 'fail' };
|
||||
});
|
||||
|
||||
on('disconnect', (client) => {
|
||||
if (client.APIPAddress != 0) {
|
||||
APIPAddress.release(client.APIPAddress);
|
||||
}
|
||||
if (client.APNumber != 0) {
|
||||
APNumber.release(client.APNumber);
|
||||
}
|
||||
if (client.APShortCode != 0) {
|
||||
APShortCode.release(client.APShortCode);
|
||||
}
|
||||
});
|
||||
|
|
@ -1,549 +0,0 @@
|
|||
const { Client } = require("../Client.js");
|
||||
let { randomUUID, createHash } = require("crypto");
|
||||
const joi = require("joi");
|
||||
const { on, register } = require("../WebSocket");
|
||||
const { termoutput } = require("../config.js");
|
||||
let term = require("terminal-kit").terminal;
|
||||
|
||||
function Sha256(update) {
|
||||
return createHash("sha256").update(update).digest("hex");
|
||||
};
|
||||
|
||||
function Room()
|
||||
{
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = randomUUID();
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = "";
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.description = "";
|
||||
/**
|
||||
* @type {Client}
|
||||
*/
|
||||
this.owner = null;
|
||||
/**
|
||||
* @type {Date}
|
||||
*/
|
||||
this.createdAt = new Date();
|
||||
/**
|
||||
* @type {Map<string, Client>}
|
||||
*/
|
||||
this.clients = new Map();
|
||||
/**
|
||||
* @type {"public"|"private"}
|
||||
*/
|
||||
this.accessType = "";
|
||||
/**
|
||||
* @type {"free"|"invite"|"password"|"lock"}
|
||||
*/
|
||||
this.joinType = "invite";
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.notifyActionInvite = false;
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.notifyActionJoined = true;
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.notifyActionEjected = true;
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this.credential = null;
|
||||
/**
|
||||
* @type {string[]}
|
||||
*/
|
||||
this.waitingInvited = new Set();
|
||||
|
||||
/**
|
||||
* @type {Map<string,any>}
|
||||
*/
|
||||
this.info = new Map();
|
||||
}
|
||||
/**
|
||||
* @param {Room} room
|
||||
*/
|
||||
Room.prototype.publish = function(){
|
||||
Room.rooms.set(this.id, this);
|
||||
termoutput && term.green("Room Published ").white(this.name," in ").yellow(this.clients.size).white(" clients")('\n');
|
||||
};
|
||||
/**
|
||||
* @return {Client[]}
|
||||
*/
|
||||
Room.prototype.filterPeers = function(optiJson){
|
||||
let peers = [];
|
||||
this.clients.forEach(client => {
|
||||
if(client.match(optiJson))
|
||||
{
|
||||
peers.push(client);
|
||||
}
|
||||
});
|
||||
return peers;
|
||||
};
|
||||
Room.prototype.toJSON = function(detailed){
|
||||
let obj = {};
|
||||
obj.id = this.id;
|
||||
obj.accessType = this.accessType;
|
||||
obj.createdAt = this.createdAt;
|
||||
obj.description = this.description;
|
||||
obj.joinType = this.joinType;
|
||||
obj.name = this.name;
|
||||
obj.owner = this.owner.id;
|
||||
obj.waitingInvited = [...this.waitingInvited];
|
||||
if(detailed)
|
||||
{
|
||||
obj.credential = this.credential;
|
||||
obj.notifyActionInvite = this.notifyActionInvite;
|
||||
obj.notifyActionJoined = this.notifyActionJoined;
|
||||
obj.notifyActionEjected = this.notifyActionEjected;
|
||||
obj.clients = [...this.clients.keys()];
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
Room.prototype.getInfo = function(){
|
||||
let obj = {};
|
||||
for (const [name, value] of this.info)
|
||||
{
|
||||
obj[name] = value;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
/**
|
||||
* @param {Object} data
|
||||
* @param {Room} room
|
||||
*/
|
||||
Room.fromJSON = function(data, room){
|
||||
room = room || new Room();
|
||||
let obj = {};
|
||||
room.id = data.id;
|
||||
room.accessType = data.accessType;
|
||||
room.createdAt = data.createdAt;
|
||||
room.description = data.description;
|
||||
room.joinType = data.joinType;
|
||||
room.name = data.name;
|
||||
if(data.owner && Client.clients.has(data.owner))
|
||||
{
|
||||
room.owner = Client.clients.get(data.owner);
|
||||
}
|
||||
room.waitingInvited = new Set(data.waitingInvited);
|
||||
obj.credential = data.credential;
|
||||
obj.notifyActionInvite = data.notifyActionInvite;
|
||||
obj.notifyActionJoined = data.notifyActionJoined;
|
||||
obj.notifyActionEjected = data.notifyActionEjected;
|
||||
obj.clients = new Map(
|
||||
data.clients.map(e => ([
|
||||
e, // map key
|
||||
Client.clients.get(e) // map value
|
||||
])
|
||||
)
|
||||
)
|
||||
return room;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param {any} obj
|
||||
* @param {string} withOut
|
||||
* @param {(client:Client) => boolean} map
|
||||
*/
|
||||
Room.prototype.send = function(obj, withOut, map){
|
||||
for (const client of this.clients.values()) {
|
||||
if(client.id != withOut)
|
||||
{
|
||||
(
|
||||
map ? map(client) : 1
|
||||
) && client.send(obj);
|
||||
}
|
||||
}
|
||||
termoutput && term.green("Room bulk message ").white(this.name," in ").yellow(this.clients.size + "").white(" clients")('\n');
|
||||
};
|
||||
/**
|
||||
* @param {Client} client
|
||||
*/
|
||||
Room.prototype.join = function(client){
|
||||
if(this.notifyActionJoined)
|
||||
{
|
||||
this.send(
|
||||
[
|
||||
{
|
||||
id: client.id,
|
||||
roomid: this.id,
|
||||
ownerid: this.owner.id
|
||||
},
|
||||
'room/joined'
|
||||
],
|
||||
void 0,
|
||||
client => client.peerInfoNotifiable()
|
||||
);
|
||||
};
|
||||
client.rooms.add(this.id);
|
||||
this.clients.set(client.id, client);
|
||||
termoutput && term.green("Client Room joined ").white(this.name," in ").yellow(this.clients.size + "").white(" clients")('\n');
|
||||
};
|
||||
Room.prototype.down = function(){
|
||||
termoutput && term.red("Room is downed ").red(this.name," in ").yellow(this.clients.size + "").red(" clients")('\n');
|
||||
this.send([{
|
||||
roomid: this.id,
|
||||
ownerid: this.owner.id
|
||||
},'room/closed']);
|
||||
Room.rooms.delete(this.id);
|
||||
};
|
||||
/**
|
||||
* @param {Client} client
|
||||
*/
|
||||
Room.prototype.eject = function(client){
|
||||
if(this.notifyActionEjected)
|
||||
{
|
||||
this.send(
|
||||
[
|
||||
{
|
||||
id: client.id,
|
||||
roomid: this.id,
|
||||
ownerid: this.owner.id
|
||||
},
|
||||
'room/ejected'
|
||||
],
|
||||
client.id,
|
||||
client => client.peerInfoNotifiable()
|
||||
);
|
||||
}
|
||||
client.rooms.delete(this.id);
|
||||
this.clients.delete(client.id);
|
||||
|
||||
if(this.clients.size == 0)
|
||||
{
|
||||
this.down();
|
||||
termoutput && term.red("Client Room closed ").red(this.name," at 0 clients")('\n');
|
||||
}
|
||||
|
||||
termoutput && term.red("Client Room ejected ").red(this.name," in ").yellow(this.clients.size + "").red(" clients")('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Map<string, Room>}
|
||||
*/
|
||||
Room.rooms = new Map();
|
||||
|
||||
on('connect', (client) => {
|
||||
let room = new Room();
|
||||
room.accessType = "private";
|
||||
room.joinType = "notify";
|
||||
room.description = 'Private room';
|
||||
room.id = client.id;
|
||||
room.name = "Your Room | " + client.id;
|
||||
room.owner = client;
|
||||
room.publish();
|
||||
room.join(client);
|
||||
});
|
||||
|
||||
on('disconnect', (client) => {
|
||||
const room = Room.rooms.get(client.id);
|
||||
if (room) room.eject(client);
|
||||
for (const roomId of client.rooms) {
|
||||
const r = Room.rooms.get(roomId);
|
||||
if (r) r.eject(client);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let CreateRoomVerify = joi.object({
|
||||
type: joi.any().required(),
|
||||
accessType: joi.string().pattern(/^public$|private$/).required(),
|
||||
notifyActionInvite: joi.boolean().required(),
|
||||
notifyActionJoined: joi.boolean().required(),
|
||||
notifyActionEjected: joi.boolean().required(),
|
||||
joinType: joi.string().pattern(/^free$|^invite$|^password$|^lock$/).required(),
|
||||
description: joi.string().required(),
|
||||
name: joi.string().required(),
|
||||
credential: joi.string().optional(),
|
||||
ifexistsJoin: joi.boolean().optional(),
|
||||
autoFetchInfo: joi.boolean().optional(),
|
||||
});
|
||||
|
||||
register('myroom-info', (client, msg) => {
|
||||
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' };
|
||||
}
|
||||
const filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {});
|
||||
return { 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' };
|
||||
}
|
||||
const filteredPeers = Room.rooms.get(roomId).filterPeers(filter || {});
|
||||
return { 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() };
|
||||
}
|
||||
}
|
||||
return { status: "fail", message: "NOT-FOUND-ROOM" };
|
||||
});
|
||||
|
||||
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' };
|
||||
}
|
||||
const room = Room.rooms.get(roomId);
|
||||
if (room.owner === client.id) {
|
||||
room.down();
|
||||
return { status: 'success' };
|
||||
}
|
||||
return { 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" };
|
||||
}
|
||||
}
|
||||
|
||||
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.owner = client;
|
||||
|
||||
if (msg.credential) {
|
||||
room.credential = Sha256(msg.credential + "");
|
||||
}
|
||||
|
||||
room.publish();
|
||||
room.join(client);
|
||||
|
||||
return { status: "success", room: room.toJSON() };
|
||||
});
|
||||
|
||||
register('joinroom', (client, msg) => {
|
||||
const { name, autoFetchInfo } = msg;
|
||||
let roomId;
|
||||
|
||||
for (const [_roomId, room] of Room.rooms) {
|
||||
if (name == room.name) {
|
||||
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 info = {};
|
||||
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") {
|
||||
let info = {};
|
||||
if (autoFetchInfo) {
|
||||
info.info = room.getInfo();
|
||||
}
|
||||
room.join(client);
|
||||
return { status: "success", room: room.toJSON(), ...info };
|
||||
}
|
||||
|
||||
if (room.joinType == "invite") {
|
||||
room.waitingInvited.add(client.id);
|
||||
if (room.notifyActionInvite) {
|
||||
room.send([{ id: client.id }, "room/invite"]);
|
||||
} else {
|
||||
room.owner.send([{ id: client.id }, "room/invite"]);
|
||||
}
|
||||
}
|
||||
|
||||
return { status: "fail", message: "NOT-FOUND-ROOM" };
|
||||
});
|
||||
|
||||
register('ejectroom', (client, msg) => {
|
||||
const { roomId } = msg;
|
||||
|
||||
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" };
|
||||
});
|
||||
|
||||
register('reject/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.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" };
|
||||
});
|
||||
|
||||
register('room/list', (client, msg) => {
|
||||
const rooms = [];
|
||||
for (const [id, room] of Room.rooms) {
|
||||
if (room.accessType == "public") {
|
||||
rooms.push({
|
||||
name: room.name,
|
||||
joinType: room.joinType,
|
||||
description: room.description,
|
||||
id
|
||||
});
|
||||
}
|
||||
}
|
||||
return { type: 'public/rooms', rooms };
|
||||
});
|
||||
|
||||
register('room/info', (client, msg) => {
|
||||
const { roomId, name } = 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" };
|
||||
}
|
||||
|
||||
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"],
|
||||
client.id,
|
||||
c => c.roomInfoNotifiable()
|
||||
);
|
||||
|
||||
return { status: "success" };
|
||||
});
|
||||
|
||||
exports.Room = Room;
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { on, emit, register } = require("../WebSocket");
|
||||
|
||||
const defaults = {
|
||||
notifyPairInfo: true,
|
||||
packrecaive: true,
|
||||
packsending: true,
|
||||
notifyRoomInfo: true
|
||||
};
|
||||
|
||||
on('connect', (client) => {
|
||||
for (const [name, value] of Object.entries(defaults)) {
|
||||
client.store.set(name, value);
|
||||
}
|
||||
});
|
||||
|
||||
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' };
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const { on } = require("../WebSocket");
|
||||
|
||||
on('connect', (client) => {
|
||||
client.send([{ type: 'id', value: client.id }, 'id']);
|
||||
});
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
let websocket = require("websocket");
|
||||
let http = null;
|
||||
let wsServer = null;
|
||||
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;
|
||||
|
||||
termoutput && console.log("Web Socket Protocol is ready");
|
||||
|
||||
http.addListener("upgrade", () => {
|
||||
termoutput && console.log("HTTP Upgrading to WebSocket");
|
||||
});
|
||||
|
||||
wsServer = new websocket.server({
|
||||
httpServer: http,
|
||||
autoAcceptConnections: true
|
||||
});
|
||||
|
||||
wsServer.addListener("connect", (socket) => {
|
||||
let client = new Client();
|
||||
let id = randomUUID();
|
||||
socket.id = id;
|
||||
client.id = id;
|
||||
client.socket = socket;
|
||||
client.created_at = new Date();
|
||||
Client.clients.set(id, client);
|
||||
|
||||
EventEmitter.emit('connect', client);
|
||||
|
||||
let pingTimer = setInterval(() => socket.ping('saQut'), 10_000);
|
||||
|
||||
socket.addListener("pong", (validationText) => {
|
||||
if (validationText.toString('utf8') != "saQut") {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
|
||||
socket.addListener("message", ({ type, utf8Data }) => {
|
||||
if (type == "utf8") {
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.addListener("close", () => {
|
||||
EventEmitter.emit('disconnect', client);
|
||||
Client.clients.delete(id);
|
||||
clearInterval(pingTimer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
235
Source/api.js
235
Source/api.js
|
|
@ -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;
|
||||
|
|
@ -1 +0,0 @@
|
|||
exports.termoutput = false;
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
require("./HTTPServer.js");
|
||||
|
||||
const { http } = require("./HTTPServer");
|
||||
|
||||
const WebSocket = require("./WebSocket");
|
||||
WebSocket.init(http);
|
||||
|
||||
require("./Services/YourID.js");
|
||||
require("./Services/Auth.js");
|
||||
require("./Services/Room.js");
|
||||
require("./Services/DataTransfer.js");
|
||||
require("./Services/IPPressure.js");
|
||||
require("./Services/Session.js");
|
||||
|
||||
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)
|
||||
});
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
import MWSE from "frontend";
|
||||
|
||||
export interface IConnection{
|
||||
endpoint: string;
|
||||
autoReconnect?: boolean | {
|
||||
timeout: number;
|
||||
}
|
||||
}
|
||||
export class Connection
|
||||
{
|
||||
public ws! : WebSocket;
|
||||
public endpoint : URL;
|
||||
public autoPair : boolean = false;
|
||||
public connected : boolean = false;
|
||||
|
||||
public autoReconnect : boolean = true;
|
||||
public autoReconnectTimeout : number = 3000;
|
||||
public autoReconnectTimer? : number;
|
||||
constructor(mwse:MWSE, options: IConnection){
|
||||
|
||||
if(options.endpoint == "auto")
|
||||
{
|
||||
const RootURL : string = ( <HTMLScriptElement> document.currentScript).src
|
||||
let scriptPath = new URL(RootURL);
|
||||
let isSecurity = scriptPath.protocol == "https:";
|
||||
let dumeUrl = scriptPath.pathname.split('/').slice(0,-1).join('/') + '/';
|
||||
let wsSocket = new URL(dumeUrl, scriptPath);
|
||||
wsSocket.protocol = isSecurity ? 'wss:' : 'ws:';
|
||||
this.endpoint = new URL(wsSocket.href);
|
||||
}else{
|
||||
try{
|
||||
// Testing
|
||||
this.endpoint = new URL(options.endpoint);
|
||||
}catch{
|
||||
throw new Error("endpoint is required")
|
||||
}
|
||||
}
|
||||
if(typeof options.autoReconnect == "boolean")
|
||||
{
|
||||
this.autoReconnect = true;
|
||||
}else if(options.autoReconnect)
|
||||
{
|
||||
this.autoReconnect = true;
|
||||
this.autoReconnectTimeout = options.autoReconnect.timeout;
|
||||
}
|
||||
}
|
||||
public connect()
|
||||
{
|
||||
if(this.autoReconnectTimer)
|
||||
{
|
||||
clearTimeout(this.autoReconnectTimer)
|
||||
};
|
||||
this.ws = new WebSocket(this.endpoint.href);
|
||||
this.addWSEvents();
|
||||
}
|
||||
public disconnect()
|
||||
{
|
||||
/**
|
||||
* Eğer bilinerek elle kapatıldıysa otomatik tekrar bağlanmasının
|
||||
* önüne geçmek için autoReconnect bayrağını her zaman kapalı tutmak gerekir
|
||||
*/
|
||||
this.autoReconnect = false;
|
||||
this.ws.close();
|
||||
}
|
||||
public addWSEvents()
|
||||
{
|
||||
this.ws.addEventListener("open", () => this.eventOpen());
|
||||
this.ws.addEventListener("close", () => this.eventClose());
|
||||
this.ws.addEventListener("error", () => this.eventError());
|
||||
this.ws.addEventListener("message", ({data}) => this.eventMessage(data as string | ArrayBuffer));
|
||||
}
|
||||
private eventOpen()
|
||||
{
|
||||
this.connected = true;
|
||||
for (const callback of this.activeConnectionEvent) {
|
||||
callback(void 0);
|
||||
}
|
||||
}
|
||||
private eventClose()
|
||||
{
|
||||
for (const callback of this.passiveConnectionEvent) {
|
||||
callback(void 0);
|
||||
}
|
||||
this.connected = false;
|
||||
if(this.autoReconnect)
|
||||
{
|
||||
this.autoReconnectTimer = setTimeout(() => this.connect(), this.autoReconnectTimeout) as unknown as number;
|
||||
}
|
||||
}
|
||||
private eventError()
|
||||
{
|
||||
this.connected = false;
|
||||
}
|
||||
private recaivePackEvent : ((data:any) => any)[] = [];
|
||||
public onRecaivePack(func:(data:any) => any)
|
||||
{
|
||||
this.recaivePackEvent.push(func);
|
||||
}
|
||||
private activeConnectionEvent : Function[] = [];
|
||||
public onActive(func:Function)
|
||||
{
|
||||
if(this.connected)
|
||||
{
|
||||
func()
|
||||
}else{
|
||||
this.activeConnectionEvent.push(func);
|
||||
}
|
||||
}
|
||||
private passiveConnectionEvent : Function[] = [];
|
||||
public onPassive(func:Function)
|
||||
{
|
||||
if(!this.connected)
|
||||
{
|
||||
func()
|
||||
}else{
|
||||
this.passiveConnectionEvent.push(func);
|
||||
}
|
||||
}
|
||||
private eventMessage(data: string | ArrayBuffer)
|
||||
{
|
||||
if(typeof data == "string")
|
||||
{
|
||||
let $data = JSON.parse(data);
|
||||
for (const callback of this.recaivePackEvent) {
|
||||
callback($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
public tranferToServer(data:any)
|
||||
{
|
||||
if(this.connected)
|
||||
{
|
||||
this.ws.send(JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import MWSE from "./index";
|
||||
import { Message } from "./WSTSProtocol";
|
||||
|
||||
export default class EventPool
|
||||
{
|
||||
public wsts : MWSE;
|
||||
public events : Map<number, [Function,Function]> = new Map();
|
||||
public signals : Map<string, Function[]> = new Map();
|
||||
|
||||
public requests : Map<number, [Function,Function]> = new Map();
|
||||
|
||||
public count = 0;
|
||||
constructor(wsts:MWSE){
|
||||
this.wsts = wsts;
|
||||
}
|
||||
/**
|
||||
* request sends a packet that expects a correlated reply and resolves with it.
|
||||
* Use it ONLY for response-bearing packets. For fire-and-forget (WOM) packets
|
||||
* use only(): registering a waiter for a packet the server never answers leaves
|
||||
* a promise pending forever (issue #33).
|
||||
*/
|
||||
public request(msg: Message) : Promise<any>
|
||||
{
|
||||
return new Promise((ok,rej) => {
|
||||
let id = ++this.count;
|
||||
this.events.set(id,[
|
||||
(data:any) => {
|
||||
ok(data);
|
||||
},
|
||||
(data:any) => {
|
||||
rej(data);
|
||||
}
|
||||
]);
|
||||
this.wsts.WSTSProtocol.SendRequest(msg, id);
|
||||
})
|
||||
}
|
||||
/**
|
||||
* only is the WOM (without-me / fire-and-forget) path: it sends the packet and
|
||||
* leaves NO pending waiter. The engine deliberately does not reply to these
|
||||
* relays (it returns nil), so there is nothing to await. This is the separation
|
||||
* issue #33 requires: request() = response-bearing, only() = WOM.
|
||||
*/
|
||||
public only(msg: Message)
|
||||
{
|
||||
this.wsts.WSTSProtocol.SendOnly(msg);
|
||||
}
|
||||
public stream(msg: Message, callback: Function)
|
||||
{
|
||||
let id = ++this.count;
|
||||
this.wsts.WSTSProtocol.StartStream(msg, id);
|
||||
this.events.set(id,[
|
||||
(data:any) => {
|
||||
callback(data);
|
||||
},
|
||||
() => { }
|
||||
]);
|
||||
}
|
||||
public signal(event: string, callback: Function)
|
||||
{
|
||||
let T = this.signals.get(event);
|
||||
if(!T)
|
||||
{
|
||||
this.signals.set(event, [callback]);
|
||||
}else{
|
||||
T.push(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
export default class EventTarget
|
||||
{
|
||||
private events : {[key:string]:Function[]} = {};
|
||||
public emit(eventName :string, ...args:any[])
|
||||
{
|
||||
if(this.events[eventName])
|
||||
{
|
||||
for (const callback of this.events[eventName]) {
|
||||
callback(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
public on(eventName :string, callback:Function)
|
||||
{
|
||||
if(this.events[eventName])
|
||||
{
|
||||
this.events[eventName].push(callback)
|
||||
}else{
|
||||
this.events[eventName] = [callback];
|
||||
}
|
||||
}
|
||||
public activeScope : boolean = false;
|
||||
scope(f:Function)
|
||||
{
|
||||
if(this.activeScope)
|
||||
{
|
||||
f()
|
||||
}else{
|
||||
this.on('scope', f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
import MWSE from "frontend";
|
||||
|
||||
export class IPPressure
|
||||
{
|
||||
public mwse : MWSE;
|
||||
public APNumber? : number;
|
||||
public APShortCode? : string;
|
||||
public APIPAddress? : string;
|
||||
constructor(mwse : MWSE){
|
||||
this.mwse = mwse;
|
||||
};
|
||||
public async allocAPIPAddress()
|
||||
{
|
||||
let {status,ip} = await this.mwse.EventPooling.request({
|
||||
type: 'alloc/APIPAddress'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
ip?:string
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APIPAddress = ip;
|
||||
return ip;
|
||||
}else{
|
||||
throw new Error("Error Allocated Access Point IP Address");
|
||||
}
|
||||
}
|
||||
public async allocAPNumber()
|
||||
{
|
||||
let {status,number} = await this.mwse.EventPooling.request({
|
||||
type: 'alloc/APNumber'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
number?:number
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APNumber = number;
|
||||
return number;
|
||||
}else{
|
||||
throw new Error("Error Allocated Access Point Number");
|
||||
}
|
||||
}
|
||||
public async allocAPShortCode()
|
||||
{
|
||||
let {status,code} = await this.mwse.EventPooling.request({
|
||||
type: 'alloc/APShortCode'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
code?:string
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APShortCode = code;
|
||||
return code;
|
||||
}else{
|
||||
throw new Error("Error Allocated Access Point Short Code");
|
||||
}
|
||||
}
|
||||
public async reallocAPIPAddress()
|
||||
{
|
||||
let {status,ip} = await this.mwse.EventPooling.request({
|
||||
type: 'realloc/APIPAddress'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
ip?:string
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APIPAddress = ip;
|
||||
return ip;
|
||||
}else{
|
||||
throw new Error("Error Reallocated Access Point IP Address");
|
||||
}
|
||||
}
|
||||
public async reallocAPNumber()
|
||||
{
|
||||
let {status,number} = await this.mwse.EventPooling.request({
|
||||
type: 'realloc/APNumber'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
number?:number
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APNumber = number;
|
||||
return number;
|
||||
}else{
|
||||
throw new Error("Error Reallocated Access Point Number");
|
||||
}
|
||||
}
|
||||
public async reallocAPShortCode()
|
||||
{
|
||||
let {status,code} = await this.mwse.EventPooling.request({
|
||||
type: 'realloc/APShortCode'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
code?:string
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APShortCode = code;
|
||||
return code;
|
||||
}else{
|
||||
throw new Error("Error Reallocated Access Point Short Code");
|
||||
}
|
||||
}
|
||||
public async releaseAPIPAddress()
|
||||
{
|
||||
let {status} = await this.mwse.EventPooling.request({
|
||||
type: 'release/APIPAddress'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APIPAddress = undefined;
|
||||
}else{
|
||||
throw new Error("Error release Access Point IP Address");
|
||||
}
|
||||
}
|
||||
public async releaseAPNumber()
|
||||
{
|
||||
let {status} = await this.mwse.EventPooling.request({
|
||||
type: 'release/APNumber'
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APNumber = undefined;
|
||||
}else{
|
||||
throw new Error("Error release Access Point Number");
|
||||
}
|
||||
}
|
||||
public async releaseAPShortCode()
|
||||
{
|
||||
let {status} = await this.mwse.EventPooling.request({
|
||||
type: 'release/APShortCode'
|
||||
}) as {
|
||||
status:string
|
||||
};
|
||||
if(status == 'success')
|
||||
{
|
||||
this.APShortCode = undefined;
|
||||
}else{
|
||||
throw new Error("Error release Access Point Short Code");
|
||||
}
|
||||
}
|
||||
public async queryAPIPAddress(ip:string)
|
||||
{
|
||||
let {status,socket} = await this.mwse.EventPooling.request({
|
||||
type: 'whois/APIPAddress',
|
||||
whois: ip
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
socket?:string
|
||||
};
|
||||
if(status == "success")
|
||||
{
|
||||
return socket;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public async queryAPNumber(number:number)
|
||||
{
|
||||
let {status,socket} = await this.mwse.EventPooling.request({
|
||||
type: 'whois/APNumber',
|
||||
whois: number
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
socket?:string
|
||||
};
|
||||
if(status == "success")
|
||||
{
|
||||
return socket;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public async queryAPShortCode(code:string)
|
||||
{
|
||||
let {status,socket} = await this.mwse.EventPooling.request({
|
||||
type: 'whois/APShortCode',
|
||||
whois: code
|
||||
}) as {
|
||||
status:"fail"|"success",
|
||||
socket?:string
|
||||
};
|
||||
if(status == "success")
|
||||
{
|
||||
return socket;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
import WebRTC from "./WebRTC";
|
||||
import Peer from "./Peer";
|
||||
|
||||
/**
|
||||
* Deneyseldir kullanılması önerilmez
|
||||
*/
|
||||
export default class P2PFileSender
|
||||
{
|
||||
public rtc : RTCPeerConnection;
|
||||
public peer : Peer;
|
||||
public webrtc : WebRTC;
|
||||
|
||||
public totalSize : number = 0;
|
||||
public isReady : boolean = false;
|
||||
public isStarted : boolean = false;
|
||||
public isSending : boolean = false;
|
||||
public isRecaiving : boolean = false;
|
||||
public processedSize : number = 0;
|
||||
public recaivedFile? : File;
|
||||
|
||||
public bufferSizePerChannel : number = 10e6;
|
||||
public bufferSizePerPack : number = 10e3;
|
||||
public safeBufferSizePerPack : number = 10e3 - 1;
|
||||
|
||||
public constructor(webrtc : WebRTC, peer : Peer)
|
||||
{
|
||||
this.webrtc = webrtc;
|
||||
this.rtc = webrtc.rtc;
|
||||
this.peer = peer;
|
||||
}
|
||||
public async RecaiveFile(
|
||||
_rtc: RTCPeerConnection,
|
||||
fileMetadata: {name:string, type:string},
|
||||
channelCount: number,
|
||||
_totalSize: number,
|
||||
onEnded: Function
|
||||
)
|
||||
{
|
||||
//let totals = {};
|
||||
// let index = 0;
|
||||
/*setChannelStatus(Array.from({length:channelCount}).map((e, index) => {
|
||||
return {
|
||||
name: `${index+1}. Kanal`,
|
||||
current: 0,
|
||||
currentTotal: 0,
|
||||
total: 0
|
||||
}
|
||||
}));*/
|
||||
let parts : Blob[] = [];
|
||||
this.webrtc.on('datachannel',(datachannel:RTCDataChannel) => {
|
||||
//let channelIndex = index++;
|
||||
let current = 0;
|
||||
let totalSize = 0;
|
||||
let currentPart = 0;
|
||||
let bufferAmount : ArrayBuffer[] = [];
|
||||
datachannel.onmessage = function({data}){
|
||||
if(totalSize == 0)
|
||||
{
|
||||
let {
|
||||
size,
|
||||
part,
|
||||
} = JSON.parse(data);
|
||||
totalSize = size;
|
||||
currentPart = part;
|
||||
/*updateChannelStatus(channelIndex, n => {
|
||||
return {
|
||||
...n,
|
||||
total: totalSize,
|
||||
current: 0
|
||||
}
|
||||
});*/
|
||||
datachannel.send("READY");
|
||||
}else{
|
||||
current += data.byteLength;
|
||||
bufferAmount.push(data);
|
||||
/*updateChannelStatus(channelIndex, n => {
|
||||
return {
|
||||
...n,
|
||||
current: data.byteLength + n.current,
|
||||
currentTotal: data.byteLength + n.currentTotal,
|
||||
}
|
||||
});
|
||||
setProcessedSize(n => n + data.byteLength);*/
|
||||
if(current == totalSize)
|
||||
{
|
||||
parts[currentPart] = new Blob(bufferAmount);
|
||||
bufferAmount = [];
|
||||
//totals[datachannel.label] += totalSize;
|
||||
totalSize = 0;
|
||||
currentPart = 0;
|
||||
current = 0;
|
||||
datachannel.send("TOTAL_RECAIVED");
|
||||
}
|
||||
}
|
||||
};
|
||||
datachannel.onclose = () => {
|
||||
channelCount--;
|
||||
if(channelCount == 0)
|
||||
{
|
||||
let file = new File(parts, fileMetadata.name, {
|
||||
type: fileMetadata.type,
|
||||
lastModified: +new Date
|
||||
});
|
||||
onEnded(file);
|
||||
}
|
||||
};
|
||||
})
|
||||
}
|
||||
public async SendFile(
|
||||
file: File,
|
||||
metadata: object
|
||||
)
|
||||
{
|
||||
this.isSending = true;
|
||||
this.isStarted = true;
|
||||
|
||||
|
||||
let buffer = await file.arrayBuffer();
|
||||
let partCount = Math.ceil(buffer.byteLength / 10e6);
|
||||
let channelCount = Math.min(5, partCount);
|
||||
|
||||
if(this.webrtc.iceStatus != "connected")
|
||||
{
|
||||
throw new Error("WebRTC is a not ready")
|
||||
}
|
||||
|
||||
this.peer.send({
|
||||
type: 'file',
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
mimetype: file.type,
|
||||
partCount,
|
||||
channelCount,
|
||||
metadata: metadata
|
||||
});
|
||||
|
||||
let channels : RTCDataChannel[] = [];
|
||||
|
||||
for(let channelIndex = 0; channelIndex < channelCount; channelIndex++)
|
||||
{
|
||||
let channel = this.rtc.createDataChannel("\\?\\file_" + channelIndex);
|
||||
channel.binaryType = "arraybuffer";
|
||||
await new Promise(ok => {
|
||||
channel.onopen = () => {
|
||||
ok(void 0);
|
||||
}
|
||||
});
|
||||
channels.push(channel);
|
||||
};
|
||||
|
||||
let currentPart = 0;
|
||||
let next = () => {
|
||||
if(currentPart < partCount)
|
||||
{
|
||||
let bufferPart = buffer.slice(currentPart * 10e6, currentPart * 10e6 + 10e6)
|
||||
currentPart++;
|
||||
return [bufferPart, currentPart - 1];
|
||||
};
|
||||
return [false,0];
|
||||
};
|
||||
let spyChannelIndex = channels.length;
|
||||
await new Promise(ok => {
|
||||
for (let channelIndex = 0; channelIndex < channels.length; channelIndex++)
|
||||
{
|
||||
this.sendPartition(
|
||||
channels[channelIndex],
|
||||
next,
|
||||
channelIndex,
|
||||
() => {
|
||||
spyChannelIndex--;
|
||||
if(spyChannelIndex == 0)
|
||||
{
|
||||
this.isSending = false;
|
||||
this.isStarted = false;
|
||||
ok(undefined)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
protected sendPartition(
|
||||
channel: RTCDataChannel,
|
||||
nextblob10mb: () => (number | ArrayBuffer)[] | (number | boolean)[],
|
||||
_channelIndex: number,
|
||||
onEnded: Function
|
||||
)
|
||||
{
|
||||
let [currentBuffer,currentPartition] = nextblob10mb();
|
||||
let currentPart = 0;
|
||||
let next = () => {
|
||||
if(!(currentBuffer instanceof ArrayBuffer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let bufferPart = currentBuffer.slice(currentPart * 16e3, currentPart * 16e3 + 16e3)
|
||||
currentPart++;
|
||||
if(bufferPart.byteLength != 0)
|
||||
{
|
||||
/*
|
||||
updateChannelStatus(channelIndex, n => {
|
||||
return {
|
||||
...n,
|
||||
current: bufferPart.byteLength + n.current,
|
||||
currentTotal: bufferPart.byteLength + n.currentTotal
|
||||
}
|
||||
});
|
||||
setProcessedSize(n => n + bufferPart.byteLength);
|
||||
*/
|
||||
return bufferPart
|
||||
}
|
||||
};
|
||||
channel.addEventListener("message",({data}) => {
|
||||
if(data == "READY")
|
||||
{
|
||||
this.sendFileChannel(channel, next)
|
||||
}
|
||||
if(data == "TOTAL_RECAIVED")
|
||||
{
|
||||
[currentBuffer,currentPartition] = nextblob10mb();
|
||||
currentPart = 0;
|
||||
if(currentBuffer != false)
|
||||
{
|
||||
/*updateChannelStatus(channelIndex, n => {
|
||||
return {
|
||||
...n,
|
||||
total: currentBuffer.byteLength,
|
||||
current: 0,
|
||||
}
|
||||
});*/
|
||||
channel.send(JSON.stringify({
|
||||
size: (currentBuffer as ArrayBuffer).byteLength,
|
||||
part: currentPartition
|
||||
}))
|
||||
}else{
|
||||
channel.close();
|
||||
onEnded();
|
||||
}
|
||||
}
|
||||
});
|
||||
channel.send(JSON.stringify({
|
||||
size: (currentBuffer as ArrayBuffer).byteLength,
|
||||
part: currentPartition
|
||||
}))
|
||||
}
|
||||
protected sendFileChannel(
|
||||
channel: RTCDataChannel,
|
||||
getNextBlob: () => ArrayBuffer | undefined
|
||||
)
|
||||
{
|
||||
channel.addEventListener("bufferedamountlow",function(){
|
||||
let buffer = getNextBlob();
|
||||
if(buffer)
|
||||
{
|
||||
channel.send(buffer);
|
||||
}
|
||||
});
|
||||
channel.bufferedAmountLowThreshold = 16e3 - 1;
|
||||
let c = getNextBlob();
|
||||
c && channel.send(c);
|
||||
}
|
||||
};
|
||||
237
frontend/Peer.ts
237
frontend/Peer.ts
|
|
@ -1,237 +0,0 @@
|
|||
import EventTarget from "./EventTarget";
|
||||
import { PeerInfo } from "./PeerInfo";
|
||||
import WebRTC from "./WebRTC";
|
||||
import MWSE from "./index";
|
||||
|
||||
interface IPeerOptions{
|
||||
|
||||
};
|
||||
|
||||
enum IMessageSymbase
|
||||
{
|
||||
PayloadMessagePack = -12873.54,
|
||||
PayloadRTCBasePack = -12884.54
|
||||
}
|
||||
|
||||
|
||||
export default class Peer extends EventTarget
|
||||
{
|
||||
public mwse : MWSE;
|
||||
public options : IPeerOptions = {};
|
||||
public socketId? : string;
|
||||
public selfSocket : boolean = false;
|
||||
public active : boolean = false;
|
||||
public info : PeerInfo;
|
||||
public rtc : WebRTC;
|
||||
public peerConnection : boolean = false;
|
||||
public primaryChannel : "websocket" | "datachannel" = "datachannel";
|
||||
constructor(wsts:MWSE){
|
||||
super();
|
||||
this.mwse = wsts;
|
||||
this.rtc = this.createRTC();
|
||||
this.info = new PeerInfo(this);
|
||||
this.on('pack',(data:{type?:string,action?:IMessageSymbase,payload?:any}) => {
|
||||
if(data.type == ':rtcpack:')
|
||||
{
|
||||
return this.rtc.emit("input", data.payload)
|
||||
};
|
||||
this.emit("message", data);
|
||||
});
|
||||
}
|
||||
public createRTC(rtcConfig?: RTCConfiguration | undefined, rtcServers?: RTCIceServer[] | undefined) : WebRTC
|
||||
{
|
||||
this.rtc = new WebRTC(rtcConfig,rtcServers);
|
||||
this.rtc.peer = this;
|
||||
this.rtc.on("connected", () => {
|
||||
this.peerConnection = true;
|
||||
});
|
||||
this.rtc.on('disconnected', () => {
|
||||
this.peerConnection = false;
|
||||
})
|
||||
this.rtc.on("output",(payload:object) => {
|
||||
this.send({
|
||||
type: ':rtcpack:',
|
||||
payload: payload
|
||||
})
|
||||
});
|
||||
this.rtc.on("message",(payload:object) => {
|
||||
this.emit("pack",payload);
|
||||
});
|
||||
return this.rtc;
|
||||
}
|
||||
public setPeerOptions(options: string | IPeerOptions){
|
||||
if(typeof options == "string")
|
||||
{
|
||||
this.setSocketId(options)
|
||||
}else{
|
||||
this.options = options;
|
||||
}
|
||||
}
|
||||
public setSocketId(uuid: string){
|
||||
this.socketId = uuid;
|
||||
}
|
||||
async metadata() : Promise<any>
|
||||
{
|
||||
if(this.socketId == 'me')
|
||||
{
|
||||
let result = await this.mwse.EventPooling.request({
|
||||
type:'my/socketid'
|
||||
});
|
||||
this.selfSocket = true;
|
||||
this.active ||= true;
|
||||
this.socketId = result;
|
||||
this.emit('scope');
|
||||
this.activeScope = true;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
async request(pack:any){
|
||||
if(this.active)
|
||||
{
|
||||
return await this.mwse.request(this.socketId as string, pack);
|
||||
}
|
||||
};
|
||||
equalTo(peer : Peer | {socketId: string})
|
||||
{
|
||||
return this.socketId == peer.socketId;
|
||||
}
|
||||
async isReachable()
|
||||
{
|
||||
return await this.mwse.EventPooling.request({
|
||||
type:'is/reachable',
|
||||
to: this.socketId
|
||||
});
|
||||
}
|
||||
async enablePairAuth(){
|
||||
await this.mwse.EventPooling.request({
|
||||
type:'auth/pair-system',
|
||||
value: 'everybody'
|
||||
});
|
||||
}
|
||||
async disablePairAuth(){
|
||||
await this.mwse.EventPooling.request({
|
||||
type:'auth/pair-system',
|
||||
value: 'disable'
|
||||
});
|
||||
}
|
||||
async enablePairInfo(){
|
||||
await this.mwse.EventPooling.request({
|
||||
type: 'connection/pairinfo',
|
||||
value: true
|
||||
});
|
||||
}
|
||||
async disablePairInfo(){
|
||||
await this.mwse.EventPooling.request({
|
||||
type: 'connection/pairinfo',
|
||||
value: false
|
||||
});
|
||||
}
|
||||
async requestPair()
|
||||
{
|
||||
let {message,status} = await this.mwse.EventPooling.request({
|
||||
type:'request/pair',
|
||||
to: this.socketId
|
||||
});
|
||||
if(
|
||||
message == "ALREADY-PAIRED" ||
|
||||
message == "ALREADY-REQUESTED"
|
||||
)
|
||||
{
|
||||
console.warn("Already paired or pair requested")
|
||||
};
|
||||
if(status == "fail")
|
||||
{
|
||||
console.error("Request Pair Error",status, message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async endPair()
|
||||
{
|
||||
await this.mwse.EventPooling.request({
|
||||
type:'end/pair',
|
||||
to: this.socketId
|
||||
});
|
||||
this.forget();
|
||||
}
|
||||
async acceptPair()
|
||||
{
|
||||
let {message,status} = await this.mwse.EventPooling.request({
|
||||
type:'accept/pair',
|
||||
to: this.socketId
|
||||
});
|
||||
if(status == "fail")
|
||||
{
|
||||
console.error("Pair Error",status, message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async rejectPair()
|
||||
{
|
||||
let {message,status} = await this.mwse.EventPooling.request({
|
||||
type:'reject/pair',
|
||||
to: this.socketId
|
||||
});
|
||||
if(status == "fail")
|
||||
{
|
||||
console.error("Pair Error",status, message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async getPairedList() : Promise<string>
|
||||
{
|
||||
let {value} = await this.mwse.EventPooling.request({
|
||||
type:'pair/list',
|
||||
to: this.socketId
|
||||
});
|
||||
return value;
|
||||
}
|
||||
async send(pack: any){
|
||||
let isOpenedP2P = this.peerConnection && this.rtc?.active;
|
||||
let isOpenedServer = this.mwse.server.connected;
|
||||
let sendChannel : "websocket" | "datachannel";
|
||||
if(isOpenedP2P && isOpenedServer)
|
||||
{
|
||||
if(this.primaryChannel == "websocket")
|
||||
{
|
||||
sendChannel = "websocket"
|
||||
}else
|
||||
{
|
||||
sendChannel = "datachannel"
|
||||
}
|
||||
}else if(isOpenedServer){
|
||||
sendChannel = "websocket"
|
||||
}else{
|
||||
sendChannel = "datachannel"
|
||||
}
|
||||
|
||||
if(sendChannel == "websocket")
|
||||
{
|
||||
if(!this.mwse.writable){
|
||||
return console.warn("Socket is not writable");
|
||||
}
|
||||
// WOM (fire-and-forget): a plain peer.send expects no reply, so use the
|
||||
// only() path and leave no pending waiter. Using request() here would
|
||||
// register a promise the engine never resolves (issue #33).
|
||||
this.mwse.EventPooling.only({
|
||||
type:'pack/to',
|
||||
pack,
|
||||
to: this.socketId
|
||||
});
|
||||
}else{
|
||||
if(pack.type != ':rtcpack:')
|
||||
{
|
||||
this.rtc?.sendMessage(pack)
|
||||
}else{
|
||||
return console.warn("Socket is not writable");
|
||||
}
|
||||
}
|
||||
}
|
||||
async forget(){
|
||||
this.mwse.peers.delete(this.socketId as string);
|
||||
this.mwse.pairs.delete(this.socketId as string);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import Peer from "./Peer";
|
||||
|
||||
export class PeerInfo
|
||||
{
|
||||
public peer : Peer;
|
||||
public info : {[key:string]: any} = {};
|
||||
constructor(mwse : Peer){
|
||||
this.peer = mwse;
|
||||
};
|
||||
public async fetch(name?:string)
|
||||
{
|
||||
if(name)
|
||||
{
|
||||
let rinfo = await this.peer.mwse.EventPooling.request(({
|
||||
type: "peer/info",
|
||||
peer: this.peer.socketId,
|
||||
name
|
||||
}));
|
||||
if(rinfo.status == "success")
|
||||
{
|
||||
this.info = rinfo.info;
|
||||
}else console.warn(rinfo.message);
|
||||
}else{
|
||||
let rinfo = await this.peer.mwse.EventPooling.request(({
|
||||
type: "peer/info",
|
||||
peer: this.peer.socketId
|
||||
}));
|
||||
if(rinfo.status == "success")
|
||||
{
|
||||
this.info = rinfo.info;
|
||||
}else console.warn(rinfo.message);
|
||||
};
|
||||
return this.info;
|
||||
}
|
||||
public set(name: string, value: string | number)
|
||||
{
|
||||
this.info[name] = value;
|
||||
this.peer.mwse.WSTSProtocol.SendOnly({
|
||||
type: "auth/info",
|
||||
name,
|
||||
value
|
||||
});
|
||||
}
|
||||
public get(name?:string)
|
||||
{
|
||||
return name ? this.info[name] : this.info;
|
||||
}
|
||||
}
|
||||
182
frontend/Room.ts
182
frontend/Room.ts
|
|
@ -1,182 +0,0 @@
|
|||
import EventTarget from "./EventTarget";
|
||||
import MWSE from "./index";
|
||||
import Peer from "./Peer";
|
||||
import { RoomInfo } from "./RoomInfo";
|
||||
|
||||
export interface IRoomOptions
|
||||
{
|
||||
name: string;
|
||||
description?:string;
|
||||
joinType: "free"|"invite"|"password"|"lock";
|
||||
credential?: string;
|
||||
ifexistsJoin?: boolean;
|
||||
accessType?: "public"|"private";
|
||||
notifyActionInvite?: boolean;
|
||||
notifyActionJoined?: boolean;
|
||||
notifyActionEjected?: boolean;
|
||||
autoFetchInfo?:boolean
|
||||
}
|
||||
|
||||
|
||||
export default class Room extends EventTarget
|
||||
{
|
||||
public mwse : MWSE;
|
||||
public options! : IRoomOptions;
|
||||
public config! : IRoomOptions;
|
||||
public roomId? : string;
|
||||
public accessType? : "public"|"private";
|
||||
public description? : string;
|
||||
public joinType? : "free"|"invite"|"password"|"lock";
|
||||
public name? : string;
|
||||
public owner? : string;
|
||||
public peers : Map<string,Peer> = new Map();
|
||||
public info : RoomInfo;
|
||||
|
||||
constructor(wsts:MWSE){
|
||||
super();
|
||||
this.mwse = wsts;
|
||||
this.info = new RoomInfo(this);
|
||||
}
|
||||
public setRoomOptions(options : IRoomOptions | string)
|
||||
{
|
||||
if(typeof options == "string")
|
||||
{
|
||||
this.roomId = options;
|
||||
}else{
|
||||
let defaultOptions = {
|
||||
joinType: "free",
|
||||
ifexistsJoin: true,
|
||||
accessType: "private",
|
||||
notifyActionInvite: true,
|
||||
notifyActionJoined: true,
|
||||
notifyActionEjected: true,
|
||||
autoFetchInfo: true
|
||||
};
|
||||
Object.assign(defaultOptions,options);
|
||||
this.config = defaultOptions as IRoomOptions;
|
||||
}
|
||||
}
|
||||
|
||||
setRoomId(uuid: string){
|
||||
this.roomId = uuid;
|
||||
}
|
||||
async createRoom(roomOptions : IRoomOptions){
|
||||
let config = this.config || roomOptions;
|
||||
let result = await this.mwse.EventPooling.request({
|
||||
type:'create-room',
|
||||
...config
|
||||
});
|
||||
if(result.status == 'fail')
|
||||
{
|
||||
if(result.message == "ALREADY-EXISTS" && this.config.ifexistsJoin)
|
||||
{
|
||||
return this.join();
|
||||
}
|
||||
throw new Error(result.message || result.messages);
|
||||
}else{
|
||||
this.options = {
|
||||
...this.config,
|
||||
...result.room
|
||||
};
|
||||
this.roomId = result.room.id;
|
||||
this.mwse.rooms.set(this.roomId as string, this);
|
||||
}
|
||||
}
|
||||
async join(){
|
||||
let result = await this.mwse.EventPooling.request({
|
||||
type:'joinroom',
|
||||
name: this.config.name,
|
||||
credential: this.config.credential,
|
||||
autoFetchInfo: this.config.autoFetchInfo || false
|
||||
});
|
||||
if(result.status == 'fail')
|
||||
{
|
||||
throw new Error(result.message);
|
||||
}else{
|
||||
this.options = {
|
||||
...this.config,
|
||||
...result.room
|
||||
};
|
||||
if(result.info)
|
||||
{
|
||||
this.info.info = result.info;
|
||||
};
|
||||
this.roomId = result.room.id;
|
||||
this.mwse.rooms.set(this.roomId as string, this);
|
||||
}
|
||||
}
|
||||
async eject(){
|
||||
let {type} = await this.mwse.EventPooling.request({
|
||||
type:'ejectroom',
|
||||
roomId: this.roomId
|
||||
});
|
||||
this.peers.clear();
|
||||
if(type == 'success')
|
||||
{
|
||||
this.mwse.rooms.delete(this.roomId as string);
|
||||
}
|
||||
}
|
||||
async send(pack: any, wom:boolean = false, handshake = false){
|
||||
if(!this.mwse.writable){
|
||||
return console.warn("Socket is not writable");
|
||||
}
|
||||
if(handshake)
|
||||
{
|
||||
let {type} = await this.mwse.EventPooling.request({
|
||||
type:'pack/room',
|
||||
pack,
|
||||
to: this.roomId,
|
||||
wom,
|
||||
handshake
|
||||
}) as {
|
||||
type:"success"|"fail"
|
||||
};
|
||||
if(type == "fail"){
|
||||
throw new Error("Cant send message to room")
|
||||
}
|
||||
}else{
|
||||
// WOM broadcast: fire-and-forget, no waiter (issue #33). When the caller
|
||||
// wants delivery confirmation it passes handshake=true (branch above),
|
||||
// which the engine answers with a {type} ack.
|
||||
this.mwse.EventPooling.only({
|
||||
type:'pack/room',
|
||||
pack,
|
||||
to: this.roomId,
|
||||
wom,
|
||||
handshake
|
||||
})
|
||||
}
|
||||
}
|
||||
async fetchPeers(filter?:{[key:string]:any}, onlyNumber:boolean = false) : Promise<Number | Peer[]>
|
||||
{
|
||||
if(onlyNumber)
|
||||
{
|
||||
let {count} = await this.mwse.EventPooling.request({
|
||||
type:'room/peer-count',
|
||||
roomId: this.roomId,
|
||||
filter: filter || {}
|
||||
}) as {count:Number};
|
||||
return count;
|
||||
}else{
|
||||
let {status, peers} = await this.mwse.EventPooling.request({
|
||||
type:'room-peers',
|
||||
roomId: this.roomId,
|
||||
filter: filter || {}
|
||||
}) as {status:"success"|"fail", peers: string[]};
|
||||
|
||||
let cup : Peer[] = [];
|
||||
|
||||
if(status == 'fail')
|
||||
{
|
||||
throw new Error("Cant using peers on room")
|
||||
}else if(status == 'success'){
|
||||
for (const peerid of peers) {
|
||||
let peer = this.mwse.peer(peerid,true);
|
||||
cup.push(peer);
|
||||
this.peers.set(peerid, peer);
|
||||
}
|
||||
};
|
||||
return cup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import Room from "./Room";
|
||||
|
||||
export class RoomInfo
|
||||
{
|
||||
public room : Room;
|
||||
public info : {[key:string]: any} = {};
|
||||
constructor(room : Room){
|
||||
this.room = room;
|
||||
this.room.on('updateinfo',(name:string,value:any) => {
|
||||
this.info[name] = value;
|
||||
})
|
||||
};
|
||||
public async fetch(name?:string)
|
||||
{
|
||||
if(name)
|
||||
{
|
||||
let rinfo = await this.room.mwse.EventPooling.request(({
|
||||
type: "room/getinfo",
|
||||
roomId: this.room.roomId,
|
||||
name
|
||||
}));
|
||||
if(rinfo.status == "success")
|
||||
{
|
||||
this.info = rinfo.value;
|
||||
}else console.warn(rinfo.message);
|
||||
}else{
|
||||
let rinfo = await this.room.mwse.EventPooling.request(({
|
||||
type: "room/info",
|
||||
roomId: this.room.roomId
|
||||
}));
|
||||
if(rinfo.status == "success")
|
||||
{
|
||||
this.info = rinfo.value;
|
||||
}else console.warn(rinfo.message);
|
||||
};
|
||||
return this.info;
|
||||
}
|
||||
public set(name: string, value: string | number)
|
||||
{
|
||||
this.info[name] = value;
|
||||
this.room.mwse.WSTSProtocol.SendOnly({
|
||||
type: "room/setinfo",
|
||||
roomId: this.room.roomId,
|
||||
name,
|
||||
value
|
||||
});
|
||||
}
|
||||
public get(name?:string)
|
||||
{
|
||||
return name ? this.info[name] : this.info;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import MWSE from "./index";
|
||||
|
||||
export interface Message {
|
||||
[key:string|number]:any;
|
||||
}
|
||||
export default class WSTSProtocol
|
||||
{
|
||||
public mwse : MWSE;
|
||||
constructor(wsts:MWSE){
|
||||
this.mwse = wsts;
|
||||
this.addListener();
|
||||
}
|
||||
public addListener()
|
||||
{
|
||||
this.mwse.server?.onRecaivePack((pack)=>{
|
||||
this.PackAnalyze(pack)
|
||||
})
|
||||
}
|
||||
public SendRaw(pack: Message)
|
||||
{
|
||||
this.mwse.server.tranferToServer(pack);
|
||||
}
|
||||
public SendOnly(pack: Message)
|
||||
{
|
||||
this.mwse.server.tranferToServer([pack,'R']);
|
||||
}
|
||||
public SendRequest(pack: Message, id: number)
|
||||
{
|
||||
this.mwse.server.tranferToServer([pack, id, 'R']);
|
||||
}
|
||||
public StartStream(pack: Message, id: number)
|
||||
{
|
||||
this.mwse.server.tranferToServer([pack, id, 'S']);
|
||||
}
|
||||
public PackAnalyze(data:any)
|
||||
{
|
||||
let [payload, id, action] = data;
|
||||
if(typeof id === 'number')
|
||||
{
|
||||
let callback = this.mwse.EventPooling.events.get(id);
|
||||
if(callback)
|
||||
{
|
||||
callback[0](payload, action);
|
||||
switch(action)
|
||||
{
|
||||
case 'E':{ // [E]ND flag
|
||||
this.mwse.EventPooling.events.delete(id);
|
||||
break;
|
||||
}
|
||||
case 'S': // [S]TREAM flag
|
||||
default:{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else console.warn("Missing event sended from server");
|
||||
}else{
|
||||
let signals = this.mwse.EventPooling.signals.get(id);
|
||||
if(signals)
|
||||
{
|
||||
for (const callback of signals) {
|
||||
callback(payload);
|
||||
}
|
||||
}else console.warn("Missing event sended from server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,522 +0,0 @@
|
|||
import P2PFileSender from "./P2PFileSender";
|
||||
import Peer from "./Peer";
|
||||
interface TransferStreamInfo
|
||||
{
|
||||
senders : RTCRtpSender[];
|
||||
recaivers : RTCRtpReceiver[];
|
||||
stream:MediaStream | undefined;
|
||||
id:string;
|
||||
name:string;
|
||||
}
|
||||
|
||||
export default class WebRTC
|
||||
{
|
||||
public static channels : Map<any,any> = new Map();
|
||||
public static requireGC : boolean = false;
|
||||
public id : any;
|
||||
public active : boolean = false;
|
||||
public connectionStatus : "closed" | "connected" | "connecting" | "disconnected" | "failed" | "new" = "new";
|
||||
public iceStatus : "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new" = "new";
|
||||
public gatheringStatus : "complete" | "gathering" | "new" = "new";
|
||||
public signalingStatus : "" | "closed" | "have-local-offer" | "have-local-pranswer" | "have-remote-offer" | "have-remote-pranswer" | "stable" = ""
|
||||
public rtc! : RTCPeerConnection;
|
||||
public recaivingStream : Map<string, TransferStreamInfo> = new Map();
|
||||
public sendingStream : Map<string, TransferStreamInfo> = new Map();
|
||||
public events : { [eventname:string]: Function[] } = {};
|
||||
public channel : RTCDataChannel | undefined;
|
||||
|
||||
public static defaultRTCConfig : RTCConfiguration = {
|
||||
iceCandidatePoolSize: 0,
|
||||
iceTransportPolicy:"all",
|
||||
rtcpMuxPolicy:"require",
|
||||
};
|
||||
|
||||
private isPolite() : boolean
|
||||
{
|
||||
let myId = this.peer?.mwse.peer('me').socketId as string;
|
||||
let peerId = this.peer?.socketId as string;
|
||||
return myId < peerId;
|
||||
}
|
||||
|
||||
public static defaultICEServers : RTCIceServer[] = [{
|
||||
urls: "stun:stun.l.google.com:19302"
|
||||
},{
|
||||
urls: "stun:stun1.l.google.com:19302"
|
||||
},{
|
||||
urls: "stun:stun2.l.google.com:19302"
|
||||
},{
|
||||
urls: "stun:stun3.l.google.com:19302"
|
||||
},{
|
||||
urls: "stun:stun4.l.google.com:19302"
|
||||
}];
|
||||
|
||||
public peer? : Peer;
|
||||
|
||||
public FileTransportChannel? : P2PFileSender;
|
||||
|
||||
public makingOffer = false;
|
||||
public ignoreOffer = false;
|
||||
public isSettingRemoteAnswerPending = false;
|
||||
|
||||
candicatePack : RTCIceCandidate[] = [];
|
||||
|
||||
|
||||
constructor(
|
||||
rtcConfig?: RTCConfiguration,
|
||||
rtcServers?: RTCIceServer[]
|
||||
)
|
||||
{
|
||||
let config : any = {};
|
||||
|
||||
if(rtcConfig)
|
||||
{
|
||||
Object.assign(
|
||||
config,
|
||||
WebRTC.defaultRTCConfig,
|
||||
rtcConfig
|
||||
)
|
||||
}else{
|
||||
Object.assign(
|
||||
config,
|
||||
WebRTC.defaultRTCConfig
|
||||
)
|
||||
}
|
||||
|
||||
config.iceServers = rtcServers || WebRTC.defaultICEServers;
|
||||
|
||||
this.rtc = new RTCPeerConnection(config as RTCConfiguration);
|
||||
this.rtc.addEventListener("connectionstatechange",()=>{
|
||||
this.eventConnectionState();
|
||||
})
|
||||
this.rtc.addEventListener("icecandidate",(...args)=>{
|
||||
this.eventIcecandidate(...args);
|
||||
})
|
||||
this.rtc.addEventListener("iceconnectionstatechange",()=>{
|
||||
this.eventICEConnectionState();
|
||||
})
|
||||
this.rtc.addEventListener("icegatheringstatechange",()=>{
|
||||
this.eventICEGatherinState();
|
||||
})
|
||||
this.rtc.addEventListener("negotiationneeded",()=>{
|
||||
this.eventNogationNeeded();
|
||||
})
|
||||
this.rtc.addEventListener("signalingstatechange",()=>{
|
||||
this.eventSignalingState();
|
||||
})
|
||||
this.rtc.addEventListener("track",(...args)=>{
|
||||
this.eventTrack(...args);
|
||||
})
|
||||
this.rtc.addEventListener("datachannel",(...args)=>{
|
||||
this.eventDatachannel(...args);
|
||||
})
|
||||
this.on('input',async (data:{[key:string]:any})=>{
|
||||
switch(data.type)
|
||||
{
|
||||
case "icecandidate":{
|
||||
try{
|
||||
if(this.rtc.remoteDescription){
|
||||
await this.rtc.addIceCandidate(new RTCIceCandidate(data.value));
|
||||
}else{
|
||||
this.candicatePack.push(new RTCIceCandidate(data.value))
|
||||
}
|
||||
}catch(error){
|
||||
debugger;
|
||||
}finally{
|
||||
console.log("ICE Canbet")
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "offer":{
|
||||
let readyForOffer = !this.makingOffer && (this.rtc.signalingState == "stable" || this.isSettingRemoteAnswerPending);
|
||||
|
||||
const offerCollision = !readyForOffer;
|
||||
|
||||
this.ignoreOffer = !this.isPolite() && offerCollision;
|
||||
|
||||
if(this.ignoreOffer){
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSettingRemoteAnswerPending = false;
|
||||
|
||||
await this.rtc.setRemoteDescription(new RTCSessionDescription(data.value));
|
||||
|
||||
this.isSettingRemoteAnswerPending = false;
|
||||
|
||||
for (const candidate of this.candicatePack) {
|
||||
await this.rtc.addIceCandidate(candidate);
|
||||
}
|
||||
|
||||
let answer = await this.rtc.createAnswer({
|
||||
offerToReceiveAudio: true,
|
||||
offerToReceiveVideo: true
|
||||
})
|
||||
await this.rtc.setLocalDescription(answer);
|
||||
this.send({
|
||||
type: 'answer',
|
||||
value: answer
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "answer":{
|
||||
await this.rtc.setRemoteDescription(new RTCSessionDescription(data.value))
|
||||
|
||||
for (const candidate of this.candicatePack) {
|
||||
await this.rtc.addIceCandidate(candidate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "streamInfo":{
|
||||
let {id,value} = data;
|
||||
let streamInfo = this.recaivingStream.get(id);
|
||||
if(!streamInfo)
|
||||
{
|
||||
this.recaivingStream.set(id,value as TransferStreamInfo);
|
||||
}else{
|
||||
this.recaivingStream.set(id,{
|
||||
...streamInfo,
|
||||
...value
|
||||
} as TransferStreamInfo);
|
||||
}
|
||||
this.send({
|
||||
type:'streamAccept',
|
||||
id
|
||||
})
|
||||
break;
|
||||
}
|
||||
case "streamRemoved":{
|
||||
let {id} = data;
|
||||
this.emit('stream:stopped', this.recaivingStream.get(id));
|
||||
this.recaivingStream.delete(id);
|
||||
break;
|
||||
}
|
||||
case "streamAccept":{
|
||||
let {id} = data;
|
||||
let sendingStream = this.sendingStream.get(id) as TransferStreamInfo;
|
||||
let senders = [];
|
||||
if(sendingStream && sendingStream.stream)
|
||||
{
|
||||
for (const track of sendingStream.stream.getTracks()) {
|
||||
senders.push(this.rtc.addTrack(track, sendingStream.stream));
|
||||
};
|
||||
sendingStream.senders = senders;
|
||||
}
|
||||
this.emit('stream:accepted', sendingStream);
|
||||
break;
|
||||
}
|
||||
case "message":{
|
||||
this.emit('message', data.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
public addEventListener(event:string,callback: Function){
|
||||
(this.events[event] || (this.events[event]=[])).push(callback);
|
||||
};
|
||||
public on(event:string,callback: Function){
|
||||
this.addEventListener(event, callback)
|
||||
};
|
||||
public async dispatch(event:string,...args:any[]) : Promise<any> {
|
||||
if(this.events[event])
|
||||
{
|
||||
for (const callback of this.events[event])
|
||||
{
|
||||
await callback(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
public async emit(event:string,...args:any[]) : Promise<any> {
|
||||
await this.dispatch(event, ...args)
|
||||
}
|
||||
public connect()
|
||||
{
|
||||
if(!this.channel)
|
||||
{
|
||||
this.createDefaultDataChannel();
|
||||
}
|
||||
}
|
||||
public sendMessage(data: any)
|
||||
{
|
||||
if(data.type == ':rtcpack:')
|
||||
{
|
||||
throw "WebRTC Kanalında Sızma";
|
||||
}
|
||||
this.send({
|
||||
type: 'message',
|
||||
payload: data
|
||||
});
|
||||
}
|
||||
public createDefaultDataChannel()
|
||||
{
|
||||
let dt = this.rtc.createDataChannel(':default:',{
|
||||
ordered: true
|
||||
});
|
||||
dt.addEventListener("open",()=>{
|
||||
this.channel = dt;
|
||||
WebRTC.channels.set(this.id, this);
|
||||
this.active = true;
|
||||
});
|
||||
dt.addEventListener("message",({data})=>{
|
||||
let pack = JSON.parse(data);
|
||||
this.emit('input', pack);
|
||||
})
|
||||
dt.addEventListener("close",()=>{
|
||||
this.channel = undefined;
|
||||
this.active = false;
|
||||
})
|
||||
}
|
||||
public destroy()
|
||||
{
|
||||
this.active = false;
|
||||
if(this.channel)
|
||||
{
|
||||
this.channel.close();
|
||||
this.channel = undefined;
|
||||
}
|
||||
if(this.rtc)
|
||||
{
|
||||
this.rtc.close();
|
||||
//this.rtc = undefined;
|
||||
};
|
||||
this.emit('disconnected');
|
||||
WebRTC.channels.delete(this.id);
|
||||
}
|
||||
public eventDatachannel(event: RTCDataChannelEvent)
|
||||
{
|
||||
if(event.channel.label == ':default:'){
|
||||
WebRTC.channels.set(this.id, this);
|
||||
this.channel = event.channel;
|
||||
this.active = true;
|
||||
event.channel.addEventListener("message",({data})=>{
|
||||
let pack = JSON.parse(data);
|
||||
this.emit('input', pack);
|
||||
})
|
||||
event.channel.addEventListener("close",()=>{
|
||||
this.channel = undefined;
|
||||
WebRTC.channels.delete(this.id);
|
||||
WebRTC.requireGC = true;
|
||||
this.active = false;
|
||||
})
|
||||
}else{
|
||||
this.emit('datachannel', event.channel);
|
||||
}
|
||||
}
|
||||
public send(data:object)
|
||||
{
|
||||
if(this.channel?.readyState == "open")
|
||||
{
|
||||
this.channel.send(JSON.stringify(data));
|
||||
}else{
|
||||
this.emit('output', data);
|
||||
}
|
||||
}
|
||||
public eventConnectionState()
|
||||
{
|
||||
this.connectionStatus = this.rtc.connectionState;
|
||||
if(this.connectionStatus == 'connected')
|
||||
{
|
||||
if(this.active == false)
|
||||
{
|
||||
this.emit('connected');
|
||||
}
|
||||
};
|
||||
|
||||
if(this.connectionStatus == 'failed')
|
||||
{
|
||||
this.rtc.restartIce();
|
||||
};
|
||||
|
||||
if(this.connectionStatus == "closed")
|
||||
{
|
||||
if(this.active)
|
||||
{
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
public eventIcecandidate(event: RTCPeerConnectionIceEvent)
|
||||
{
|
||||
if(event.candidate)
|
||||
{
|
||||
this.send({
|
||||
type:'icecandidate',
|
||||
value: event.candidate
|
||||
})
|
||||
}
|
||||
}
|
||||
public eventICEConnectionState()
|
||||
{
|
||||
this.iceStatus = this.rtc.iceConnectionState;
|
||||
}
|
||||
public eventICEGatherinState()
|
||||
{
|
||||
this.gatheringStatus = this.rtc.iceGatheringState;
|
||||
}
|
||||
public async eventNogationNeeded()
|
||||
{
|
||||
try{
|
||||
this.makingOffer = true;
|
||||
let offer = await this.rtc.createOffer({
|
||||
iceRestart: true,
|
||||
offerToReceiveAudio: true,
|
||||
offerToReceiveVideo: true
|
||||
});
|
||||
await this.rtc.setLocalDescription(offer);
|
||||
this.send({
|
||||
type: 'offer',
|
||||
value: offer
|
||||
});
|
||||
}catch(error){
|
||||
console.error(`Nogation Error:`, error)
|
||||
}
|
||||
finally{
|
||||
this.makingOffer = false;
|
||||
}
|
||||
}
|
||||
public eventSignalingState()
|
||||
{
|
||||
this.signalingStatus = this.rtc.signalingState;
|
||||
}
|
||||
public eventTrack(event: RTCTrackEvent)
|
||||
{
|
||||
let rtpRecaiver = event.receiver;
|
||||
if(event.streams.length)
|
||||
{
|
||||
for (const stream of event.streams) {
|
||||
let streamInfo = this.recaivingStream.get(stream.id) as TransferStreamInfo;
|
||||
(streamInfo.recaivers || (streamInfo.recaivers = [])).push(rtpRecaiver);
|
||||
if((this.recaivingStream.get(stream.id) as {stream : MediaStream | undefined}).stream == null)
|
||||
{
|
||||
streamInfo.stream = stream;
|
||||
this.emit('stream:added', this.recaivingStream.get(stream.id));
|
||||
}else{
|
||||
streamInfo.stream = stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public sendStream(stream:MediaStream,name:string,info:{[key:string]:any}){
|
||||
this.send({
|
||||
type: 'streamInfo',
|
||||
id: stream.id,
|
||||
value: {
|
||||
...info,
|
||||
name: name
|
||||
}
|
||||
});
|
||||
this.sendingStream.set(stream.id,{
|
||||
...info,
|
||||
id:stream.id,
|
||||
name: name,
|
||||
stream
|
||||
} as TransferStreamInfo);
|
||||
};
|
||||
public stopStream(_stream:MediaStream){
|
||||
if(this.connectionStatus != 'connected'){
|
||||
return
|
||||
}
|
||||
if(this.sendingStream.has(_stream.id))
|
||||
{
|
||||
let {stream} = this.sendingStream.get(_stream.id) as {stream:MediaStream};
|
||||
|
||||
for (const track of stream.getTracks()) {
|
||||
for (const RTCPSender of this.rtc.getSenders()) {
|
||||
if(RTCPSender.track?.id == track.id)
|
||||
{
|
||||
this.rtc.removeTrack(RTCPSender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.send({
|
||||
type: 'streamRemoved',
|
||||
id: stream.id
|
||||
});
|
||||
this.sendingStream.delete(_stream.id)
|
||||
}
|
||||
}
|
||||
public stopAllStreams()
|
||||
{
|
||||
if(this.connectionStatus != 'connected'){
|
||||
return
|
||||
}
|
||||
for (const [, {stream}] of this.sendingStream) {
|
||||
if(stream == undefined)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const track of stream.getTracks()) {
|
||||
for (const RTCPSender of this.rtc.getSenders()) {
|
||||
if(RTCPSender.track?.id == track.id)
|
||||
{
|
||||
this.rtc.removeTrack(RTCPSender);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.send({
|
||||
type: 'streamRemoved',
|
||||
id: stream.id
|
||||
});
|
||||
};
|
||||
|
||||
this.sendingStream.clear();
|
||||
}
|
||||
public async SendFile(file:File, meta: object)
|
||||
{
|
||||
if(!this.peer)
|
||||
{
|
||||
throw new Error("Peer is not ready");
|
||||
}
|
||||
this.FileTransportChannel = new P2PFileSender(this, this.peer);
|
||||
|
||||
await this.FileTransportChannel.SendFile(file, meta);
|
||||
}
|
||||
public async RecaiveFile(
|
||||
chnlCount:number,
|
||||
filemeta: {
|
||||
name: string;
|
||||
type: string;
|
||||
},
|
||||
totalSize: number
|
||||
) : Promise<File>
|
||||
{
|
||||
if(!this.peer)
|
||||
{
|
||||
throw new Error("Peer is not ready");
|
||||
}
|
||||
this.FileTransportChannel = new P2PFileSender(this, this.peer);
|
||||
|
||||
return await new Promise(recaivedFile => {
|
||||
if(this.FileTransportChannel)
|
||||
{
|
||||
this.FileTransportChannel.RecaiveFile(
|
||||
this.rtc,
|
||||
filemeta,
|
||||
chnlCount,
|
||||
totalSize,
|
||||
(file: File) => {
|
||||
recaivedFile(file)
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
WebRTC.requireGC = false;
|
||||
setInterval(()=>{
|
||||
if(WebRTC.requireGC == false) return;
|
||||
let img = document.createElement("img");
|
||||
img.src = window.URL.createObjectURL(new Blob([new ArrayBuffer(5e+7)]));
|
||||
img.onerror = function() {
|
||||
window.URL.revokeObjectURL(this.src);
|
||||
};
|
||||
WebRTC.requireGC = false;
|
||||
}, 3000);
|
||||
|
||||
declare global {
|
||||
interface MediaStream {
|
||||
senders : RTCRtpSender[];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
import {Connection,IConnection} from "./Connection";
|
||||
import EventPool from "./EventPool";
|
||||
import EventTarget from "./EventTarget";
|
||||
import { IPPressure } from "./IPPressure";
|
||||
import Peer from "./Peer";
|
||||
import Room, { IRoomOptions } from "./Room";
|
||||
import WSTSProtocol, { Message } from "./WSTSProtocol";
|
||||
import WebRTC from "./WebRTC";
|
||||
//import {Gzip} from "fflate";
|
||||
export default class MWSE extends EventTarget {
|
||||
public static rtc : WebRTC;
|
||||
public server! : Connection;
|
||||
public WSTSProtocol! : WSTSProtocol;
|
||||
public EventPooling! : EventPool;
|
||||
public rooms : Map<string, Room> = new Map();
|
||||
public pairs : Map<string, Peer> = new Map();
|
||||
public peers : Map<string, Peer> = new Map();
|
||||
public virtualPressure : IPPressure;
|
||||
public me! : Peer;
|
||||
/*public static compress(message:string, callback:(e:any) => any)
|
||||
{
|
||||
let u : any= [];
|
||||
let C = new Gzip({
|
||||
level: 9,
|
||||
mem: 12
|
||||
},(stream,isLast) => {
|
||||
u.push(stream);
|
||||
if(isLast)
|
||||
{
|
||||
callback(u);
|
||||
}
|
||||
});
|
||||
C.push(new TextEncoder().encode(message), true);
|
||||
}*/
|
||||
constructor(options: IConnection){
|
||||
super();
|
||||
MWSE.rtc = MWSE as unknown as WebRTC;
|
||||
this.server = new Connection(this,options);
|
||||
this.WSTSProtocol = new WSTSProtocol(this);
|
||||
this.EventPooling = new EventPool(this);
|
||||
this.virtualPressure = new IPPressure(this);
|
||||
this.server.connect();
|
||||
this.me = new Peer(this);
|
||||
this.me.scope(()=>{
|
||||
this.peers.set('me', this.me);
|
||||
this.peers.set(this.me.socketId as string, this.me);
|
||||
})
|
||||
this.server.onActive(async ()=>{
|
||||
this.me.setSocketId('me');
|
||||
await this.me.metadata();
|
||||
this.emit('scope');
|
||||
this.activeScope = true;
|
||||
});
|
||||
this.server.onPassive(async ()=>{
|
||||
this.emit('close');
|
||||
});
|
||||
this.packMessagingSystem();
|
||||
}
|
||||
|
||||
public writable = 1;
|
||||
public readable = 1;
|
||||
|
||||
public destroy()
|
||||
{
|
||||
this.server.disconnect();
|
||||
}
|
||||
|
||||
public enableRecaiveData(){
|
||||
this.WSTSProtocol.SendOnly({ type: 'connection/packrecaive', value: 1 })
|
||||
this.readable = 1
|
||||
}
|
||||
public disableRecaiveData(){
|
||||
this.WSTSProtocol.SendOnly({ type: 'connection/packrecaive', value: 0 })
|
||||
this.readable = 0
|
||||
}
|
||||
|
||||
public enableSendData(){
|
||||
this.WSTSProtocol.SendOnly({ type: 'connection/packsending', value: 1 })
|
||||
this.writable = 1
|
||||
}
|
||||
public disableSendData(){
|
||||
this.WSTSProtocol.SendOnly({ type: 'connection/packsending', value: 0 })
|
||||
this.writable = 0
|
||||
}
|
||||
|
||||
public enableNotifyRoomInfo(){
|
||||
this.WSTSProtocol.SendOnly({ type: 'connection/roominfo', value: 1 })
|
||||
}
|
||||
public disableNotifyRoomInfo(){
|
||||
this.WSTSProtocol.SendOnly({ type: 'connection/roominfo', value: 0 })
|
||||
}
|
||||
|
||||
public async request(peerId: string, pack:Message)
|
||||
{
|
||||
let {pack:answer} = await this.EventPooling.request({
|
||||
type: 'request/to',
|
||||
to: peerId,
|
||||
pack
|
||||
});
|
||||
return answer;
|
||||
}
|
||||
public async response(peerId: string, requestId:number, pack:Message)
|
||||
{
|
||||
this.WSTSProtocol.SendOnly({
|
||||
type: 'response/to',
|
||||
to: peerId,
|
||||
pack,
|
||||
id: requestId
|
||||
})
|
||||
}
|
||||
private packMessagingSystem()
|
||||
{
|
||||
this.EventPooling.signal('pack',(payload : {from:string,pack:any}) => {
|
||||
if(this.readable)
|
||||
{
|
||||
let {from,pack} = payload;
|
||||
this.peer(from, true).emit('pack', pack);
|
||||
}
|
||||
})
|
||||
this.EventPooling.signal('request',(payload : {from:string,pack:any,id:number}) => {
|
||||
let {from,pack, id} = payload;
|
||||
let scope = {
|
||||
body: pack,
|
||||
response: (pack: Message) => {
|
||||
this.response(from, id, pack);
|
||||
},
|
||||
peer: this.peer(from, true)
|
||||
};
|
||||
this.peer(from, true).emit('request', scope);
|
||||
this.peer('me').emit('request', scope);
|
||||
})
|
||||
this.EventPooling.signal('pack/room',(payload : {from:string,pack:any,sender:string}) => {
|
||||
if(this.readable)
|
||||
{
|
||||
let {from,pack,sender} = payload;
|
||||
this.room(from).emit('message', pack, this.peer(sender));
|
||||
}
|
||||
})
|
||||
this.EventPooling.signal('room/joined',(payload : {id:string,roomid:any,ownerid:string}) => {
|
||||
let {id,roomid} = payload;
|
||||
let room = this.room(roomid);
|
||||
let peer = this.peer(id, true);
|
||||
room.peers.set(peer.socketId as string, peer);
|
||||
room.emit('join', peer);
|
||||
})
|
||||
this.EventPooling.signal('room/info',(payload : {roomId:string,value:any,name:string}) => {
|
||||
let {roomId,name,value} = payload;
|
||||
this.room(roomId).emit('updateinfo', name,value);
|
||||
})
|
||||
this.EventPooling.signal('room/ejected',(payload : {id:string,roomid:any,ownerid:string}) => {
|
||||
let {id,roomid} = payload;
|
||||
let room = this.room(roomid);
|
||||
let peer = this.peer(id, true);
|
||||
room.peers.delete(peer.socketId as string);
|
||||
room.emit('eject', peer);
|
||||
})
|
||||
this.EventPooling.signal('room/closed',(payload : {roomid:any}) => {
|
||||
let {roomid} = payload;
|
||||
let room = this.room(roomid);
|
||||
room.peers.clear();
|
||||
room.emit('close');
|
||||
this.rooms.delete(roomid);
|
||||
})
|
||||
this.EventPooling.signal("pair/info", (payload : {from : string,name: string, value: string | number | boolean}) => {
|
||||
let {from, name, value} = payload;
|
||||
let peer = this.peer(from, true);
|
||||
peer.info.info[name] = value;
|
||||
peer.emit("info", name, value);
|
||||
})
|
||||
this.EventPooling.signal("request/pair", (payload : {from : string,info: any}) => {
|
||||
let {from, info} = payload;
|
||||
let peer = this.peer(from, true);
|
||||
peer.info.info = info;
|
||||
peer.emit("request/pair", peer);
|
||||
this.peer('me').emit('request/pair', peer);
|
||||
})
|
||||
this.EventPooling.signal("peer/disconnect", (payload : {id : string}) => {
|
||||
let {id} = payload;
|
||||
let peer = this.peer(id, true);
|
||||
peer.emit("disconnect", peer);
|
||||
})
|
||||
this.EventPooling.signal("accepted/pair", (payload : {from : string,info: any}) => {
|
||||
let {from, info} = payload;
|
||||
let peer = this.peer(from, true);
|
||||
peer.info.info = info;
|
||||
peer.emit("accepted/pair", peer);
|
||||
this.peer('me').emit('accepted/pair', 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);
|
||||
})
|
||||
}
|
||||
public room(options: IRoomOptions | string) : Room
|
||||
{
|
||||
if(typeof options == "string")
|
||||
{
|
||||
if(this.rooms.has(options))
|
||||
{
|
||||
return this.rooms.get(options) as Room
|
||||
}
|
||||
}
|
||||
let room = new Room(this);
|
||||
room.setRoomOptions(options);
|
||||
this.emit('room');
|
||||
return room;
|
||||
}
|
||||
public peer(options: string | IRoomOptions, isActive = false) : Peer
|
||||
{
|
||||
if(typeof options == "string")
|
||||
{
|
||||
if(this.peers.has(options))
|
||||
{
|
||||
return this.peers.get(options) as Peer
|
||||
}
|
||||
if(this.pairs.has(options))
|
||||
{
|
||||
return this.pairs.get(options) as Peer
|
||||
}
|
||||
}
|
||||
let peer = new Peer(this);
|
||||
peer.setPeerOptions(options);
|
||||
peer.active = isActive;
|
||||
this.peers.set(peer.socketId as string, peer);
|
||||
this.emit('peer', peer);
|
||||
return peer;
|
||||
}
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
MWSE: any;
|
||||
}
|
||||
}
|
||||
|
||||
window.MWSE = MWSE;
|
||||
14
index.js
14
index.js
|
|
@ -1,14 +0,0 @@
|
|||
require("./Source/index");
|
||||
|
||||
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)
|
||||
});
|
||||
|
|
@ -14,9 +14,8 @@ type Config struct {
|
|||
Host string // bind address, e.g. "0.0.0.0"
|
||||
Port int // listen port, default 7707
|
||||
|
||||
PublicDir string // static assets directory (default "./public")
|
||||
ScriptDir string // legacy compiled SDK bundle directory (default "./script")
|
||||
SDKDir string // ES-module SDK directory (default "./sdk")
|
||||
PublicDir string // static assets served at /<file> (default "./public")
|
||||
SDKDir string // ES-module SDK files served at /sdk/ (default "./sdk")
|
||||
|
||||
ReadHeaderTimeout time.Duration // HTTP read-header timeout
|
||||
ShutdownTimeout time.Duration // grace period for in-flight work on shutdown
|
||||
|
|
@ -55,17 +54,15 @@ type ConnConfig struct {
|
|||
WriteWait time.Duration // deadline for a single socket write
|
||||
}
|
||||
|
||||
// Load reads configuration from the environment, applying defaults that match the
|
||||
// original server. Recognised variables:
|
||||
// Load reads configuration from the environment. Recognised variables:
|
||||
//
|
||||
// MWSE_HOST, MWSE_PORT, MWSE_PUBLIC_DIR, MWSE_SCRIPT_DIR,
|
||||
// MWSE_HOST, MWSE_PORT, MWSE_PUBLIC_DIR, MWSE_SDK_DIR,
|
||||
// MWSE_SHUTDOWN_TIMEOUT (seconds), MWSE_TERM_OUTPUT (1/true)
|
||||
func Load() Config {
|
||||
return Config{
|
||||
Host: env("MWSE_HOST", "0.0.0.0"),
|
||||
Port: envInt("MWSE_PORT", 7707),
|
||||
PublicDir: env("MWSE_PUBLIC_DIR", "./public"),
|
||||
ScriptDir: env("MWSE_SCRIPT_DIR", "./script"),
|
||||
SDKDir: env("MWSE_SDK_DIR", "./sdk"),
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
ShutdownTimeout: time.Duration(envInt("MWSE_SHUTDOWN_TIMEOUT", 10)) * time.Second,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func testEngine(t *testing.T) string {
|
|||
|
||||
cfg := config.Load()
|
||||
cfg.Conn.PingInterval = 80 * time.Millisecond
|
||||
cfg.ScriptDir = t.TempDir() // static routes are irrelevant here
|
||||
cfg.SDKDir = t.TempDir() // static routes are irrelevant here
|
||||
cfg.PublicDir = t.TempDir()
|
||||
|
||||
srv := httptest.NewServer(New(hub, cfg).Handler)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Package httpserver assembles the HTTP surface of the engine: the WebSocket
|
||||
// upgrade endpoint, the static asset routes (the built SDK and the public files),
|
||||
// and the /api control plane. It mirrors the routing of the original
|
||||
// HTTPServer.js while adding timeouts and graceful shutdown (#25).
|
||||
// upgrade endpoint, the ES-module SDK routes, the public asset directory, and
|
||||
// the /api control plane.
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
|
|
@ -27,10 +26,8 @@ type ServerOptions struct {
|
|||
Approver ws.Approver
|
||||
}
|
||||
|
||||
// New builds the *http.Server. WebSocket upgrades are detected on ANY path and
|
||||
// routed to the engine (the SDK derives its endpoint from wherever the script was
|
||||
// served, so the upgrade may arrive at "/" or "/script/"). All other requests go
|
||||
// through the static/API mux.
|
||||
// New builds the *http.Server. WebSocket upgrades are detected on any path and
|
||||
// routed to the engine; all other requests go through the static/API mux.
|
||||
func New(hub *ws.Hub, cfg config.Config, srvOpts ...ServerOptions) *http.Server {
|
||||
var so ServerOptions
|
||||
if len(srvOpts) > 0 {
|
||||
|
|
@ -68,32 +65,24 @@ func New(hub *ws.Hub, cfg config.Config, srvOpts ...ServerOptions) *http.Server
|
|||
|
||||
// registerStatic wires the asset routes:
|
||||
//
|
||||
// - /sdk.js -> redirect to /sdk/index.js (so import.meta.url resolves correctly)
|
||||
// - /sdk/ -> ES-module SDK files (sdk/EventTarget.js, etc.)
|
||||
// - /script -> legacy compiled SDK entry (script/index.js)
|
||||
// - /script/<file> -> files under the script directory
|
||||
// - / -> the SDK entry (so a bare visit returns the script)
|
||||
// - /<file> -> a matching file under the public directory
|
||||
// - anything else -> the status document (status.xml)
|
||||
// - /sdk.js -> 301 /sdk/index.js (import.meta.url resolves correctly for relative imports)
|
||||
// - /sdk/ -> ES-module SDK files served from cfg.SDKDir
|
||||
// - / -> /sdk/index.js redirect (bare URL returns the SDK entry)
|
||||
// - /<file> -> matching file under cfg.PublicDir
|
||||
// - anything -> public/status.xml fallback
|
||||
func registerStatic(mux *http.ServeMux, cfg config.Config) {
|
||||
scriptIndex := filepath.Join(cfg.ScriptDir, "index.js")
|
||||
statusDoc := filepath.Join(cfg.ScriptDir, "status.xml")
|
||||
statusDoc := filepath.Join(cfg.PublicDir, "status.xml")
|
||||
|
||||
// ES-module SDK: redirect /sdk.js → /sdk/index.js so that import.meta.url
|
||||
// resolves to /sdk/index.js and all relative imports go to /sdk/*.
|
||||
// /sdk.js → /sdk/index.js: keeps import.meta.url = /sdk/index.js so that
|
||||
// ./EventTarget.js etc. resolve to /sdk/EventTarget.js (same origin).
|
||||
mux.HandleFunc("/sdk.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/sdk/index.js", http.StatusMovedPermanently)
|
||||
})
|
||||
mux.Handle("/sdk/", http.StripPrefix("/sdk/", http.FileServer(http.Dir(cfg.SDKDir))))
|
||||
|
||||
mux.HandleFunc("/script", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, scriptIndex)
|
||||
})
|
||||
mux.Handle("/script/", http.StripPrefix("/script/", http.FileServer(http.Dir(cfg.ScriptDir))))
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
http.ServeFile(w, r, scriptIndex)
|
||||
http.Redirect(w, r, "/sdk/index.js", http.StatusFound)
|
||||
return
|
||||
}
|
||||
if f, ok := safePublicFile(cfg.PublicDir, r.URL.Path); ok {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
61
package.json
61
package.json
|
|
@ -1,61 +0,0 @@
|
|||
{
|
||||
"name": "mwse",
|
||||
"version": "0.1.0",
|
||||
"description": "Mikro WebSocket Engine",
|
||||
"scripts": {
|
||||
"compile": "parcel watch --no-hmr",
|
||||
"build": "parcel build --no-optimize"
|
||||
},
|
||||
"source": "./frontend/index.ts",
|
||||
"targets": {
|
||||
"default": {
|
||||
"distDir": "./script/",
|
||||
"publicUrl": "./",
|
||||
"sourceMap": true,
|
||||
"outputFormat": "global",
|
||||
"optimize": true,
|
||||
"context": "browser",
|
||||
"engines": {
|
||||
"chrome": "65",
|
||||
"android": "4.4.3",
|
||||
"edge": "16",
|
||||
"firefox": "59",
|
||||
"ie": "10",
|
||||
"ios": "10",
|
||||
"safari": "10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://git.saqut.com/saqut/MWSE"
|
||||
},
|
||||
"keywords": [
|
||||
"WebSocket",
|
||||
"server",
|
||||
"microservice",
|
||||
"ws"
|
||||
],
|
||||
"author": "Abdussamed ULUTAŞ <abdussamedulutas@yandex.com.tr>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"fflate": "^0.8.1",
|
||||
"joi": "^17.11.0",
|
||||
"knex": "^3.0.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"systemjs": "^6.14.2",
|
||||
"terminal": "^0.1.4",
|
||||
"terminal-kit": "^3.0.0",
|
||||
"typescript": "^5.2.2",
|
||||
"webrtc-adapter": "^8.2.3",
|
||||
"websocket": "^1.0.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/packager-ts": "^2.7.0",
|
||||
"@parcel/transformer-typescript-types": "^2.7.0",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
}
|
||||
1672
script/index.js
1672
script/index.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
107
tsconfig.json
107
tsconfig.json
|
|
@ -1,107 +0,0 @@
|
|||
{
|
||||
"include": [
|
||||
"./frontend/**/*.ts",
|
||||
"./frontend/*.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"lib": ["DOM","ES6","ES2015.Promise"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "ES2015", /* Specify what module code is generated. */
|
||||
"rootDir": "./frontend", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./script", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./script", /* Specify an output folder for all emitted files. */
|
||||
"removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
"importHelpers": false, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
"importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
//"inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
//"inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
"declarationDir": "./script", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
"strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
"strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
"noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
"noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
"allowUnusedLabels": false, /* Disable error reporting for unused labels. */
|
||||
"allowUnreachableCode": false, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue