134 lines
4.8 KiB
JavaScript
134 lines
4.8 KiB
JavaScript
/*
|
||
! /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 }; |