Initial commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
! /servers/Dashboard/functions.js – Central Dashboard Server Functions
|
||||
? This modules exports the functionality for the express dashboard server
|
||||
*/
|
||||
|
||||
// % core variables
|
||||
const ip = process.env.INTERNAL_HOST_IP;
|
||||
const port = process.env.INTERNAL_DASHBOARD_PORT;
|
||||
|
||||
// $ server start function
|
||||
async function boot(server) {
|
||||
// ~ start express server
|
||||
server.listen(port, ip, () => {
|
||||
console.info(`Express server is now running on http://${ip}:${port}`)
|
||||
});
|
||||
|
||||
// ~ return running express server reference
|
||||
return server;
|
||||
};
|
||||
|
||||
// $ server shutdown function
|
||||
async function shutdown(server) {
|
||||
// ~ shutdown running express server
|
||||
server.close();
|
||||
|
||||
// ~ return express server reference
|
||||
return server;
|
||||
};
|
||||
|
||||
// $ server restart function
|
||||
async function restart(server) {
|
||||
// ~ shutdown the running express server
|
||||
await shutdown(server);
|
||||
|
||||
// ~ restart the express server
|
||||
await boot(server);
|
||||
|
||||
// ~ return the server reference
|
||||
return server;
|
||||
};
|
||||
|
||||
// $ server status function
|
||||
async function status(server) {
|
||||
// ~ get and return server listening status
|
||||
return server.listening ? 'online' : 'offline';
|
||||
};
|
||||
|
||||
// § export server functions module
|
||||
module.exports = { boot, shutdown, restart, status };
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
! /servers/dashboard/routers/index.js – Central Gateway Routing Aggregator
|
||||
? This module dynamically collects and registeres all modular ".router.js" files within the "routers" directory
|
||||
*/
|
||||
|
||||
// % import nodejs packages
|
||||
const router = require('express').Router();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// % import external dependencies
|
||||
const rootDir = path.resolve(__dirname, '..', '..', '..');
|
||||
const { dirs } = require(path.resolve(rootDir, 'configuration', 'mapping.js'));
|
||||
|
||||
// % read router files recursively
|
||||
const routerRoot = path.resolve(dirs.dashboardDir, 'routers');
|
||||
const routerFiles = fs.readdirSync(routerRoot)
|
||||
.filter(file => /\.router\.js$/.test(file))
|
||||
.sort((a, b) => {
|
||||
if (a === 'root.router.js') return 1;
|
||||
if (b === 'root.router.js') return -1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
// $ configure used routers
|
||||
routerFiles.forEach(file => {
|
||||
const routerPath = path.resolve(routerRoot, file);
|
||||
router.use(require(routerPath));
|
||||
});
|
||||
|
||||
// § export router module
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
! /servers/dashboard/server.js – Central Dashboard Server Manager
|
||||
? This module orchestrates the express lifecycle, handles initialization, middleware configs and router integrations.
|
||||
*/
|
||||
|
||||
// % import nodejs packages
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
|
||||
// % import external dependencies
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const { dirs } = require(path.resolve(rootDir, 'configuration', 'mapping.js'));
|
||||
|
||||
// % set up express server
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
|
||||
// $ server setup
|
||||
app.use(express.json()); // add json support for express
|
||||
app.use(express.static(path.resolve(dirs.interfaceDir, 'resources'))); // set resources directory (e.g. scripts, style sheets, images ...)
|
||||
app.set('view engine', 'ejs'); // set "embedded javascript" as view engine
|
||||
app.set('views', path.resolve(dirs.interfaceDir, 'views')); // set the folder where the .ejs files are located
|
||||
|
||||
// $ router setup
|
||||
const router = require(path.resolve(dirs.dashboardDir, 'routers'));
|
||||
app.use(router);
|
||||
|
||||
// $ server functions
|
||||
const {
|
||||
boot, shutdown, restart, status
|
||||
} = require(path.resolve(dirs.dashboardDir, 'functions.js'));
|
||||
|
||||
// $ export server module
|
||||
module.exports = { boot, shutdown, restart, status, server };
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
! /servers/gateway/functions.js – Central Gateway Server Functions
|
||||
? This modules exports the functionality for the express gateway server
|
||||
*/
|
||||
|
||||
// % import nodejs dependencies
|
||||
const WebSocket = require('ws');
|
||||
|
||||
// % core variables
|
||||
const ip = process.env.INTERNAL_HOST_IP;
|
||||
const port = process.env.INTERNAL_GATEWAY_PORT;
|
||||
|
||||
// % create a reference for the websocket client
|
||||
let wsClient = null;
|
||||
|
||||
// % create an event emitter for the gateway
|
||||
const EventEmitter = require('events');
|
||||
const gatewayEvents = new EventEmitter();
|
||||
|
||||
// $ helper: send messages through websocket
|
||||
function sendToWs(payload) {
|
||||
if (wsClient && wsClient.readyState === WebSocket.OPEN) {
|
||||
wsClient.send(JSON.stringify(payload));
|
||||
} else {
|
||||
console.error('WebSocket not connected.');
|
||||
}
|
||||
}
|
||||
|
||||
// $ server start function
|
||||
async function boot(server) {
|
||||
// ~ start express server
|
||||
server.listen(port, ip, () => {
|
||||
console.info(`Express server is now running on http://${ip}:${port}`);
|
||||
});
|
||||
|
||||
// ~ connect to the websocket server
|
||||
const wsUrl = `ws://${process.env.INTERNAL_HOST_IP}:${process.env.INTERNAL_WEBSOCKET_PORT}?key=${process.env.INTERNAL_SECRET}&codename=gateway`;
|
||||
wsClient = new WebSocket(wsUrl);
|
||||
|
||||
wsClient.on('open', () => {
|
||||
console.info('Connected to central websocket server');
|
||||
});
|
||||
|
||||
wsClient.on('message', (data) => {
|
||||
const msg = JSON.parse(data);
|
||||
gatewayEvents.emit('ws_message', msg);
|
||||
});
|
||||
|
||||
wsClient.on('error', (err) => {
|
||||
console.error('WebSocket error:', err.message);
|
||||
});
|
||||
|
||||
// ~ return running express server reference
|
||||
return server;
|
||||
};
|
||||
|
||||
// $ server shutdown function
|
||||
async function shutdown(server) {
|
||||
// ~ close websocket server
|
||||
if (wsClient) {
|
||||
wsClient.close();
|
||||
wsClient = null;
|
||||
}
|
||||
|
||||
// ~ shutdown running express server
|
||||
return new Promise((resolve) => {
|
||||
server.close(() => {
|
||||
console.info('Express server shut down.');
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// $ server restart function
|
||||
async function restart(server) {
|
||||
// ~ shutdown the running express server
|
||||
await shutdown(server);
|
||||
|
||||
// ~ restart the express server
|
||||
await boot(server);
|
||||
|
||||
// ~ return the server reference
|
||||
return server;
|
||||
};
|
||||
|
||||
// $ server status function
|
||||
async function status(server) {
|
||||
// ~ get and return server listening status
|
||||
return server.listening ? 'online' : 'offline';
|
||||
};
|
||||
|
||||
// § export server functions module
|
||||
module.exports = { boot, shutdown, restart, status, sendToWs, gatewayEvents };
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
! /servers/gateway/routers/index.js – Central Gateway Routing Aggregator
|
||||
? This module dynamically collects and registeres all modular ".router.js" files within the "routers" directory
|
||||
*/
|
||||
|
||||
// % import nodejs packages
|
||||
const router = require('express').Router();
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// % import external dependencies
|
||||
const rootDir = path.resolve(__dirname, '..', '..', '..');
|
||||
const { dirs } = require(path.resolve(rootDir, 'configuration', 'mapping.js'));
|
||||
|
||||
// % read router files recursively
|
||||
const routerRoot = path.resolve(dirs.gatewayDir, 'routers');
|
||||
const routerFiles = fs.readdirSync(routerRoot)
|
||||
.filter(file => /\.router\.js$/.test(file))
|
||||
.sort((a, b) => {
|
||||
if (a === 'root.router.js') return 1;
|
||||
if (b === 'root.router.js') return -1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
// $ configure used routers
|
||||
routerFiles.forEach(file => {
|
||||
const routerPath = path.resolve(routerRoot, file);
|
||||
router.use(require(routerPath));
|
||||
});
|
||||
|
||||
// § export router module
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
! /servers/gateway/routers/template.router.js –
|
||||
*/
|
||||
|
||||
// % import nodejs dependencies
|
||||
const router = require('express').Router();
|
||||
|
||||
// % import external dependencies
|
||||
const rootDir = path.resolve(__dirname, '..', '..', '..');
|
||||
const { dirs } = require(path.resolve(rootDir, 'configuration', 'mapping.js'));
|
||||
const { gatewayEvents, sendToWs } = path.resolve(dirs.gateway, 'functions.js');
|
||||
|
||||
// $ template router with websocket integration
|
||||
router.post('/test', (req, res) => {
|
||||
const correlationId = '';
|
||||
|
||||
sendToWs({
|
||||
type: 'actionName',
|
||||
correlationId,
|
||||
data: req.body
|
||||
});
|
||||
|
||||
// ~ wait for callback through central event bus
|
||||
gatewayEvents.once('ws_message', (msg) => {
|
||||
if (msg.correlationId === correlationId) res.json(msg.data);
|
||||
});
|
||||
});
|
||||
|
||||
// § export routers
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
! /servers/gateway/server.js – Central Gateway Server Manager
|
||||
? This module orchestrates the express lifecycle, handles initialization, middleware configs and router integrations
|
||||
*/
|
||||
|
||||
// % import nodejs packages
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
|
||||
// % import external dependencies
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const { dirs } = require(path.resolve(rootDir, 'configuration', 'mapping.js'));
|
||||
|
||||
// % set up express server
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
|
||||
// $ server setup
|
||||
app.use(express.json()); // add json support for express
|
||||
|
||||
// $ router setup
|
||||
const router = require(path.resolve(dirs.gatewayDir, 'routers'));
|
||||
app.use(router);
|
||||
|
||||
// $ server functions
|
||||
const {
|
||||
boot, shutdown, restart, status
|
||||
} = require(path.resolve(dirs.gatewayDir, 'functions.js'));
|
||||
|
||||
// $ export server module
|
||||
module.exports = { boot, shutdown, restart, status, server };
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
! /servers/websocket/actions/template.js – A simple websocket action template
|
||||
? This module is just a template for the websocket actions
|
||||
*/
|
||||
|
||||
module.exports = (connectionManager, data, id) => {
|
||||
|
||||
};
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
! /servers/websocket/connections.js – Central WebSocket Connection Manager
|
||||
? This module handles all websocket connections as a pool and automatically sends heartbeats
|
||||
*/
|
||||
|
||||
// % import nodejs dependencies
|
||||
const path = require('path');
|
||||
|
||||
// % import external dependencies
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const { generatePrefixed } = require(path.resolve(rootDir, 'services', 'idgen.js'));
|
||||
|
||||
// % create a new connection mapping
|
||||
let connections = new Map();
|
||||
let registry = new Map();
|
||||
|
||||
// % set heartbeat settings
|
||||
let heartbeatInterval = null;
|
||||
const heartbeatTime = 5000; // 5 seconds
|
||||
|
||||
// $ add a connection
|
||||
function add(socket, codename = null) {
|
||||
// % generate a unique id for websocket connections
|
||||
const id = generatePrefixed('ws');
|
||||
|
||||
// ~ add connection to connection pool
|
||||
const conn = { socket, codename, lastHeartbeat: Date.now() };
|
||||
connections.set(id, conn);
|
||||
|
||||
// ~ add registry keys if codename is provided
|
||||
if (codename) registry.set(codename, id);
|
||||
|
||||
// ~ listen for 'pong' events from client to update heartbeat timestamp
|
||||
socket.on('pong', () => conn.lastHeartbeat = Date.now());
|
||||
|
||||
// ~ error handling
|
||||
socket.on('error', (err) => remove(id));
|
||||
|
||||
// ~ close connection
|
||||
socket.on('close', () => remove(id));
|
||||
|
||||
// ~ return client id
|
||||
return id;
|
||||
}
|
||||
|
||||
// $ remove a connection by id
|
||||
function remove(id) {
|
||||
// % find id in connection map
|
||||
const conn = connections.get(id);
|
||||
|
||||
// ~ close and remove connection
|
||||
if (conn) {
|
||||
// ~ cleanup registry if codename exists
|
||||
if (conn.codename) registry.delete(conn.codename);
|
||||
|
||||
// ~ close connection
|
||||
if (conn.socket.readyState === conn.socket.OPEN) conn.socket.close();
|
||||
|
||||
// ~ remove connection from pool
|
||||
connections.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
// $ remove all connections
|
||||
function removeAll() {
|
||||
// ~ loop through connection mapping
|
||||
for (const id of connections.keys()) {
|
||||
remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
// $ get a connection by id
|
||||
function get(id) {
|
||||
return connections.get(id);
|
||||
}
|
||||
|
||||
// $ get a connection by codename
|
||||
function getByCodename(codename) {
|
||||
// % get id from codename
|
||||
const id = registry.get(codename);
|
||||
|
||||
// ~ return connection
|
||||
return id ? connections.get(id) : null;
|
||||
}
|
||||
|
||||
// $ get all connections
|
||||
function getAll() {
|
||||
// % create a reference for all connections
|
||||
const all = {};
|
||||
|
||||
// ~ loop through connections and fill "all" reference
|
||||
for (const [id, data] of connections.entries()) {
|
||||
all[id] = {
|
||||
codename: data.codename,
|
||||
lastHeartbeat: data.lastHeartbeat
|
||||
};
|
||||
}
|
||||
|
||||
// ~ return the connection reference
|
||||
return all;
|
||||
}
|
||||
|
||||
// $ send a message to a specific connection by id
|
||||
function send(id, message) {
|
||||
// % get id
|
||||
const conn = connections.get(id);
|
||||
|
||||
// ~ check if connection exists and is open
|
||||
if (conn && conn.socket.readyState === conn.socket.OPEN) {
|
||||
conn.socket.send(JSON.stringify(message));
|
||||
return true;
|
||||
}
|
||||
|
||||
// ~ return immediately after a failure
|
||||
return false;
|
||||
}
|
||||
|
||||
// $ send a message to a specific connection by codename
|
||||
function sendTo(codename, message) {
|
||||
// % get id
|
||||
const id = registry.get(codename);
|
||||
|
||||
// ~ send a message if id exists
|
||||
if (id) return send(id, message);
|
||||
|
||||
// ~ immediately return if id not exists
|
||||
return false;
|
||||
}
|
||||
|
||||
// $ broadcast to all connections
|
||||
function broadcast(message) {
|
||||
// % serialize payload
|
||||
const payload = JSON.stringify(message);
|
||||
|
||||
// ~ loop through all established connections
|
||||
for (const conn of connections.values()) {
|
||||
if (conn.socket.readyState === conn.socket.OPEN) {
|
||||
conn.socket.send(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// $ start the heartbeat system
|
||||
function startBeat() {
|
||||
if (heartbeatInterval) return;
|
||||
|
||||
heartbeatInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
|
||||
for (const [id, conn] of connections.entries()) {
|
||||
// ~ send ping to active connections
|
||||
if (conn.socket.readyState === conn.socket.OPEN) {
|
||||
conn.socket.ping();
|
||||
}
|
||||
|
||||
// ~ remove stale connections (no pong in 2 heartbeat intervals)
|
||||
if (now - conn.lastHeartbeat > heartbeatTime * 2) {
|
||||
remove(id);
|
||||
}
|
||||
}
|
||||
}, heartbeatTime);
|
||||
}
|
||||
|
||||
// $ stop the heartbeat system
|
||||
function stopBeat() {
|
||||
if (heartbeatInterval) {
|
||||
clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// § export the websocket connection module
|
||||
module.exports = {
|
||||
add,
|
||||
remove,
|
||||
removeAll,
|
||||
get,
|
||||
getByCodename,
|
||||
getAll,
|
||||
send,
|
||||
sendTo,
|
||||
broadcast,
|
||||
startBeat,
|
||||
stopBeat
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
! /servers/websocket/server.js – Central Websocket Server Manager
|
||||
? This module orchestrates the websocket lifecycle, handles connections, heartbeats and actions
|
||||
*/
|
||||
|
||||
// % import nodejs packages
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
|
||||
// % import external dependencies
|
||||
const rootDir = path.resolve(__dirname, '..', '..');
|
||||
const { dirs } = require(path.resolve(rootDir, 'configuration', 'mapping.js'));
|
||||
const connectionManager = require(path.resolve(dirs.websocketDir, 'connections.js'));
|
||||
|
||||
// % declare action directories
|
||||
const wsActionsDir = path.resolve(dirs.websocketDir, 'actions');
|
||||
|
||||
// % declare reference for actions
|
||||
const actions = {};
|
||||
|
||||
// % declare references for the websocket server
|
||||
const ip = process.env.INTERNAL_HOST_IP;
|
||||
const port = process.env.INTERNAL_WEBSOCKET_PORT;
|
||||
let server = null;
|
||||
|
||||
// $ load all websocket actions recursively
|
||||
// ? The loader is loading all actions with a dot notation
|
||||
// ?? Usage: actionName || folder.actionName || folder.folder2.actionName ....
|
||||
async function loadActions(dir = wsActionsDir, prefix = '') {
|
||||
// ~ reset the action container to ensure a clean state during reloads
|
||||
for (const key in actions) delete actions[key];
|
||||
|
||||
// % read all content synchronously
|
||||
const files = fs.readdirSync(dir);
|
||||
|
||||
// ~ loop through each file or folder found in the directory
|
||||
for (const file of files) {
|
||||
// % construct the absolute path to the current item
|
||||
const fullPath = path.resolve(dir, file);
|
||||
|
||||
// ~ retrieve file system metadata
|
||||
const stat = fs.statSync(fullPath);
|
||||
|
||||
// ~ check if item is a folder
|
||||
if (stat.isDirectory()) {
|
||||
// ~ recurse into subdirs and append the folder name to the prefix
|
||||
loadActions(fullPath, prefix + file + '.');
|
||||
} else if (file.endsWith('.js')) {
|
||||
// ~ generate a unique action name by stripping the file extension and include the prefix
|
||||
const actionName = prefix + file.replace('.js', '');
|
||||
|
||||
// ~ invalidate the require cache to allow loading the updated code
|
||||
delete require.cache[require.resolve(fullPath)];
|
||||
|
||||
// ~ load the fresh module version into the actions registry
|
||||
actions[actionName] = require(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// $ function to start the websocket server
|
||||
async function start() {
|
||||
// ~ initially load all websocket actions
|
||||
await loadActions();
|
||||
|
||||
try {
|
||||
// ~ set up a new websocket server
|
||||
const wss = new WebSocket.Server({
|
||||
host: ip,
|
||||
port: port
|
||||
});
|
||||
|
||||
wss.on('connection', async (socket, req) => {
|
||||
// ~ authorize connection
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
const key = url.searchParams.get('key');
|
||||
const codename = url.searchParams.get('codename');
|
||||
if (key !== process.env.INTERNAL_SECRET) {
|
||||
socket.close(1008, 'Unauthorized');
|
||||
return;
|
||||
}
|
||||
|
||||
// ~ get connection id and establish connection
|
||||
const id = await connectionManager.add(socket, codename);
|
||||
connectionManager.sendTo(codename, id);
|
||||
|
||||
// ~ register websocket messages
|
||||
socket.on('message', async (data) => {
|
||||
// ~ parse json data
|
||||
const msg = JSON.parse(data);
|
||||
|
||||
// % get user id and validate
|
||||
const valid = connectionManager.get(id);
|
||||
if (valid.socket.readyState !== valid.socket.OPEN) {
|
||||
connectionManager.remove(id);
|
||||
return;
|
||||
};
|
||||
|
||||
// % declare action type (e.g. folder.actionName)
|
||||
const type = msg.type;
|
||||
if (!type) {
|
||||
connectionManager.send(id, 'Action value is empty. Please declare an action value.');
|
||||
return;
|
||||
};
|
||||
|
||||
// ~ set action handler
|
||||
const actionHandler = actions[msg.type];
|
||||
if (!actionHandler) {
|
||||
connectionManager.send(id, `Action ${actionHandler} not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// ~ send action to handler
|
||||
try {
|
||||
await actionHandler(connectionManager, msg.data, id);
|
||||
} catch(e) {
|
||||
console.error('Error while trying to execute the websocket action!', e);
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ~ start the heartbeat system with the connection manager
|
||||
connectionManager.startBeat();
|
||||
} catch(e) {
|
||||
console.error('Error while trying to start the websocket server', e);
|
||||
connectionManager.stopBeat();
|
||||
}
|
||||
}
|
||||
|
||||
// § export websocket server and functions
|
||||
module.exports = { start, loadActions };
|
||||
Reference in New Issue
Block a user