Files
Unify/framework/server/socketClient.js

1065 lines
20 KiB
JavaScript
Raw Permalink Normal View History

2025-12-25 11:16:59 +01:00
import fs from 'fs-extra';
import http from 'http';
//import validator from 'validator';
import os from 'os';
import util from 'util';
import deepclone from '../unify/clonedeep.js';
import Console from '../server/console.js';
import socketMessage from '../unify/socketMessage.js';
import tools from '../unify/tools.js';
import shared from '../unify/shared.js';
import collection from '../unify/collection.js';
import Error from '../unify/error.js';
import applications from '../configs/applications.js';
import consoleColors from '../unify/consoleColors.js';
import { RateLimiterMemory, BurstyRateLimiter } from '../node_modules/node-rate-limiter-flexible/index.js';
//console.log( RateLimiterMemory, BurstyRateLimiter );
global.objects = new Array();
// Controllers
import tableController from './cachedControllers/tableController.js';
import columnController from './cachedControllers/columnController.js';
import settingsController from './cachedControllers/settingsController.js';
import objectController from './cachedControllers/objectController.js';
var controllers = new Object();
controllers.table = new tableController();
controllers.column = new columnController();
controllers.settings = new settingsController();
controllers.object = new objectController();
export default class socketClient{
socket;
userID = 0;
applicationID = 0;
application;
type = "socket";
destroyTime = 10000;
constructor() {
this.initializeLimiter();
}
createInitiationMessage() {
this.sessionKey = tools.createRandomKey( 40 );
var message = new socketMessage();
message.id = 0;
message.data = "connected";
message.type = "promise";
message.sessionKey = this.sessionKey;
return message;
}
bindObjects( cacheObject, cacheClient ) {
this.application = cacheObject.application;
this.objects = cacheClient.objects;
this.classObjects = cacheClient.classObjects;
this.application.client = this;
}
async createSocket( socket ) {
var cacheObject = global.applicationCache[ this.userID ];
if( !cacheObject ) {
console.log("No cache available, Waiting for cache", this.userID);
await global.cachePromise[ this.userID ];
console.log("Cache created, proceed with code.");
var cacheObject = global.applicationCache[ this.userID ];
this.socketManager.createCache( this.socketManager.cacheSize )
}
console.log("cache exists", typeof global.applicationCache[ this.userID ]);
var cacheClient = cacheObject.client;
this.bindObjects( cacheObject, cacheClient );
//if( this.socket.constructor.name == "WebSocket" ) {
//this.createEventListeners();
//}
//var message = this.createInitiationMessage
this.sessionKey = tools.createRandomKey( 40 );
return true;
//this.send( message );
}
addObjectMessage( messageID, object, eventName ) {
var message = new socketMessage();
message.id = messageID;
message.type = "addObject";
message.eventName = eventName;
message.data = object.clean();
message.applicationPath = tools.getApplicationPath( object );
Console.log( "send message to client:", message );
Console.log("-------------------------------------------");
this.send( message );
}
updateMessage( messageID, object, eventName ) {
var message = new socketMessage();
message.id = messageID;
message.type = "clientUpdate";
message.eventName = eventName;
message.data = object.clean();
message.applicationPath = tools.getApplicationPath( object );
Console.log( "send message to client:", message );
Console.log("-------------------------------------------");
this.send( message );
}
debugMessage( parameters ) {
var message = new socketMessage();
message.type = "debug";
message.parameters = parameters;
this.send( message );
}
logClient( parameters ) {
var message = new socketMessage();
message.type = "logClient";
message.parameters = parameters;
this.send( message );
}
destroy() {
if( global.mode == "development" ) {
console.log("");
console.log( "CLient disconnected, ID: ", this.userID );
}
if( this.destroyTimerCallback ) {
clearTimeout( this.destroyTimer );
console.log( "destroyTimerCallback cleared." );
}
//console.log("client is removed");
this.socketManager.removeClient( this.userID );
if( global.mode == "development" ) {
console.log( "Number connected clients: ", Object.keys( this.socketManager.clients ).length );
console.log( "" );
}
}
createEventListeners() {
var client = this;
console.log("creating event listeners for socket", this.socket);
this.socket.message = async function( json ){ await client.message( json, client ) }
this.socket.close = function( json ){
console.log("socket.closed on cluster:", global.clusterID );
var socketManager = client.socketManager;
var userID = client.userID;
console.log("socket closed", userID, Object.keys( socketManager.clients) );
var exists = socketManager.clients[ userID ];
if( exists ) {
console.log("client still exists, starting timer.");
client.destroyTimerCallback = function() {
client.destroy();
var exists = socketManager.clients[ userID ];
console.log("Timer exceeded, Client is removed.", typeof exists);
}
client.destroyTimer = setTimeout( client.destroyTimerCallback, client.destroyTime );
} else {
//console.log( "client is destroyed" );
}
}
//this.socket.on('message', async function( json ){ await client.message( json, client ) });
/*
this.socket.on('close', function( json ){
console.log("socket.closed on cluster:", global.clusterID );
var socketManager = client.socketManager;
var userID = client.userID;
console.log("socket closed", userID, Object.keys( socketManager.clients) );
var exists = socketManager.clients[ userID ];
if( exists ) {
console.log("client still exists, starting timer.");
client.destroyTimerCallback = function() {
client.destroy();
var exists = socketManager.clients[ userID ];
console.log("Timer exceeded, Client is removed.", typeof exists);
}
client.destroyTimer = setTimeout( client.destroyTimerCallback, client.destroyTime );
} else {
//console.log( "client is destroyed" );
}
});
*/
}
send( socketMessage ) {
//if( this.type == "socket" ) {
this.socket.send( JSON.stringify( socketMessage ) );
//} else {
// send via ajax
//}
}
getObjectFromJson( json, className ) {
if( typeof json != "object" ) {
return false;
}
var object = json[ className ];
if( !object ) {
return false;
}
return object;
}
jsonToObject( object, path, method ) {
var applicationObject = tools.getObjectByPath( this.application, path ); // 1 save
if( !applicationObject ) {
console.error( object );
throw new Error( "Object not found at path" );
}
if( !applicationObject.parent ) {
throw new Error( "parent missing" );
}
applicationObject.setUser( this.user );
applicationObject.parent.setUser( this.user );
object.serializationType = "object";
//applicationObject.serialize( object ); // 3 save
console.log("");
console.log("");
if( !applicationObject.isTable() && applicationObject.type == "table" ){
applicationObject.id = object.id;
console.log( consoleColors.yellow("Set id of application"), consoleColors.yellow( applicationObject.getClassName() ) );
if( method == "save" ) {
applicationObject.serialize( object );
}
}
if( applicationObject.isColumn() && applicationObject.type != "table" && method == "save" ){
applicationObject.id = object.id;
console.log( consoleColors.yellow("Set id of application"), consoleColors.yellow( applicationObject.getClassName() ) );
applicationObject.serialize( object, false, true );
}
if( object.nodeMethodArguments && method == "callNodeMethod" ) {
console.log( consoleColors.yellow("Set nodeMethodName"), consoleColors.yellow( applicationObject.getClassName() ) );
console.log( consoleColors.yellow("Set nodeMethodArguments"), consoleColors.yellow( applicationObject.getClassName() ) );
applicationObject.nodeMethodName = tools.validateValue( object.nodeMethodName );
applicationObject.nodeMethodArguments = tools.validateValue( object.nodeMethodArguments );
}
return applicationObject;
}
validateApplicationObjects( applicationObject ) {
if( !applicationObject ) {
console.log( "object not found at path: ", path, this.application );
return false;
}
if( !applicationObject.parent ) {
console.log( "object has no parent", applicationObject, path );
return false;
}
}
/*
Deprecated.
addObject( path, className, object ) {
path.shift();
var parent = this.getObjectByPath( this.application, path );
var applicationObject = deepclone( this.core.getObjectByclassName( className ) );
if( !applicationObject ) {
Console.log( "object does not exist with className: ", className );
}
if( applicationObject.type == "renderCollection" ) {
applicationObject = this.createRenderCollection( applicationObject, object.collection.objectName );
}
applicationObject.setParent( parent );
applicationObject.serialize( object );
applicationObject.id = object.id;
this.updateObject( object, applicationObject );
parent.setUser( this.user );
if( parent ) {
parent.add( applicationObject, false );
} else {
Console.log( "parent on path does not exist:", path );
}
//Console.logClient( this, "Client connected to socket" );
if( applicationObject ) {
shared.cores[0].parse( applicationObject );
}
return applicationObject;
}
createRenderCollection( renderCollection, collectionObjectClassName ) {
var collectionObject = deepclone( this.core.getObjectByclassName( collectionObjectClassName ) ); // dit moet anders in de toekomst object.collectionObject word object.collection.object
renderCollection.setCollectionObject( collectionObject.constructor );
// storageCollection moet hier komen anders werkt dat select gedeelte niet
return renderCollection;
}
*/
logMessage( object, message, applicationPath, controller, method, eventName ) {
if( object && object.debug ) {
///console.log( "________________________________________________ \n");
console.log( "\n>>recieved message" );
console.log( tools.addTabToBegin( message, 9 ) );
if( object.getClassName ) {
Console.addColumn( "class name:", applicationPath[0] );
Console.addColumn( "propertyName:", object.getClassName() );
}
Console.addColumn( "id:", object.id);
if( Array.isArray( applicationPath ) ) {
Console.addColumn( "path:", applicationPath.join("/") );
}
Console.addColumn( "controller:", controller );
Console.addColumn( "method:", method );
Console.addColumn( "eventName:", eventName );
console.log("\n Request:");
Console.logTable();
}
}
handleObject( message, applicationPath, method ) {
switch( typeof message.object ) {
case "string":
return message.object
break;
case "object":
return this.jsonToObject( message.object, deepclone( applicationPath ), method ); // save
break;
default:
}
}
async parseMessage( message ) {
var applicationPath = tools.validateArray( message.applicationPath ); // save
var controller = tools.validateString( message.controller ); // save
var method = tools.validateString( message.method ); // save
var eventName = tools.validateString( message.eventName ); // save
if( controller == "ping" ) {
//console.log("ping");
return false;
}
if( !message.object ) {
throw new Error("jsonToObject failed");
}
//if( object && object.debug ) {
console.log( "________________________________________________ \n");
//}
var object = this.handleObject( message, applicationPath, method );
this.logMessage( object, message, applicationPath, controller, method, eventName );
try{
return await this.createControllerCall( controller, method, object, eventName );
} catch( e ) {
throw e;
}
}
initializeLimiter() {
this.burstyLimiter = new BurstyRateLimiter(
new RateLimiterMemory({
points: 50,
duration: 1,
}),
new RateLimiterMemory({
points: 150,
duration: 3,
})
);
this.rateLimiter = new RateLimiterMemory({
points: 500,
duration: 1,
});
}
async checkRateLimiter() {
var reject = await this.rateLimiter.consume( this.userID, 2 )
.then(() => {
return false;
})
.catch(() => {
return true;
});
if( reject ) {
if( global.mode == "development" ) {
console.log("Request Rejected, to many requests.");
}
}
return reject;
}
checkMaxMessageSize( json ) {
if( json.length > global.maxSocketBufferSize ) {
if( global.mode == "development" ) {
console.log("Request Rejected, Request to big.");
}
}
}
serializeString( json ) {
if( Buffer.isBuffer( json ) ) {
return json.toString();
} else {
return json;
}
}
createExceptionHanler( json ) {
}
createErrorMessage( error ) {
console.error("error:", error );
var newMessage = new socketMessage();
newMessage.error = error;
return newMessage;
}
createValidMessage( message, object ) {
var newMessage = new socketMessage();
newMessage.id = message.id;
newMessage.eventName = message.eventName;
newMessage.type = "promise";
if( object ) {
if( object.clean ) {
newMessage.data = object.clean();
} else {
newMessage.data = object;
}
} else if( message.data ) {
newMessage.data = message.data;
}
if( object ){
if( object.debug && newMessage ) {
console.log( "\n" );
console.log( newMessage );
}
}
return newMessage;
}
logMemoryStatistics() {
//console.log( "freemem:", os.freemem() );
//console.log( "totalmem:", os.totalmem() );
const formatMemoryUsage = ( data ) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`
const memoryData = process.memoryUsage()
const memoryUsage = {
rss: `${formatMemoryUsage(memoryData.rss)} -> Resident Set Size - total memory allocated for the process execution`,
heapTotal: `${formatMemoryUsage(memoryData.heapTotal)} -> total size of the allocated heap`,
heapUsed: `${formatMemoryUsage(memoryData.heapUsed)} -> actual memory used during the execution`,
external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`,
}
//console.log( memoryUsage );
}
validateJSON( jsonString ) {
try {
var o = JSON.parse( jsonString );
// Handle non-exception-throwing cases:
// Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
// but... JSON.parse(null) returns null, and typeof null === "object",
// so we must check for that, too. Thankfully, null is falsey, so this suffices:
if (o && typeof o === "object") {
return o;
}
}
catch (e) { }
return false;
}
async handleJSON( json ) {
if( this.validateJSON( json ) ) {
var message = JSON.parse( json );
try{
var object = await this.parseMessage( message );
} catch( e ) {
var error = e;
}
} else {
var error = new Error("No Valid json provided.");
}
if( error ) {
var newMessage = this.createErrorMessage( error );
} else {
//if( global.mode == "development" ) {
this.saveCache( object );
//}
var newMessage = this.createValidMessage( message, object );
}
return newMessage;
}
saveCache( newObject ) {
if( !newObject ) {
//console.log("object not defined");
return false;
}
var className = newObject.__className;
//if(object.clean)
//console.log("create cache for object", newObject.clean(), newObject, newObject.getClassName());
//console.log(newObject.useCache, object.performCacheUpdate, className);
//console.log( util.inspect( newObject, { showHidden: false, depth: 1, colors: true }) )
//console.log("use cachew", newObject.useCache);
if( !newObject.useCache ) {
return false;
}
if( !newObject.performCacheUpdate ) {
return false;
}
newObject.performCacheUpdate = false;
if( !className ) {
return false;
}
var cachePath = path.resolve( "./assets/cache/cache.json" );
var oldCache = fs.readFileSync( cachePath, 'utf8' )
var oldObject = JSON.parse( oldCache );
var id = newObject.id;
var cleanedObject = tools.getFirstChild( newObject.clean() );
if( !oldObject[ className ] ) {
oldObject[ className ] = new Object();
}
if( id ) {
//if( !object[ className ][ id ] ) {
// object[ className ][ id ] = new Object();
//}
//object[ className ][ id ] = newObject;
} else {
console.log("save class in cache", className);
oldObject[ className ] = cleanedObject;
}
var jsonString = JSON.stringify( oldObject );
fs.writeFileSync( cachePath, jsonString );
var jscachePath = path.resolve( "./assets/cache/cache.js" );
fs.writeFileSync( jscachePath, "export default `" + jsonString + "`;" );
console.log( "Cache has been updated", className );
}
async message( rawMessage ) {
this.checkMaxMessageSize( rawMessage );
var reject = await this.checkRateLimiter( rawMessage );
if( reject ) {
console.log("reject request")
return true;
}
var json = this.serializeString( rawMessage );
//console.log("recieved string", json);
var newMessage = await this.handleJSON( json );
this.logMemoryStatistics();
this.send( newMessage );
}
validateController( controllers, controller, method ){
if( !controllers[ controller ] ) {
console.log("Controller does not exist:", controller + "Controller." + method + "()");
throw new Error("Controller does not exist");
}
if( !controllers[ controller ][ method ] ) {
console.log("Controller method does not exist:", controller + "Controller." + method + "()");
throw new Error("Access denied");
return false;
}
var publicMethods = controllers[ controller ].__publicMethods;
if( !publicMethods.includes( method ) ) {
console.log("Trying to access private controller method", controller + "Controller." + method + "()")
throw new Error("Access denied");
return false;
}
}
createControllerClasses( controller ) {
var core = this.socketManager.core;
controller.objectManager = this.socketManager.objectManager;
controller.db = core.db;
controller.databaseManager = core.databaseManager;
controller.socketManager = this.socketManager;
controller.core = core;
controller.sql = this.socketManager.core.sql;
}
async createControllerCall( controller, method, applicationObject, eventName ) {
this.validateController( controllers, controller, method ); // save
var controllerInstance = controllers[ controller ]; // save
this.createControllerClasses( controllerInstance ); // save
try{
return await controllerInstance[ method ]( applicationObject, this, eventName ); // save
} catch( e ) {
throw e;
}
} // save
}