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 }